bharathdotexe
v1.0.0
Published
A premium, modern terminal experience for classic Nokia-era games. Snake, Tetris, Pong, Pac-Man and more - right in your terminal.
Maintainers
Readme
Retro CLI
Classic Nokia-era games, reborn as a premium modern terminal experience.
Snake ships today, fully playable, with smooth controls, real-time rendering, score tracking, a live difficulty ramp, pause/resume, animated intros and game-overs, four selectable themes, sound feedback, and local high scores. The whole codebase is built so Tetris, Pong, Pac-Man, Minesweeper, 2048, and Tic-Tac-Toe can be added later as self-contained plugins, with zero changes to the core engine, renderer, input, menu, or theme systems.
Install & run
npm install -g retro-cli
retro-cliOr run it once without a global install:
npx retro-cliCLI flags
retro-cli Launch the interactive menu
retro-cli --theme <name> Launch with a specific theme
(retroAscii | emoji | neon | cyberpunk)
retro-cli --smoke-test Run a non-interactive self-check (for CI)
retro-cli --help Show help
retro-cli --version Show the installed versionControls
| Action | Keys | |---------------|-------------------| | Move | Arrow keys / WASD | | Confirm | Enter | | Back | Esc | | Pause/Resume | P / Space | | Quit | Q / Ctrl+C |
Requirements
Node.js 18+. Works in any modern terminal emulator; truecolor terminals get
the full gradient themes, older terminals gracefully degrade to a reduced
color palette (handled automatically by chalk).
Project architecture
The codebase is split into clean, independent layers so that "add a new game" never requires touching menus, rendering, input, or theming.
bin/
retro-cli.js CLI entry point (arg parsing, TTY guard)
src/
app.js Orchestrates the menu <-> game flow
core/ Engine-agnostic building blocks
GameEngine.js Abstract base class every game extends
GameLoop.js Drives ticking, rendering, and input for any engine
Renderer.js Flicker-free frame buffer (wraps log-update)
InputHandler.js Normalizes raw keypresses into semantic actions
Registry.js Plugin registry games register themselves into
ui/ Reusable, theme-aware UI primitives
Menu.js Keyboard-navigable menu (main menu, game/theme pickers)
Panel.js Bordered box renderer used by menus & dialogs
StatusBar.js HUD strip (score / high score / level)
ProgressBar.js Loading bar + spinner animation
StartupAnimation.js Animated banner shown on launch
GameOverAnimation.js Animated score reveal + high-score celebration
themes/ Visual themes - colors, glyphs, gradients
ThemeManager.js Active theme state + persistence
palettes/*.js One file per theme (retroAscii, emoji, neon, cyberpunk)
audio/
SoundManager.js Terminal-bell-based feedback with an on/off toggle
storage/ Local persistence (no external services)
paths.js Cross-platform config directory resolution
ConfigStore.js Settings (theme, sound, last played) as JSON
HighScoreStore.js Per-game high scores as JSON
games/
index.js The ONE file that registers every game
snake/
SnakeGame.js Game logic (extends GameEngine)
SnakeRenderer.js Builds Snake's frame string using the active theme
constants.js Grid size & difficulty curve
utils/
keys.js Raw keypress -> semantic action mapping
term.js Terminal sizing/centering/width helpersThe plugin contract
Every game is a class extending GameEngine (src/core/GameEngine.js):
class GameEngine {
init() // set up initial state
tick() // advance state by one time step
handleAction(action) // "up" | "down" | "left" | "right" | "pause" | "back" | "quit"
render() // return the full frame as a string
getScore()
isOver()
}GameLoop only ever talks to this interface. It doesn't know or care whether
it's driving Snake, Tetris, or Tic-Tac-Toe - it schedules tick() at the
engine's current tickRateMs, re-renders after every tick and every input
action, and resolves once the engine reports status === 'over' or the
player quits.
Adding a new game
- Create
src/games/<yourGame>/mirroring thesnake/folder:<YourGame>Game.js- extendsGameEngine, holds the rules<YourGame>Renderer.js- a pure function(game) => frameStringthat readsgame.ctx.theme.currentfor colors/glyphsconstants.js- grid size, difficulty curve, key bindings
- Register it in
src/games/index.js:import { YourGame } from './yourGame/YourGameGame.js'; registry.register({ id: 'yourGame', title: 'Your Game', tagline: 'One line describing it', icon: '🎮', minTermSize: { columns: 40, rows: 20 }, createEngine: (ctx) => new YourGame(ctx), }); - That's it. The game automatically appears in the "Select a Game" menu,
gets its own high-score table, works with all four themes (as long as
your renderer reads glyphs/colors from
theme.currentrather than hardcoding them), and inherits pause/resume, quit, and the game-over animation for free.
If your game needs theme glyphs beyond what snake uses (e.g. Tetris piece
colors), add a new namespaced key to each palette file (alongside the
existing snake: {...} key) - e.g. tetris: {...} - so themes stay in one
place per visual identity.
Adding a new theme
Create src/themes/palettes/yourTheme.js exporting the same shape as the
existing palettes (bannerColor/bannerGradient, styles, and one glyph
block per game), then add it to src/themes/index.js. No other file needs
to change - ThemeManager and every menu read the theme list dynamically.
Local high scores & settings
Stored as plain JSON in your OS's standard config directory (no telemetry, no network calls):
- macOS:
~/Library/Application Support/retro-cli/ - Linux:
$XDG_CONFIG_HOME/retro-cli/(defaults to~/.config/retro-cli/) - Windows:
%APPDATA%\retro-cli\
Development
git clone <this-repo>
cd retro-cli
npm install
npm start # run interactively
npm run smoke-test # non-interactive self-check (used in CI)Sound
Full multi-channel audio isn't reliably cross-platform from a plain Node CLI
without native dependencies, which would break "install with npm and run
anywhere." Instead, SoundManager uses the ANSI terminal bell for tasteful,
low-latency feedback (eat/level-up/game-over cues), the same mechanism tools
like vim and git use for audible/visual alerts. It's toggleable from the
main menu. The SoundManager interface is intentionally shaped like a "real"
sound engine, so swapping in richer playback later is a one-file change.
License
MIT
