Pokémon Browser Game: Build the battle loop
Outcome
Section titled “Outcome”Resolve a wild encounter with Attack, Throw Poké Ball, or Run. One activation makes one ordered state change. A surviving wild Pokémon responds once to Attack. Defeat, capture, Run, and starter fainting end the encounter and return focus to movement.
What you will practice
- Describe each battle action as a before-state, ordered changes, and one final result.
- Keep battle buttons available only while an encounter is active.
- Stop an Attack after defeat so the wild Pokémon cannot respond.
- Keep hit points at or above zero and complete fainting recovery before rendering.
- Test failed and successful captures at the documented threshold.
- Move focus after a section change without moving focus to the status message.
Why this stage matters
Section titled “Why this stage matters”The encounter already has a stable wild-Pokémon record. Battle actions change that record and the starter record. Order matters: if the wild Pokémon reaches zero, it cannot respond; if the starter faints after a response, the Center recovery must finish before the page renders.
one button activation → check active encounter → change state → choose one result → render → place focusA terminal result ends the current encounter. Defeat, successful capture, Run, and starter fainting are terminal results. After one of them, the same button activation must not continue to another battle step.
What is new and what is reused
Section titled “What is new and what is reused”- New in this build: Fixed battle damage and capture threshold, three battle buttons, a terminal-result helper, and a temporary capture handoff record.
- Reused from Module 2: Conditions and branch checks, one native button action, button availability, focus after a section change, and the event → state → render cycle.
- Reused from this build:
state.starter,state.activeEncounter,state.mapPosition,renderGame(), the movement buttons, and the controlled known-encounter setting.
Keep type effectiveness, multiple attacks, animation, and a party of Pokémon for later work. The Pokédex stays static.
Starting point
Before you start
- The Enter a wild encounter article passes its self-check and has a passing Git checkpoint.
- game.html has a hidden battle section with a heading, wild image, wild hit points, and starter details. It has no battle buttons.
- game.js creates one active encounter from an accepted grass move. renderGame() shows the encounter section and disables movement.
- The source setting can select a known species for repeatable tests. Restore normal mode after each test set.
- Current state
- A wild encounter appears, but only a new trainer submission can leave it. No attack or capture changes hit points.
- First action
- In your project notes, choose fixed player damage, fixed wild damage, and a capture threshold. Check that your encounter species can reach the test boundaries below.
- First checkpoint
- The three numbers and their exact-zero, below-zero, and capture-boundary test species are written down. The existing encounter still works.
- Help trigger
- Use the nearest Assistance level or ask for help if one click applies two responses, a defeated Pokémon responds, a capture changes hit points, a terminal result leaves buttons active, or focus disappears.
Required result
Section titled “Required result”Complete this stage when:
- Attack, Throw Poké Ball, and Run are native
type="button"controls, disabled in saved HTML and whenever no encounter is active; - a newly shown encounter focuses its heading, and the next Tab stop is Attack;
- one Attack removes the fixed player damage from wild hit points, stopping at zero;
- a surviving wild Pokémon responds once, removing the fixed wild damage from starter hit points;
- defeat ends the encounter without a wild response or a caught-collection change;
- a Poké Ball above the threshold fails without changing either Pokémon’s hit points or triggering a wild response;
- a Poké Ball at or below the threshold confirms one species identity before ending the encounter, without a wild response;
- Run ends the encounter without changing hit points or the caught collection;
- if a wild response makes the starter faint, the trainer returns to the Pokémon Center and the starter is healed before the result renders;
- continuing actions keep focus on their button; every terminal result enables movement and focuses North; and
- the battle works with pointer, Enter, and Space, near 320 CSS pixels, and at 200% browser zoom.
The caught collection is completed in the next stage. For now, keep a single pendingCapture record with the confirmed Pokédex number and name. It proves the capture handoff; it is not the caught collection. A later catch replaces this interim record, and the current caught count stays at 0 in this stage. Start a fresh trainer before each capture test. Do not add duplicate checks or caught-entry rendering yet.
1. Fix the rules and test routes
Section titled “1. Fix the rules and test routes”Write the battle numbers in your project notes before editing game.js. If you are completing the Level 2 assignment, put them in project-brief.md and copy the transition table below into interactive-test-report.md.
The sample uses player damage 8, wild damage 6, and capture threshold 12. These are one possible set of rules. If you choose other values, keep them fixed during tests and find species that make every required boundary reachable. A temporary change to a normal battle rule is not a controlled test.
| Sample route | Before the decisive action | Expected result |
|---|---|---|
| Bounsweet, 24 hit points | Two Attacks leave 8; the third removes 8 | Exactly zero; no third wild response |
| Mareep, 30 hit points | Three Attacks leave 6; the fourth removes 8 | Clamped to zero; no fourth wild response |
| Misdreavus, 28 hit points | Two Attacks leave 12 | Catch succeeds at equality |
| Bounsweet, 24 hit points | Two Attacks leave 8 | Catch succeeds below the threshold |
| Mareep, 30 hit points | No Attack yet | Catch fails above the threshold |
| Mareep, 30 hit points | Starter has 6 hit points before the first Attack | Mareep survives, responds once, and the starter faints |
The last route needs a controlled precondition. During that test only, start a known Mareep encounter, set the changing starter record’s current hit points to 6 in DevTools, and call renderGame(). Then activate Attack. This uses the same action code as normal play. Reload and start a fresh trainer after the test.
Use this action contract while you work. A battle phase is the point reached inside one activation: before the player action, after that action, after an allowed wild response, or encounter ended.
| Action and condition | State changes in order | Status and focus |
|---|---|---|
| Attack; wild survives | Subtract player damage; subtract wild damage | Report both hit-point results; Attack keeps focus |
| Attack; wild reaches or passes zero | Clamp wild hit points to zero; end encounter; skip wild response | Report defeat; focus North |
| Throw Poké Ball; wild above threshold | No hit-point or collection change | Explain failure; Throw Poké Ball keeps focus |
| Throw Poké Ball; wild at or below threshold | Save its number and name as one pending handoff; end encounter | Report capture; focus North |
| Run | End encounter; preserve hit points and collection | Report Run; focus North |
| Wild response makes starter faint | Return position to Center; heal starter; end encounter | Report fainting and recovery; focus North |
Point to the first condition that stops an Attack after defeat. Point to the separate condition that checks for starter fainting after a surviving wild Pokémon responds. If your numbers cannot reach both zero-boundary tests, revise the rules now.
Assistance 1 — Identify the two stopping points
In the Attack route, the first stop is after player damage and before the wild response. The second stop is after the wild response and before the continuing-battle render. Each terminal result needs one exit from the handler.
Checkpoint: Battle rules have reachable checks
- What now works
- Fixed numbers, known species, and the order of all six action results are recorded before code changes.
- Files changed
project notes or project-brief.md, interactive-test-report.md if used- What remains
- Add controls that match the active-encounter state.
- Next action
- Place one disabled Attack button in the existing battle section, then add the other two controls.
- If it does not work
- Calculate the hit points after each Attack on paper. Change the fixed plan if exact zero, below zero, or threshold equality is unreachable.
2. Add battle controls and render their availability
Section titled “2. Add battle controls and render their availability”In game.html, replace the old encounter instruction with a short action instruction. Add a named battle-control group after the starter details. Put Attack, Throw Poké Ball, and Run in that source order. Use type="button" and disabled in saved HTML. Keep the trainer-setup link after the controls, so Tab from the newly focused battle heading reaches Attack first.
Start with this partial pattern and give each control its own stable ID:
<button id="battle-attack" type="button" disabled>Attack</button>In game.js, select all three buttons beside the existing DOM selections and add them to the missing-elements guard. In renderGame(), use state.activeEncounter to enable or disable all three. Register each click listener once, outside renderGame(). The handlers can remain empty while you check the controls.
In styles.css, reuse the movement-control button treatment for battle buttons. Give the group a wrapping layout, readable disabled state, and visible focus outline. The button labels, not color, identify the actions.
Before trainer setup, the battle section is hidden and its buttons are disabled in the DOM. Start a trainer and trigger a known encounter. The battle heading receives focus; one Tab reaches Attack. All three actions are enabled, and movement is disabled. Start a new trainer: the encounter hides, battle buttons disable, and North receives focus.

