Breakout in Assembly

Technical details follow, and while I've tried my best to make it interesting and accessible I know that some of you just want to play the game. Here is a link to do just that.

Consolite (coming from "console lite") is the name I've given to my recent project of making a tiny hobbyist game console and associated toolchain. My end goal is to write a custom microprocessor and video controller that I can put on my Mimas V2 FPGA board. For prototyping purposes, I have first written an assembler and an emulator so that I can test out how writing games will work while easily making tweaks to the instruction set.

What is an assembler? In the case of Consolite, the assembler is a program that reads in a human-readable text file written in assembly language and converts it to binary instructions that the processor understands. It takes some code like this:

ADD A B ;This is a comment, it
SUB B A ;doesn't affect the output.

And turns each line into a 4-byte instruction like this (the following in hexadecimal):

0a 02 03 00
0b 03 02 00

In the first four bytes above, 0a represents the ADD instruction, 02 represents the register A, and 03 represents the register B. In the second four bytes, 0b represens the SUB instruction, and the registers are reversed. The fourth byte is unused in these instructions.

Documentation on the Consolite assembly language can be found here. It has special instructions for drawing pixels to the screen, setting colors, getting input, getting random numbers, and getting timing information, which facilitate the development of graphical applications (like games!). Source code for the assembler can be found written in C++ here and written in JavaScript here.

The emulator is a program that mimics the hardware that Consolite will run on, so that you can run it in a host environment like your desktop or browser. The Consolite microprocessor will have 64 KiB of RAM available for main memory, 48 KiB of video RAM available for the 8-bit 256 x 192 pixel display, and 16 general purpose registers, each of which are 16 bits wide. Each of these components as well as miscellaneous items like the instruction pointer must be emulated. At the end of this post you get to see the emulator in action when you play Breakout in your browser. Source code for the emulator can be found written in C++ here and written in JavaScript here.

After completing both the assembler and emulator, I wrote a port of the classic video game Breakout in my new assembly language so that I could test that everything was working. Source code for Breakout can be found here.

Breakout Memory Layout

When the emulator starts up, it first copies the binary instructions produced by the assembler into main memory. From there the emulator jumps to the first instruction and starts executing.

Breakout memory layout diagram

For Breakout the 64 KiB of main memory are logically separated into different sections, as you can see above.

  • The bootloader does some initialization that is done only once, like drawing the white walls, then it jumps over the global variable storage to the start of the main loop.
  • The global variables section stores values such as the x-coordinate of the paddle, or the user's score.
  • The main loop handles input and drawing and moving around game components, and is run 60 times per second. At the end of the main loop, it waits until the full 16 milliseconds is up for the current frame and then jumps back to the beginning of the loop.
  • The functions section contains code for such things as moving the paddle, checking for collision, destroying bricks, drawing rectangles, etc. The main loop switches control to these functions, which switch control back to the main loop when they are finished.
  • The stack stores the values of any non-global variables that will not fit in the 16 general purpose registers. Most programs need to keep track of more than 16 values, so they use the stack. The stack grows down, away from the instructions so that it doesn't overwrite them.
  • For Breakout, most of the memory is unused. The binary file is only 3,588 bytes as of this writing and it uses very few bytes for the stack.

Playing the Game

Before playing the game, you must click the Assemble and Run Emulator button below. This saves your browser the trouble of running an emulator while you're reading the article.

Controls
  • Hit the spacebar or tap to start
  • Left arrow key or touch the left side of the screen to move left
  • Right arrow key or touch the right side of the screen to move right
; Breakout in Consolite Assembly
; Written by Robert Fotino

bootloader:
        MOVI SP stack           ; Initialize the stack pointer.
        CALL draw_walls
        CALL init
        JMPI main               ; Start the main loop

bricks_blue:
        0xffff
bricks_orange:
        0xffff
bricks_green:
        0xffff
paddle_x:                       ; The (variable) x position of the paddle.
        0x0070
ball_x:                         ; The position of the ball is in 8.8 fixed point
        0x7e00                  ; format.
ball_x_prev:
        0x0000
ball_y:
        0xb300
ball_y_prev:
        0x0000
ball_speed:                     ; The ball's speed is multiplied by its
        0x0000                  ; direction to get the velocity. Speed is in
                                ; 15.1 fixed point format.
