Loading...
Searching...
No Matches
main.c File Reference

Breakout game with brick collision, paddle physics, and level cycling. More...

#include <snes.h>

Macros

#define ST_BLANK   str_blank
 
#define ST_GAMEOVER   str_gameover
 
#define ST_PAUSED   str_paused
 
#define ST_READY   str_ready
 

Functions

static void check_bricks (void)
 Check collision with bricks.
 
static void check_paddle (void)
 Check collision with paddle.
 
static void die (void)
 Handle player losing a life.
 
static void draw_screen (void)
 Update all sprite positions.
 
static void handle_pause (void)
 Handle pause functionality.
 
int main (void)
 Game initialization and main loop.
 
static void move_ball (void)
 Update ball position and handle wall collision.
 
static void move_paddle (void)
 Handle paddle movement from joypad input.
 
static void mycopy (u8 *dest, const u8 *src, u16 len)
 Simple byte-by-byte memory copy.
 
static void new_level (void)
 Initialize a new level.
 
static void remove_brick (void)
 Remove a brick and update score.
 
static void run_frame (void)
 Run one frame of gameplay.
 
static void writenum (u16 num, u8 len, u16 *tilemap, u16 pos, u16 offset)
 Write a right-aligned number to a tilemap buffer.
 
static void writestring (const char *st, u16 *tilemap, u16 pos, u16 offset)
 Write a null-terminated string to a tilemap buffer.
 

Variables

static u16 a
 
static u16 b
 
u16 backmap []
 BG3 tilemap RAM copy (0x400 entries = 2KB) at WRAM $1000.
 
u8 backpal []
 7 level color sets for palette cycling (7 x 16 bytes = 112 bytes)
 
u8 backpal_end []
 
u8 bg1map []
 BG1 tilemap: playfield border frame and HUD layout (2KB)
 
u8 bg1map_end []
 
u8 bg2map []
 BG3 tilemap: all 4 background fill patterns concatenated (2KB)
 
u8 bg2map0 []
 Individual BG3 patterns (pointers into bg2map for per-level selection)
 
u8 bg2map1 []
 
u8 bg2map2 []
 
u8 bg2map3 []
 
u8 bg2map_end []
 
static u16 blockcount
 
u16 blockmap []
 BG1 tilemap RAM copy (0x400 entries = 2KB) at WRAM $0800.
 
u8 blocks []
 Mutable brick state array (100 entries, one per grid cell).
 
const u8 brick_map []
 Default brick layout (10x10 grid, 100 bytes).
 
static u16 bx
 
static u16 by
 
static u16 c
 
static u16 color
 
static u16 hiscore
 
static u8 i
 
static u8 j
 
static u8 k
 
static u16 level
 
static u16 level2
 
static u16 lives
 
static u16 obx
 
static u16 oby
 
static u16 pad0
 
u16 pad_keys []
 Raw joypad state buffer written by the NMI handler every frame.
 
u16 pal []
 Palette RAM copy (0x100 entries = 512 bytes) at WRAM $1800.
 
u8 palette []
 Full 256-color palette for BG and sprite layers (512 bytes)
 
u8 palette_end []
 
static s16 pos_x
 
static s16 pos_y
 
static u16 px
 
static u16 score
 
const char str_blank []
 Blank string (spaces) for clearing message areas.
 
const char str_gameover []
 "GAME OVER" message string
 
const char str_paused []
 "PAUSED" message string
 
const char str_ready []
 "READY" message string (in data.asm for guaranteed bank $00 placement)
 
u8 tiles1 []
 BG tiles: border, bricks, and font glyphs (4bpp, ~3840 bytes)
 
u8 tiles1_end []
 
u8 tiles2 []
 Sprite tiles: ball, paddle, and shadow shapes (4bpp, ~592 bytes)
 
u8 tiles2_end []
 
static s16 vel_x
 Ball X velocity (-2 to +2 pixels/frame).
 
static s16 vel_y
 

Detailed Description

Breakout game with brick collision, paddle physics, and level cycling.

A complete Breakout game demonstrating how multiple SNES subsystems work together in a real game. The playfield uses background tiles (not sprites) for bricks, enabling 100 on-screen bricks without hitting the 128-sprite hardware limit. The paddle and ball are hardware sprites with shadow sprites for depth effect.

