Projects

codytris

Web Tetris

Personal
TypeScript
Files
README

Codytris

Codytris is a polished, browser-based falling-block puzzle game created by CodyKoInABox. It combines classic seven-piece gameplay with a clean, modern, responsive interface.

The application is fully static, has no backend, and is prepared for deployment from a GitHub repository subpath.

Features

  • Seven standard tetrominoes generated with a deterministic seven-bag randomizer
  • SRS-style clockwise and counter-clockwise rotation with wall kicks
  • Responsive movement, soft drop, hard drop, ghost piece, and hold queue
  • Classic line scoring, progressive levels, and increasing gravity speed
  • Ten selectable modes with separate personal bests
  • A short lock delay for precise floor movement
  • Pause/resume, guarded restart, game-over handling, and automatic focus-loss pause
  • Persistent per-mode high scores and local lifetime statistics
  • Keyboard controls plus visible touch controls and board gestures
  • Persistent settings with conflict-safe keyboard remapping
  • Optional ghost piece, motion effects, and mobile touch vibration
  • Responsive portrait, landscape, tablet, and desktop layouts
  • Accessible controls, visible focus states, high contrast, and reduced-motion support
  • Pure, deterministic game rules covered by Vitest
  • GitHub Pages workflow with automatic repository subpath detection

No images, fonts, audio, game engines, or other runtime assets are downloaded.

Screenshots

Screenshots are intentionally not included yet. After publishing, add desktop and mobile captures under a docs/screenshots/ directory and replace this note with Markdown images, for example:

![Codytris desktop game](docs/screenshots/desktop.png)
![Codytris mobile controls](docs/screenshots/mobile.png)

Technology

  • React 18
  • TypeScript
  • Vite 8
  • Modern CSS
  • Vitest
  • ESLint
  • GitHub Actions and GitHub Pages

The board uses a 200-cell CSS grid rather than Canvas. At this scale, DOM rendering remains fast while preserving sharp responsive cells, straightforward animation, semantic surrounding controls, and maintainable styling. The timing loop only commits state when gameplay changes, so it does not force a React render on every display frame.

Local development

Requirements: Node.js 20 or newer and npm.

npm install
npm run dev

Vite prints the local URL, normally http://localhost:5173.

Available scripts

CommandPurpose
npm run devStart the Vite development server
npm run typecheckRun strict TypeScript validation
npm run lintLint the full codebase with zero warnings allowed
npm testRun the core game test suite once
npm run test:watchRun tests in watch mode
npm run buildTypecheck and create the production build in dist/
npm run previewPreview the production build locally

Run the complete local quality check with:

npm run typecheck
npm run lint
npm test
npm run build

Controls

ActionKeyboardTouch
Move left / rightLeft / Right arrowsLeft / right buttons or horizontal swipe
Soft dropDown arrowDown button or downward swipe
Rotate clockwiseUp arrowRotate button or tap the board
Rotate counter-clockwiseZ
Hard dropSpaceDrop button
HoldCHold button
Pause / resumeEscapePause button
RestartShift + RRestart button on larger layouts

Every keyboard control can be changed from the in-game settings panel. Conflicting assignments are swapped so no action becomes accidentally unbound. Settings persist in localStorage alongside the high score.

Restarting an active game always requires confirmation. The game also pauses automatically when its tab becomes hidden, the browser window loses focus, or settings are opened.

Game modes

ModeRules
RegularThe original Codytris rules. Gravity speeds up every 10 cleared lines.
ZenGravity stays at the starting speed, even as levels and scoring progress.
RushGravity speeds up every 5 cleared lines for a quicker difficulty curve.
SurvivalStarts at level 5 and disables hold.
FogHides the ghost piece and all next-piece previews.
OverdriveStarts at level 3 and speeds up every 3 cleared lines.
TurboStarts at level 7 with gravity fixed at a relentless 275 ms interval.
DraftShows only one upcoming piece and disables hold.
ContractTargets 1, 2, 3, then 4 lines at once; matching clears score triple.
Zero-GDisables automatic gravity so every descent is player-controlled.

Choose a mode before starting, after game over, or from the restart confirmation. Each mode keeps its own local personal best; an existing pre-mode high score is retained as the Regular record.

Local statistics

Open the statistics button in the top bar to see lifetime runs, score, lines, four-line clears, and per-mode records. These statistics are stored only in the current browser using localStorage; Codytris does not upload or share them. Clearing the site's browser data also clears the saved statistics.

Runs are counted when they start. Progress is saved in small batches while playing and flushed when a run ends, restarts, or the page closes, avoiding storage work in the animation loop.

Architecture

src/
├── components/       React board, preview, overlay, and touch controls
├── game/             Pure rules, tetromino data, randomizer, types, persistence
├── hooks/            Frame-rate-independent timing and input lifecycle
├── settings/         Keybind definitions, preference validation, and tests
├── App.tsx           Application layout and UI orchestration
└── styles.css        Responsive theme, feedback, and accessibility states

The engine is independent from React. User input produces pure state transitions, while a requestAnimationFrame loop accumulates elapsed milliseconds for gravity and lock timing. As a result, falling speed is based on real elapsed time rather than monitor refresh rate.

Configuration

Repository and project metadata live in one place: site.config.ts.

Before publishing, update:

  • repositoryName if the GitHub repository is not named codytris.
  • repositoryUrl once the final public URL is known. This activates the repository link in the footer.
  • githubOwner if the repository moves to another account.

For GitHub Actions, Vite automatically reads the repository name from GITHUB_REPOSITORY and emits URLs under /<repository-name>/. Local development always uses /. You can override either environment with VITE_BASE_PATH, including the leading slash—for example, VITE_BASE_PATH=/my-project/ npm run build.

GitHub Pages deployment

The workflow at .github/workflows/deploy-pages.yml validates and deploys the project after a push to main. It runs type checking, linting, tests, and the production build before uploading dist/ to Pages. The build also includes .nojekyll.

To publish:

  1. Create the GitHub repository and update site.config.ts if its name differs.
  2. Push this project to the repository's main branch.
  3. In GitHub, open Settings → Pages.
  4. Under Build and deployment, select GitHub Actions as the source.
  5. Run Deploy to GitHub Pages from the Actions tab, or push a new commit to main.

For a user or organization site repository named <owner>.github.io, set VITE_BASE_PATH=/ in the workflow build environment because that kind of Pages site is hosted at the domain root.

This repository only prepares the workflow; no deployment is performed during local development.

Customization

  • Change public project metadata in site.config.ts.
  • Adjust the Codytris color palette and interface tokens near the top of src/styles.css.
  • Tune gravity in gravityInterval() and the lock delay in src/hooks/useGame.ts.
  • Keep changes to rules in src/game/ so they remain independently testable.

License

Codytris is open-source software released under the MIT License. Copyright © 2026 Maruan Biasi El Achkar.