ball_dir_x:                     ; The ball's direction is a unit vector in
        0x0000                  ; 1.7.8 fixed point format.
ball_dir_y:
        0x0000
                                ; Precalculated directions for -60 through 60
                                ; degrees, at 15 degree intervals. Numbers in
lookup_dir_x:                   ; 1.7.8 fixed point format.
        0x80dd 0x80b5 0x8080 0x8042 0x8009 0x0042 0x0080 0x00b5 0x00dd
lookup_dir_y:
        0x8080 0x80b5 0x80dd 0x80f7 0x8100 0x80f7 0x80dd 0x80b5 0x8080
score:                          ; The current score
        0x0000
score_prev:                     ; The previous score
        0x0000
num_bitmaps:                    ; Bitmaps for drawing 0-9
        0x7b6f 0x2c97 0x73e7 0x72cf 0x5bc9 0x79cf 0x79ef 0x7249 0x7bef 0x7bcf
is_running:                     ; If the game is currently running or
        0x0000                  ; if it is waiting for input.

init:                           ; Initializes the game
        PUSH FP
        PUSH A
        PUSH B
        MOV FP SP

        MOVI A 0x0              ; Set the score to zero.
        STORI A score
        MOVI A 0xffff           ; Set the bricks to all alive.
        STORI A bricks_blue
        STORI A bricks_orange
        STORI A bricks_green
        CALL draw_bricks        ; Draw all of the bricks.

        RND A                   ; Get a random number between 0 and 8 to index
        MOVI B 0xff             ; the directions lookup table to load
        AND A B
        MOVI B 0x9
        MUL A B
        MOVI B 0x100
        DIV A B
        CALL change_direction

        MOVI A 0x4              ; Initialize the ball's speed to integer 2.
        STORI A ball_speed

        MOV SP FP
        POP B
        POP A
        POP FP
        RET

main:                           ; Main loop, called 60 times per second
        TIMERST                 ; Resets the timer
        LOADI A ball_x          ; Set the previous ball position equal
        STORI A ball_x_prev     ; to the current ball position.
        LOADI A ball_y
        STORI A ball_y_prev
        LOADI A score           ; Set the previous score equal to the current
        STORI A score_prev      ; score.
        CALL move_paddle        ; Move the paddle according to input.
        LOADI A is_running      ; Check if the game is running, if so
        MOVI L 0x1              ; do stuff like collision and scoring.
        CMP A L
        JEQ main_running
        CALL move_ball_with_paddle
        MOVI A 0x0              ; Check if the user has hit the space
        INPUT A A               ; bar, and if so begin the game.
        CMP A L
        JNE main_done
        STORI L is_running      ; Set is_running true
        CALL init               ; Initialize the game state
main_running:
        CALL move_ball
        CALL collide_walls
        CALL collide_paddle
        CALL collide_bricks_all
        CALL check_endgame
main_done:
        CALL draw_ball
        CALL draw_paddle
        CALL draw_score
        CALL wait               ; Wait until the 16ms for this iteration
        JMPI main               ; have elapsed, then restart.

wait:                           ; Waits until the time is 16 ms after
                                ; the last call to TIMERST.
        PUSH FP
        PUSH A
        PUSH B
        MOV FP SP

        MOVI A 0x10
wait_again:     
        TIME B
        CMP B A
        JB wait_again

        MOV SP FP
        POP B
        POP A
        POP FP
        RET

move_paddle:                    ; Moves the paddle to the left or the
        PUSH FP                 ; right or not at all depending on user
        PUSH A                  ; input.
        PUSH B
        PUSH C
        PUSH D
        PUSH E
        PUSH F
        PUSH L
        MOV FP SP

        MOVI L 0x0              ; Set the color to black.
        COLOR L

        MOVI B 0xb8             ; The y coord of the paddle.
        MOVI C 0x3              ; Speed at which the paddle moves
        MOVI D 0x5              ; Height of the paddle

        MOVI L 0x1
        MOVI E 0x1
        INPUT F E
        CMP F L
        JNE move_paddle_right
move_paddle_left:
        LOADI A paddle_x
        MOVI L 0x10             ; Make sure the paddle x position is
        CMP A L                 ; greater than the wall position.
        JBE move_paddle_done
        SUB A C
        STORI A paddle_x
        MOVI L 0x20
        ADD A L
        CALL draw_rectangle
        JMPI move_paddle_done
