// 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