COMP1521 doesn’t cover this, so we use global variables

// railroad_runners.c
// An adaptation of a popular mobile game involving subways and surfing.
// Prior to translating this program into MIPS assembly, you may wish to make
// use of the provided `railroad_runners.simple.c` file. In it, you can replace
// complex C constructs like loops with constructs which will be easier to
// translate into assembly. To help you check that you haven’t altered the
// behaviour of the game, you can run some automated tests using the command
// 1521 autotest railroad_runners.simple
// The simplified C version of this code is not marked.

#include
#include
#include

/* ————————————————————————– */
/* Constants */
/* ————————————————————————– */

#define TRUE 1
#define FALSE 0

#define PLAYER_RUNNING_SPRITE (“ð���”)
#define PLAYER_CROUCHING_SPRITE (“ð�§�”)
#define PLAYER_JUMPING_SPRITE (“ð�€ž”)

#define JUMP_KEY (‘w’)
#define LEFT_KEY (‘a’)
#define CROUCH_KEY (‘s’)
#define RIGHT_KEY (‘d’)
#define TICK_KEY (‘\”)
#define QUIT_KEY (‘q’)

#define ACTION_DURATION (3)
#define CHUNK_DURATION (10)

#define SCROLL_SCORE_BONUS (1)
#define TRAIN_SCORE_BONUS (1)
#define BARRIER_SCORE_BONUS (2)
#define CASH_SCORE_BONUS (3)

#define MAP_HEIGHT (20)
#define MAP_WIDTH (5)
#define PLAYER_ROW (1)

#define PLAYER_RUNNING (0)
#define PLAYER_CROUCHING (1)
#define PLAYER_JUMPING (2)

#define STARTING_COLUMN (MAP_WIDTH / 2)

// Hint: You don’t need this string in your MIPS translation.
#define EOF_MESSAGE \
(“EOF received. You don’t have to handle this case for your assignment ” \
“ð���.\n”)

#define TRAIN_SPRITE (“ð���”)
#define TRAIN_CHAR (‘t’)
#define BARRIER_SPRITE (“ð��§”)
#define BARRIER_CHAR (‘b’)
#define CASH_SPRITE (“ð��µ”)
#define CASH_CHAR (‘c’)
#define EMPTY_SPRITE (” “)
#define EMPTY_CHAR (‘ ‘)
#define WALL_SPRITE (“ð�§±”)
#define WALL_CHAR (‘w’)
#define RAIL_EDGE (‘|’)

// Chunks are a sequence of blocks that can spawn in the game.
// A block is just a character, and a chunk is an array of characters with a
// terminating null (just like a normal C string).

