Skip to content

error311/vimmonsters-academy

Repository files navigation

VimMonsters Academy

VimMonsters Academy is a browser game that teaches Vim motions through a retro creature-catching RPG. It is also structured as a learning codebase: people should be able to read it, fork it, mod it, and extend it without first untangling one giant file.

The project is released under the MIT License in LICENSE.

VimMonsters Academy is an independent learning project and is not affiliated with or endorsed by Vim.

Preview

Overworld and progression screenshot:

VimMonsters overworld preview

Drill system screenshot:

VimMonsters drill preview

Battle and mini-drill screenshot:

VimMonsters battle preview

Goals

  • Teach Vim motions through play, drills, and battle interactions
  • Stay readable enough to use as a programming learning project
  • Be easy to fork for new lessons, new creatures, new art, and new maps

Requirements

  • Node.js 22 or newer
  • npm
  • Docker, if you want to run the container build

The repo includes .nvmrc and the GitHub Actions workflows use Node 22.

Run Locally

cd vimmonsters-academy
npm install
npm run serve

Then open http://localhost:8002.

If 8002 is busy:

PORT=8004 npm run serve

Leaderboard data is written to leaderboard.json in the project root unless you override it:

LEADERBOARD_PATH=./data/leaderboard.json npm run serve

If you want the public competition API to persist its used-run state too:

LEADERBOARD_PATH=./data/leaderboard.json \
COMPETITION_STATE_PATH=./data/competition-state.json \
npm run serve

Run With Docker

Build the image:

cd vimmonsters-academy
docker build -t vimmonsters-academy .

Run it:

docker run --rm -p 8002:8002 -v "$(pwd)/data:/data" vimmonsters-academy

Then open http://localhost:8002.

Notes:

  • leaderboard data is written to /data/leaderboard.json inside the container
  • the bind mount keeps leaderboard data between container runs
  • if you do not care about persistence, you can drop the -v "$(pwd)/data:/data" mount
  • if 8002 is busy on your machine, change the published port: docker run --rm -p 8004:8002 -v "$(pwd)/data:/data" vimmonsters-academy

For public hosting, pass a real competition secret and persist both JSON files:

docker run --rm \
  -p 8002:8002 \
  -e COMPETITIVE_SECRET="replace-this-with-a-long-random-secret" \
  -e LEADERBOARD_PATH=/data/leaderboard.json \
  -e COMPETITION_STATE_PATH=/data/competition-state.json \
  -v "$(pwd)/data:/data" \
  vimmonsters-academy

Public Hosting

The server is now safer for public hosting than the old trust-the-browser flow:

  • the browser can no longer overwrite the full leaderboard
  • runs are append-only submissions, not arbitrary leaderboard writes
  • each run starts with a server-issued signed run ticket
  • submissions are rate-limited and body-size limited
  • the server only serves public game files, not repo internals like package.json, server.js, or dotfiles
  • static responses ship with stricter security headers

Recommended environment variables for public hosting:

  • COMPETITIVE_SECRET: required in practice; use a long random secret
  • LEADERBOARD_PATH: persistent JSON file for leaderboard entries
  • COMPETITION_STATE_PATH: persistent JSON file for used run tickets
  • HOST: bind host, defaults to 0.0.0.0
  • PORT: listen port, defaults to 8002
  • MIN_RUN_MS, MAX_RUN_MS, MAX_SCORE, MAX_SCORE_PER_SECOND: optional competition guardrails

Start from .env.example when setting up a real hosted instance.

Important limitation:

This is still a client-side game. The public leaderboard flow is much safer now, but it is not fully cheat-proof against a determined attacker who modifies the browser client. To get truly strong anti-cheat, you would need server-authoritative gameplay or a server-side replay verifier with a much richer proof-of-play model.

Development Workflow

cd vimmonsters-academy
npm install
npm run build:assets
npm run serve

Use this when you are editing game logic, content, sprites, or UI. Re-run npm run build:assets after changing sprite frame data or palette-driven art in src/content.js.

Checks

cd vimmonsters-academy
npm install
npm run build:assets
npm test
npm run build:readme-media
npm run lint
npm run check
npm run smoke

