$35
Learning Outcomes
After completion of this programming project you should be able to:
Implement non-trivial algorithms that require conditional execution and iteration.
Design and implement functions that implement the MIPS assembly function calling conventions.
Implement algorithms that process 2D arrays of values.
Read files from disk using MIPS system calls.
Getting Started
Visit the course website and download the files hwk3.zip and MarsFall2020.jar. Fill in the following information at the top of hwk3.asm:
your first and last name as they appear in Blackboard
your Net ID (e.g., jsmith)
your Stony Brook ID # (e.g., 111999999)
Having this information at the top of the file helps us locate your work. If you forget to include this information but don’t remember until after the deadline has passed, don’t worry about it – we will track down your submission.
Inside hwk3.asm you will find several function stubs that consist simply of jr $ra instructions. Your job in this assignment is to implement all the functions as specified below. Do not change the function names, as the grading scripts will be looking for functions of the given names. However, you may implement additional helper functions of your own, but they must be saved in hwk3.asm. Helper functions will not necessarily be graded, but might be spot-checked to ensure you are following the MIPS function calling conventions.
If you are having difficulty implementing these functions, write out pseudocode or implement the functions in a higher-level language first. Once you understand the algorithm and what steps to perform, then translate the logic into MIPS assembly code.
Be sure to initialize all of your values (e.g., registers) within your functions. Never assume registers or memory will hold any particular values (e.g., zero). MARS initializes all of the registers and bytes of main memory to zeroes. The grading scripts will fill the registers and/or main memory with random values before calling your functions.
Finally, do not define a .data section in your hwk3.asm file. A submission that contains a .data section will probably result in a score of zero.
Important Information about CSE 220 Programming Projects
Read the entire project description document twice before starting. Questions posted on Piazza whose answers are clearly stated in the documents will be given lowest priority by the course staff.
You must use the Stony Brook version of MARS posted on the course website. Do not use the version of MARS posted on the official MARS website. The Stony Brook version has a reduced instruction set, added tools, and additional system calls you might need to complete the homework assignments.
When writing assembly code, try to stay consistent with your formatting and to comment as much as possible. It is much easier for your TAs and the professor to help you if we can quickly figure out what your code does.
You personally must implement the programming assignments in MIPS Assembly language by yourself. You may not write or use a code generator or other tools that write any MIPS code for you. You must manually write all MIPS assembly code you submit as part of the assignments.
Do not copy or share code. Your submissions will be checked against other submissions from this semester and from previous semesters.
Do not submit a file with the function/label main defined. You are also not permitted to start your label names with two underscores (__). You will obtain a zero for the assignment if you do this.
Register Conventions
You must follow the register conventions taught in lecture and reviewed in recitation. Failure to follow them will result in loss of credit when we grade your work. Here is a brief summary of the register conventions and how your use of them will impact grading:
It is the callee’s responsibility to save any $s registers it overwrites by saving copies of those registers on the stack and restoring them before returning.
If a function calls a secondary function, the caller must save $ra before calling the callee. In addition, if the caller wants a particular $a, $t or $v register’s value to be preserved across the secondary function call, the best practice would be to place a copy of that register in an $s register before making the function call.
A function which allocates stack space by adjusting $sp must restore $sp to its original value before returning.
Registers $fp and $gp are treated as preserved registers for the purposes of this course. If a function modifies one or both, the function must restore them before returning to the caller. There really is no reason for your code to touch the $gp register, so leave it alone.
The following practices will result in loss of credit:
“Brute-force” saving of all $s registers in a function or otherwise saving $s registers that are not overwritten by a function.
Callee-saving of $a, $t or $v registers as a means of “helping” the caller.
“Hiding” values in the $k, $f and $at registers or storing values in main memory by way of offsets to $gp. This is basically cheating or at best, a form of laziness, so don’t do it. We will comment out any such code we find.
How to Test Your Functions
To test your implemented functions, open the provided “main” files in MARS. Next, assemble the main file and run it. MARS will include the contents of any .asm files referenced with the .include directive(s) at the end of the file and then append the contents of your hwk3.asm file before assembling the program.
Each main file calls a single function with one of the sample test cases and prints any return value(s). You will need to change the arguments passed to the functions to test your functions with the other cases. To test each of your functions thoroughly, create your own test cases in those main files. Your submission will not be graded using the examples provided in this document or using the provided main file(s). Do not submit your main files with your hwk3.asm file – we will delete them. Also please note that these testing main files will not be used in grading.
Again, any modifications to the main files will not be graded. You will submit only your hwk3.asm for grading. Make sure that all code required for implementing your functions is included in the hwk3.asm file. To make sure that your code is self-contained, try assembling your hwk3.asm file by itself in MARS. If you get any errors (such as a missing label), this means that your hwk3.asm file is attempting to reference labels in the main file, which will not have the same names during grading!
Preliminaries
For this assignment you will be implementing a version of the classic game of Snak e in MIPS. Briefly, the snake consists of several body segments (represented by ASCII characters) that can move around a 2D game board, eating apples. Each time the snake eats an apple, its length increases by one. In the original game, the game ends when the snake runs into a wall, runs into itself, or runs out of bounds. We will implement the same basic logic in our version.
The head of the snake is represented by the ASCII character ‘1’, with each following segment in the body given by consecutive digit characters (i.e., ‘2’, ‘3’, ‘4’, etc.) After ‘9’, body segments are denoted with uppercase letters, starting with ‘A’ and going all the way through the alphabet to ‘Z’. Thus, the snake can be at most 9 + 26 = 35 body segments in length.
The game board consists of a 2D grid of ASCII characters (“slots”), with the period, ‘.’, representing an empty slot; a pound sign, ‘#’, representing a wall; the lowercase letter ‘a’ representing an apple, and the digits and uppercase letters representing parts of the snake. Below is an example of an in-progress game on a 15x30 grid (15 rows, 30 columns).
..............................
..............................
......######......######......
......#................#......
......#................#......
......#................#......
..............................
....a..........123............
.................4............
......#..........56....#......
......#................#......
......#................#......
......######......######......
..............................
..............................
The player scores points as he/she moves the snake about the game grid and eats apples. Scoring is explained later.
Data Structures
To characterize the state of the game we track several pieces of data in the form of a struct , which is like an object with data fields only (no methods). We will call this data type GameState throughout this document.
num_rows: the number of rows in the grid (an unsigned byte)
num_cols: the number of columns in the grid (an unsigned byte)
head_row: the row of the snake’s head (an unsigned byte)
head_col: the column of the snake’s head (an unsigned byte)
length: the length of the snake (an unsigned byte)
grid: the contents of the game board (a null-terminated string that stores the board in row-major order)
The topmost row of the grid is row #0, and the leftmost column is column #0. Similarly, the bottom-most row is num_rows-1 , and the rightmost column is num_cols-1 . The maximum grid size is 99x99.
As an example, the game board shown above would be represented in MIPS as follows:
.align 2 # ensures that the data structure is on a word-aligned boundary state: # name of the data structure (game state)
.byte 15 # num_rows (byte #0)
.byte 30 # num_cols (byte #1)
.byte 7 # head_row (byte #2)
.byte 15 # head_col (byte #3)
.byte 6 # length (byte #4)
# Game grid: (bytes #5-end)
.asciiz
"..................................................................######.
.....######............#................#............#................#...
.........#................#........................................a......
....123.............................4..................#..........56....#.
...........#................#............#................#............### ###......######...........................................................
......."
To access the fields of the struct, we need to be provided its starting address (i.e., the address of the num_rows field). From there we can use lbu with appropriate offsets to access the field values, including the contents of the grid.
Part 1: Load a Game Board from Disk
int, int load_game(GameState* state, string filename)
The load_game function reads the contents of a file that defines a game board and initializes the referenced GameState data structure. The notation GameState* indicates that state is the starting address of a GameState struct. You may assume that state points to a block of memory large enough to store the struct represented inside the file. If the file exists, you may assume that the file’s contents are valid. If the file does not exist, the function simply returns -1, -1.
The file format is very simple:
# of rows in the game grid # of columns in the game grid
contents of grid in which each row is on its own line
Important note: on Microsoft Windows, a line ends with the character combination \r\n , whereas on Mac and other Unix-like operating systems like Linux, a line ends only with \n . Your code must be able to handle both line-ending styles. All lines of the file, including the final row of the grid, are guaranteed to end with \n or \r\n . As an example, the above game grid would be saved in a Microsoft Windows-generated file as:
15\r\n
30\r\n
..............................\r\n
..............................\r\n
......######......######......\r\n
......#................#......\r\n
......#................#......\r\n
......#................#......\r\n
..............................\r\n
....a..........123............\r\n
.................4............\r\n
......#..........56....#......\r\n
......#................#......\r\n
......#................#......\r\n
......######......######......\r\n
..............................\r\n
..............................\r\n
Again, your code must be able to contend with \r\n line-endings and with \n line-endings.
To assist with reading and writing files, MARS provides several system calls:
Service
Code
in $v0
Arguments
Results
open file
13
$a0 = address of null-terminated filename string
$a1 = flags
$a2 = mode
$v0 contains file descriptor
(negative if error)
read from file
14
$a0 = file descriptor
$a1 = address of input buffer
$a2 = maximum # of characters to read
$v0 contains # of characters read (0 if end-of-file, negative if error)
write to file
15
$a0 = file descriptor
$a1 = address of output buffer (negative if error)
$a2 = maximum # of characters to write
$v0 contains # of characters written
close file
16
$a0 = file descriptor
Service 13: MARS implements three flag values: 0 for read-only, 1 for write-only with create, and 9 for write-only with create and append. It ignores mode. The returned file descripto r will be negative if the operation failed. MARS maintains file descriptors internally and allocates them starting with 3. File descriptors 0, 1 and 2 are always open for reading from standard inpu t, writing to standard outpu t, and writing to standard erro r, respectively. An example of how to use these syscalls can be found on the MARS syscall web page.
Some advice: read the contents of the file one character at a time using system call #14. This system call requires a memory buffer to hold the character read from the disk. You should allocate four bytes of memory on the stack (by adjusting $sp) to store that byte temporarily. Discard newline characters (both \r and \n ) as you read them and do not store them in the GameState struct. Finally, remember to reset $sp once you have finished reading the file contents and to close the file with system call #16.
MARS can be a little buggy when it comes to opening files. Therefore, either:
put all your .asm and .txt game files in the same directory as the MARS .jar file, or ● use absolute path names when giving the filename in your testing mains.
The function load_game takes the following arguments, in this order:
state: a pointer to (i.e., starting memory address of) an uninitialized GameState struct large enough to hold the contents of the game represented by file to be loaded. Assume that the contents of the struct are filled with random garbage.
filename: a string containing the filename to open and read the contents of
Returns in $v0:
-1 if the input file does not exist; otherwise: 1 if a lowercase letter ‘a’ representing an apple was found in the file; 0, if not
Returns in $v1:
-1 if the input file does not exist; otherwise: the number of wall characters, ‘#’, found in the file
Additional requirements:
The function must not write any changes to main memory except as needed. If the file does not exist, no changes may be written to state .
Example #1:
filename = “game01.txt”
Return value in $v0: 1
Return value in $v1: 6
Contents of state must be updated to reflect the data structure defined in the file.
Example #2:
filename = “game02.txt”
Return value in $v0: 0
Return value in $v1: 16
Contents of state must be updated to reflect the data structure defined in the file.
Example #3:
filename = “junk.txt” (non-existent file)
Return value in $v0: -1
Return value in $v1: -1
Contents of state must be left unchanged.
Part 2: Get the Character Stored in the Game Grid
int get_slot(GameState* state, byte row, byte col)
The function get_slot returns the character stored in the slot at state.grid[row][col]. A recommendation: use lbu to read tile values from the game board. If row is outside the valid range or col is outside the valid range for the given GameState, the function returns -1. For instance, the valid range for the row argument is [0, board.num_rows-1].
The function takes the following arguments, in this order:
state: a pointer to a valid GameState struct
row: the row of the grid array from where we want to read a value. This is an 8-bit, two’s complement value.
col: the column of the grid array from where we want to read a value. This is an 8-bit, two’s complement value.
Returns in $v0:
the character located at grid[row][col], or
-1 for the error condition explained above
Additional requirements:
The function must not write any changes to main memory.
Examples:
In the examples below, imagine that we initialized state by loading it with game01.txt:
5
12
............
.a.#.1..#...
...#.2..#...
...#.3..#...
.....4567...
Return values other than -1 are the ASCII codes of the returned characters.
row
col
Return Value ($v0)
1
5
49
2
3
35
3
16
-1
-3
5
-1
Part 3: Set the Character Stored in the Game Grid
int set_slot(GameState* state, byte row, byte col, char ch)
The function set_slot sets the value stored in the slot at state.grid[row][col] to ch. A recommendation: use sb to write a character in the game grid. If row is outside the valid range or col is outside the valid range for the given GameState, the function returns -1. Otherwise,the function returns ch. The function does not need to validate that ch is a valid character for the Snake game.
The function takes the following arguments, in this order:
state: a pointer to a valid GameState struct
row: the row of the grid array at which we want to write a character. This is an 8-bit, two’s complement value.
col: the column of the grid array at which we want to write a character. This is an 8-bit, two’s complement value.
ch: the character to write at grid[row][col]
Returns in $v0:
ch, provided that row and col are both valid, or
-1 for the error condition explained above
Additional requirements:
The function must not write any changes to main memory except as necessary.
Examples:
In the examples below, imagine that we initialized state by loading it with game01.txt.
5
12
............
.a.#.1..#...
...#.2..#...
...#.3..#...
.....4567...
Example #1:
row = 0 col = 5 ch = ‘Q’
Return value in $v0: 81 Updated game state:
.....Q......
.a.#.1..#...
...#.2..#...
...#.3..#...
.....4567...
Example #2: row = 2 col = 11 ch = ‘Z’
Return value in $v0: 90 Updated game state:
............
.a.#.1..#...
...#.2..#..Z ...#.3..#...
.....4567...
Example #3: row = 3 col = 7 ch = ‘?’
Return value in $v0: 63 Updated game state:
............
.a.#.1..#...
...#.2..#...
...#.3.?#...
.....4567...
Example #4: row = -5 col = 2 ch = ‘*’
Return value in $v0: -1
The game state should be unchanged.
Part 4: Place an Apple in the Game Grid
int, int place_next_apple(GameState* state, byte[] apples, int apples_length)
The function place_next_apple searches the apples array for a valid apple to place in the game grid and places it. The apples array consists of apples_length pairs of integers stored in row-major order that represent (row, col) coordinates where an apple hasn’t yet been placed in the game grid. The pair (-1, -1) in the array indicates that an apple has already been placed at that position in the grid. For example, perhaps that apple was placed during a prior call to place_next_apple.
As an example, suppose apples begins [4, 2, 7, 3, 1, 9, -1, -1, ...]. This indicates that potential locations in the game grid where an apple could be placed include (4, 2), (7, 3), (1, 9), among others. To find an apple to place in the grid, the function will start with (4, 2) and see if that position in the grid is empty (i.e., state.grid[4][2] contains ‘.’). If so, state.grid[4][2] is changed to ‘a’ with set_slot, and then apples[0] and apples[1] are both changed to -1. On the other hand, suppose state.grid[4][2] contains a wall or a part of the snake or another apple. In this case, place_next_apple skips over the (4, 2) pair and tries (7, 3). The search continues in this fashion until a valid apple can be placed. When the pair (-1, -1) is encountered in apples, place_next_apple skips over it and continues to the next pair. You may safely assume that place_next_apple will always be able to find a valid apple to place in the game grid.
The function takes the following arguments, in this order:
state: a pointer to a valid GameState struct
apples: an array of apples_length pairs of 8-bit, two’s complement integers ● apples_length: the number of pairs of integers in apples
Returns in $v0:
the row of the apple that was placed
Returns in $v1:
the column of the apple that was placed
Additional requirements:
The function must not write any changes to main memory except as necessary.
The function must call get_slot and set_slot.
Examples:
In the examples below, imagine that we initialized state by loading it with game01.txt :
5
12
............
.a.#.1..#...
...#.2..#...
...#.3..#...
.....4567...
Example #1:
apples = [3, 2, 1, 4, 4, 3] apples_length = 3
Return value in $v0: 3
Return value in $v1: 2
Updated apples : [-1, -1, 1, 4, 4, 3] Updated game state:
............
.a.#.1..#...
...#.2..#...
..a#.3..#...
.....4567...
Example #2:
apples = [2, 5, 3, 5, 1, 4, 4, 3, 2, 9] apples_length = 5
Return value in $v0: 1
Return value in $v1: 4
Updated apples : [2, 5, 3, 5, -1, -1, 4, 3, 2, 9] Updated game state:
............
.a.#a1..#...
...#.2..#...
...#.3..#...
.....4567...
Example #3:
apples = [2, 5, 3, 5, -1, -1, -1, -1, 4, 1, 4, 3, 2, 9] apples_length = 7
Return value in $v0: 4
Return value in $v1: 1
Updated apples: [2, 5, 3, 5, -1, -1, -1, -1, -1, -1, 4, 3, 2, 9] Updated game state:
............
.a.#.1..#...
...#.2..#...
...#.3..#...
.a...4567...
Part 5: Find the Next Body Segment of the Snake
int, int find_next_body_part(GameState* state, byte row, byte col, char target_part)
The function find_next_body_part inspects the four grid slots up, down, to the left and to the right of state.grid[row][col] for the character target_part. If target_part is found in one of those locations, then the coordinates of target_part are returned. Otherwise, the function returns -1, -1. The function also returns -1, -1 if row or col is outside its valid range for state.grid. This function is called by other functions inside of a loop to find all of the body segments of the snake.
The function takes the following arguments, in this order:
state: a pointer to a valid GameState struct
row: the row of the grid array slot around which we search for target_part. This is an 8-bit, two’s complement value.
col: the column of the grid array slot around which we search for target_part. This is an 8-bit, two’s complement value.
target_part: the part of the snake’s body segment we are looking for
Returns in $v0:
the row of target_part, if it was found; -1, otherwise
Returns in $v1:
the column of target_part, if it was found; -1, otherwise
Additional requirements:
The function must not write any changes to main memory.
The function must call get_slot.
Examples:
In the examples below, imagine that we initialized state by loading it with game01.txt :
5
12
............
.a.#.1..#...
...#.2..#...
...#.3..#...
.....4567...
row
col
target_part
Return Values
3
5
‘4’
4, 5
4
7
‘7’
4, 8
3
5
‘9’
-1, -1
Part 6: Slide (Slither) the Snake Forward One Slot
int slide_body(GameState* state, byte head_row_delta, byte head_col_delta, byte[] apples, int apples_length)
The function slide_body attempts to slide (slither) the snake one place forward in the direction
(head_row_delta, head_col_delta):
(1, 0): move the snake down one row, starting from the head and with the body following, one segment at a time
(-1, 0): move the snake up one row
(0, 1): move the snake right one column
(0, -1): move the snake left one column
This function does not merely slide the entire snake up, down, left or right. Rather, the pair (head_row_delta, head_col_delta) indicates how the snake’s head should lead the movement. The fields state.head_row and state.head_row must be updated, as appropriate. For instance, suppose the snake is in the configuration below:
..........
.a........
.....1....
.....234..
.......56.
and we call slide_body with (0, -1) for ( head_row_delta, head_col_delta). The snake is now positioned as follows:
..........
.a........
....12....
.....345..
.......6..
Suppose we call slide_body with (0, -1) for ( head_row_delta, head_col_delta) two more times. Now we have this:
..........
.a........
..1234....
.....56...
..........
Notice how the body slithers along with the head leading the way.
The function takes the following arguments, in this order:
state: a pointer to a valid GameState struct
head_row_delta: the vertical direction in which to move the snake’s head. This is an 8-bit, two’s complement value.
head_col_delta: the horizontal direction in which to move the snake’s head. This is an 8-bit, two’s complement value.
apples: an array of apples_length pairs of 8-bit, two’s complement integers apples_length: the number of pairs of integers in apples
You may assume that the pair ( head_row_delta, head_col_delta) is one of the four combinations of -1, 0, 1 listed above.
The function returns a code in $v0 which indicates what happened during the attempted move:
0 if the snake’s head moved onto an empty slot (i.e., ‘.’) and slithered forward without any trouble
1 if the snake’s head moved onto a slot containing an apple (i.e., ‘a’) and slithered forward without any trouble
-1 if the snake could not move forward because doing so would take it outside the game board, would intersect a wall slot, or would intersect with a body part of the snake. Return -1 without making any changes to the state of the game grid.
To make the snake slither forward, first move the head one slot forward in the desired direction. Then, call find_next_body_part repeatedly in a loop to iteratively move each other part forward one place. Specifically, body segment ‘2’ will take the place of ‘1’ (the head), ‘3’ will take the place of ‘2’, ‘4’ will take the place of ‘3’, and so on, until the final body segment of the snake has moved.
In the case that the snake is about to move onto an apple, call place_next_apple before moving any segment of the snake forward. Then have the entire snake slither forward in the desired direction, with the head replacing the apple. Note that slide_body does not increase the length of the snake when the snake consumes an apple. That task is handled by a different function
(increase_snake_length ).
Additional requirements:
The function must not write any changes to main memory except as necessary.
The function must call get_slot , set_slot, place_next_apple and find_next_body_part.
Examples:
In the examples below, imagine that we initialized state by loading it with game03.txt :
8
14
..............
......##......
..............
..#..a.....#..
..#..1234..#..
........56...E
......##.7..CD .........89AB.
Example #1:
head_row_delta = 0 head_col_delta = -1 apples = [1, 7, 3, 2, 1, 4, 4, 3] apples_length = 4
Return value in $v0: 0
Expected apples: [1, 7, 3, 2, 1, 4, 4, 3] Updated game state:
..............
......##......
..............
..#..a.....#..
..#.12345..#..
........67....
......##.8..DE
.........9ABC.
Example #2:
head_row_delta = -1 head_col_delta = 0 apples = [1, 7, 3, 2, 1, 4, 4, 3] apples_length = 4
Return value in $v0: 1
Updated apples: [1, 7, 3, 2, -1, -1, 4, 3] Updated game state:
..............
....a.##......
..............
..#..1.....#..
..#..2345..#..
........67....
......##.8..DE
.........9ABC.
Example #3:
head_row_delta = 0 head_col_delta = 1 apples = [1, 7, 3, 2, 1, 4, 4, 3] apples_length = 4
Return value in $v0: -1 apples remains unchanged.
Game state remains unchanged.
Part 7: Add a New Body Segment to the Tail of the Snake
int add_tail_segment(GameState* state, char direction, byte tail_row, byte tail_col)
The function add_tail_segment takes the coordinates of the snake’s tail at ( tail_row, int tail_col) and attempts to add a new segment to the tail of the snake in the given direction ( char direction). Note that this function is essentially a helper function for increase_snake_length. For example, suppose that direction = ‘U’ and tail_row = 3, tail_col = 6. These arguments indicate that the function should try to add a tail segment at index state.grid[2][6]. You may assume that state.grid[tail_row][tail_col] is the valid, actual tail of the snake. The new tail segment may only be placed on an empty grid slot, denoted by the period character, ‘.’. If the new tail segment is successfully added, the length field of the state object must be incremented by 1. That new length is returned by add_tail_segment. If the tail segment cannot be added (e.g., due to a collision or because the snake’s length is already 35), the function makes no changes to main memory and returns -1.
The function takes the following arguments, in this order:
state: a pointer to a valid GameState struct
direction: the direction relative to the tail of the snake to append a new segment. Must be one of ‘U’, ‘D’, ‘L’ or ‘R’. For example, ‘D’ indicates that the new tail segment should be placed in the row immediately below the current tail segment (if possible). As another example, ‘R’ indicates that the new tail segment should be placed in the column immediately to the right of the current tail segment (if possible).
tail_row: the row number of the final segment in the tail. You may assume that this value is valid. This is an 8-bit, two’s complement value.
tail_col: the column number of the final segment in the tail. You may assume that this value is valid. This is an 8-bit, two’s complement value.
Returns in $v0:
The updated value of length if the new tail segment was placed. If the tail segment could not be placed or if direction is not one of ‘U’, ‘D’, ‘L’ or ‘R’, then $v0 is -1.
If direction is invalid, the function must not make any changes to memory and return -1.
Additional requirements:
The function must not write any changes to main memory except as necessary.
The function must call get_slot and set_slot.
Examples:
In the examples below, imagine that we initialized state by loading it with game01.txt:
5
12
............
.a.#.1..#...
...#.2..#...
...#.3..#...
.....4567...
Example #1:
tail_row = 4 tail_col = 8 direction = ‘R’
Return value in $v0: 8 Updated game state:
............
.a.#.1..#...
...#.2..#...
...#.3..#...
.....45678..
Example #2:
tail_row = 4 tail_col = 8 direction = ‘D’
Return value in $v0: -1
Game state remains unchanged.
Example #3:
tail_row = 4 tail_col = 8 direction = ‘U’
Return value in $v0: -1
Game state remains unchanged.
Part 8: Increase the Length of the Snake’s Body
int increase_snake_length(GameState* state, char direction)
The function increase_snake_length calls upon find_next_body_part (iteratively) and then add_tail_segment to find the tail of the snake in the game and append a new tail segment in the opposite direction that the snake’s head is moving. For example, if direction = ‘L’ , then increase_snake_length will attempt to add a new segment to the right of the current tail. Likewise, if direction = ‘D’ , then increase_snake_length will attempt to add a new segment above the current tail segment. If this attempt is not successful, the function will search the cells in the neighborhood of the tail in counterclockwise order to find a position for the new tail segment. This aspect of the function will be made clear with a set of examples below. Note that increase_snake_length does not change the value of the length field in state .
The function takes the following arguments, in this order:
state: a pointer to a valid GameState struct
direction: the direction that the snake’s head is moving. Must be one of ‘U’, ‘D’, ‘L’ or ‘R’.
Returns in $v0:
length if a new tail segment was successfully added by add_tail_segment, or -1 if not.
If direction is invalid, the function must not make any changes to memory and return -1.
Additional requirements:
The function must not write any changes to the main memory itself. Any changes written to memory must be made via add_tail_segment.
The function must call find_next_body_part and add_tail_segment .
Example #1: The snake’s head is moving to the left (direction = ‘L’), so increase_snake_length calls add_tail_segment to try appending a segment to the right end of the current tail. add_tail_segment returns 8, indicating success.
The game state has been initialized by loading it with game01.txt:
5
12
............
.a.#.1..#...
...#.2..#...
...#.3..#...
.....4567...
direction = ‘L’
Return value in $v0: 8 Updated game state:
............
.a.#.1..#...
...#.2..#...
...#.3..#...
.....45678..
Example #2: The snake’s head is moving to the up (direction = ‘U’), so increase_snake_length calls add_tail_segment to try appending a segment below the current tail. add_tail_segment returns -1 because a body segment is in the way. So, increase_snake_length calls add_tail_segment to try appending a segment to the right of the current tail. add_tail_segment once again returns -1 because that slot is out of bounds.
increase_snake_length continues by calling add_tail_segment to try appending a segment to the above the end of the current tail. add_tail_segment returns 15, indicating success.
The game state has been initialized by loading it with game04.txt:
8
14
..............
......##......
.............. ..#........#..
a.#..1234..#.. ........56...E
......##.7..CD
.........89AB.
direction = ‘U’
Return value in $v0: 15 Updated game state:
..............
......##......
.............. ..#........#..
a.#..1234..#.F ........56...E
......##.7..CD .........89AB.
Example #3: The snake’s head is moving down (direction = ‘D’ ) , so increase_snake_length calls add_tail_segment to try appending a segment above the current tail. add_tail_segment returns -1 because an apple is in the way. So, increase_snake_length calls add_tail_segment to try appending a segment left of the current tail. add_tail_segment returns 14, indicating success.
The game state has been initialized by loading it with game05.txt :
8
14
..............
......##......
..............
..#........#..
..#..1234..#a.
........56..D.
......##.7..C.
.........89AB.
direction = ‘D’
Return value in $v0: 14
Updated game state:
..............
......##......
..............
..#........#..
..#..1234..#a.
........56.ED.
......##.7..C.
.........89AB.
Example #4: The snake’s head is moving up (direction = ‘U’), so increase_snake_length calls add_tail_segment to try appending a segment below the current tail. add_tail_segment returns -1, indicating failure. Next, increase_snake_length will add_tail_segment three more times, attempting to append tail segments to the right, above and to the left of the current tail segment. In each case, add_tail_segment returns -1, indicating failure. Because in every case add_tail_segment returned -1, increase_snake_length returns -1.
The game state has been initialized by loading it with game06.txt:
5
5
....a ..1..
.#2#.
.#3#.
.###.
direction = ‘U’
Return value in $v0: -1
The grid after call to increase_snake_length remains unchanged.
Part 9: Move the Snake Forward One Slot
int, int move_snake(GameState* state, char direction, byte[] apples, int apples_length)
The function move_snake is a top-level function of the game that moves the snake through the game grid one slot at a time. Given the value of direction, it makes an appropriate call to slide_body in an attempt to move the snake forward one slot in the grid.
If the return value of slide_body is -1, then move_snake returns (0, -1) to indicate, respectively, that no points were scored during this call to move_snake and that ultimately the movement was unsuccessful.
On the other hand, if slide_body returned 1, this means the snake ate an apple. In response, move_snake calls increase_snake_length to attempt to increase the length of the snake, using the direction of the head’s movement as the value for direction. The call to increase_snake_length returns either -1, indicating failure to increase the length of the body, or a value other than -1 to indicate success. If increase_snake_length failed, then move_snake returns (0, -1). Otherwise, it returns (100, 1) indicating that 100 points were scored.
The last possibility for slide_body is that it returned 0, indicating that the snake’s head (and body) moved forward successfully and the head moved into an empty slot. In this case, move_snake returns (0, 1) to indicate that no points were scored, but that the snake successfully moved forward.
Note that if direction is invalid, move_snake returns (0, -1).
The function takes the following arguments, in this order:
state: a pointer to a valid GameState struct
direction: the direction that the snake’s head is moving. Must be one of ‘U’, ‘D’, ‘L’ or ‘R’.
apples: an array of apples_length pairs of (signed) integers ● apples_length: the number of pairs of integers in apples
Returns in $v0:
The number of points scored during this function call. Must be 0 or 100.
Returns in $v1:
1 if increase_snake_length successfully added a new body segment to the tail of the snake or the snake simply moved forward into an empty slot;
or -1 if increase_snake_length does not add a segment or if direction is invalid.
Additional requirements:
The function must not write any changes to the main memory itself. Any changes written to memory must be made via slide_body and increase_snake_length.
The function must call slide_body and increase_snake_length.
Example #1:
Suppose that we initialized state by loading it with game03.txt:
8
14
..............
......##......
..............
..#..a.....#..
..#..1234..#..
........56...E
......##.7..CD .........89AB.
direction = ‘U’
apples = [1, 2, 2, 9, 0, 5, 1, 7, 6, 10, 3, 10, 3, 11, 2, 10, 2, 1, 2, 4,
2, 5, 1, 13] apples_length = 12
Return value in $v0: 100
Return value in $v1: 1
Updated apples : [-1, -1, 2, 9, 0, 5, 1, 7, 6, 10, 3, 10, 3, 11, 2, 10, 2, 1,
2, 4, 2, 5, 1, 13]
Updated game state:
..............
..a...##......
..............
..#..1.....#..
..#..2345..#..
........67....
......##.8..DE
.........9ABCF
Example #2:
Suppose that we initialized state by loading it with game03.txt :
8
14
..............
......##......
..............
..#..a.....#..
..#..1234..#..
........56...E
......##.7..CD .........89AB.
direction = ‘D’
apples = [1, 2, 2, 9, 0, 5, 1, 7, 6, 10, 3, 10, 3, 11, 2, 10, 2, 1, 2, 4,
2, 5, 1, 13] apples_length = 12
Return value in $v0: 0 Return value in $v1: 1 apples remains unchanged. Updated game state: ..............
......##......
..............
..#..a.....#..
..#..2345..#..
.....1..67....
......##.8..DE .........9ABC.
Example #3:
Suppose that we initialized state by loading it with game07.txt :
5
12
............
.a.#....#...
...#12..#...
...#.3..#...
.....4567...
direction = ‘L’ apples = [4, 4, 2, 7, 3, 5, 1, 8, 1, 7, 3, 11, 1, 11, 0, 4] apples_length = 8
Return value in $v0: 0 Return value in $v1: -1 apples remains unchanged.
Game state remains unchanged.
Example #4:
Suppose that we initialized state by loading it with game07.txt :
5
12
............
.a.#....#...
...#12..#...
...#.3..#...
.....4567...
direction = ‘Z’ apples = [4, 4, 2, 7, 3, 5, 1, 8, 1, 7, 3, 11, 1, 11, 0, 4] apples_length = 8
Return value in $v0: 0 Return value in $v1: -1 apples remains unchanged.
Game state remains unchanged.
Part 10: Simulate a Game of Snake
int, int simulate_game(GameState* state, string filename, string directions, int num_moves_to_execute, byte[] apples, int apples_length)
The function simulate_game simulates at most num_moves_to_execute moves of a game of Snake. The initial configuration of the game grid is given by the contents of the file with name filename. The null-terminated string directions consists of the uppercase letters ‘L’, ‘R’, ‘U’ and ‘D’ in any order and combination and represents the attempted moves of the player. The array apples contains the positions of potential apples that can be placed during the simulation. The simulation algorithm that you must implement is as follows:
Call load_game to attempt to initialize the uninitialized state struct with the contents of filename.If load_game fails to initialize the struct, simulate_game returns -1, -1 and makes no changes to main memory. Otherwise, we continue on to the next step.
If load_game did not find an apple while initializing the state struct, call place_next_apple with appropriate arguments.
Initial a variable to store the total score to 0.
While num_moves_to_execute is not yet 0 and the snake’s length is still less than 35 and the player hasn’t lost (e.g., by running into a wall or intersecting the snake itself) and there are still directions left to consider:Get the next direction to move from directions.
Call move_snake with appropriate arguments.If move_snake returned a score of 100, then add score * (state.length - 1) to the total score.
On the other hand, if move_snake returned a result of -1, indicating failure to move the snake, immediately break out of the loop.
Decrement num_moves_to_execute by 1.
Return the number of executed moves in $v0 and the total score in $v1.
The function takes the following arguments, in this order:
state: a pointer to (i.e., starting memory address of) an uninitialized GameState struct large enough to hold the contents of the game represented by file to be loaded. Assume that the contents of the struct are filled with random garbage.
filename: a string containing the filename to open and read the contents of
directions: a null-terminated string of characters representing directions to move the snake. Each character is guaranteed to be one of ‘U’, ‘D’, ‘L’ or ‘R’. You do not need to validate the contents of directions .
num_moves_to_execute: the maximum number of characters to read from directions apples: an array of apples_length pairs of 8-bit, two’s complement integers Available at 0($sp).
apples_length: the number of pairs of integers in apples . Available at 4($sp).
Returns in $v0:
The number of moves executed.
Returns in $v1:
The total score at the end of the simulated game.
Additional requirements:
The function must not write any changes to main memory.
The function must call load_game , place_next_apple and move_snake
Examples:
Due to the complexity of the function arguments, examples of simulate_game are provided in simulate_game_test1.txt, simulate_game_test2.txt , etc., along with function arguments.