Back to KB
Difficulty
Intermediate
Read Time
9 min

Tetris in Vanilla JS β€” SRS Rotation, 7-Bag Randomizer, and Why You Should Separate Game Logic from Rendering

By Codcompass TeamΒ·Β·9 min read

Architecting Deterministic Game Engines: Input Handling, State Isolation, and Guideline Compliance in Vanilla JavaScript

Current Situation Analysis

Building grid-based puzzle games appears straightforward until you attempt to align them with competitive standards. Most tutorial implementations treat the game loop as a visual feedback mechanism rather than a deterministic simulation. This mindset creates three systemic failures that only surface under production conditions:

  1. State-Rendering Coupling: When board state, piece positioning, and collision detection are entangled with Canvas or DOM manipulation, the core logic becomes untestable without a browser environment. This forces developers to rely on manual playtesting for every rule change, dramatically increasing regression risk.
  2. Naive Distribution & Physics: Using Math.random() for piece selection creates statistical droughts that break player trust. Similarly, tying gravity to requestAnimationFrame makes difficulty hardware-dependent. A 120Hz monitor effectively doubles the game speed compared to a 60Hz display, rendering competitive balance impossible.
  3. OS-Level Input Reliance: Browser keydown events trigger OS auto-repeat rates (typically ~30Hz with a ~500ms initial delay). This latency and inconsistency destroy the precise, rhythmic input required for advanced techniques like wall kicks or rapid column stacking.

These issues are overlooked because they don't break the game visually; they break the feel. Players rarely articulate why a prototype feels "off," but they will abandon it. The solution requires treating the game as a pure state machine, decoupled from presentation, governed by millisecond-accurate timers, and validated through headless unit testing.

WOW Moment: Key Findings

When you replace academic approximations with guideline-compliant architecture, the measurable improvements span testing velocity, cross-hardware consistency, and player retention metrics.

ApproachRNG FairnessRotation ReliabilityInput LatencyTestabilityFrame-Rate Independence
Naive ImplementationHigh variance (streaks up to 5+)Fails at boundaries500ms+ OS delayRequires JSDOM/BrowserTied to refresh rate
Guideline-CompliantZero droughts (max 2 consecutive)5-step wall-kick resolution~170ms custom DAS100% headless (Node)Millisecond accumulator

Why this matters: The guideline-compliant approach transforms the game from a visual demo into a verifiable system. By isolating state, you can run hundreds of rule validations in under 100ms without launching a browser. The 7-bag distribution guarantees mathematical fairness, while the millisecond accumulator ensures identical gameplay across 60Hz, 120Hz, and 144Hz displays. This architecture is the foundation of any competitive or commercially viable puzzle engine.

Core Solution

Building a production-ready grid engine requires strict layering, deterministic timers, and guideline-compliant algorithms. Below is the implementation blueprint using TypeScript.

1. Architecture & State Isolation

The dependency graph must flow in one direction: Application β†’ Renderer β†’ Engine β†’ Rules. The engine and rules layers must remain pure functions with zero side effects. This enables headless testing and guarantees that rendering is merely a projection of state, not a driver of it.

// types.ts
export interface GridState {
  board: number[][];
  currentPiece: Piece | null;
  score: number;
  level: number;
  linesCleared: number;
}

export interface Piece {
  type: PieceType;
  rotation: number; // 0-3
  x: number;
  y: number;
}

export type PieceType = 'I' | 'O' | 'T' | 'S' | 'Z' | 'J' | 'L';
// engine.ts
import { GridState, Piece, PieceType } from './types';

const COLS = 10;
const ROWS = 20;

export function createInitialState(): GridState {
  return {
    board: Array.from({ length: ROWS }, () => Array(COLS).fill(0)),
    currentPiece: null,
    score: 0,
    level: 0,
   

πŸŽ‰ Mid-Year Sale β€” Unlock Full Article

Base plan from just $4.99/mo or $49/yr

Sign in to read the full article and unlock all 635+ tutorials.

Sign In / Register β€” Start Free Trial

7-day free trial Β· Cancel anytime Β· 30-day money-back