Skip to content

Pokémon Browser Game: Build the battle loop

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.

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 focus

A 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.

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.

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.

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:

game.html — first battle control
<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.

Sample battle view with Mareep at 30 of 30 hit points, Snivy at 40 of 40, three available actions, and focus on the battle heading.
The active encounter shows both hit-point values and the three native actions. The focused heading leads into Attack.
Sample battle view with Mareep at 30 of 30 hit points, Snivy at 40 of 40, three available actions, and focus on the battle heading.

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.

Sample battle after one Attack: Mareep has 22 of 30 hit points, Snivy has 34 of 40, the status reports both actions, and Attack retains focus.
One Attack changes the wild record, then the starter record. The encounter stays active and Attack remains the next action.
Sample battle after one Attack: Mareep has 22 of 30 hit points, Snivy has 34 of 40, the status reports both actions, and Attack retains focus.

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 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
  1. Guard: is a battle active?
  2. Player action: subtract fixed damage; clamp wild hit points to zero.
  3. First terminal check: if wild hit points are zero, end and return.
  4. Wild response: subtract fixed damage from starter hit points.
  5. Second terminal check: if the starter fainted, complete Center recovery, end, and return.
  6. 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:

game.js — partial Attack order
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.

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 one state.pendingCapture record. 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.

Sample game after capturing Misdreavus at the 12-hit-point threshold: the map and movement controls return, North has focus, and the status names the capture.
A capture at the threshold ends the encounter. Movement returns; the collection count stays unchanged until the next stage.
Sample game after capturing Misdreavus at the 12-hit-point threshold: the map and movement controls return, North has focus, and the status names the capture.

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.

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.

  1. 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.
  2. From the focused battle heading, Tab reaches Attack first. Pointer, Enter, and Space each activate exactly one Attack in separate fresh encounters.
  3. One surviving Attack applies one player hit, one wild response, one status, and one render; focus remains on Attack.
  4. 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.
  5. A failed catch above the threshold keeps both hit-point values, the encounter, and the collection unchanged; focus stays on Throw Poké Ball.
  6. 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.
  7. Run ends an encounter without changing hit points or the collection; North receives focus.
  8. 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.
  9. After each result, the visible encounter, map, summary, status, disabled controls, and state agree. The Console has no errors.
  10. Restore the saved encounter setting to normal, reload, and confirm one ordinary accepted grass move still reaches the normal chance decision.
  11. Check the battle near 320 CSS pixels and at 200% browser zoom. Buttons wrap, text remains readable, and the page has no horizontal scroll.

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.