move_paddle_right:
        MOVI E 0x2
        INPUT F E
        CMP F L
        JNE move_paddle_done
        LOADI A paddle_x
        MOVI L 0xd0             ; Make sure the paddle x position is
        CMP A L                 ; less than the wall position minus
        JAE move_paddle_done    ; the paddle width.
        ADD A C
        STORI A paddle_x
        SUB A C
        CALL draw_rectangle
move_paddle_done:
        MOV SP FP
        POP L
        POP F
        POP E
        POP D
        POP C
        POP B
        POP A
        POP FP
        RET

move_ball:
        PUSH FP
        PUSH A
        PUSH B
        PUSH C
        PUSH D
        PUSH E
        PUSH L
        PUSH M
        PUSH N
        MOV FP SP

        LOADI A ball_dir_x
        LOADI B ball_dir_y
        LOADI C ball_speed
        LOADI M ball_x
        LOADI N ball_y

        MOVI L 0xf              ; Get the leading bits for the direction, used
        MOV D A                 ; to determine the sign. Shift the direction by
        SHRL D L                ; 15 to get 0x1 for negative or 0x0 for positive
        MOV E B
        SHRL E L

        MOVI L 0x7fff           ; Get rid of the sign bit from the direction
        AND A L                 ; so that we can do math with it.
        AND B L

        MUL A C                 ; Multiply the speed by the direction to get
        MUL B C                 ; the velocity to add to / sub from position.
        MOVI L 0x1              ; The velocity must be shifted right by one
        SHRL A L                ; because the speed is in 15.1 fixed point
        SHRL B L                ; format.

        MOVI L 0x1              ; Add or subtract the x and y velocities to or
        CMP D L                 ; from the ball's position, to get its new
        JEQ move_ball_x_neg     ; position.
        ADD M A
        JMPI move_ball_x_done
move_ball_x_neg:
        SUB M A
move_ball_x_done:
        CMP E L
        JEQ move_ball_y_neg
        ADD N B
        JMPI move_ball_y_done
move_ball_y_neg:
        SUB N B
move_ball_y_done:
        STORI M ball_x
        STORI N ball_y

        MOV SP FP
        POP N
        POP M
        POP L
        POP E
        POP D
        POP C
        POP B
        POP A
        POP FP
        RET

move_ball_with_paddle:
        PUSH FP
        PUSH A
        PUSH B
        MOV FP SP

        LOADI A paddle_x        ; Set the ball x equal to the paddle
        MOVI B 0xe              ; x plus 14 (to center it).
        ADD A B
        MOVI B 0x8              ; Shift the ball x left by 8 because
        SHL A B                 ; it is in 8.8 fixed point.
        STORI A ball_x

        MOVI A 0xb300           ; Set the ball y equal to the paddle y minus
        STORI A ball_y          ; 5 (to be floating 1 pixel above the paddle).

        MOV SP FP
        POP B
        POP A
        POP FP
        RET

change_direction:
        PUSH FP
        PUSH B
        PUSH C
        PUSH L
        MOV FP SP

        MOVI L 0x1
        MOV C A
        SHL C L

        MOVI B lookup_dir_x
        ADD B C
        LOAD B B
        STORI B ball_dir_x

        MOVI B lookup_dir_y
        ADD B C
        LOAD B B
        STORI B ball_dir_y

        MOV SP FP
        POP L
        POP C
        POP B
        POP FP
        RET

collide_walls:
        PUSH FP
        PUSH A
        PUSH B
        MOV FP SP

        LOADI A ball_x          ; Check for collision with the left wall. If
        MOVI B 0x1000           ; there is collision, reverse the x direction
        CMP A B                 ; of the ball and set its position to the
        JA collide_walls_right  ; minimum x position.
        MOV A B
        STORI A ball_x
        LOADI A ball_dir_x
        MOVI B 0x7fff
        AND A B
        STORI A ball_dir_x
        JMPI collide_walls_top
collide_walls_right:            ; Check for collision with the right wall.
        MOVI B 0xec00
        CMP A B
        JB collide_walls_top
        MOV A B
        STORI A ball_x
        LOADI A ball_dir_x
        MOVI B 0x8000
        OR A B
        STORI A ball_dir_x
