Wow, it’s finally out! It’s small.
Grading rubric
- [40] for loading the map
- [20] for loading the tiles correctly
- [20] for loading the objects correctly
- [60] for the goo
- [40] for the core growth algorithm
- [20] for probabilistic growth
0. Stuff to download and getting started
Download this file and extract it somewhere sensible. Then rename the abc123_proj1.asm
file appropriately.
Overview:
- The
display_
files are the driver for the display plugin. macros.asm
has some useful macros (likelstr
andprint_str
but several others too)proj1_constants.asm
are named constants (.eqv
s)proj1_editor.asm
is a janky in-game map editorproj1_graphics.asm
contains all the graphics and their palettesproj1_levels.asm
contains map data for 2 levelsproj1_nesfont.asm
contains a font that is loaded to display text on the screen
and of course, _proj1.asm
is the program itself.
If you assemble and run right now, you’ll see this:
It’s….. a big empty concrete floor! With some numbers in the bottom corners. Huh.
1. Updating load_map
If you successfully wrote load_map
on lab 3, great! This will be easy.
If you didn’t get to load_map
on lab 3, or didn’t finish it successfully, go back and do it. I’m serious. You’re not gonna understand what I’m talking about until you do. (Note that load_map
in lab 3 requires place_tile
to work properly. Oh look at that, there’s an implementation of place_tile
in this project! How nice of me)
Okay, so, your project 1 load_map
is gonna be very similar to the one from lab 3, with the following differences:
- Instead of being hard-coded to load from
map_data
, it takes the array as an argument (a0
).- Since you’re calling functions (with
jal
) in this function, that means you will have to move that argument into a SsssSSSafe place at the beginning of the function. - Also, you can’t use the short form of array access
lb t0, map_data(t1)
or whatever. You’ll have to do the addition yourself.
- Since you’re calling functions (with
- Instead of 16 rows and 16 columns, it’s 32 rows and 32 columns.
- That also goes for the multiplication inside the loop to calculate the offset into the map data array.
Then, char_to_tile_type
needs to be changed so it works like so:
'#'
returnsTILE_BRICK
'g'
returnsTILE_GOO
'e'
returnsTILE_GOO_EDGE
'%'
returnsTILE_GOAL
- any other character returns
TILE_EMPTY
(note! not exiting here like we did on lab 3)
Also, it seemed to escape a lot of people on lab 3 that you can just write e.g. li v0, TILE_WHATEVER
to return a constant
go go go make those changes
your goal is to see this:
woo nice!
2. Extending load_map
to include objects
The map data also has some characters that represent objects, which are new for the project. Here’s how you do it:
- make a
char_to_obj_type
function which takes a character and maps it to anOBJ_
constant, very much likechar_to_tile_type
. It works like:'$'
returnsOBJ_PLAYER
'o'
returnsOBJ_PUMPKIN
- any other character returns
OBJ_EMPTY
- in
load_map
, after placing the tile, then pass the character intochar_to_obj_type
- if it doesn’t return
OBJ_EMPTY
:- do
new_object((col * 8) + 4, (row * 8) + 4, v0)
- do
- if it doesn’t return
Be careful about the ATV rule! Since you called char_to_tile_type
, you cannot just reuse the character that was in a0
when calling char_to_obj_type
. It’s up to you to solve that issue.
This is what you want to see:
Try it out!
The little yellow ball is you, the player. Click on the display and use the W, A, S, D keys to move the player around. Things to note:
- you can’t walk through the brick walls. duh.
- if you touch the pumpkins, you collect them (they disappear and your pumpkin counter increases).
- if you step on either kind of green tile, you start flashing and your ❤️ health goes down.
- the green tiles are goo and it’s bad.
- if your health reaches 0, it’s game over.
- if you step on the 🏁 checkered tile, you win. that’s the “goal” tile.
- also, after picking up some pumpkins, you can press the space bar to… splash some water in front of you? listen, pumpkins are full of water, okay? yeah sure they could be buckets of water instead of pumpkins but I made the pumpkin sprite and it was super cute so it’s staying
- that water can destroy the green goo tiles. try it!
3. Periodic events
Those goo tiles are the hazard in this little game. The light green goo is static, but the dark green goo grows. Or, it’s supposed to.
First, we need to make a periodic event. We don’t want the goo to grow on every single frame. If it did that, it would fill the entire map in about 1 second. Instead, we’ll only update the goo about once per second by only doing it on some frames but not others.
We’ll do this by using the goo_timer
variable (defined around line 21). Here’s how it works:
- if
goo_timer
is not 0,- decrement it.
- else,
- set
goo_timer
toGOO_DELAY
, - and call
update_goo
.
- set
You’re going to implement that logic in the empty update_goo_if_needed
function.
Then in the empty update_goo
function, put print_str "update! "
.
When you run, you should see the word “update!” printed in the console about once per second.
If it’s printing many many times per second, something is wrong. Most likely, you wrote the if-else wrong. Don’t forget the jump over the else.
4. Making the goo grow
The basic algorithm for making the goo grow is pretty simple. It’s something like this:
- Call
copy_tm_to_prev_tm
.- This makes a backup copy of the tilemap (
display_tm_table
) intoprev_tm
.
- This makes a backup copy of the tilemap (
- For each tile in the tilemap,
- if this tile was a goo edge tile previously (i.e. in
prev_tm
),- if the tile above was empty previously, make it a goo edge.
- if the tile below was empty previously, make it a goo edge.
- if the tile to the left was empty previously, make it a goo edge.
- if the tile to the right was empty previously, make it a goo edge.
- make this tile a goo tile (instead of a goo edge)
- if this tile was a goo edge tile previously (i.e. in
Here are some tips to approach this:
- Wherever I say “previously” above, I mean in the
prev_tm
array. - Wherever I say “make it a…” above, I mean in the
display_tm_table
array. - The constant names are
TILE_GOO_EDGE
,TILE_EMPTY
, andTILE_GOO
. - A doubly-nested
for
loop is not really needed here. We can treat the tilemap as a single-dimensional array of lengthN_TM_TILES
. - These are arrays of
2 byte entries
. That means you multiply the loop counter by 2 to index them.- However, you are only accessing the first byte of each entry, so you will only use
lb
andsb
.- (The second byte controls some other properties of the tile that we aren’t using.)
- However, you are only accessing the first byte of each entry, so you will only use
- But wait, how would you then access the tiles above/below/left/right of the current tile?
- There’s yet another form of load and store instructions:
lb t0, array + constant(index)
- For example,
lb t1, prev_tm + -2(t0)
will get the tile to the left, because that tile is 2 bytes before the current one. The one to the right is 2 bytes after. Above is 64 bytes before, and below is 64 bytes after.
- There’s yet another form of load and store instructions:
- But don’t try to implement all four directions at once.
-
Try just making the goo grow upwards at first. Once it’s working, it should add one “row” of goo tiles per second, eventually stopping at the horizontal brick wall, like this:
- Then you can add growing down…
- And then left, and then right.
-
Once all 4 directions are implemented, it should look like this after a few steps:
-
5. Making the goo grow, but slower and more organically
Now the goo grows, but it’s kind of fast. It’s also very… geometric. But it’s goo!! Goo is organic!!! I guess idk lol it’s halloween-y
Fortunately, fixing this is pretty easy.
In update_goo
, right after you check if this tile is goo, but before you check the tiles around it, you’re gonna insert some code:
- generate a random number in the range [0, 99) (look back at lab 4 for how to do that)
- only update this tile if that number is less than
GOO_EXPAND_PROBABILITY
.- you’re really just adding a second condition to the
if
that checkedis this a goo edge tile?
- you’re really just adding a second condition to the
that’s it. Now when you run it, the goo should expand slower, and randomly, making it different every time. For example:
If it still grows fast, even if it’s random, you inverted the condition. Like, this is too fast:
Try it out
Once you’re sure it’s working right, look around line 85 in main
. You’ll see this:
la a0, test_level
#la a0, level_1
jal load_map
Comment out the la a0, test_level
and uncomment the la a0, level_1
. Now (assuming you wrote load_map
right!) you should see a different level when you run:
See if you can get to the exit!
There is also a line at the top of your project like .eqv INVULNERABILITY 0
. Change that 0 to a 1, and now you cannot be hurt by the goo. Cheater!
Last, if you uncomment the lines with ################### EDITOR ###################
on them, you have access to a very simple level editor in-game. 1, 2, 3, 4 choose which tile to place; click to place tiles; P to print out a representation of the map to the console. It doesn’t print object locations though, so you’ll have to add those yourself by putting $
and o
in the righe places.
Yep that’s it, submitting
There’ll be a gradescope open like tomorrow, I’m tired okay