Key technical patterns: RAM tilemap buffers for runtime brick destruction, overlapping BG1/BG3 tilemaps in VRAM to save memory (requiring atomic dual-DMA updates), palette cycling for per-level color variation, and direct oamMemory[] writes instead of oamSet() for performance (10 sprites per frame would cause visible slowdown with oamSet's 158-byte stack frame).

Ball bounce angle varies by paddle hit zone (4 zones), creating the classic Breakout aiming mechanic. Scoring uses level multipliers.

Based on SNES SDK game by Ulrich Hecht, PVSnesLib port by alekmaul.

SNES Concepts
  • Mode 1: BG1 (4bpp playfield), BG3 (2bpp background pattern), OBJ sprites
  • Overlapping VRAM tilemaps (BG1 at $0000, BG3 at $0400 share $0400-$07FF)
  • Atomic dual-DMA: both tilemaps uploaded in same VBlank to prevent flicker
  • RAM tilemap buffers for runtime modification (brick destruction, HUD)
  • Palette cycling: 7 color sets rotated per level via CGRAM DMA
  • Direct oamMemory[] writes for 10 sprites (ball + paddle + shadows)
  • Secondary sprite name table access (tile | 256 for VRAM $2000+)
  • Force blank for bulk VRAM uploads exceeding VBlank DMA budget
What to Observe
  • Press START to begin; paddle moves with D-pad (hold A for speed boost)
  • Ball bounces off walls, paddle, and bricks with angled deflection
  • Bricks disappear on hit; score and high score update in the HUD
  • Background color changes each level (palette cycling)
  • Shadow sprites create a subtle depth effect under ball and paddle
  • START pauses/unpauses the game
Modules Used
console, sprite, dma, background, input
See also
sprite.h, dma.h, background.h, input.h

Macro Definition Documentation

◆ ST_BLANK

#define ST_BLANK   str_blank

Alias for the blank clearing string

◆ ST_GAMEOVER

#define ST_GAMEOVER   str_gameover

Alias for the GAME OVER message

◆ ST_PAUSED

#define ST_PAUSED   str_paused

Alias for the PAUSED message

◆ ST_READY

#define ST_READY   str_ready

Alias for the READY message

Function Documentation

◆ check_bricks()

static void check_bricks ( void  )
static

Check collision with bricks.

GRID-BASED COLLISION: Convert ball pixel position to brick grid coordinates, then check if that grid cell contains a brick.

Grid layout: 10 columns x 10 rows Each brick is 16x8 pixels Grid offset from screen edge: 14 pixels

◆ check_paddle()

static void check_paddle ( void  )
static

Check collision with paddle.

BOUNCE PHYSICS: Ball bounce angle depends on where it hits the paddle: Left edge: Sharp left angle (vel_x = -2, vel_y = -1) Left-mid: Slight left (vel_x = -1, vel_y = -2) Right-mid: Slight right (vel_x = +1, vel_y = -2) Right edge: Sharp right angle (vel_x = +2, vel_y = -1)

This creates the classic breakout gameplay where paddle position matters for aiming the ball.

◆ die()

static void die ( void  )
static

Handle player losing a life.

< Alias for the GAME OVER message

< Alias for the READY message

< Alias for the blank clearing string

< Alias for the blank clearing string

◆ draw_screen()

static void draw_screen ( void  )
static

Update all sprite positions.

SPRITE ORGANIZATION: This game uses 10 hardware sprites for the ball and paddle. Sprite 0 is skipped due to a corruption issue (possibly WRAM mirroring).

Sprite Assignment: Sprite 1: Ball Sprites 2-5: Paddle (left cap, 2x middle mirrored, right cap) Sprite 6: Ball shadow (offset +3,+3 pixels, lower priority) Sprites 7-10: Paddle shadow (offset +4,+4 pixels, lower priority)

TILE NUMBERING: Sprite tiles are at VRAM $2000 (secondary name table). To access secondary table, set bit 8 of tile number: tile | 256

Tile Layout in tiles2: Tile 15: Paddle left cap Tile 16: Paddle middle (used twice, second time H-flipped) Tile 17: Paddle right cap Tile 18: Paddle shadow left Tile 19: Paddle shadow middle Tile 20: Ball Tile 21: Ball shadow

SHADOW TECHNIQUE: Shadows are separate sprites with:

  • Same shape but darker color (shadow palette)
  • Offset position (+3 or +4 pixels down-right)
  • Lower priority (1 vs 3) so they appear behind main sprites

◆ handle_pause()

static void handle_pause ( void  )
static

Handle pause functionality.

< Alias for the PAUSED message

< Alias for the blank clearing string

◆ main()

int main ( void  )

Game initialization and main loop.

INITIALIZATION SEQUENCE:

  1. Force blank (screen off) during setup
  2. Load tiles to VRAM
  3. Copy ROM data to RAM buffers
  4. Initialize game state
  5. Configure video mode and backgrounds
  6. Enable display
  7. Wait for START, then run game loop

< Alias for the READY message

< Alias for the blank clearing string

< Alias for the blank clearing string

◆ move_ball()

static void move_ball ( void  )
static

Update ball position and handle wall collision.

NOTE: Uses explicit temp variables instead of direct assignment to work around QBE compiler issues with compound operations.

◆ move_paddle()

static void move_paddle ( void  )
static

Handle paddle movement from joypad input.

Paddle moves at 2 pixels/frame normally, 4 with A held. Clamped to playfield boundaries (16-144).

◆ mycopy()

static void mycopy ( u8 dest,
const u8 src,
u16  len 
)
static

Simple byte-by-byte memory copy.

Used instead of library memcpy to avoid bank addressing complexity. Works for Bank 0 addresses only (ROM $8000+ and RAM $0000-$1FFF).

◆ new_level()

static void new_level ( void  )
static

Initialize a new level.

This function handles level transitions:

  1. Reset ball/paddle positions
  2. Reset background tilemap and brick array from ROM
  3. Cycle background color
  4. Rebuild brick wall in existing BG1 tilemap (preserves HUD)
  5. Upload everything to VRAM
  6. Wait for player to press START

CRITICAL DMA LESSON: BG1 tilemap (0x0000-0x07FF) and BG3 tilemap (0x0400-0x0BFF) OVERLAP in VRAM at addresses 0x0400-0x07FF. Both tilemaps must be uploaded in the SAME VBlank, or the intermediate frame shows corruption.

This was a hard-learned lesson: splitting "to be safe" actually caused a visible pink flash. PVSnesLib does 4.5KB+ in one VBlank.

< Alias for the READY message

< Alias for the blank clearing string

< Alias for the blank clearing string

◆ remove_brick()

static void remove_brick ( void  )
static

Remove a brick and update score.

When a brick is destroyed:

  1. Update score (brick value + 1, multiplied by level)
  2. Reverse ball velocity based on collision axis
  3. Clear brick from tilemap
  4. Remove shadow effect from background
  5. DMA updated tilemaps to VRAM
  6. Check for level completion

◆ run_frame()

static void run_frame ( void  )
static

Run one frame of gameplay.

◆ writenum()

static void writenum ( u16  num,
u8  len,
u16 tilemap,
u16  pos,
u16  offset 
)
static

Write a right-aligned number to a tilemap buffer.

Parameters
numNumber to display
lenField width (pads with spaces on left)
tilemapPointer to tilemap buffer
posStarting position (leftmost digit position)
offsetValue added to digit (0-9) to get tile index

◆ writestring()

static void writestring ( const char *  st,
u16 tilemap,
u16  pos,
u16  offset 
)
static

Write a null-terminated string to a tilemap buffer.

Converts ASCII characters to tile indices by adding an offset. Supports newline (
) for multi-line text.

Parameters
stNull-terminated string
tilemapPointer to tilemap buffer (u16 per entry)
posStarting position in tilemap (0-0x3FF for 32x32)
offsetValue added to ASCII code to get tile index

Tilemap entry format (16-bit): Bits 0-9: Tile number (0-1023) Bits 10-12: Palette (0-7) Bit 13: Priority Bit 14: H-flip Bit 15: V-flip

Variable Documentation

◆ a

u16 a
static

◆ b

u16 b
static

Brick init temporaries (reused in new_level/main)

◆ backmap

u16 backmap[]
extern

BG3 tilemap RAM copy (0x400 entries = 2KB) at WRAM $1000.

Modified for shadow effects under bricks (palette bit toggling). DMAs to VRAM $0400-$0BFF. Overlaps BG1 at VRAM $0400-$07FF, so both must be uploaded atomically in the same VBlank.

◆ backpal

u8 backpal[]
extern

7 level color sets for palette cycling (7 x 16 bytes = 112 bytes)

◆ backpal_end

u8 backpal_end[]

◆ bg1map

u8 bg1map[]
extern

BG1 tilemap: playfield border frame and HUD layout (2KB)

◆ bg1map_end

u8 bg1map_end[]

◆ bg2map

u8 bg2map[]
extern

BG3 tilemap: all 4 background fill patterns concatenated (2KB)

◆ bg2map0

u8 bg2map0[]
extern

Individual BG3 patterns (pointers into bg2map for per-level selection)

◆ bg2map1

u8 bg2map1[]

◆ bg2map2

u8 bg2map2[]

◆ bg2map3

u8 bg2map3[]

◆ bg2map_end

u8 bg2map_end[]

◆ blockcount

u16 blockcount
static

Number of bricks remaining (0 triggers next level)

◆ blockmap

u16 blockmap[]
extern

BG1 tilemap RAM copy (0x400 entries = 2KB) at WRAM $0800.

Modified at runtime when bricks are destroyed and HUD text updates. DMAs to VRAM $0000-$07FF each time visuals change.

◆ blocks

u8 blocks[]
extern

Mutable brick state array (100 entries, one per grid cell).

Values 0-7 = active brick with that color, 8 = destroyed/empty. Initialized from the ROM brick_map[] template at each level start.

◆ brick_map

const u8 brick_map[]
extern

Default brick layout (10x10 grid, 100 bytes).

Each byte encodes the brick type at that grid position: values 0-7 select a colored brick (palette index), value 8 = empty cell. Copied to the mutable blocks[] array at level start so bricks can be destroyed at runtime without modifying the ROM template.

◆ bx

u16 bx
static

◆ by

u16 by
static

Ball position in brick grid coordinates (0-9 each)

◆ c

u16 c
static

◆ color

u16 color
static

Background palette set index (0-6, cycles per level)

◆ hiscore

u16 hiscore
static

Current score and all-time high score

◆ i

u8 i
static

◆ j

u8 j
static

◆ k

u8 k
static

Loop counters (global to reduce 65816 stack overhead)

◆ level

u16 level
static

Internal level counter (0-based, used as score multiplier)

◆ level2

u16 level2
static

Display level number (1-based, shown in HUD)

◆ lives

u16 lives
static

Lives remaining (0 = game over on next death)

◆ obx

u16 obx
static

◆ oby

u16 oby
static

Previous frame's grid position (determines bounce axis)

◆ pad0

u16 pad0
static

Current frame's joypad button bitmask

◆ pad_keys

u16 pad_keys[]
extern

Raw joypad state buffer written by the NMI handler every frame.

pad_keys[0] = port 1 buttons, pad_keys[1] = port 2 buttons. Used directly (instead of padHeld()) for the game loop because Breakout was ported from PVSnesLib which reads pad_keys[] directly.

◆ pal

u16 pal[]
extern

Palette RAM copy (0x100 entries = 512 bytes) at WRAM $1800.

◆ palette

u8 palette[]
extern

Full 256-color palette for BG and sprite layers (512 bytes)

◆ palette_end

u8 palette_end[]

◆ pos_x

s16 pos_x
static

Ball X pixel position

◆ pos_y

s16 pos_y
static

Ball Y pixel position

◆ px

u16 px
static

Paddle X position in pixels (clamped to 16-144)

◆ score

u16 score
static

◆ str_blank

const char str_blank[]
extern

Blank string (spaces) for clearing message areas.

◆ str_gameover

const char str_gameover[]
extern

"GAME OVER" message string

◆ str_paused

const char str_paused[]
extern

"PAUSED" message string

◆ str_ready

const char str_ready[]
extern

"READY" message string (in data.asm for guaranteed bank $00 placement)

◆ tiles1

u8 tiles1[]
extern

BG tiles: border, bricks, and font glyphs (4bpp, ~3840 bytes)

◆ tiles1_end

u8 tiles1_end[]

◆ tiles2

u8 tiles2[]
extern

Sprite tiles: ball, paddle, and shadow shapes (4bpp, ~592 bytes)

◆ tiles2_end

u8 tiles2_end[]

◆ vel_x

s16 vel_x
static

Ball X velocity (-2 to +2 pixels/frame).

Separate from a struct because the cc65816 compiler has quirks with compound operations on struct members (e.g., negation, += assignment).

◆ vel_y

s16 vel_y
static

Ball Y velocity (-2 to +2 pixels/frame)