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

Tetris with 3-layer BG architecture, DAS input, and SNESMOD audio. More...

#include <snes.h>
#include <snes/snesmod.h>
#include "soundbank.h"
#include "board.h"
#include "piece.h"
#include "render.h"
#include "hud.h"

Macros

#define DAS_INITIAL_DELAY   16
 DAS initial delay before auto-repeat starts (~267ms at 60fps)
 
#define DAS_REPEAT_DELAY   6
 DAS repeat interval once auto-repeat is active (~100ms at 60fps)
 
#define FLASH_FRAMES   10
 Duration of the line clear flash animation in frames.
 
#define MSG_COLOR_COUNT   8
 Number of colors in the rainbow cycle array.
 
#define MSG_COLOR_SPEED   8
 Frames to display each color before advancing to the next.
 
#define SPEED_TABLE_LEN   30
 Number of entries in speed_table (levels 0-29)
 
#define STATE_GAME_OVER   3
 Game over: board frozen, "GAME OVER" rainbow cycle, await restart.
 
#define STATE_LINE_CLEAR   2
 Line clear animation: flashing rows + screen shake.
 
#define STATE_PLAYING   1
 Active gameplay: piece falling, player input, gravity.
 
#define STATE_TITLE   0
 Title screen: displaying "PRESS START" with rainbow cycle.
 

Functions

static void addScore (u8 lines_cleared)
 Add points for cleared lines using NES scoring formula.
 
static u8 getGravitySpeed (void)
 Look up the gravity speed (frames per drop) for the current level.
 
static void handleInput (void)
 Process player input with DAS (Delayed Auto Shift) for piece movement.
 
int main (void)
 Main entry point – Tetris initialization and state machine loop.
 
static u8 spawnPiece (void)
 Spawn the next piece at the top of the board.
 
static void startGame (void)
 Initialize a new game: clear board, reset state, start music.
 
static void stateGameOver (void)
 Game over state: display frozen board with rainbow "GAME OVER" text.
 
static void stateLineClear (void)
 Line clear animation state: flash rows, shake screen, then collapse.
 
static void statePlaying (void)
 Main gameplay state: handle input, apply gravity, lock pieces.
 
static void stateTitle (void)
 Title screen state: rainbow "PRESS START" text until player begins.
 

Variables

static LineClearResult clear_result
 Result of the last boardFindFullLines() call (which rows are full)
 
static s8 cur_col
 
static u8 cur_rot
 
static s8 cur_row
 
static u8 cur_type
 
static u8 das_down_timer
 
static u8 das_left_timer
 
static u8 das_right_timer
 
static u8 flash_timer
 
static u8 game_state
 
static u8 gravity_speed
 
static u8 gravity_timer
 
static u16 level
 
static u16 line_scores [] = { 0, 40, 100, 300, 1200 }
 NES scoring table: base points per number of simultaneous line clears.
 
static u8 lines_until_levelup
 
static u16 msg_colors []
 Rainbow color cycle palette for animated message text (BGR555).
 
static u8 next_type
 
u16 pad_keys []
 Raw joypad state buffer written by the NMI handler every frame.
 
static u8 paused
 
static u16 prev_pad
 Previous frame's joypad state (for edge-triggered button detection)
 
static u16 score
 
static s8 shake_dx [] = { 2, -2, 1, -1, 2, -1, 1, 0 }
 Screen shake X offsets during line clear animation.
 
static s8 shake_dy [] = { -1, 1, -2, 1, 0, -1, 1, 0 }
 Screen shake Y offsets during line clear animation.
 
static u8 speed_table []
 NES NTSC gravity speed table (frames per automatic drop).
 
const char str_gameover []
 "GAME OVER" overlay message string
 
const char str_paused []
 Global frame counter incremented by the NMI handler.
 
const char str_start []
 "PRESS START" title screen message string
 
static u16 total_lines
 

Detailed Description

Tetris with 3-layer BG architecture, DAS input, and SNESMOD audio.