Use TESTING.md for the manual runtime checklist after gameplay, UI, or refactor changes.

Lesson Flow

  1. Home Row House: learn h j k l, then choose a starter VimMonster
  2. Word Meadow: learn w, b, e, and ge, then catch your first wild VimMonster
  3. Line Ridge: learn 0, $, ^, x, and party switching with [ ]
  4. Count Grove: learn count prefixes like 3w and 2j, plus dd and cw
  5. Finder Fen: learn f, t, F, T, and repeat-find with ; and ,
  6. Operator Studio: learn dw and ciw
  7. Macro Tower: learn gg, G, /, and :replace, then beat or catch the final boss

Main Controls

  • h j k l: move
  • i: inspect or talk to what is in front of you
  • o: toggle the in-game VimTree
  • Enter: focus the selected VimTree section
  • w b: move one tile forward or backward once unlocked
  • e ge: word-end motions in drills
  • f t F T: character find motions in drills and battle mini-drills
  • ; ,: repeat the last find forward or in reverse
  • 1-9 + motion: counted motions like 3w or 2j
  • 0 $ ^: line motions once unlocked
  • gg G: file motions once unlocked
  • [ ]: cycle the active VimMonster once unlocked
  • .: repeat the last learned motion once unlocked
  • R: rename the current run
  • m: mute or unmute music and sound
  • :: command mode for :help, :party, :map, :lesson, :name, :q, :w, :load, :heal

Battle Controls

  • a: attack
  • x: Quick Jab after the ridge lesson
  • dd: Heavy Slam after the grove lesson
  • cw: Focus Ball, which powers the next VimOrb after the grove lesson
  • dw: Break Word after the studio lesson
  • ciw: Inner Word after the studio lesson
  • f: throw a VimOrb
  • r: run
  • [ ]: switch to a different party member

Project Structure

Modding Quick Start

Start here if you want to change content without rewriting engine code:

If you change sprite frame data or palettes:

npm run build:assets

Contributing

Use CONTRIBUTING.md.

The short version:

  • keep the code readable for learners
  • keep the game friendly for new Vim users
  • run npm run build:assets, npm test, npm run build:readme-media, npm run lint, npm run check, and npm run smoke
  • use TESTING.md after gameplay or UI changes

GitHub Automation

The repo now includes:

Repo Readiness

For a clean public repo, make sure each change set does these before merge:

  • update docs if controls, lessons, or run steps changed
  • rebuild generated art if sprite definitions changed
  • run npm test, npm run lint, npm run check, and npm run smoke
  • use TESTING.md for gameplay or UI changes
  • include screenshots or GIFs in the PR if the visible UI changed

Notes

  • the leaderboard is JSON-backed through server.js
  • public competition runs now use server-issued run tickets plus append-only leaderboard submission
  • the game uses browser ES modules and a shared runtime app object centered in src/game.js
  • run and leaderboard orchestration now live in src/game-run-runtime.js, which keeps that slice out of the main bootstrap file
  • house-route and map/tile helpers now live in src/game-world-helpers.js, which keeps those cross-cutting helpers out of the bootstrap file too
  • overworld motion routing and locked-control messaging now live in src/game-motion-runtime.js, which keeps input-to-world behavior out of the bootstrap file too
  • encounter setup, party switching, defeat reset, and finish/reward handling now live in src/battle-flow.js, which keeps that lifecycle logic out of the main battle runtime
  • player techniques, VimOrb throws, and catch resolution now live in src/battle-techniques.js, which keeps that player-action logic out of the coordinator
  • enemy status pressure, cooldowns, and enemy-turn move resolution now live in src/battle-enemy.js, which keeps enemy behavior out of the coordinator too
  • battle mini-drill cursor flow and motion resolution now live in src/battle-challenge-runtime.js, which keeps that challenge state machine out of the broader battle runtime
  • the runtime is split so learners can study one system at a time instead of reading a single giant file first
  • docs/media now contains the repository screenshots used in the README preview section

About

A retro browser game that teaches Vim motions through drills, exploration, and creature battles.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages