A config-driven Whac-A-Mole game in vanilla JavaScript — no framework, no bundler, no build step.
Moles pop out of holes one at a time. Whack one before it ducks and you score; swing at an empty hole and you lose points. Clear the target score before the timer runs out to advance — run out of time and it costs you a life.
Every stage is described in config.json: grid size, points, mole
speed, target score and time limit. Change that file and the game changes. No code,
no rebuild.
git clone https://github.com/codeAesthetic/whac-a-mole.git
cd whac-a-mole
npm startThen open http://localhost:8080.
There are no dependencies to install — npm start runs a ~50-line static server
from scripts/serve.mjs. It exists only because ES modules and fetch are blocked
on file://, so the page needs to come over http; opening index.html directly
will not work.
- One hole is occupied at a time, and the mole moves on every
highlightDurationtick. - Whack the mole → +
rewardPt. - Swing at an empty hole → −
deductionPt. Score never drops below zero. - Reach
passingScorebeforedurationexpires → the stage clears. - Timer hits zero first → lose a life and retry that stage. Earlier progress is kept.
- Out of lives → game over, back to stage one.
- Clear the final stage and you win.
config.json drives everything:
{
"lives": 3,
"config": [
{
"name": "The Meadow",
"row": 5,
"col": 5,
"rewardPt": 10,
"deductionPt": 5,
"highlightDuration": 800,
"passingScore": 50,
"duration": 30
}
]
}| Key | Type | Meaning |
|---|---|---|
lives |
number | Retries for the whole run, shared across stages |
config[] |
array | One entry per stage, played in order |
name |
string | Stage name shown in the HUD |
row / col |
number | Grid size. The board scales to fit the screen — it is never truncated |
rewardPt |
number | Points for hitting the mole |
deductionPt |
number | Points lost for hitting an empty hole |
highlightDuration |
number | Milliseconds before the mole moves. Lower is harder |
passingScore |
number | Score needed to clear the stage |
duration |
number | Seconds allowed for the stage |
The config is validated on load, and a mistake is reported up front rather than
showing up later as a broken board. That includes catching stages nobody could
clear — if duration and highlightDuration don't allow enough moles to reach
passingScore, it says so and names the numbers.
Plain ES modules, loaded straight from index.html with <script type="module">.
index.html markup + the inline SVG sprite sheet
config.json every stage, and the number of lives
src/js/
main.js bootstrap: load config, wire it up, show the intro
core/ game rules — no DOM access at all
game.js stage flow, scoring, lives, win/lose
board.js grid model: dimensions and which hole is active
countdown.js pausable stage clock, driven by a wall-clock deadline
ui/ everything that touches the DOM
board-view.js renders holes, animates moles, reports whacks
hud.js score, target, lives, countdown bar
modal.js intro and every end-of-stage outcome
utils/ dom helpers, randomness, config loading + validation
src/styles/ base / board / hud / modal
scripts/serve.mjs dev server (the only tooling in the project)
The split that matters is core/ versus ui/: the rules never read the DOM, and
the DOM layer never decides anything. Board size comes from the config and is fitted
by CSS, so there is no layout measurement that can race with the game starting.
This started as a spec-driven exercise. The brief was:
Design a game which will be driven completely by a config. Each stage of the game will have an n × n matrix of boxes.
- One box in the matrix will be highlighted till the user clicks on it or the time gets over. Highlighted boxes will keep on changing randomly.
- If the user clicks on the highlighted matrix, he will be rewarded with some points given in the configuration.
- If the user clicks on a non-highlighted matrix, some points will be deducted from his total score.
- Every stage will have a passing score (given in the configuration).
- If the user reaches the passing score, the next stage of the game will be triggered.
- Once the user passes the last stage, we will have to congratulate him with some animation.
The 2.0 rewrite keeps all of that and adds the stage timer, lives, and the artwork — and drops the webpack build the original needed.
ISC © codeAesthetic