A full Tetris implementation demonstrating a 3-layer Mode 1 architecture where each BG layer serves a distinct purpose: BG1 (4bpp) renders the playfield with border, locked blocks, and falling piece; BG2 (4bpp) displays the HUD with score, level, lines, and next piece preview; BG3 (2bpp) provides a message overlay (PRESS START, PAUSED, GAME OVER) that floats above the playfield using MODE1_PRIORITY_HIGH.

Input uses NES-authentic Delayed Auto Shift (DAS): an initial delay of 16 frames before auto-repeat kicks in at 6-frame intervals. Gravity follows the NES NTSC speed table (48 frames/drop at level 0 down to 1 frame/drop at level 29+). Scoring uses NES multipliers.

The renderer uses dirty flags to minimize VBlank DMA – only changed layers are flushed each frame. Heavy DMA frames (line clear completion, game over) use force blank to guarantee all VRAM writes succeed. An HDMA gradient provides a color wash effect on the background.

SNES Concepts
  • Mode 1 three-layer architecture (playfield / HUD / message overlay)
  • BG3 priority bit for floating overlay above 4bpp layers
  • Dirty-flag DMA: selective per-layer VRAM updates each VBlank
  • Force blank for heavy multi-layer DMA (BG1+BG2+BG3 > 4KB)
  • HDMA gradient effect on background palette
  • Delayed Auto Shift (DAS) for responsive piece movement
  • NES gravity speed table and scoring system
  • SNESMOD: SPC700 music playback (Tetris theme)
  • State machine: TITLE, PLAYING, LINE_CLEAR, GAME_OVER
  • Screen shake effect during line clear animation
What to Observe
  • Rainbow-cycling "PRESS START" on the title screen
  • Pieces fall with increasing speed as level rises
  • D-pad moves pieces (DAS auto-repeat after holding); A/B rotates
  • Completed lines flash and shake the screen before clearing
  • Score, level, and line count update in the right-side HUD
  • Next piece preview shows the upcoming tetromino
  • START pauses with a "PAUSED" overlay; GAME OVER cycles rainbow text
Modules Used
console, sprite, dma, background, input
See also
background.h, dma.h, input.h, snesmod.h

Macro Definition Documentation

◆ DAS_INITIAL_DELAY

#define DAS_INITIAL_DELAY   16

DAS initial delay before auto-repeat starts (~267ms at 60fps)

◆ DAS_REPEAT_DELAY

#define DAS_REPEAT_DELAY   6

DAS repeat interval once auto-repeat is active (~100ms at 60fps)

◆ FLASH_FRAMES

#define FLASH_FRAMES   10

Duration of the line clear flash animation in frames.

◆ MSG_COLOR_COUNT

#define MSG_COLOR_COUNT   8

Number of colors in the rainbow cycle array.

◆ MSG_COLOR_SPEED

#define MSG_COLOR_SPEED   8

Frames to display each color before advancing to the next.

◆ SPEED_TABLE_LEN

#define SPEED_TABLE_LEN   30

Number of entries in speed_table (levels 0-29)

◆ STATE_GAME_OVER

#define STATE_GAME_OVER   3

Game over: board frozen, "GAME OVER" rainbow cycle, await restart.

◆ STATE_LINE_CLEAR

#define STATE_LINE_CLEAR   2

Line clear animation: flashing rows + screen shake.

◆ STATE_PLAYING

#define STATE_PLAYING   1

Active gameplay: piece falling, player input, gravity.

◆ STATE_TITLE

#define STATE_TITLE   0

Title screen: displaying "PRESS START" with rainbow cycle.

Function Documentation

◆ addScore()

static void addScore ( u8  lines_cleared)
static

Add points for cleared lines using NES scoring formula.

Points = line_scores[n] * (level + 1). Uses repeated addition instead of multiplication to avoid the expensive __mul16 runtime call on the

  1. Caps score at 65000 to prevent u16 overflow.
Parameters
lines_clearedNumber of lines cleared simultaneously (1-4)

◆ getGravitySpeed()

static u8 getGravitySpeed ( void  )
static

Look up the gravity speed (frames per drop) for the current level.

