๐ŸŽฎ Games Guide

February 12, 2026 ยท View on GitHub

All games are compiled to WebAssembly from nbsdgames (CC0 public domain). They run entirely in Node.js โ€” no native binaries or C compiler needed.

Only turn-based games are included, perfect for playing during pauses while waiting for Copilot.

Universal Controls

All games share these common controls:

KeyAction
Arrow keysMove cursor / navigate
W / โ†‘Move up
A / โ†Move left
S / โ†“Move down
D / โ†’Move right
H/J/K/LVim-style movement (left/down/up/right)
EnterConfirm / select / act
QQuit game (returns to menu)
Ctrl-GPause game & return to menu (game stays alive!)
F1In-game help (controls)
F2In-game help (gameplay)

Game Menu Controls

KeyAction
โ†‘โ†“ / W/SNavigate game list
EnterLaunch game (or resume paused game)
NStart new game (replaces paused game)
QReturn to Copilot
Ctrl-GToggle between Copilot and game menu

Fifteen Puzzle

Similar to: 15-Puzzle, Sliding Puzzle

Slide numbered tiles on a grid to arrange them in numerical order. The empty space allows adjacent tiles to be slid into position.

ControlAction
Arrow keys / WASD / HJKLMove cursor to a tile
EnterSlide the selected tile toward the empty space

Goal: Arrange all tiles in ascending order (1, 2, 3, ...) with the empty space in the bottom-right corner.

Tip: Work row by row from top to bottom. Get the first row in place, then the second, etc.


Mines

Similar to: Minesweeper (Windows)

Uncover cells on a grid without hitting any hidden mines. Numbers reveal how many mines are adjacent to that cell.

ControlAction
Arrow keys / WASD / HJKLMove cursor
EnterReveal a cell
SpaceFlag/unflag a cell as a mine

Goal: Reveal all safe cells. Use the number clues to deduce where mines are hiding.

Tip: Start from the edges. If a "1" has only one unrevealed neighbor, that neighbor is a mine.


Sudoku

Similar to: Sudoku

Fill a 9ร—9 grid so every row, column, and 3ร—3 box contains the digits 1-9 exactly once.

ControlAction
Arrow keys / WASD / HJKLMove cursor
1-9Place a digit
Space / 0Clear a cell

Goal: Complete the grid with valid digits. No repeats in any row, column, or box.

Tip: Look for rows/columns/boxes that are almost complete โ€” they have the fewest possibilities.


Reversi

Similar to: Othello

Place discs to capture your opponent's pieces by flanking them.

ControlAction
Arrow keys / WASD / HJKLMove cursor
EnterPlace a disc

Goal: Have the most discs of your color when the board is full. A valid move must flip at least one opponent disc.

Tip: Corners are the most valuable positions โ€” they can never be flipped. Avoid giving your opponent corner access.


Checkers

Similar to: Draughts, English Checkers

Move diagonally and jump over opponent pieces to capture them.

ControlAction
Arrow keys / WASD / HJKLMove cursor
EnterSelect piece, then select destination

Goal: Capture all opponent pieces or block them from moving. Pieces that reach the far side become "kings" and can move backward.

Tip: Keep your back row intact as long as possible to prevent opponent kings.


SOS

Similar to: Tic-Tac-Toe (extended variant)

Take turns placing 'S' or 'O' on a grid to form the sequence S-O-S.

ControlAction
Arrow keys / WASD / HJKLMove cursor
SPlace an 'S'
OPlace an 'O'

Goal: Form more S-O-S sequences (horizontal, vertical, or diagonal) than your opponent.

Tip: Try to set up positions where placing one letter creates multiple S-O-S patterns at once.


Battleship

Similar to: Battleship (board game by Milton Bradley)

Take turns firing at a grid to find and sink your opponent's hidden ships.

ControlAction
Arrow keys / WASD / HJKLMove targeting cursor
EnterFire at the selected cell

Goal: Sink all of your opponent's ships before they sink yours.

Tip: When you get a hit, fire at adjacent cells to find the rest of the ship. Ships are always straight (horizontal or vertical).


Memoblocks

Similar to: Concentration, Memory Game, Pexeso

Flip cards to find matching pairs.

ControlAction
Arrow keys / WASD / HJKLMove cursor
EnterFlip a card

Goal: Match all pairs with the fewest flips possible. Two cards are shown briefly โ€” remember their positions!

Tip: Systematically work through the grid. Focus on remembering the first few cards you flip.


Rabbit Hole

Similar to: Maze Runner, Labyrinth

Navigate through a maze as a rabbit collecting items.

ControlAction
Arrow keys / WASD / HJKLMove through the maze

Goal: Find your way through the maze and collect all items along the way.


Revenge

Similar to: Sokoban, Puzzle Box

Push blocks around a grid to solve spatial puzzles.