If it does not work
Section titled “If it does not work”If Tab skips Attack, confirm the button is enabled after rendering and that the trainer-setup link follows the controls. If buttons work before setup, compare their saved disabled attributes with the renderGame() condition.
Assistance 2 — Keep event setup separate from rendering
Select three buttons once. Put them in one array if that helps you apply the same enabled state. Register one listener per button after the handler functions. renderGame() changes attributes from state; it does not register listeners.
Checkpoint: Battle controls follow the encounter state
- What now works
- An active encounter enables exactly three battle actions. Setup and terminal states disable them, and the first Tab from the encounter heading reaches Attack.
- Files changed
game.html, game.js, styles.css- What remains
- Make Attack produce one ordered result.
- Next action
- Implement only the surviving-wild-Pokémon branch of Attack, then test one activation.
- If it does not work
- Inspect the button IDs in HTML, the matching selectors, the guard, and the active-encounter condition in that order.
3. Complete Attack before connecting the other actions
Section titled “3. Complete Attack before connecting the other actions”In the Attack handler, return immediately if state.activeEncounter is null. This guard protects state even if someone calls the handler directly while the button is disabled.
Implement the surviving branch first. Read the current wild record, subtract the fixed player damage, and check the result. When the wild Pokémon still has hit points, subtract the fixed wild damage from the starter. Set one status message naming both results, call renderGame() once, and keep focus on Attack. Start a known Mareep encounter: one Attack leaves Mareep at 22 and a full-health starter at 34 with the sample rules. One activation must not produce two wild responses.