collide_walls_top:
        LOADI A ball_y          ; Check for collision with the top wall.
        MOVI B 0x1000
        CMP A B
        JA collide_walls_done
        MOV A B
        STORI A ball_y
        LOADI A ball_dir_y
        MOVI B 0x7fff
        AND A B
        STORI A ball_dir_y
collide_walls_done:

        MOV SP FP
        POP B
        POP A
        POP FP
        RET

collide_paddle:
        PUSH FP
        PUSH A
        PUSH B
        PUSH C
        PUSH D
        PUSH E
        PUSH L
        MOV FP SP

        LOADI A paddle_x
        LOADI B ball_x
        LOADI C ball_y
        LOADI D ball_x_prev
        LOADI E ball_y_prev
        MOVI L 0x8              ; Turn the 8.8 position into an integer value
        SHRL B L                ; by shifting it 8 to the right.
        SHRL C L
        SHRL D L
        SHRL E L

        MOVI L 0x4              ; Check if the ball is to the left of the
        MOV F B                 ; leftmost part of the paddle, meaning no
        ADD F L                 ; collision.
        CMP F A
        JB collide_paddle_left
        MOVI L 0x20             ; Check if the ball is to the right of the
        MOV F A                 ; rightmost part of the paddle, meaning no
        ADD F L                 ; collision.
        CMP B F
        JAE collide_paddle_left
        MOVI F 0xb4             ; Check if the ball is above the paddle's
        CMP C F                 ; surface, meaning no collision.
        JB collide_paddle_left
        CMP E F                 ; Check if the ball's previous position was
        JAE collide_paddle_left ; below the paddle, meaning no collision.

        MOVI L 0xb400           ; Set the ball's y position to the maximum
        STORI L ball_y          ; and set it up with a new velocity.
        SUB B A
        MOVI L 0x4
        ADD B L
        MOVI L 0x2
        SHRL B L
        MOV A B
        CALL change_direction
        JMPI collide_paddle_done

collide_paddle_left:            ; Now we check for horizontal collision
        MOVI L 0xb4             ; Check that ball_y + ball_height < paddle_y,
        CMP C L                 ; meaning no collision.
        JB collide_paddle_done
        MOVI L 0xbd             ; Check that ball_y > paddle_y + paddle_height,
        CMP C L                 ; meaning no collision.
        JA collide_paddle_done
        MOVI L 0x4
        MOV F D                 ; Check ball_x_prev + ball_width > paddle_x,
        ADD F L                 ; meaning no left collision.
        CMP F A
        JA collide_paddle_right
        MOV F B                 ; Check that ball_x + ball_width < paddle_x,
        ADD F L                 ; meaning no left collision.
        CMP F A
        JB collide_paddle_right
        MOV F A                 ; We had collision, set the ball in its correct
        MOVI L 0x4              ; position.
        SUB F L
        MOVI L 0x8
        SHL F L
        STORI F ball_x
        LOADI F ball_dir_x      ; Update the direction.
        MOVI L 0x8000
        OR F L
        STORI F ball_dir_x
        JMPI collide_paddle_done
collide_paddle_right:
        MOVI L 0x20
        MOV F A                 ; Check ball_x_prev < paddle_x + paddle_width,
        ADD F L                 ; meaning no collision.
        CMP F D
        JA collide_paddle_done
        CMP F B                 ; Check ball_x > brick_x + brick_width,
        JB collide_paddle_done   ; meaning no collision.
        MOVI L 0x8              ; We had collision, set the ball in its correct
        SHL F L                 ; position.
        STORI F ball_x
        LOADI F ball_dir_x      ; Update the direction.
        MOVI L 0x7fff
        AND F L
        STORI F ball_dir_x

collide_paddle_done:
        MOV SP FP
        POP L
        POP E
        POP D
        POP C
        POP B
        POP A
        POP FP
        RET

