• Jump To … +
    cnibbles.c nibbles.S start.S
  • nibbles.S

  • ¶

    by Simon Holmberg

    This is a document generated by docco. My report is interleaved with the source code in the form of comments in 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