Next, complete defeat. If player damage reaches or passes zero, set the wild record’s current hit points to 0. Report the zero result, end the encounter, render, focus North, and leave the handler. The wild response must be after this branch. Use return as you did for invalid setup and rejected movement.
Finally, check starter fainting after a surviving wild response. If the starter reaches or passes zero, restore the map position to the Center, restore the starter to maximum hit points, end the encounter, set one recovery status, render, focus North, and leave the handler. Do all state changes before the render. No negative hit-point value should appear on the page.
Use separate known encounters for each row. Bounsweet reaches exactly zero after the third Attack. Mareep would pass below zero after the fourth Attack. Both end without a last wild response or a caught record. For fainting, use the controlled 6-hit-point starter precondition above: Mareep survives one Attack, responds once, and the summary shows the Center and a healed starter. After each terminal result, battle buttons are disabled and North has focus.
If it does not work
Section titled “If it does not work”If defeat also reduces starter hit points, move the defeat check before the wild response and return from that branch. If fainting shows an empty or negative starter value, finish Center recovery before calling renderGame(). If one click applies two turns, inspect listener registration rather than changing damage values.
Assistance 3 — Trace Attack without JavaScript syntax
- Guard: is a battle active?
- Player action: subtract fixed damage; clamp wild hit points to zero.
- First terminal check: if wild hit points are zero, end and return.
- Wild response: subtract fixed damage from starter hit points.
- Second terminal check: if the starter fainted, complete Center recovery, end, and return.
- Continuing result: set one message, render once, and focus Attack.
Assistance 4 — Use a partial Attack branch
Keep the terminal helper small: clear the active encounter, set one status, render once, then focus North. Complete the missing conditions yourself in this outline:
function handleAttackClick() { if (state.activeEncounter === null) { return; }
const wildPokemon = state.activeEncounter; // Subtract player damage and clamp wild hit points to zero. // If wildPokemon has zero hit points, finish the battle and return.
// Subtract wild damage from the starter. // If the starter fainted, restore Center state, finish, and return.
// Set the continuing status, render once, and focus Attack.}Checkpoint: Attack has one ordered result
- What now works
- One surviving Attack causes one wild response. Exact-zero and below-zero defeats stop before the response. Fainting completes Center recovery before rendering.
- Files changed
game.js- What remains
- Connect failed capture, successful capture, and Run without changing Attack's order.
- Next action
- Start a fresh known Mareep encounter above the capture threshold and connect Throw Poké Ball.
- If it does not work
- Trace one click from the active-encounter guard through each return. Count render calls and wild responses before changing another branch.
4. Add Throw Poké Ball and Run
Section titled “4. Add Throw Poké Ball and Run”Connect the Throw Poké Ball handler next. Guard against no active encounter. Compare the wild Pokémon’s current hit points with the fixed threshold.
- Above the threshold: Keep the encounter and all hit points unchanged. Set a reason that names the current value and threshold. Render once and return focus to Throw Poké Ball. There is no wild response.
- At or below the threshold: Before clearing
state.activeEncounter, copy its Pokédex number and display name into onestate.pendingCapturerecord. Then end the encounter, set the capture status, render once, and focus North. There is no wild response.
Add pendingCapture: null to the initial state and clear it when a valid trainer setup starts a new game. This is an interim handoff, not a second species definition and not the finished collection. Keep the caught collection and its count unchanged. A successful capture status should state that the collection comes in the next build stage, so a visible count of 0 is not misleading.
Connect Run last. Guard against no active encounter, remember the wild name for the status, end the encounter, render, and focus North. Run changes neither Pokémon’s hit points nor either collection property.
In a fresh Mareep encounter at 30, Throw Poké Ball fails and keeps focus on that button. In a fresh Misdreavus encounter, two Attacks leave 12; Throw Poké Ball succeeds at equality. In a fresh Bounsweet encounter, two Attacks leave 8; capture succeeds below the threshold. Inspect state.pendingCapture in DevTools before starting another test: it contains the number and name from the confirmed species. A fresh Run ends a Mareep encounter without a wild response or hit-point change.

If it does not work
Section titled “If it does not work”If equality fails, check whether your condition rejects values greater than the threshold rather than values equal to it. If the saved capture identity is empty, copy it before clearing the active encounter. If Run lowers hit points, remove any shared wild-response call from the Run path.
Assistance 1 — Find the capture decision
A single comparison divides the Throw Poké Ball handler. The value above the threshold stays in battle. Equality and values below it take the confirmed-capture path.
Assistance 2 — Keep the capture handoff separate from the collection
The pending record needs only the wild Pokémon’s stable Pokédex number and display name. Copy those while the active encounter still exists. The next stage will decide how to add a species to the collection and show it.
Assistance 3 — Compare terminal action order
Successful capture and Run both finish with active encounter cleared, one status, one render, and North focus. Capture first saves identity. Run does not. Neither action subtracts damage or calls a wild response.
Checkpoint: Every action reaches its own result
- What now works
- Failed capture keeps the encounter, successful capture saves one identity, and Run ends without damage. All three keep the correct status and focus.
- Files changed
game.js- What remains
- Run the full battle matrix, keyboard checks, and layout checks.
- Next action
- Return the encounter setting to normal after the controlled cases, then run the complete self-check.
- If it does not work
- Compare the before-state and after-state for one action at a time. Check the first branch that should return before editing shared rendering.
Verify the complete battle loop
Section titled “Verify the complete battle loop”Start each controlled route from a fresh trainer setup. Record the starting species, both hit-point values, action, state after the action, visible status, focused control, and Console result. If you are completing the Level 2 assignment, record these in interactive-test-report.md. Use Test every conditional branch and the controlled-test pattern as you work.
Self-check
Complete these checks against the required result.
- Before setup and after every terminal result, all battle buttons are disabled. During an encounter, movement is disabled and the three battle buttons are enabled.
- From the focused battle heading, Tab reaches Attack first. Pointer, Enter, and Space each activate exactly one Attack in separate fresh encounters.
- One surviving Attack applies one player hit, one wild response, one status, and one render; focus remains on Attack.
- An exact-zero defeat and a would-be-below-zero defeat both report zero, end the encounter, skip the final wild response, and leave the caught collection unchanged.
- A failed catch above the threshold keeps both hit-point values, the encounter, and the collection unchanged; focus stays on Throw Poké Ball.
- A catch at the threshold and a catch below it each save the expected Pokédex number and name before ending the encounter. No wild response occurs.
- Run ends an encounter without changing hit points or the collection; North receives focus.
- A controlled surviving wild response that makes the starter faint returns the trainer to the Center, restores full starter hit points, ends the encounter, and focuses North.
- After each result, the visible encounter, map, summary, status, disabled controls, and state agree. The Console has no errors.
- Restore the saved encounter setting to normal, reload, and confirm one ordinary accepted grass move still reaches the normal chance decision.
- Check the battle near 320 CSS pixels and at 200% browser zoom. Buttons wrap, text remains readable, and the page has no horizontal scroll.
Safe stopping point
Section titled “Safe stopping point”Save a Git checkpoint when the full self-check passes, normal encounter mode is restored, and temporary DevTools values are gone. One battle can now continue or end through every required action. Continue to Complete the caught collection to turn the confirmed capture identity into a visible, duplicate-free list.