collide_bricks_all:
        PUSH FP
        PUSH A
        PUSH B
        PUSH C
        PUSH D
        MOV FP SP

        LOADI A bricks_blue
        MOVI B 0x20             ; Starting y position.
        MOVI C 0x64             ; Point value if collided, 100.
        MOVI D 0x7              ; Min speed if collided.
        CALL collide_bricks_line
        STORI A bricks_blue

        LOADI A bricks_orange
        MOVI B 0x30             ; Starting y position.
        MOVI C 0x32             ; Point value if collided, 50.
        MOVI D 0x5              ; Min speed if collided.
        CALL collide_bricks_line
        STORI A bricks_orange

        LOADI A bricks_green
        MOVI B 0x40             ; Starting y position.
        MOVI C 0x19             ; Point value if collided, 25.
        MOVI D 0x4              ; Min speed if collided.
        CALL collide_bricks_line
        STORI A bricks_green

        MOV SP FP
        POP D
        POP C
        POP B
        POP A
        POP FP
        RET

collide_bricks_line:            ; A is brick vector, B is y position, C is point
                                ; value of bricks, D is min speed if collided.
        PUSH FP
        PUSH E
        PUSH F
        PUSH G
        PUSH H
        PUSH L
        PUSH M
        PUSH N
        MOV FP SP

        MOVI E 0x10             ; Starting x position
        MOVI F 0xf0             ; Maximum x position
        MOV G B
        MOVI L 0x10
        ADD G L                 ; Maximum y position
        MOVI H 0x8000           ; The bit we're testing for

collide_bricks_line_next:
        TST A H                 ; Check if the bit we're testing is set.
        JEQ collide_bricks_line_inc ; If the bit is a zero, don't do collide.
        MOV L A                 ; Save the value of A in L, and set up A to
        MOV A E                 ; be the x position for calling collide_brick.
        CALL collide_brick      ; This sets A to be 1 if there was a collision
        MOV M A                 ; Set return value in M
        MOV A L                 ; Restore A
        MOVI N 0x1              ; Check if the return value was 1, if it wasn't
        CMP M N                 ; then don't do anything.
        JNE collide_bricks_line_inc
        LOADI L score           ; Update the score
        ADD L C
        STORI L score
        XOR A H                 ; Set the bit for this brick to 0.
        LOADI L ball_speed      ; Increase the ball speed if it is not at the
        CMP L D                 ; minimum.
        JAE collide_bricks_line_inc
        STORI D ball_speed
collide_bricks_line_inc:
        MOVI L 0x1              ; Shift the bit we're testing
        SHRL H L
        MOVI L 0x1c             ; Increment the x position, check if it's
        ADD E L                 ; greater than the maximum.
        CMP E F
        JB collide_bricks_line_next
        MOVI E 0x10
        MOVI L 0x8
        ADD B L
        CMP B G
        JB collide_bricks_line_next

        MOV SP FP
        POP N
        POP M
        POP L
        POP H
        POP G
        POP F
        POP E
        POP FP
        RET

collide_brick:                  ; Check for collision with a single brick.
                                ; A is the x position, B is the y position.
                                ; If there is a collision, this function will
                                ; handle updating the ball's position and
                                ; direction and will black out the brick.
        PUSH FP
        PUSH C
        PUSH D
        PUSH E
        PUSH F
        PUSH G
        PUSH H
        PUSH I
        PUSH L
        PUSH N
        MOV FP SP

        MOVI N 0x0              ; Return value

        MOVI C 0x1c             ; Brick width
        MOVI D 0x8              ; Brick height

        LOADI E ball_x          ; Get the ball x and y and shift them to their
        LOADI F ball_y          ; integer values.
        LOADI G ball_x_prev
        LOADI H ball_y_prev
        MOVI L 0x8
        SHRL E L
        SHRL F L
        SHRL G L
        SHRL H L

        MOVI L 0x4              ; Check that ball_x + ball_width < brick_x,
        MOV I E                 ; in which case there is no vertical collision.
        ADD I L
        CMP I A
        JB collide_brick_vert_done
        MOV I A                 ; Check that ball_x > brick_x + brick_width,
        ADD I C                 ; in which case there is no vertical collision.
        CMP E I
        JA collide_brick_vert_done
        MOV I F                 ; Check that ball_y + ball_height > brick_y,
        ADD I L                 ; meaning no top collision
        CMP I B
        JB collide_brick_top_done
        MOV I H                 ; Check ball_y_prev + ball_height < brick_y,
        ADD I L                 ; meaning no top collision
        CMP I B
        JA collide_brick_top_done
        MOV I B                 ; We had collision, set the ball in its correct
        MOVI L 0x4              ; position.
        SUB I L
        MOVI L 0x8
        SHL I L
        STORI I ball_y
        LOADI I ball_dir_y      ; Update the direction.
        MOVI L 0x8000
        OR I L
        STORI I ball_dir_y
        MOVI L 0x0              ; Black out the brick
        COLOR L
        CALL draw_rectangle
        MOVI N 0x1              ; Set the return value
        JMPI collide_brick_done
