Files

454 lines
8.6 KiB
ArmAsm

// by Simon Holmberg
//
// This is a document generated by [docco](http://jashkenas.github.io/docco/).
// My report is interleaved with the source code in the form of comments in
// [markdown](http://daringfireball.net/projects/markdown/) format.
//
//
#define WIDTH 60
#define HEIGHT 40
// .bss
// ====
.bss
// Array of longs representing the game field
//
// `world` is an array of increasing time values. By having the snake head set
// the time value to 0 at its current position, the actual **snake** will be
// defined by the locations where the time value is lesser than the snake
// length. This makes the tail of the snake automatically "move" visually, and
// allows for a snake with an arbitrary length without complicated arrays of
// coordinates!
//
// **Apples** are defined by negative time values which do not get incremented
// each frame. If we wanted to, we could instead decrease this value and
// compare it with a lifetime value, to conveniently make apples disappear
// after a set amount of time.
.align 4
world: .space WIDTH*HEIGHT*4
// .data
// =====
.data
// Current length of the snake
snakeLength: .long 0
// Snake position (starts in the middle of the field)
snakePosX: .long WIDTH / 2
snakePosY: .long HEIGHT / 2
// Snake direction (start moving to the right)
snakeDirX: .long 1
snakeDirY: .long 0
// Number of apples
numApples: .long 0
// Game over message
sgameover: .string " Game over! Score: %i "
sgameover_end:
// Various utility strings mainly used for debugging
string1: .string "Started with snake length %i and %i apples!\n"
string2: .string "Snake position: (%i, %i)\n"
stringi: .string "%i\n"
stringtest: .string "> %i %i %c\n"
// .text
// =====
.text
// start_game
// ============
// `start_game(int len, int n_apples)`
.global start_game
start_game:
// Initialization
// --------------
// Save our function arguments for later
// `snakeLength = len`
movl 4(%esp), %eax
movl %eax, snakeLength
// `numApples = n_apples`
movl 8(%esp), %eax
movl %eax, numApples
// Store all registers so we can restore their state before we return later
pusha
// Call nib_init
call nib_init
// Seed randomness by calling `srand` with a time value from `time`
pushl $0
call time
addl $4, %esp
pushl %eax
call srand
addl $4, %esp
// Initialize the snake array by setting every cell to a time value greater
// than `snakeLength`
movl $world, %edi
movl snakeLength, %eax
movl $WIDTH*HEIGHT, %ebx
xorl %ecx, %ecx
1:
movl %eax, (%edi, %ecx, 4)
incl %ecx
cmpl %ebx, %ecx
jl 1b
// Spawn initial apples
movl numApples, %ecx
1:
pushl %ecx
call spawnApple
popl %ecx
loop 1b
// Game loop
// ---------
loop:
// Sleep for a while using `usleep` so the snake doesn't wiggle at the
// speed of light!
pushl $250*1000
call usleep
addl $4, %esp
// Jump to `tick`, our updating function, which later chain-jumps to `draw`
jmp tick
// Restore all our registers as they were before `start_game` was called
// and return
popa
ret
// tick
// ====
// This is where we update the world.
tick:
movl $world, %edi
// Increment cell times
// --------------------
// Loop
movl snakeLength, %ebx
xorl %ecx, %ecx
1:
movl (%edi, %ecx, 4), %eax
// If the value is negative it's an apple we don't want to disappear
test %eax, %eax
js 2f
// No need to increment if it's already greater than snakeLength
cmpl %ebx, %eax
jg 2f
// Increment the cell value
incl %eax
movl %eax, (%edi, %ecx, 4)
2: incl %ecx // Loop increment
cmpl $WIDTH*HEIGHT, %ecx
jne 1b
// Input
// -----
// Call nib_poll_kbd and check down, up, left and right in succession
call nib_poll_kbd
// Down
movl $258, %ebx
cmpl %eax, %ebx
jne 1f
movl $0, snakeDirX
movl $1, snakeDirY
// Up
1: movl $259, %ebx
cmpl %eax, %ebx
jne 1f
movl $0, snakeDirX
movl $-1, snakeDirY
// Left
1: movl $260, %ebx
cmpl %eax, %ebx
jne 1f
movl $-1, snakeDirX
movl $0, snakeDirY
// Right
1: movl $261, %ebx
cmpl %eax, %ebx
jne 1f
movl $1, snakeDirX
movl $0, snakeDirY
1:
// Move snake
// ----------
// `snakePosX = (snakePosX + snakeDirY + WIDTH) % WIDTH`
movl snakePosX, %eax
movl snakeDirX, %ebx
addl %ebx, %eax
// Add width for modulo wrap-around
addl $WIDTH, %eax
xorl %edx, %edx
movl $WIDTH, %ecx
divl %ecx
mov %edx, snakePosX
// `snakePosY = (snakePosY + snakeDirY + HEIGHT) % HEIGHT`
movl snakePosY, %eax
movl snakeDirY, %ebx
addl %ebx, %eax
// Add height for modulo wrap-around
addl $HEIGHT, %eax
xorl %edx, %edx
movl $HEIGHT, %ecx
divl %ecx
mov %edx, snakePosY
// Collision
// ---------
// Get value at head position
movl snakePosY, %eax
movl $WIDTH, %edx
mull %edx
addl snakePosX, %eax
movl (%edi, %eax, 4), %edx
// Collision with apple if negative
test %edx, %edx
js 1f
// Compare value with `snakeLength`
movl snakeLength, %ebx
cmpl %ebx, %edx
// End the game if the snake collides with itself, i.e. `%edx < snakeLength`
jge 2f
jmp gameOver
// Spawn a new apple and extend the snake!
1: pushl %edi
pushl %eax
call spawnApple
popl %eax
popl %edi
incl snakeLength
2:
// Update world
// ------------
// Set time value to 0 at snake head position
//
// `i = x + y * WIDTH`
//
// `%edi = %ebx + %ecx * $WIDTH`
movl $0, (%edi, %eax, 4)
// Proceed to `draw`
jmp draw
//jmp debug_draw
// draw
// ====
draw:
// Loop on Y-axis
movl $HEIGHT, %ecx
1:
// Loop on X-axis
movl $WIDTH, %ebx
2:
// Save loop registers
pushl %ecx
pushl %ebx
subl $1, %ecx
subl $1, %ebx
// Call `nib_put_scr` depending of the value in a cell
// `i = x + y * WIDTH`
//
// `%eax = %ebx + %ecx * $WIDTH`
movl %ecx, %eax
movl $WIDTH, %edx
mull %edx
addl %ebx, %eax
// Fetch the value of the cell
movl $world, %edi
movl (%edi, %eax, 4), %eax
// Compare with the snake length
cmpl snakeLength, %eax
// If the value is negative, it's an apple
test %eax, %eax
js 4f
// If it's greater or equal to snakeLength, it's empty
cmpl snakeLength, %eax
jge 3f
// Else, it's part of the snake
pushl $'@'
jmp 5f
3: pushl $' '
jmp 5f
4: pushl $'o'
5:
// Push Y-coordinate
movl %ecx, %eax
pushl %eax
// Push X-coordinate
movl %ebx, %eax
pushl %eax
// `nib_put_scr(int x, int y, int c)`
call nib_put_scr
addl $4*3, %esp
// Restore loop registers
popl %ebx
popl %ecx
// Decrement X
decl %ebx
// End of X-loop
jnz 2b
// End of Y-loop
loop 1b
// Jump back to the start of our game loop!
jmp loop
// gameOver
// ========
// Called when the game ends. It displays a game over message
// and score, and quits after **3 seconds**.
gameOver:
// Calculate message position
// --------------------------
// Calculate string length
movl $sgameover_end, %eax
subl $sgameover, %eax
// Discout null terminator
subl $1, %eax
// Calculate X-pos in the middle of the screen
movl $2, %ebx
xorl %edx, %edx
divl %ebx
negl %eax
addl $WIDTH/2, %eax
// Print game over message
// -----------------------
// Push `snakeLength` as score
pushl snakeLength
// Push printf-string
pushl $sgameover
// Push X-coordinate
pushl %eax
// Push Y-coordinate
pushl $HEIGHT/2
// `mvprintw(int y, int x, const char *fmt, ...)`
call mvprintw
add $4*3, %esp
// Call `refresh` to refresh the screen
call refresh
// Sleep for 3 seconds before calling `nib_end`
pushl $3000*1000
call usleep
addl $4, %esp
call nib_end
// spawnApple
// ==========
// Spawns an **apple** in a random location in the `world`.
spawnApple:
pushl %ebx
// `%ecx = rand() % HEIGHT`
call rand
xorl %edx, %edx
movl $HEIGHT, %ebx
divl %ebx
movl %edx, %ecx
// `%ebx = rand() % WIDTH`
pushl %ecx
call rand
xorl %edx, %edx
movl $WIDTH, %ebx
divl %ebx
movl %edx, %ebx
popl %ecx
// `i = x + y * WIDTH`
//
// `%eax = %ebx + %ecx * $WIDTH`
movl %ecx, %eax
movl $WIDTH, %edx
mull %edx
addl %ebx, %eax
// Set the cell negative to represent an apple
movl $world, %ecx
movl $-1, %edx
movl %edx, (%ecx, %eax, 4)
popl %ebx
ret
// debug_draw
// =============
// This is basically the same as `draw`, except it draws the actual ASCII-value
// of the world for debugging purposes.
debug_draw:
// Loop Y
movl $HEIGHT, %ecx
1:
// Loop X
movl $WIDTH, %ebx
2:
// Save loop registers
pushl %ecx
pushl %ebx
subl $1, %ecx
subl $1, %ebx
// Call nib_put_scr
// `i = x + y * WIDTH`
//
// `%edi = %ebx + %ecx * $WIDTH`
movl %ecx, %eax
movl $WIDTH, %edx
mull %edx
addl %ebx, %eax
// Fetch the value of the cell
movl $world, %edi
movl (%edi, %eax, 4), %eax
pushl %eax
// Y coordinate
movl %ecx, %eax
pushl %eax
// X coordinate
movl %ebx, %eax
pushl %eax
call nib_put_scr
addl $4*3, %esp
// Restore loop registers
popl %ebx
popl %ecx
// Decrement X
decl %ebx
// End of X-loop
jnz 2b
// End of Y-loop
loop 1b
// Jump back to the start of our game loop!
jmp loop