// The safe chunk ensure the player always has a path to go through.
char const SAFE_CHUNK[] = {
EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR,
EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR, ‘\0’,
char const CHUNK_1[] = {
EMPTY_CHAR, CASH_CHAR, EMPTY_CHAR, WALL_CHAR, CASH_CHAR,
CASH_CHAR, CASH_CHAR, BARRIER_CHAR, ‘\0’,
char const CHUNK_2[] = {
CASH_CHAR, EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR, BARRIER_CHAR,
EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR, CASH_CHAR, ‘\0’,
char const CHUNK_3[] = {
EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR, TRAIN_CHAR, TRAIN_CHAR, TRAIN_CHAR,
TRAIN_CHAR, TRAIN_CHAR, TRAIN_CHAR, TRAIN_CHAR, ‘\0’,
char const CHUNK_4[] = {
EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR, TRAIN_CHAR, TRAIN_CHAR,
TRAIN_CHAR, TRAIN_CHAR, EMPTY_CHAR, CASH_CHAR, ‘\0’,
char const CHUNK_5[] = {
EMPTY_CHAR, EMPTY_CHAR, CASH_CHAR, TRAIN_CHAR, TRAIN_CHAR, TRAIN_CHAR,
EMPTY_CHAR, TRAIN_CHAR, EMPTY_CHAR, EMPTY_CHAR, ‘\0’,
char const CHUNK_6[] = {
EMPTY_CHAR, EMPTY_CHAR, CASH_CHAR, BARRIER_CHAR, EMPTY_CHAR, EMPTY_CHAR,
CASH_CHAR, CASH_CHAR, EMPTY_CHAR, BARRIER_CHAR, ‘\0’,
char const CHUNK_7[] = {
EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR, WALL_CHAR, WALL_CHAR, WALL_CHAR,
WALL_CHAR, WALL_CHAR, WALL_CHAR, WALL_CHAR, ‘\0’,
char const CHUNK_8[] = {
CASH_CHAR, EMPTY_CHAR, CASH_CHAR, EMPTY_CHAR, CASH_CHAR, EMPTY_CHAR,
CASH_CHAR, EMPTY_CHAR, CASH_CHAR, EMPTY_CHAR, ‘\0’,
char const CHUNK_9[] = {
CASH_CHAR, EMPTY_CHAR, EMPTY_CHAR, WALL_CHAR, TRAIN_CHAR,
TRAIN_CHAR, TRAIN_CHAR, TRAIN_CHAR, TRAIN_CHAR, ‘\0’,
char const CHUNK_10[] = {
CASH_CHAR, CASH_CHAR, CASH_CHAR, CASH_CHAR, CASH_CHAR, CASH_CHAR,
CASH_CHAR, CASH_CHAR, CASH_CHAR, CASH_CHAR, ‘\0’,
char const CHUNK_11[] = {
EMPTY_CHAR, EMPTY_CHAR, CASH_CHAR, WALL_CHAR, TRAIN_CHAR,
TRAIN_CHAR, TRAIN_CHAR, TRAIN_CHAR, ‘\0’,
char const CHUNK_12[] = {
EMPTY_CHAR,
EMPTY_CHAR,
CASH_CHAR,
char const CHUNK_13[] = {
EMPTY_CHAR, EMPTY_CHAR, EMPTY_CHAR, WALL_CHAR, WALL_CHAR, ‘\0’,

// Hint: You can ignore `const` for the purposes of MIPS translation.

// A list of all chunks that can spawn in the game.
// Chunk 0 should always be safe chunk.
char const *const CHUNKS[] = {
SAFE_CHUNK, CHUNK_1, CHUNK_2, CHUNK_3, CHUNK_4, CHUNK_5, CHUNK_6,
CHUNK_7, CHUNK_8, CHUNK_9, CHUNK_10, CHUNK_11, CHUNK_12, CHUNK_13,
#define SAFE_CHUNK_INDEX (0)
#define NUM_CHUNKS (14)

/* ————————————————————————– */
/* Types */
/* ————————————————————————– */

// A data structure for tracking which blocks to spawn next in each column.
// A block is a char, and a chunk is an array of chars with a terminating null
// character.
struct BlockSpawner {
// An array of pointers pointing to the next block to spawn in each column.
// E.g. next_block[0] points to the next block to spawn in the leftmost
// column, and next_block[1] points to the next block to spawn in the second
// leftmost column.
char const *next_block[MAP_WIDTH];
// Which index is the current safe column
int safe_column;

// A data structure for tracking the state of the player.
struct Player {
// Which column the player is in.
int column;
// The state of the player (running, crouching, jumping).
int state;
// How many ticks the player has left in their current action.
int action_ticks_left;
// Whether the player is currently on a train.
int on_train;
// The player’s score.
int score;

/* ————————————————————————– */
/* Global Variables */
/* ————————————————————————– */

// In a real C program, most of these would be stack variables instead of global
// variables. However, COMP1521 doesn’t cover this, so we use global variables
// and pass them around as pointers.

// Global variables are prefixed with `g_`.

// The block spawner.
struct BlockSpawner g_block_spawner = {
.next_block = {0},
.safe_column = STARTING_COLUMN,

// The game map, (0, 0) is the bottom left corner.
char g_map[MAP_HEIGHT][MAP_WIDTH];

// The current state of the player.
struct Player g_player = {
.column = STARTING_COLUMN,
.state = PLAYER_RUNNING,
.action_ticks_left = 0,
.on_train = FALSE,
.score = 0,

// Hint: `g_rng_state` is only used in the provided code. You shouldn’t have to
// use it directly.

// The random number generator state.
unsigned g_rng_state = 1;

/* ————————————————————————– */
/* Function Prototypes */
/* ————————————————————————– */

/* ——————————– Subset 0 ——————————– */
void print_welcome(void);

/* ——————————– Subset 1 ——————————– */
char get_command(void);
int main(void);
void init_map(char map[MAP_HEIGHT][MAP_WIDTH]);

/* ——————————– Subset 2 ——————————– */
int run_game(char map[MAP_HEIGHT][MAP_WIDTH], struct Player *player,
struct BlockSpawner *block_spawner, char input);
void display_game(char map[MAP_HEIGHT][MAP_WIDTH], struct Player *player);
int maybe_print_player(struct Player *player, int row, int column);
void handle_command(char map[MAP_HEIGHT][MAP_WIDTH], struct Player *player,
struct BlockSpawner *block_spawner, char input);

/* ——————————– Subset 3 ——————————– */
int handle_collision(char map[MAP_HEIGHT][MAP_WIDTH], struct Player *player);
void maybe_pick_new_chunk(struct BlockSpawner *block_spawner);
void do_tick(char map[MAP_HEIGHT][MAP_WIDTH], struct Player *player,
struct BlockSpawner *block_spawner);

/* ——————————– Provided ——————————– */
void get_seed(void);
unsigned rng(void);
char read_char(void);

/* ————————————————————————– */
/* Function Implementations */
/* ————————————————————————– */

// Subset 0
// Print the welcome message.
void print_welcome(void) {
// Hint: All the strings you need to print have already been defined at the
// top of the starter code, in the `.data` section.

printf(“Welcome to Railroad Runners!\n”);
printf(“Use the following keys to control your character: (%s):\n”,
PLAYER_RUNNING_SPRITE);
printf(“%c: Move left\n”, LEFT_KEY);
printf(“%c: Move right\n”, RIGHT_KEY);
printf(“%c: Crouch (%s)\n”, CROUCH_KEY, PLAYER_CROUCHING_SPRITE);
printf(“%c: Jump (%s)\n”, JUMP_KEY, PLAYER_JUMPING_SPRITE);
printf(“or press %c to continue moving forward.\n”, TICK_KEY);
printf(“You must crouch under barriers (%s)\n”, BARRIER_SPRITE);
printf(“and jump over trains (%s).\n”, TRAIN_SPRITE);
printf(“You should avoid walls (%s) and collect cash (%s).\n”, WALL_SPRITE,
CASH_SPRITE);
“On top of collecting cash, running on trains and going under barriers ”
“will get you extra points.\n”);
printf(“When you’ve had enough, press %c to quit. Have fun!\n”, QUIT_KEY);

// Subset 1
// Basic logic to ensure the user input is valid.
// Returns the command the user entered as a character.
char get_command(void) {
// Loop until the user enters a valid command
while (TRUE) {
// Hint: You must translate the below line as a function call,
// *not* as a syscall.
char input = read_char();

if (input == QUIT_KEY || input == JUMP_KEY || input == LEFT_KEY ||
input == CROUCH_KEY || input == RIGHT_KEY || input == TICK_KEY) {
return input;

printf(“Invalid input!\n”);

// Subset 1
// Entry point for the game, contains the main game loop.
int main(void) {
// Hint: Any variable prefixed with `g_` is a global variable, and resides
// in the `.data` section.

print_welcome();
get_seed();
init_map(g_map);

display_game(g_map, &g_player);
} while (run_game(g_map, &g_player, &g_block_spawner, get_command()));

printf(“Game over, thanks for playing ð���!\n”);

// Subset 1
// Initialise the map to be empty (plus some hard coded things for testing).
// Parameters:
// – map: The game map.
void init_map(char map[MAP_HEIGHT][MAP_WIDTH]) {
for (int i = 0; i < MAP_HEIGHT; ++i) { for (int j = 0; j < MAP_WIDTH; ++j) { map[i][j] = EMPTY_CHAR; // Hard code some things onto the map for easier testing. map[6][0] = WALL_CHAR; map[6][1] = TRAIN_CHAR; map[6][2] = CASH_CHAR; map[8][2] = BARRIER_CHAR; // Subset 2 // A single iteration of the user's input. // Most of the work is delegated to handle_command and handle_collision. // Parameters: // - map: The game map. // - player: A pointer to the player struct containing its state. // - block_spawner: A pointer to the struct which tracks which blocks to spawn // next. // - input: The command the user entered. // Returns FALSE if the user has quit or lost, TRUE otherwise. int run_game(char map[MAP_HEIGHT][MAP_WIDTH], struct Player *player, struct BlockSpawner *block_spawner, char input) { if (input == QUIT_KEY) { return FALSE; handle_command(map, player, block_spawner, input); return handle_collision(map, player); // Subset 2 // Display the current state of the game. // Parameters: // - map: The game map. // - player: A pointer to the player struct containing its state. void display_game(char map[MAP_HEIGHT][MAP_WIDTH], struct Player *player) { for (int i = MAP_HEIGHT - 1; i >= 0; –i) {
for (int j = 0; j < MAP_WIDTH; ++j) { putchar(RAIL_EDGE); if (!maybe_print_player(player, i, j)) { char map_char = map[i][j]; if (map_char == EMPTY_CHAR) { printf(EMPTY_SPRITE); } else if (map_char == BARRIER_CHAR) { printf(BARRIER_SPRITE); } else if (map_char == TRAIN_CHAR) { printf(TRAIN_SPRITE); } else if (map_char == CASH_CHAR) { printf(CASH_SPRITE); } else if (map_char == WALL_CHAR) { printf(WALL_SPRITE); putchar(RAIL_EDGE); putchar('\n'); // Hint: All struct member offsets have already been defined at the top of // the starter code. printf("Score: %d\n", player->score);

// Subset 2
// Print the player if they are in the given row and column.
// Parameters:
// – player: A pointer to the player struct containing its state.
// – row: The row the map printing is up to.
// – column: The column the map printing is up to.
// Returns TRUE if the player was printed, FALSE otherwise.
int maybe_print_player(struct Player *player, int row, int column) {
if (row == PLAYER_ROW && column == player->column) {
if (player->state == PLAYER_RUNNING) {
printf(PLAYER_RUNNING_SPRITE);
} else if (player->state == PLAYER_CROUCHING) {
printf(PLAYER_CROUCHING_SPRITE);
} else if (player->state == PLAYER_JUMPING) {
printf(PLAYER_JUMPING_SPRITE);
return TRUE;
return FALSE;

// Subset 2
// Handle the user’s input, moving and updating the player’s state.
// Parameters:
// – map: The game map.
// – player: A pointer to the player struct containing its state.
// – block_spawner: A pointer to the struct which tracks which blocks to spawn
// next.
// – input: The command the user entered.
void handle_command(char map[MAP_HEIGHT][MAP_WIDTH], struct Player *player,
struct BlockSpawner *block_spawner, char input) {
if (input == LEFT_KEY) {
if (player->column > 0) {
–player->column;

} else if (input == RIGHT_KEY) {
if (player->column < MAP_WIDTH - 1) { ++player->column;

} else if (input == JUMP_KEY) {
// Player must be in normal state (running) to jump
if (player->state == PLAYER_RUNNING) {
player->state = PLAYER_JUMPING;
player->action_ticks_left = ACTION_DURATION;

} else if (input == CROUCH_KEY) {
// Player must be in normal state (running) to crouch
if (player->state == PLAYER_RUNNING) {
player->state = PLAYER_CROUCHING;
player->action_ticks_left = ACTION_DURATION;

} else if (input == TICK_KEY) {
do_tick(map, player, block_spawner);

// Subset 3
// Handle the player colliding with things on the map.
// Parameters:
// – map: The game map.
// – player: A pointer to the player struct containing its state.
// Returns FALSE if the player has lost, TRUE otherwise.
int handle_collision(char map[MAP_HEIGHT][MAP_WIDTH], struct Player *player) {
char *map_char = &map[PLAYER_ROW][player->column];

if (*map_char == BARRIER_CHAR) {
// Player must be crouching to pass under a barrier
if (player->state != PLAYER_CROUCHING) {
printf(“ð��¥ You ran into a barrier! ð��µ\n”);
return FALSE;

player->score += BARRIER_SCORE_BONUS;

if (*map_char == TRAIN_CHAR) {
// Player must be jumping (or already on a train) to pass over a train
if (player->state != PLAYER_JUMPING && !player->on_train) {
printf(“ð��¥ You ran into a train! ð��µ\n”);
return FALSE;

player->on_train = TRUE;

// Player mustn’t be jumping to get the train bonus
if (player->state != PLAYER_JUMPING) {
player->score += TRAIN_SCORE_BONUS;

player->on_train = FALSE;

if (*map_char == WALL_CHAR) {
printf(“ð��¥ You ran into a wall! ð��µ\n”);
return FALSE;

// Cash collection
if (*map_char == CASH_CHAR) {
player->score += CASH_SCORE_BONUS;
*map_char = EMPTY_CHAR;

return TRUE;

// Subset 3
// Pick a new chunk for each column if required. If the safe column has been
// exhausted, randomly override a column to be the safe column.
// Parameters:
// – block_spawner: A pointer to the struct which tracks which blocks to spawn
// next.
void maybe_pick_new_chunk(struct BlockSpawner *block_spawner) {
int new_safe_column_required = FALSE;

// Iterate through each column, checking if we need to choose a new chunk
for (int column = 0; column < MAP_WIDTH; ++column) { char const **next_block_ptr = &block_spawner->next_block[column];
if (*next_block_ptr && **next_block_ptr) {
// We still have blocks left to spawn in this chunk, no need to pick
// a new one

// We’ve exhausted all the blocks, time to pick a new chunk
int chunk = rng() % NUM_CHUNKS;
printf(“Column %d: %d\n”, column, chunk);
*next_block_ptr = CHUNKS[chunk];

if (column == block_spawner->safe_column) {
// That was the safe column, we need to pick a new one to ensure the
// game is still (somewhat) playable
new_safe_column_required = TRUE;

if (new_safe_column_required) {
// Pick a new safe column
int safe_column = rng() % MAP_WIDTH;
printf(“New safe column: %d\n”, safe_column);
block_spawner->safe_column = safe_column;

block_spawner->next_block[safe_column] = CHUNKS[SAFE_CHUNK_INDEX];

// Subset 3
// Move the game forward one tick, scrolling the map down and generating new
// chunks if required.
// Globals used:
// – map: The game map.
// – player: A pointer to the player struct containing its state.
// – block_spawner: A pointer to the struct which tracks which blocks to spawn
// next.
void do_tick(char map[MAP_HEIGHT][MAP_WIDTH], struct Player *player,
struct BlockSpawner *block_spawner) {
if (player->action_ticks_left > 0) {
–player->action_ticks_left;
player->state = PLAYER_RUNNING;

player->score += SCROLL_SCORE_BONUS;

maybe_pick_new_chunk(block_spawner);

// Move everything down one row
for (int i = 0; i < MAP_HEIGHT - 1; ++i) { for (int j = 0; j < MAP_WIDTH; ++j) { map[i][j] = map[i + 1][j]; // Use the block spawner to generate the next row for (int column = 0; column < MAP_WIDTH; ++column) { char const **next_block = &block_spawner->next_block[column];

// Hint: The next line is equivalent to the following 2 lines:
// 1. map[MAP_HEIGHT – 1][column] = **next_block;
// 2. ++*next_block;
map[MAP_HEIGHT – 1][column] = *(*next_block)++;

/* ——————————– Provided ——————————– */

// Hint: You don’t have to understand the how the following functions work, only
// how to use them.

// Generate a random number using a 32 bit linear feedback shift register.
// More information here:
// https://en.wikipedia.org/wiki/Linear-feedback_shift_register.
// Mutates rng_state and returns it.
unsigned rng(void) {
unsigned bit = (g_rng_state >> 31) ^ (g_rng_state >> 30) ^
(g_rng_state >> 28) ^ (g_rng_state >> 0);
g_rng_state = (g_rng_state >> 1) | (bit << 31); return g_rng_state; // The following 2 functions help mimic the way mipsy handles input. Their MIPS // counterparts will look completely different. // Try to mimic mipsy's read character syscall. char read_char(void) { int c = '\0'; while ((c = getchar()) != EOF) { if (!isspace(c)) { printf(EOF_MESSAGE); exit(EXIT_FAILURE); // Get a seed from the user to initialise the rng state. void get_seed(void) { printf("Enter a non-zero number for the seed: "); int scanf_ret = scanf(" %d", &g_rng_state); // In MIPS, we can assume the user enters a valid number if (scanf_ret == EOF) { printf(EOF_MESSAGE); exit(EXIT_FAILURE); if (g_rng_state == 0 || scanf_ret != 1) { printf("Invalid seed!\n"); exit(EXIT_FAILURE); printf("Seed set to %d\n", g_rng_state); // scanf leaves a newline in the buffer, so we need to consume it getchar();