collide_brick_top_done:
        MOV I B                 ; Check that ball_y > brick_y + brick_height,
        ADD I D                 ; meaning no collision.
        CMP I F
        JB collide_brick_vert_done
        CMP I H                 ; Check ball_y_prev < brick_y + brick_height
        JA collide_brick_vert_done
        MOV I B                 ; We had collision, set the ball in its correct
        ADD I D                 ; position.
        MOVI L 0x8
        SHL I L
        STORI I ball_y
        LOADI I ball_dir_y      ; Update the direction.
        MOVI L 0x7fff
        AND I L
        STORI I ball_dir_y
        MOVI L 0x0              ; Black out the brick
        COLOR L
        CALL draw_rectangle
        MOVI N 0x1              ; Set the return value
        JMPI collide_brick_done
collide_brick_vert_done:
        MOV I F                 ; Check that ball_y + ball_height < brick_y,
        MOVI L 0x4              ; meaning no collision.
        ADD I L
        CMP I B
        JB collide_brick_done
        MOV I B                 ; Check that ball_y > brick_y + brick_height,
        ADD I D                 ; meaning no collision.
        CMP F I
        JA collide_brick_done
        MOV I G                 ; Check ball_x_prev + ball_width > brick_x,
        ADD I L                 ; meaning no left collision.
        CMP I A
        JA collide_brick_left_done
        MOV I E                 ; Check that ball_x + ball_width < brick_x,
        ADD I L                 ; meaning no left collision.
        CMP I A
        JB collide_brick_left_done
        MOV I A                 ; We had collision, set the ball in its correct
        MOVI L 0x4              ; position.
        SUB I L
        MOVI L 0x8
        SHL I L
        STORI I ball_x
        LOADI I ball_dir_x      ; Update the direction.
        MOVI L 0x8000
        OR I L
        STORI I ball_dir_x
        MOVI L 0x0              ; Black out the brick
        COLOR L
        CALL draw_rectangle
        MOVI N 0x1              ; Set the return value
        JMPI collide_brick_done
collide_brick_left_done:
        MOV I A                 ; Check ball_x_prev < brick_x + brick_width,
        ADD I C                 ; meaning no collision.
        CMP I G
        JA collide_brick_done
        CMP I E                 ; Check ball_x > brick_x + brick_width,
        JB collide_brick_done   ; meaning no collision.
        MOVI L 0x8              ; We had collision, set the ball in its correct
        SHL I L                 ; position.
        STORI I ball_x
        LOADI I ball_dir_x      ; Update the direction.
        MOVI L 0x7fff
        AND I L
        STORI I ball_dir_x
        MOVI L 0x0              ; Black out the brick
        COLOR L
        CALL draw_rectangle
        MOVI N 0x1              ; Set the return value
        JMPI collide_brick_done
collide_brick_done:
        MOV A N                 ; We stored the return value in N temporarily,
                                ; set it back to A.
        MOV SP FP
        POP N
        POP L
        POP I
        POP H
        POP G
        POP F
        POP E
        POP D
        POP C
        POP FP
        RET

check_endgame:
        PUSH FP
        PUSH A
        PUSH B
        MOV FP SP

        LOADI A ball_y
        MOVI B 0xf000
        CMP A B
        JBE check_endgame_done
        MOVI A 0x0
        STORI A is_running

check_endgame_done:
        MOV SP FP
        POP B
        POP A
        POP FP
        RET