Clamps the level index to SPEED_TABLE_LEN-1 so levels beyond 29 all run at maximum speed (1 frame/drop).

Returns
Frames per automatic piece drop

◆ handleInput()

static void handleInput ( void  )
static

Process player input with DAS (Delayed Auto Shift) for piece movement.

Implements NES-authentic input handling:

  • A/B: edge-triggered rotation (clockwise / counterclockwise)
  • LEFT/RIGHT: DAS with 16-frame initial delay, then 6-frame repeat
  • DOWN: soft drop (3-frame initial delay, then every-frame repeat)
  • START: toggle pause (blocks input until unpaused)

DAS prevents accidental double-moves on button press while allowing fast continuous movement when held. Each direction has its own timer.

◆ main()

int main ( void  )

Main entry point – Tetris initialization and state machine loop.

Initialization sequence (all during force blank):

  1. Wait 5 frames for NMI joypad reads to stabilize
  2. renderInit() sets up Mode 1, loads tiles/palettes, configures all 3 BG layers
  3. hudInit() + hudShowMessage() prepare the title screen content
  4. renderFlush() DMAs everything to VRAM (guaranteed during force blank)
  5. SNESMOD audio initialization (SPC700 module + soundbank)
  6. setScreenOn() + renderEnableGradient() begin display with HDMA gradient

The main loop dispatches to the current state handler (title, playing, line_clear, game_over) each frame, then calls snesmodProcess() for SPC700 communication and renderFlush() for selective VRAM DMA.

Returns
0 (never reached – infinite game loop)

◆ spawnPiece()

static u8 spawnPiece ( void  )
static

Spawn the next piece at the top of the board.

Promotes next_type to the current piece, generates a new random next piece, renders it in the preview box, and checks whether the spawn position is blocked (which means game over).

Returns
1 if spawn position is blocked (game over), 0 if successful

◆ startGame()

static void startGame ( void  )
static

Initialize a new game: clear board, reset state, start music.

Resets all game variables to initial state, seeds the RNG from frame_count (which varies based on how long the player waited on the title screen), spawns the first piece, and performs a force-blank flush to upload all three BG layers to VRAM simultaneously.

◆ stateGameOver()

static void stateGameOver ( void  )
static

Game over state: display frozen board with rainbow "GAME OVER" text.

Renders the final board state with the piece that caused the block-out, shows the GAME OVER message on the BG3 overlay, and cycles through rainbow colors until the player presses START to restart.

◆ stateLineClear()

static void stateLineClear ( void  )
static

Line clear animation state: flash rows, shake screen, then collapse.

Runs for FLASH_FRAMES (10) frames. Each frame applies screen shake via BG scroll offsets and toggles the clearing rows' visual appearance. After the animation completes: removes lines from the board, updates scoring/level, performs a force-blank flush (heavy DMA of BG1+BG2), and spawns the next piece.

◆ statePlaying()

static void statePlaying ( void  )
static

Main gameplay state: handle input, apply gravity, lock pieces.

Uses delta rendering: erases the piece from the tilemap buffer, processes input and gravity, then redraws the piece at its new position. This way only the changed cells need updating rather than the entire board.

When a piece locks, checks for completed lines. If found, transitions to STATE_LINE_CLEAR. Otherwise, re-renders the board and spawns next piece.

◆ stateTitle()

static void stateTitle ( void  )
static

Title screen state: rainbow "PRESS START" text until player begins.

Cycles through msg_colors[] at MSG_COLOR_SPEED frames per color, creating a rainbow shimmer on the BG3 overlay text. The frame_count accumulates during this wait, providing RNG seed entropy proportional to how long the player watches the title screen.

Variable Documentation

◆ clear_result

LineClearResult clear_result
static

Result of the last boardFindFullLines() call (which rows are full)

◆ cur_col

s8 cur_col
static

Current piece column on the board

◆ cur_rot

u8 cur_rot
static

Current piece rotation (0-3, quarter turns)

◆ cur_row

s8 cur_row
static

Current piece row on the board (signed: can be negative during spawn)

◆ cur_type

u8 cur_type
static

