#Pseudo random number generation citation
#Marsaglia, George. Xorshift RNGs, The Florida State University, 2003. Retrieved from https://www.jstatsoft.org/article/view/v008i14/916
.data
welcomemsg: .string "Welcome to Sokoban!\n"
gridmsg: .string "Please enter the grid size:\n"
invalidmsg: .string "Invalid input, please try again:\n"
winmsg: .string "Congratulations! You completed the level!\n"
movemsg: .string "Please enter a move:\n"
invalidmove: .string "Invalid move, please try again:\n"
quitmsg: .string "Type <q> to exit or <r> to restart game:\n"
.align 4
invalidsize: .string "Grid too big, please enter a new value.\n"
.align 4
seed: .word 12345678 # Initial seed value
gridsize: .byte 8,8
character: .byte 0,0
box: .byte 0,0
target: .byte 0,0
gamestate: .byte 0
# Symbols for rendering
wall_sym: .byte '#'
player_sym: .byte '@'
box_sym: .byte 'D'
target_sym: .byte 'x'
empty_sym: .byte ' '
.text
.globl _start
_start:
j main_loop
generate_boxlocx:
#initialize x location for box:
la t0, gridsize
lb t2, 0(t0)
addi t2, t2, -1
mv a0, t2
jal ra, xorshift
la t1, box
sb a0, 0(t1)
li t3, 0
beq a0, t3, generate_boxlocx
li t3, 1
beq a0, t3, check_corners
la t0, gridsize
lb t3, 1(t0) #grid height
addi t3, t3, -2 #bottom corners
beq a0, t3, check_corners
j generate_boxlocy
check_corners:
#initialize y location for box:
la t0, gridsize
lb t2, 0(t0) #get width
addi t2, t2, -1
mv a0, t2
jal ra, xorshift
la t1, box
sb a0, 1(t1)
li t3, 0
beq a0, t3, check_corners #check if y=0
li t3, 1
beq a0, t3, check_corners #check if at (1,1) or (height-2, 1)which is corner
la t0, gridsize
lb t2, 0(t0) #get width
addi t2, t2, -2
beq a0, t2, check_corners #check if at (1,width-2) or (height-2, width-2) which is corner
j check_beside_wall
generate_boxlocy:
#initialize y location for box:
la t0, gridsize
lb t2, 1(t0)
addi t2, t2, -1
mv a0, t2
jal ra, xorshift
la t1, box
sb a0, 1(t1)
li t3, 0
beq a0, t3, generate_boxlocy
j check_beside_wall
check_beside_wall:
#check if the box is beside a wall so the target must be on the same wall to be solveable
# Load grid size (width and height)
la t0, gridsize
lb t2, 0(t0) # Load grid width
addi t2, t2, -2 # Subtract 2 to get grid width limit
lb t3, 1(t0) # Load grid height
addi t3, t3, -2 # Subtract 2 to get grid height limit
# Load box coordinates
la t1, box
lb t4, 0(t1) # box x
lb t5, 1(t1) # box y
# Check if the box is against the left or right wall
li t6, 1
beq t4, t6, target_on_x_wall # if x = 1 (top wall), place target on x wall
beq t4, t2, target_on_x_wall # if x = grid width-2 (bottom wall), place target on x wall
# Check if the box is against the top or bottom wall
li t6, 1
beq t5, t6, target_on_y_wall # if y = 1 (left wall), place target on y wall
beq t5, t3, target_on_y_wall # if y = grid height-2 (right wall), place target on y wall
# if the box is not near a wall, generate target randomly
j generate_target_locx
target_on_x_wall:
la t1, box
lb t2, 0(t1) # box x
lb t3, 1(t1) # box y
la t4, target
lb t5, 0(t4)
lb t6, 1(t4)
sb t2, 0(t4) # target x location must be same as box x loc
la t0, gridsize
lb t2, 1(t0)
addi t2, t2, -1
#initialize y location for box:
mv a0, t2
jal ra, xorshift
la t2, target # Load target address
sb a0, 1(t2) # store target y coord
la t2, box # Load box address
lb t0, 1(t2) # Load box y coord
li t3, 0
beq a0, t0, target_on_x_wall
beq a0, t3, target_on_x_wall
j generate_charlocx
target_on_y_wall:
la t1, box
lb t2, 0(t1) # Box x-coordinate
lb t3, 1(t1) # Box y-coordinate
la t4, target
lb t5, 0(t4)
lb t6, 1(t4)
sb t3, 1(t4) # target x location must be same as box x loc
la t0, gridsize
lb t2, 0(t0)
addi t2, t2, -1
#initialize x location for box:
mv a0, t2
jal ra, xorshift
la t2, target # Load target address
sb a0, 0(t2) # store target x coord
la t2, box # Load box address
lb t0, 0(t2) # Load target x coord
li t3, 0
beq a0, t0, target_on_y_wall
beq a0, t3, target_on_y_wall
j generate_charlocx
generate_target_locx:
la t0, gridsize
lb t2, 0(t0)
addi t2, t2, -1
#initialize x location for box:
mv a0, t2
jal ra, xorshift
la t2, target # Load target address
sb a0, 0(t2) # store target x coord
la t4, box # Load box address
lb t0, 0(t4) # Load box x coord
li t3, 0
beq a0, t3, generate_target_locx
beq a0, t0, generate_target_diffy
j generate_target_locy
generate_target_diffy:
la t0, gridsize
lb t2, 1(t0)
addi t2, t2, -1
#initialize y location for box:
mv a0, t2
jal ra, xorshift
la t2, target # Load target address
sb a0, 1(t2) # store target y coord
la t2, box # Load box address
lb t0, 1(t2) # Load box y coord
li t3, 0
beq a0, t3, generate_target_diffy
beq a0, t0, generate_target_diffy
j generate_charlocx
generate_target_locy:
la t0, gridsize
lb t2, 1(t0)
addi t2, t2, -1
#initialize y location for box:
mv a0, t2
jal ra, xorshift
la t2, target # Load target address
sb a0, 1(t2) # store target y coord
li t3, 0
beq a0, t3, generate_target_locy
generate_charlocx:
la t0, gridsize
lb t2, 0(t0)
addi t2, t2, -1
#initialize y location for box:
mv a0, t2
jal ra, xorshift
la t0, character #t0 = character address
sb a0, 0(t0)
la t1, target # t1 = target address
la t2, box # t2 = box address
lb t3, 0(t1) # t3 = target[0]
lb t4, 0(t2) #t4 = box[0]
li t5, 0
beq a0, t5, generate_charlocx
beq a0, t3, generate_chardifftargety
beq a0, t4, generate_chardiffboxy
j generate_charlocy
generate_chardifftargety:
la t0, gridsize
lb t2, 1(t0)
addi t2, t2, -1
#initialize y location for box:
mv a0, t2
jal ra, xorshift
la t0, character #t0 = character address
sb a0, 1(t0)
la t1, target # t1 = target address
la t2, box # t2 = box address
lb t3, 1(t1) # t3 = target[1]
lb t4, 1(t2) #t4 = box[1]
li t5, 0
beq a0, t5, generate_chardifftargety #if 0, regenerate
beq a0, t3, generate_chardifftargety #if same y as target, regenerate
beq a0, t4, generate_chardiffboxx #if same y as box, regenerate
j main_loop
generate_chardiffboxx:
la t0, target
lb t1, 0(t0)
la t2, box # t2 = box address
lb t6, 0(t2) # t6 = box[0]
beq t1, t6, generate_charlocx #if same target x as box, then regenerate x
j main_loop
generate_chardiffboxy:
la t0, gridsize
lb t2, 1(t0)
addi t2, t2, -1
#initialize y location for box:
mv a0, t2
jal ra, xorshift
la t0, character #t0 = character address
sb a0, 1(t0)
la t1, target # t1 = target address
la t2, box # t2 = box address
lb t3, 1(t1) # t3 = target[1]
lb t4, 1(t2) #t4 = box[1]
li t5, 0
beq a0, t5, generate_chardiffboxy #if 0, regenerate
beq a0, t4, generate_chardiffboxy #if same y as box, regenerate
beq a0, t3, generate_chardifftargetx #if same y as target, regenerate
j main_loop
generate_chardifftargetx:
la t0, box
lb t1, 0(t0)
la t2, target # t2 = box address
lb t6, 0(t2) # t6 = box[0]
beq t1, t6, generate_charlocx #if same target x as box, then regenerate x
j main_loop
generate_charlocy:
la t0, gridsize
lb t2, 1(t0)
addi t2, t2, -1
#initialize y location for box:
mv a0, t2
jal ra, xorshift
la t0, character #t0 = character address
sb a0, 1(t0)
la t1, target # t1 = target address
la t2, box # t2 = box address
lb t3, 1(t1) # t3 = target[1]
lb t4, 1(t2) #t4 = box[1]
li t5, 0
beq a0, t3, generate_charlocy
beq a0, t4, generate_charlocy
beq a0, t5, generate_charlocy
j main_loop
print_grid:
la t0, gridsize
lb t1, 0(t0) # Grid width
lb t2, 1(t0) # Grid height
li s0, 0 # Row index (i)
li s1, 0 # Column index (j)
li a0, 10 # Load newline character `\n`
li a7, 11 # Syscall: write character
ecall
print_row:
bge s0, t2, move
# Reset column index at the start of each row
li s1, 0 # Set column index to 0
print_col:
bge s1, t1, newline
beq s0, x0, print_wall
beq s1, x0, print_wall
addi t6, t1, -1
beq s0, t6, print_wall
beq s1, t6, print_wall
check_player_x:
la t3, character
lb t4, 0(t3) # Character x-coordinate
lb t5, 1(t3) # Character y-coordinate
beq s1, t4, check_player_y
j check_target_x
check_player_y:
beq s0, t5, print_player
check_target_x:
la t3, target
lb t4, 0(t3) # Character x-coordinate
lb t5, 1(t3) # Character y-coordinate
beq s1, t4, check_target_y
j check_box_x
check_target_y:
beq s0, t5, print_target
check_box_x:
la t3, box
lb t4, 0(t3) # Box x-coordinate
lb t5, 1(t3) # Box y-coordinate
beq s1, t4, check_box_y
j print_empty
check_box_y:
beq s0, t5, print_box
print_empty:
li a0, ' '
j print_char
print_wall:
li a0, '#'
j print_char
print_player:
li a0, '@'
j print_char
print_box:
li a0, 'D'
j print_char
print_target:
li a0, 'x'
j print_char
print_char:
# Print the character in a0
li a7, 11 # Syscall: write character
ecall
# Move to the next column
addi s1, s1, 1 # Increment column index
j print_col # Jump to column loop
newline:
# Print a newline at the end of the row
li a0, 10 # Load newline character `\n`
li a7, 11 # Syscall: write character
ecall
# Move to the next row
addi s0, s0, 1 # Increment row index
j print_row # Jump to row loop
main_loop:
la t0, gamestate # Load the gamestate address
lb t1, 0(t0) # Load the current gamestate
li t2, 0
li t3, 2
li t4, 1
beq t1, t2, main_menu # If gamestate == 0, go to main menu
beq t1, t3, game_loop # If gamestate == 2, continue to game loop
beq t1, t4, gamewin # If gamestate == 1 (win)
j main_loop # Continue the main loop
main_menu:
# Display main menu
li a7, 4
la a0, welcomemsg
ecall
li a7, 4
la a0, gridmsg
ecall
# Get grid size
li a7, 5
ecall
mv t2, a0
li t5, 99
bge t2, t5, invalid_size
la t3, gridsize
sb a0, 0(t3)
sb a0, 1(t3)
# Set the gamestate to ongoing (2)
la t0, gamestate # Load the gamestate address
li t1, 2
sb t1, 0(t0) # Set gamestate to 2
j generate_boxlocx # Jump back to main loop
invalid_size:
li a7, 4
la a0, invalidsize
ecall
j main_menu
game_loop:
j print_grid
move:
la t0, gamestate
lb t1, 0(t0) # Load the current gamestate
li t2, 1
beq t2, t1, main_loop
li a7, 4
la a0, movemsg
ecall
# Get move
li a7, 12
ecall
mv t1, a0
# Check if the input is 'w', 'a', 's', or 'd' for movement
li t2, 'w'
beq t1, t2, move_up # If input is 'w', move player up
li t2, 'a'
beq t1, t2, move_left # If input is 'a', move player left
li t2, 's'
beq t1, t2, move_down # If input is 's', move player down
li t2, 'd'
beq t1, t2, move_right # If input is 'd', move player right
li t2, 'r'
beq t1, t2, restart # If input is 'r', restart game
li t2, 'q'
beq t1, t2, exit # If input is 'q', quit game
j invalid_move # if not anything then move is invalid
restart:
j generate_boxlocx
gamewin:
li a0, 10 # Load newline character `\n`
li a7, 11 # Syscall: write character
ecall
li a7, 4
la a0, winmsg
ecall
li a7, 4
la a0, quitmsg
ecall
# Get input
li a7, 12
ecall
mv t1, a0
li t2, 'q'
beq t1, t2, exit # If input is 'q', quit game
la t0, gamestate
li t3, 0
sb t3, 0(t0)
li t2, 'r'
beq t1, t2, main_loop # If input is 'r', restart game
li a7, 4
la a0, invalidmsg
ecall
j gamewin
invalid_move:
li a0, 10 # Load newline character `\n`
li a7, 11 # Syscall: write character
ecall
li a7, 4
la a0, invalidmove
ecall
j move
move_up:
la t0, character
lb t1, 0(t0) #t1 = x coord of player
lb t2, 1(t0) #t2 = y coord of player
addi t2, t2, -1
li t6, 0
beq t6, t2, invalid_move #if the player hits the top wall at y = 0
la t3, box
lb t4, 0(t3)
lb t5, 1(t3)
bne t1, t4, move_player_up #if player doesnt have a box on top move y loc
beq t2, t5, checkbox_above #if player has box on top
bne t2, t5, move_player_up #if player doesnt have a box on top move y loc
move_player_up:
la t0, character
lb t1, 1(t0) #t1 = y coord of player
addi t1, t1, -1
sb t1, 1(t0) #y coord of player = y coord of player - 1
la t0, gamestate
li t1, 2
sb t1, 0(t0) # Set gamestate to 2
j check_win #check if box is now on target
checkbox_above:
la t3, box
lb t4, 0(t3)
lb t5, 1(t3)
addi t5, t5, -1
li t6, 0
beq t6, t5, invalid_move #if the box hits the top wall at y = 0 after the player pushes it
sb t5, 1(t3) #y coord of box = y coord of box + 1
bne t6, t5, move_player_up #move player too if move is valid
move_down:
la t0, character
lb t1, 0(t0) #t1 = x coord of player
lb t2, 1(t0) #t2 = y coord of player
addi t2, t2, 1
la t6, gridsize
lb s0, 1(t6) #s0 = grid height
addi s0, s0, -1
beq s0, t2, invalid_move #if the player hits the bottom wall at y = grid height - 1
la t3, box
lb t4, 0(t3) #t4 = x coord of box
lb t5, 1(t3) #t5 = y coord of box
bne t1, t4, move_player_down #if player doesnt have a box on top move y loc
beq t2, t5, checkbox_down #if player has box on top
bne t2, t5, move_player_down #if player doesnt have a box on top move y loc
move_player_down:
la t0, character
lb t1, 1(t0) #t1 = y coord of player
addi t1, t1, 1
sb t1, 1(t0) #y coord of player = y coord of player + 1
la t0, gamestate
li t1, 2
sb t1, 0(t0) # Set gamestate to 2
j check_win #check if box is now on target
checkbox_down:
la t3, box
lb t4, 0(t3)
lb t5, 1(t3)
addi t5, t5, 1
la t6, gridsize
lb s0, 1(t6) #s0 = grid height
addi s0, s0, -1
beq s0, t5, invalid_move #if the box hits the bottom wall at y = grid height - 1 after the player pushes it
sb t5, 1(t3) #y coord of box = y coord of box - 1
j move_player_down #move player too if move is valid
move_left:
la t0, character
lb t1, 0(t0) #t1 = x coord of player
lb t2, 1(t0) #t2 = y coord of player
addi t1, t1, -1
li t6, 0
beq t6, t1, invalid_move #if the player hits the left wall at x = 0
la t3, box
lb t4, 0(t3)
lb t5, 1(t3)
bne t2, t5, move_player_left #if player doesnt have a box on left move x loc
beq t1, t4, checkbox_left #if player has box on left
bne t1, t4, move_player_left #if player doesnt have a box on top move x loc
move_player_left:
la t0, character
lb t1, 0(t0) #t1 = x coord of player
addi t1, t1, -1
sb t1, 0(t0) #y coord of player = x coord of player - 1
la t0, gamestate
li t1, 2
sb t1, 0(t0) # Set gamestate to 2
j check_win #check if box is now on target
checkbox_left:
la t3, box
lb t4, 0(t3)
lb t5, 1(t3)
addi t4, t4, -1
li t6, 0
beq t6, t4, invalid_move #if the box hits the left wall at x = 0 after the player pushes it
sb t4, 0(t3) #y coord of box = y coord of box + 1
j move_player_left #move player too if move is valid
move_right:
la t0, character
lb t1, 0(t0) #t1 = x coord of player
lb t2, 1(t0) #t2 = y coord of player
addi t1, t1, 1
la t6, gridsize
lb s0, 0(t6) #s0 = grid width
addi s0, s0, -1
beq s0, t1, invalid_move #if the player hits the bottom wall at x = grid width - 1
la t3, box
lb t4, 0(t3) #t4 = x coord of box
lb t5, 1(t3) #t5 = y coord of box
bne t2, t5, move_player_right #if player doesnt have a box on right move x loc
beq t1, t4, checkbox_right #if player has box on right
bne t1, t4, move_player_right #if player doesnt have a box on right move x loc
move_player_right:
la t0, character
lb t1, 0(t0) #t1 = x coord of player
addi t1, t1, 1
sb t1, 0(t0) #x coord of player = x coord of player + 1
la t0, gamestate
li t1, 2
sb t1, 0(t0) # Set gamestate to 2
j check_win #check if box is now on target
checkbox_right:
la t3, box
lb t4, 0(t3)
lb t5, 1(t3)
addi t4, t4, 1
la t6, gridsize
lb s0, 0(t6) #s0 = grid width
addi s0, s0, -1
beq s0, t4, invalid_move #if the box hits the right wall at x = grid width - 1 after the player pushes it
sb t4, 0(t3) #x coord of box = x coord of box + 1
j move_player_right #move player too if move is valid
check_win:
la t0, target
la t1, box
lb t3, 0(t0)
lb t4, 1(t0)
lb t5, 0(t1)
lb t6, 1(t1)
bne t3, t5, main_loop #if x coords dont match then box isnt on target
bne t4, t6, main_loop #if y coords dont match then box isnt on target
la t0, gamestate
li t1, 1
sb t1, 0(t0) # Set gamestate to win = 1
j print_grid
exit:
li t0, 0
li t1, 0
li t2, 0
li t3, 0
li t4, 0
li t5, 0
li t6, 0
li s0, 0
li s1, 0
li a7, 10
ecall
# Arguments: an integer MAX in a0
# Return: A number from 0 (inclusive) to MAX (exclusive)
xorshift:
# Load the MAX from a0
mv t2, a0
la t1, seed # Load address of the seed
lw t0, 0(t1) # Load the current seed into t0
# XORShift algorithm for 32-bit
slli t3, t0, 13 # t1 = t0 << 13
xor t0, t0, t3 # t0 ^= t1
srli t3, t0, 17 # t1 = t0 >> 17
xor t0, t0, t3 # t0 ^= t1
slli t3, t0, 5 # t1 = t0 << 5
xor t0, t0, t3 # t0 ^= t1
# Store the new seed back
sw t0, 0(t1)
# Apply modulus to limit to range 0-MAX
remu t0, t0, t2 # t0 = t0 % MAX
mv a0, t0
# Return with random number in a0
jr ra # Return to caller