draw_walls:                     ; Draws a white 16px border on the top, left,
        PUSH FP
        PUSH A                  ; and right.
        PUSH B
        PUSH C
        PUSH D
        MOV FP SP

        MOVI A 0xff             ; Set the color to white
        COLOR A

        MOVI A 0x0              ; Draws a 256x16 rectangle at (0, 0)
        MOVI B 0x0
        MOVI C 0x100
        MOVI D 0x10
        CALL draw_rectangle

        MOVI B 0x10             ; Draws a 16x176 rectangle at (0, 16)
        MOVI C 0x10
        MOVI D 0xb0
        CALL draw_rectangle

        MOVI A 0xf0             ; Draws a 16x176 rectangle at (240, 16)
        CALL draw_rectangle

        MOV SP FP
        POP D
        POP C
        POP B
        POP A
        POP FP
        RET

draw_bricks:
        PUSH FP
        PUSH A
        PUSH B
        PUSH C
        PUSH D
        PUSH E
        PUSH F
        PUSH G
        PUSH H
        PUSH I
        PUSH L
        MOV FP SP

        MOVI A 0x10             ; Starting x position of brick
        MOVI B 0x20             ; Starting y position of brick
        MOVI C 0x1b             ; Width of brick is 27 pixels
        MOVI D 0x7              ; Height of brick is 7 pixels
        MOVI E 0x0              ; x counter
        MOVI F 0x8              ; x limit
        MOVI G 0x0              ; y counter
        MOVI H 0x6              ; y limit
        MOVI L 0x1              ; Used to increment by 1

        MOVI I 0x3              ; Set the initial brick color to blue
        COLOR I

draw_bricks_next:
        CALL draw_rectangle
        ADD A C
        ADD A L                 ; Add 1px gap
        ADD E L
        CMP E F
        JB draw_bricks_next
        MOVI A 0x10
        MOVI E 0x0
        ADD B D
        ADD B L                 ; Add 1px gap
        ADD G L
        MOVI H 0x2              ; If the row is less than 2, it is
        CMP G H                 ; still blue so we don't need to change
        JB draw_bricks_next     ; the color.
        MOVI I 0xec             ; Else set the color to orange. If the
        COLOR I                 ; row is less than 4, draw the next
        MOVI H 0x4              ; row in orange.
        CMP G H
        JB draw_bricks_next
        MOVI I 0x1c             ; Else set the color to green. If the
        COLOR I                 ; row is less than 6, draw the next
        MOVI H 0x6              ; row in blue. Otherwise we're done
        CMP G H                 ; drawing bricks.
        JB draw_bricks_next

        MOV SP FP
        POP L
        POP I
        POP H
        POP G
        POP F
        POP E
        POP D
        POP C
        POP B
        POP A
        POP FP
        RET

draw_ball:
        PUSH FP
        PUSH A
        PUSH B
        PUSH C
        PUSH D
        PUSH L
        MOV FP SP

        MOVI C 0x4              ; The width and height of the ball.
        MOVI D 0x4

        LOADI A ball_x
        LOADI B ball_x_prev
        CMP A B
        JNE draw_ball_black
        LOADI A ball_y
        LOADI B ball_y_prev
        CMP A B
        JNE draw_ball_black
        JMPI draw_ball_white

draw_ball_black:
        MOVI L 0x0              ; Set the color to black
        COLOR L
        LOADI A ball_x_prev     ; Draw the ball at the prev x and y coords
        LOADI B ball_y_prev     ; in memory.
        MOVI L 0x8              ; The x and y coords are in 8.8 fixed
        SHRL A L                ; point, so we have to shift them right
        SHRL B L                ; to get the integer values.
        CALL draw_rectangle

draw_ball_white:
        MOVI L 0xff             ; Set the color to white
        COLOR L
        LOADI A ball_x          ; Draw teh ball at the current x and
        LOADI B ball_y          ; y coords in memory.
        MOVI L 0x8
        SHRL A L
        SHRL B L
        CALL draw_rectangle

draw_ball_done:
        MOV SP FP
        POP L
        POP D
        POP C
        POP B
        POP A
        POP FP
        RET

draw_paddle:
        PUSH FP
        PUSH A
        PUSH B
        PUSH C
        PUSH D
        MOV FP SP

        MOVI A 0xff             ; Set the color to white
        COLOR A

        LOADI A paddle_x        ; Draw the paddle at the x position
        MOVI B 0xb8             ; stored in memory.
        MOVI C 0x20
        MOVI D 0x5
        CALL draw_rectangle

        MOV SP FP
        POP D
        POP C
        POP B
        POP A
        POP FP
        RET

