Documentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/cartpole/llms.txt
Use this file to discover all available pages before exploring further.
app.js is the orchestration layer that connects CartPoleEnvironment and DQNAgent to the browser UI. It manages the requestAnimationFrame animation loop, canvas rendering for both the physics simulation and the score chart, live metric display, all button and keyboard event handlers, and mode switching between AI agent control and manual play.
Global State Variables
These module-level variables hold all mutable runtime state. They are declared at the top ofapp.js and read or written by every function in the module.
The single shared
CartPoleEnvironment instance. Created once at startup and reused across episodes; its internal state is refreshed by calling env.reset().The
DQNAgent instance. Replaced with a fresh new DQNAgent() when the user clicks Reset, discarding all learned weights and replay memory.The current 4-element observation
[x, xDot, theta, thetaDot]. Updated after every call to takeStep() with the state field of env.step()’s return value.true while continuous training is active. The animation loop executes 4 steps per frame when running is true.true when the user is controlling the cart directly via buttons or keyboard. In manual mode the agent does not call remember() or experienceReplay().Running count of completed episodes. Incremented by
finishEpisode() and reset to 0 by the reset handler.Rolling array of the most recent episode scores (up to 100 entries). Used by
drawChart() and updateUI(). Oldest entry is evicted with scores.shift() once the array exceeds 100 elements.true while a single-episode run (triggered by the Run Episode button) is in progress. The loop executes 1 step per frame in this mode and finishEpisode() sets it back to false.Core Functions
takeStep(action)
The fundamental step function called from the animation loop and from the manual-control handlers. It advances the simulation by one step, stores the transition (AI mode only), triggers learning (AI mode only), and ends the episode if terminal is returned.
| Mode | agent.remember() | agent.experienceReplay() |
|---|---|---|
| AI agent | ✅ called | ✅ called |
| Manual | ❌ skipped | ❌ skipped |
finishEpisode()
Called by takeStep() whenever env.step() returns terminal: true. Pushes the episode score to scores, increments episode, appends a line to the training log, resets the environment, and — if not in manual mode — runs one final experienceReplay() pass.
pendingSingleEpisode is reset to false here so that a single-episode run stops automatically after the episode concludes.
loop()
The requestAnimationFrame callback that drives the entire simulation. It decides how many physics steps to execute per frame, redraws both canvases, and updates the stat panel.
| Simulation state | Steps per frame |
|---|---|
Continuous training (running = true) | 4 |
Single episode (pendingSingleEpisode = true) | 1 |
| Paused / manual (neither flag set) | 0 (no takeStep) |
drawCartPole(), drawChart(), and updateUI() are called on every frame regardless of whether the simulation is running, keeping the display in sync with any state changes.
Rendering Functions
drawCartPole()
Renders the physics simulation on the cartpoleCanvas element each frame.
0 (centre of track) maps to the centre of the canvas. Each metre of physical displacement maps to 125 px of canvas movement.
Visual elements:
| Element | Colour | Detail |
|---|---|---|
| Background | #FFFDF6 | Fills entire canvas |
| Track | #D7DCE8 | Horizontal line at y=420 with 21 tick marks |
| Cart body | #FF5F57 (coral red) | Rounded rectangle 156 × 74 px, corner radius 20 |
| Wheels | #11172B (dark) / #FFFDF6 (hub) | Two circles at cartX ± 55 |
| Pole | #FFD65A (yellow) | Line 225 px long, 18 px wide, angled by state[2] |
| Pivot hub | #11172B outer, #FFFDF6 inner | Circles of radius 24 and 10 at (cartX, baseY) |
All canvas dimensions are fixed at 920 × 520 px for the simulation canvas (
cartpoleCanvas) and a separate fixed-size canvas for the score chart (scoreChart). The pixel scaling factor of 125 in the cart-position mapping is baked in to those dimensions.drawChart()
Renders the episode score history as a line chart on the scoreChart canvas.
- Displays the last 100 scores from the
scoresarray. - The Y-axis maximum is
Math.max(500, ...visible), so the scale never drops below the episode maximum of 500 even early in training. - The chart line is drawn in blue (
#4E78FF).
updateUI()
Reads the current global state and writes formatted values to every stat DOM element.
| Element ID | Value displayed |
|---|---|
episodeCount | episode |
scoreValue | env.steps (current episode step count) |
averageScore | Mean of scores array, 1 decimal place |
bestScore | Math.max(...scores) or 0 |
epsilonValue | agent.explorationRate (3 decimal places) |
memoryValue | agent.memory.length (locale-formatted with commas) |
cartPosition | state[0] (3 decimal places) |
cartVelocity | state[1] (3 decimal places) |
poleAngle | state[2] converted to degrees: state[2] * 180 / Math.PI, 2 decimal places, ° suffix |
poleVelocity | state[3] (3 decimal places) |
statusText | 'Training' / 'Running Episode' / 'Manual Control' / 'Ready' |
Event Handlers
Simulation Control Buttons
Starts continuous training. Sets
manualMode = false, calls setModeUI(), sets running = true, clears pendingSingleEpisode.Runs a single episode then stops. Sets
manualMode = false, calls setModeUI(), sets pendingSingleEpisode = true, sets running = false.Pauses all simulation activity. Sets
running = false and pendingSingleEpisode = false.Full reset. Stops simulation, zeroes
episode and scores, creates a new DQNAgent (discarding all weights and memory), calls env.reset(), and resets the training log.Clears all entries from the training log by setting
trainingLog.innerHTML = ''.Mode Buttons
Switches to AI agent mode. Sets
manualMode = false, stops any active run, calls setModeUI().Switches to manual control. Sets
manualMode = true, stops the simulation, resets the environment to a fresh state, calls setModeUI().Manual Control
Applies a left force for one step: calls
takeStep(0).Applies a right force for one step: calls
takeStep(1).When
manualMode is true, calls takeStep(0) and prevents default scroll behaviour.When
manualMode is true, calls takeStep(1) and prevents default scroll behaviour.setModeUI()
Synchronises all mode-dependent UI elements with the current value of manualMode.
| DOM element | AI agent mode | Manual mode |
|---|---|---|
agentModeBtn class active | ✅ present | ❌ absent |
manualModeBtn class active | ❌ absent | ✅ present |
manualControls visibility | Hidden | Visible |
modeText content | 'AI Agent Mode' | 'Manual Mode' |
setModeUI() is called on page load (before loop()) to set the initial UI state to AI agent mode, and is called again by startBtn, episodeBtn, agentModeBtn, and manualModeBtn handlers whenever the mode changes.