ControlAction
Arrow keys / WASD / HJKLMove your character
EnterPush a block

Goal: Push blocks to their target positions to complete each level.

Tip: Plan your moves carefully โ€” you can only push blocks, not pull them. Avoid pushing blocks into corners where they get stuck.


2048

Similar to: 2048 (Gabriele Cirulli)

Slide tiles on a 4x4 grid to combine them and reach the 2048 tile.

ControlAction
Arrow keys / WASDSlide tiles in the chosen direction
RRestart game

Goal: Combine tiles with the same number to create larger numbers. The game is won when a tile with the value 2048 is created.


Similar to: Cookie Clicker

Bake cookies by clicking, then spend them on upgrades and buildings to bake even more.

ControlAction
Space / EnterClick the cookie to bake
Arrow keysNavigate through upgrades and buildings
BBuy the selected item
RReset game

Goal: Bake as many cookies as possible and unlock all achievements and upgrades.


Tic-Tac-Toe

Similar to: Tic-Tac-Toe

Classic game where two players take turns marking spaces in a 3x3 grid.

ControlAction
Arrow keys / WASDMove cursor
EnterPlace your mark (X or O)
RRestart game
MToggle AI opponent mode

Goal: Get three of your marks in a horizontal, vertical, or diagonal row.


Technical Notes

How WASM Games Work

Each game is a C program originally using ncurses for terminal I/O. We compile them to WebAssembly using Emscripten with a custom ncurses shim (wasm/curses.h) that maps all curses calls to ANSI escape codes:

  • initscr() โ†’ enters alternate screen buffer
  • mvaddch() / mvprintw() โ†’ ANSI cursor positioning + output
  • getch() โ†’ async stdin read via Emscripten ASYNCIFY
  • Colors โ†’ ANSI 16-color escape sequences
  • Box drawing โ†’ ASCII fallback characters

Platform Support

The WASM games run on any platform with Node.js 18+:

  • โœ… Linux
  • โœ… macOS
  • โœ… Windows (via Windows Terminal, PowerShell, or WSL)
  • โœ… Any terminal emulator supporting ANSI escape codes

Adding Custom Games

You can add your own terminal games to copilot-fun by creating single-file Node.js scripts in ~/.copilot-fun/games/.

1. Create the Directory

mkdir -p ~/.copilot-fun/games

2. Create your Game Script

Create a .js file (e.g., pong.js). It must start with metadata header comments for copilot-fun to recognize it:

// @game.id pong
// @game.name Pong
// @game.desc Classic paddle ball game
// @game.controls W/S to move paddle
// @game.goal Score more points than the opponent
// @game.similar Pong (Atari)

// Game code follows...

Required Metadata

  • @game.id: Unique identifier (must match the filename without .js).
  • @game.name: Display name in the menu (max 16 chars).

Optional Metadata

  • @game.desc: Short description shown in the menu (max 36 chars).
  • @game.controls: Explained in the game details section.
  • @game.goal: What the player needs to achieve.

3. Game Architecture

Your game runs as a standalone Node.js process spawned by copilot-fun.

Environment Variables

  • process.env.LINES: Terminal height (excluding the status bar).
  • process.env.COLS: Terminal width.

Requirements

  • Single File: No local imports or external npm dependencies.
  • Raw Mode: Use process.stdin.setRawMode(true) for keyboard input.
  • ANSI Output: Use ANSI escape codes written to process.stdout.
  • Exit Cleanly: The game should exit with process.exit(0) when finished.
  • No q or ESC Handling: These keys are intercepted by the copilot-fun wrapper to quit or pause the game.

Minimal Template

#!/usr/bin/env node
// @game.id skeleton
// @game.name Skeleton Game
// @game.desc A minimal game template

const ROWS = parseInt(process.env.LINES) || 23;
const COLS = parseInt(process.env.COLS) || 80;
const CSI = '\x1b[';

let playerX = Math.floor(COLS / 2);
let playerY = Math.floor(ROWS / 2);

function render() {
  process.stdout.write(`${CSI}2J${CSI}H${CSI}?25l`); // Clear screen & hide cursor
  process.stdout.write(`${CSI}${playerY};${playerX}H${CSI}32m@${CSI}0m`);
  process.stdout.write(`${CSI}${ROWS};1H${CSI}2mArrow keys: move | Any other key: quit${CSI}0m`);
}

process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', (key) => {
  if (key === '\x1b[A') playerY = Math.max(1, playerY - 1);
  else if (key === '\x1b[B') playerY = Math.min(ROWS, playerY + 1);
  else if (key === '\x1b[C') playerX = Math.min(COLS, playerX + 1);
  else if (key === '\x1b[D') playerX = Math.max(1, playerX - 1);
  else process.exit(0);
  render();
});

// Show cursor on exit
process.on('exit', () => process.stdout.write('\x1b[?25h'));

render();