Current falling piece type (0-6, one per tetromino shape)

◆ das_down_timer

u8 das_down_timer
static

DAS timer for DOWN button (soft drop)

◆ das_left_timer

u8 das_left_timer
static

DAS timer for LEFT button (0=idle, >=DAS_INITIAL_DELAY=repeating)

◆ das_right_timer

u8 das_right_timer
static

DAS timer for RIGHT button

◆ flash_timer

u8 flash_timer
static

Frame counter during line clear flash animation

◆ game_state

u8 game_state
static

Current game state (STATE_TITLE/PLAYING/etc.)

◆ gravity_speed

u8 gravity_speed
static

Frames per drop at current level (from speed_table)

◆ gravity_timer

u8 gravity_timer
static

Frames since last automatic drop

◆ level

u16 level
static

Current level (0-based, increases every 10 lines)

◆ line_scores

u16 line_scores[] = { 0, 40, 100, 300, 1200 }
static

NES scoring table: base points per number of simultaneous line clears.

Index 0 = 0 lines (unused), 1 = single (40), 2 = double (100), 3 = triple (300), 4 = Tetris (1200). Multiplied by (level + 1). Not const – see speed_table comment about bank $00 requirement.

◆ lines_until_levelup

u8 lines_until_levelup
static

Countdown from 10; avoids expensive __div16 for modulo

◆ msg_colors

u16 msg_colors[]
static
Initial value:
= {
0x7FFF,
0x307C,
0x0B5E,
0x2B61,
0x6EF0,
0x6E2E,
0x01BC,
0x3E22,
}

Rainbow color cycle palette for animated message text (BGR555).

Cycled through at MSG_COLOR_SPEED frames per color to create a rainbow shimmer on "PRESS START" and "GAME OVER" overlay text. Not const – see speed_table comment about bank $00 requirement.

◆ next_type

u8 next_type
static

Next piece type (shown in the preview box)

◆ pad_keys

u16 pad_keys[]
extern

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

Used directly (instead of padHeld()) for precise edge detection: pressed = pad_keys[0] & ~prev_pad gives newly-pressed buttons this frame.

◆ paused

u8 paused
static

Nonzero when game is paused

◆ prev_pad

u16 prev_pad
static

Previous frame's joypad state (for edge-triggered button detection)

◆ score

u16 score
static

Player's current score (capped at 65000 to avoid u16 overflow)

◆ shake_dx

s8 shake_dx[] = { 2, -2, 1, -1, 2, -1, 1, 0 }
static

Screen shake X offsets during line clear animation.

Indexed by (flash_timer & 7). Alternating positive/negative values create a jarring shake effect. Not const – bank $00 requirement.

◆ shake_dy

s8 shake_dy[] = { -1, 1, -2, 1, 0, -1, 1, 0 }
static

Screen shake Y offsets during line clear animation.

◆ speed_table

u8 speed_table[]
static
Initial value:
= {
48, 43, 38, 33, 28, 23, 18, 13, 8, 6,
5, 5, 5, 4, 4, 4, 3, 3, 3,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1
}

NES NTSC gravity speed table (frames per automatic drop).

Index = level number. Level 0 = 48 frames/drop (slowest), level 29+ = 1 frame/drop (fastest). Values match the original NES Tetris for authentic feel.

NOT const – const arrays go to SUPERFREE ROM which may land in bank $01+. The compiler generates lda.l $0000,x (always bank $00) for array access, so const data in bank $01 would read garbage. Mutable arrays go to WRAM (bank $00) via CopyInitData at startup.

◆ str_gameover

const char str_gameover[]
extern

"GAME OVER" overlay message string

◆ str_paused

const char str_paused[]
extern

Global frame counter incremented by the NMI handler.

Used to seed srand() at game start – the player's variable-length wait on the title screen produces a different seed each game, ensuring different piece sequences.

"PAUSED" overlay message string (in data.asm for bank $00 placement)

◆ str_start

const char str_start[]
extern

"PRESS START" title screen message string

◆ total_lines

u16 total_lines
static

Total lines cleared this game