We’re continuing to use the Keypad and LED Display Simulator plugin for this lab. Refer back to lab3 if you’re fuzzy on how to open and connect to it.

For your next project, you’ll make a real-time interactive video game. This lab has all the same parts as a game. This is probably a new way of writing a program for you, so we’ll start with something simple. It will look something like the animation on the right when you’re done.

When you get input from the user, you’re used to waiting for the user to type something before your program can continue, like in the first project.

But real-time interactive programs do not wait for the user. They continually run, and only respond to the user when they input something. They work like this:

  1. check for user inputs without waiting!
  2. update your program’s state (variables, data structures, etc.)
  3. output the new, changed state (here, the output is the screen)
  4. wait for a a fraction of a second
  5. loop back to step 1, until the program ends

This kind of program is everywhere. Every graphical program or app you’ve ever used works like this. Embedded devices run programs that work like this. It’s probably a far more common design than interactive command-line programs now.


Getting started

First, stop using the MARS code editor. Your programs will be getting more complicated from now on, and the MARS editor is Bad. Here’s how you use an external editor:

  1. Open the same .asm file in MARS and your editor of choice at the same time.
    • Ask in the Discord for help about what a good editor is and how to make it highlight MIPS code!
  2. Edit the file in your editor, and save.
  3. Switch to MARS, assemble, and run.
    • MARS will not show the updated code in its editor. But this is fine.
    • It will see your updated code when you assemble! Trust me!

Now for the lab:

  1. Make a file named abc123_lab5.asm in a new directory, and copy this into it:
     # YOUR FULL NAME HERE
     # YOUR USERNAME HERE
    
     .data
     frame_counter:    .word 0
     last_frame_time:  .word 0
     .text
    
     .globl main
     main:
         # here is where you would put any initialization stuff that
         # needs to happen *before* the game begins.
    
     _main_loop:
         # check_input()
         # display_update_and_clear()
         # wait_for_next_frame(16)
         j _main_loop
    
  2. Right-click this link and “save link” or “download link”.
    • When you save it, make sure it’s really named lab5_include.asm and not lab5_include.asm.txt.
    • DO NOT MODIFY THIS FILE! You are not going to submit it and there is no need for you to modify it.
  3. Put it in the same directory as your abc123_lab5.asm file.
  4. Then, in your abc123_lab5.asm file, add this at the top of the file (after your comments):
     .include "lab5_include.asm"
    

Including a file is like copying and pasting it into this file when you assemble. Now you can use the constants and call the functions from lab5_include.asm!


1. A real-time interactive loop

From now on I’m not going to tell you how to make and call functions and which registers to use. I am assuming that you have learned the calling convention and register usage rules. Please follow them!

  1. Make an empty check_input function.
  2. In the _main_loop, there are some comments. These say what you need to do.
    • Translate those three comments to function calls.
    • The main loop is not going to really do much besides calling other functions. This is good! Remember “top-down design?”
    • display_update_and_clear() copies the display RAM onto the display, and then clears the display RAM.
      • Think of it like drawing a picture in your notebook, ripping out that page, putting it on the fridge, and now you have a new blank page to draw on. Over and over.
    • wait_for_next_frame(16) tells it to wait for at most 16 milliseconds - 1/60th of a second.
      • It does some fancy stuff to ensure a smooth 60FPS framerate.
  3. Assemble and run.
    • Right now, it should do nothing. But it’s in an infinite loop, so stop it.
  4. In the main loop, before calling check_input, do the equivalent of this (using syscalls):
     print_int(frame_counter / 60) // do NOT store this into the frame_counter variable!
     print_char('\n')
    
    • The frame_counter variable is incremented every time you call wait_for_next_frame. You don’t have to change it.
  5. Now when you assemble and run, you should see it printing numbers very quickly.
    • It will print a bunch of 0s, then 1s, then 2s… and it increments once a second.
    • If that works, good! You now have the skeleton of a real-time program.
  6. Now remove the prints. They were just to test that it was working. :)

2. Making state variables and drawing a dot