draw_score:
        PUSH FP
        PUSH A
        PUSH B
        PUSH C
        PUSH E
        PUSH F
        PUSH L
        PUSH M
        PUSH N
        MOV FP SP

        LOADI E score            ; Load the score and previous score.
        LOADI F score_prev
        CMP E F
        JEQ draw_score_blank_done
        MOVI L 0xff             ; If the current score is different than the
        COLOR L                 ; previous score, first clear the score rect.
        MOVI A 0x10
        MOVI B 0x3
        MOVI C 0x1e
        MOVI D 0xa
        CALL draw_rectangle
draw_score_blank_done:

        MOVI L 0x0              ; Set the color to black
        COLOR L

        MOVI M 0x0              ; Counter for drawing digits
        MOVI N 0x4              ; Number of digits
        MOVI A 0x28             ; x position start
        MOVI B 0x3              ; y position

draw_score_digit:
        MOV F E
        MOVI L 0xa
        DIV F L
        MUL F L
        MOV C E
        SUB C F
        DIV E L
        CALL draw_number
        MOVI L 0x8
        SUB A L
        MOVI L 0x1
        ADD M L
        CMP M N
        JB draw_score_digit

        MOV SP FP
        POP N
        POP M
        POP L
        POP F
        POP E
        POP C
        POP B
        POP A
        POP FP
        RET

draw_number:                    ; Draws the number C to (A, B)
        PUSH FP
        PUSH C
        PUSH D
        PUSH E
        PUSH F
        PUSH G
        PUSH H
        PUSH L
        PUSH M
        MOV FP SP

        MOVI M num_bitmaps
        MOVI L 0x1              ; C is the number argument 0-9.
        SHL C L                 ; We shift it because each bitmap is
        ADD M C                 ; two bytes.
        LOAD M M                ; Now the bitmap is stored in D

        MOVI L 0x6              ; Width of bitmap
        MOV E A                 ; E is the x counter
        MOV F A                 ; F is the x limit
        ADD F L

        MOVI L 0xa              ; Height of bitmap
        MOV G B                 ; G is the y counter
        MOV H B                 ; H is the y limit
        ADD H L

        MOVI C 0x2              ; Width of bitmap pixel
        MOVI D 0x2              ; Height of bitmap pixel

draw_number_pixel:
        MOVI L 0x1
        SHL M L                 ; Get the next bit in bitmap
        MOVI L 0x8000           ; Only the high bit set
        TST M L                 ; Check if the high bit is set
        JEQ draw_number_pixel_done ; If not, skip over drawing
        PUSH A
        PUSH B
        MOV A E
        MOV B G
        CALL draw_rectangle
        POP B
        POP A
draw_number_pixel_done:
        ADD E C
        CMP E F
        JB draw_number_pixel
        MOV E A
        ADD G D
        CMP G H
        JB draw_number_pixel

        MOV SP FP
        POP M
        POP L
        POP H
        POP G
        POP F
        POP E
        POP D
        POP C
        POP FP
        RET

draw_rectangle:                 ; Draws rectangle at (A, B) to (A+C, B+D).
        PUSH FP
        PUSH E                  ; Save all the registers we will use
        PUSH F                  ; so that we can restore them later.
        PUSH G
        PUSH H
        PUSH L
        MOV FP SP

        MOVI L 0x1              ; Used to increment by 1
        MOV E A                 ; X-coordinate counter
        MOV F B                 ; Y-coordinate counter
        MOV G E                 ; G is (x + width), the x-coord limit
        ADD G C
        MOV H F                 ; H is (y + height), the y-coord limit
        ADD H D

draw_rectangle_pixel:           ; Label to jump to if we still want to
        PIXEL E F               ; draw the next pixel.
        ADD E L                 ; Increment the x-coordinate counter
        CMP E G                 ; If x-counter < x + width, continue
        JB draw_rectangle_pixel ; drawing
        ADD F L                 ; Else increment the y-coordinate
        MOV E A                  ; and set the x-coord counter to x-start
        CMP F H                 ; If y-counter < y + height, continue
        JB draw_rectangle_pixel ; drawing

        MOV SP FP
        POP L                   ; Restore the registers we used in
        POP H                   ; reverse order.
        POP G
        POP F
        POP E
        POP FP

        RET                     ; Jump back to the saved location of IP

stack:                          ; The location of the stack