Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 14: Complete Example - A Bouncing Ball Game

In this final chapter, we’ll put everything together by examining a complete game that demonstrates all assembler features.

The Bouncing Ball Demo

This program displays a white ball that bounces around the screen, demonstrating:

  • Hardware initialization
  • Game loop structure
  • Sprite movement and collision
  • Video memory access

Memory Map

; Memory-mapped I/O registers
.equ VID_PTR  0xFD       ; Video page pointer (page number, not high byte)
.equ RANDOM   0xFE       ; Random number generator
.equ INPUT    0xFF       ; Input register

; VRAM location
.equ VRAM     0x1000     ; Start of video RAM
.equ VID_PAGE 0x01       ; Video page number (0x1000 >> 12)

; Screen dimensions
.equ WIDTH    64
.equ HEIGHT   64

; Zero page variables
.equ BALL_X   0x00       ; Ball X position
.equ BALL_Y   0x01       ; Ball Y position
.equ VEL_X    0x02       ; X velocity (signed)
.equ VEL_Y    0x03       ; Y velocity (signed)
.equ OLD_X    0x04       ; Previous X position
.equ OLD_Y    0x05       ; Previous Y position
.equ TEMP     0x06       ; Temporary variable

Using .equ for constants makes the code readable and maintainable.

Initialization

.org 0x8000

reset:
    ; Set video page to VRAM (page 1 = 0x1000-0x1FFF)
    lda #VID_PAGE
    sta VID_PTR

    ; Initialize ball position to center
    lda #32
    sta BALL_X
    sta BALL_Y

    ; Initialize velocity (moving down-right)
    lda #1
    sta VEL_X
    sta VEL_Y

    ; Clear the screen
    jsr clear_screen

The VID_PTR register takes a page number (0-15), where each page is 4KB. Page 1 corresponds to addresses 0x1000-0x1FFF.

The Main Loop

The main loop is driven by the IRQ (VBLANK):

main_loop:
    ; Save old position for erasing
    lda BALL_X
    sta OLD_X
    lda BALL_Y
    sta OLD_Y

    ; Update ball position
    jsr update_ball

    ; Erase old ball
    ldx OLD_X
    ldy OLD_Y
    lda #0          ; black
    jsr draw_pixel

    ; Draw new ball
    ldx BALL_X
    ldy BALL_Y
    lda #1          ; white
    jsr draw_pixel

    ; Wait for next frame
    rti

The rti (return from interrupt) waits until the next VBLANK.

Ball Update with Bouncing

update_ball:
    ; Update X position
    lda BALL_X
    clc
    adc VEL_X
    sta BALL_X

    ; Check X bounds
    cmp #WIDTH - 1
    bcs .bounce_x
    cmp #0
    beq .bounce_x
    jmp .check_y

.bounce_x:
    ; Reverse X velocity: VEL_X = 0 - VEL_X
    lda #0
    sec
    sbc VEL_X
    sta VEL_X

    ; Clamp X position
    lda BALL_X
    cmp #WIDTH
    bcc .clamp_x_done
    lda #WIDTH - 2
    sta BALL_X
.clamp_x_done:
    lda BALL_X
    bne .check_y
    lda #1
    sta BALL_X

.check_y:
    ; Similar logic for Y...
    rts

This demonstrates:

  • Local labels (.bounce_x, .check_y)
  • Expression in immediate (#WIDTH - 1)
  • Conditional branching

Pixel Drawing

; Draw a pixel at (X, Y) with color in A
; X = column (0-63)
; Y = row (0-63)
; A = color
draw_pixel:
    sta TEMP            ; Save color

    ; Calculate VRAM offset: Y * 64 + X
    tya                 ; A = Y
    asl a               ; A = Y * 2
    asl a               ; A = Y * 4
    asl a               ; A = Y * 8
    asl a               ; A = Y * 16
    asl a               ; A = Y * 32
    asl a               ; A = Y * 64

    ; Add X
    stx TEMP + 1        ; Save X
    clc
    adc TEMP + 1        ; A = Y * 64 + X

    ; Store to VRAM
    tax
    lda TEMP            ; Get color back
    sta VRAM,x          ; Write to VRAM

    rts

Screen Clearing

clear_screen:
    ldx #0
    lda #0
.loop:
    sta VRAM,x
    sta VRAM + 0x100,x
    sta VRAM + 0x200,x
    sta VRAM + 0x300,x
    ; ... (more pages)
    inx
    bne .loop
    rts

Using expressions like VRAM + 0x100 makes the code clearer.

Interrupt Vectors

; Set up reset and IRQ vectors
.org 0xFFFC
.dw reset           ; Reset vector
.dw main_loop       ; IRQ vector (VBLANK)

The .dw directive writes 16-bit addresses in little-endian format.

Building and Running

# Assemble the game
cargo run -p byte_asm -- bouncing_ball.s -o game.bin

# Run in emulator
cargo run -p byte_emu -- game.bin

With verbose output:

$ cargo run -p byte_asm -- -v bouncing_ball.s -o game.bin

Assembling: bouncing_ball.s
Parsed 85 statements
Generated 312 bytes
Defined 15 symbols
Wrote: game.bin

Features Demonstrated

This example uses all major assembler features:

FeatureExample
.org.org 0x8000
.equ.equ WIDTH 64
.db(could add strings)
.dw.dw reset
Labelsreset:, main_loop:
Local labels.bounce_x:, .loop:
Immediatelda #32
Zero Pagesta BALL_X
Absolutesta VRAM,x
Indexedsta VRAM + 0x100,x
Expressions#WIDTH - 1, VRAM + 0x100
Branchesbne .loop, bcs .bounce_x
Subroutinesjsr update_ball, rts

Extending the Example

Ideas for additions:

  • User input to control ball direction
  • Multiple balls
  • Score counter
  • Sound effects
  • Paddle for a Pong-like game

Complete Source

See byte_asm/examples/bouncing_ball.s for the full source code.

Summary

In this tutorial, we built a complete 6502 assembler from scratch:

  1. Scanner: Tokenizes source into meaningful chunks
  2. Parser: Builds an AST representing program structure
  3. Symbol Table: Tracks labels and constants
  4. Two-Pass Assembler: Resolves forward references
  5. Code Generator: Emits correct machine code
  6. Expression Evaluator: Computes values at assembly time
  7. Directive Handlers: Processes .org, .db, .dw, .equ
  8. Error Handling: Provides helpful error messages
  9. CLI: User-friendly command-line interface
  10. Testing: Verifies correctness

The result is a fully functional assembler that can build real programs for the Byte fantasy console.

What’s Next?

  • Macros: Add .macro and .endmacro for code reuse
  • Conditional Assembly: Add .if, .else, .endif
  • Include Path: Search multiple directories for includes
  • Listing Files: Generate human-readable assembly listings
  • Debug Symbols: Output symbol tables for debuggers

Happy assembling!


Previous: Chapter 13 - Testing | Back to Index