This program lets you move a dot around the screen. So, you need to remember the dot’s position: its X and Y coordinates.

  1. Create two global variables named dot_x and dot_y.
    • Make them words, and initialize them both to 32.
  2. Make a new function called draw_dot. In it, write the equivalent of this code:
     display_set_pixel(dot_x, dot_y, COLOR_WHATEVER);
    
    • Don’t forget to push/pop ra at the beginning and end of the function.
    • display_set_pixel is a function I gave you in lab5_include.asm. You do not have to write it!
    • dot_x and dot_y are your state variables. You have to get their values out of memory and into the argument registers.
    • COLOR_WHATEVER is whatever color you want to use.
      • Since it’s a constant, you’re gonna use li.
      • Use the named constant, not a number! That’s why we name constants!
  3. Call draw_dot in the main loop after calling check_input.
  4. Now if you run it, you should see something like this (I used li a2, COLOR_ORANGE):


3. Moving it around

The way input works in this plugin is that you use the arrow keys and B key on your keyboard. Then, your program can detect that by using input_get_keys, another function from lab5_include.asm. It returns which keys are being held down, but it does so by returning flags.

Flags

A special case of bitfields is when all the fields are 1 bit in size. In that way, we can think of an integer as a small array of boolean values. We call these flags.

input_get_keys returns an integer where the lower 5 bits represent the four arrow keys and the 🅱️ key. For example:

Important: when testing flags, we can mask (use AND). But do not test if the result is 1. Test if it’s 0 or not-0.

OK, tangent over

  1. In check_input, do this. Don’t forget to push/pop ra at the beginning and end of the function.
    • This is not an if-else if-else if and it is not a switch-case. This is a sequence of 4 separate ifs.
    • This way, they will be able to press multiple keys at once.
     // READ THE COMMENTS HERE FIRST!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     // KEY_L, KEY_R etc. are constants. don't hardcode the key values. use the constants.
     // DO NOT change v0! put the result of the AND into another register.
     // so you can do e.g. `and t0, v0, KEY_L` and then check if t0 is equal/not equal to 0.
    
     v0 = input_get_keys()
    
     if((v0 & KEY_L) != 0)
         dot_x--;
     if((v0 & KEY_R) != 0)
         dot_x++;
     if((v0 & KEY_U) != 0)
         dot_y--;
     if((v0 & KEY_D) != 0)
         dot_y++;
    
  2. Now run your program, click the display, and use the arrow keys on your keyboard.
    • Make sure you can move diagonally by pressing e.g. left and up at the same time.
  3. Try moving the dot up past the top of the screen. What happens?
    • Uh oh. It hit a trap instruction in display_draw_dot. This means the arguments were invalid.
    • Click the “Registers” window and look at the a0 and a1 arguments. What’s wrong?

4. Moving it around, without crashing

Remember from lab3, when you’d draw past the right side of the screen, what happened?

What’s happening is your x and y coordinates are going negative or off the sides of the screen. On the top side, you would be writing into a part of memory you’re not allowed to, so display_set_pixel checks for that and crashes.

To prevent this, you have to limit the x and y coordinates.

At the end of your check_input function, before it returns, do the equivalent of the following:

dot_x = dot_x & 63; // bitwise AND!
dot_y = dot_y & 63;

Remember what this does? We learned about this as a shortcut for another mathematical operation…

Now your dot should wrap around to the other side like in the gif.


5. More dots!!!

One dot is not enough. Let’s have three. Here’s how it will work:

Now I am going to step back and have you try to solve this on your own. Here are some hints:


Submitting

Do not submit lab5_include.asm. Just your lab file, thanks.

See if your program works the way mine does in the video at the top of this page. If it doesn’t, at least make sure it doesn’t crash. A partially-working program is easier to grade than a crashing program.

Make sure your file is named correctly. My username is jfb42, so:

Submit here.

Drag your asm file into your browser to upload. If you can see your file in the folder, you uploaded it correctly!

You can also re-upload if you made a mistake and need to fix it.

Try this: