Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/treasure-hunt-game/llms.txt

Use this file to discover all available pages before exploring further.

The TreasureMaze class is the authoritative rulebook for every game of Treasure Hunt. It manages an 8×8 matrix of open and blocked cells, tracks the player’s position, marks which cells have been visited, and enforces which moves are legal on every turn. Every player action passes through updateState() before a reward is issued, ensuring the maze’s physical rules are never bypassed regardless of how a move is requested — from a keyboard press, an on-screen button, or the agent’s own logic.

Grid Structure

The maze is stored as an 8×8 two-dimensional array where each cell holds one of two values:
ValueMeaning
1Open cell — the player or agent may stand here
0Blocked cell / wall — movement into this cell is forbidden
The player always starts at [0, 0] (top-left) and the treasure target is always at [7, 7] (bottom-right). Both cells are guaranteed to be open before a game begins.

The ORIGINAL_MAZE Constant

When randomized generation fails after 70 attempts, the engine falls back to this hand-crafted layout:
export const ORIGINAL_MAZE = [
  [1, 0, 1, 1, 1, 1, 1, 1],
  [1, 0, 1, 1, 1, 0, 1, 1],
  [1, 1, 1, 1, 0, 1, 0, 1],
  [1, 1, 1, 0, 1, 1, 1, 1],
  [1, 1, 0, 1, 1, 1, 1, 1],
  [1, 1, 1, 0, 1, 0, 0, 0],
  [1, 1, 1, 0, 1, 1, 1, 1],
  [1, 1, 1, 1, 0, 1, 1, 1]
];

Action Constants and Deltas

Four named integer constants represent every possible direction of movement. Each constant maps to a [rowDelta, colDelta] pair in ACTION_DELTAS that describes how the player’s grid position changes when that action is applied:
export const LEFT  = 0;
export const UP    = 1;
export const RIGHT = 2;
export const DOWN  = 3;

export const ACTION_DELTAS = {
  [LEFT]:  [0, -1],   // column decreases by 1
  [UP]:    [-1, 0],   // row decreases by 1
  [RIGHT]: [0,  1],   // column increases by 1
  [DOWN]:  [1,  0]    // row increases by 1
};
LEFT = 0 moves one column to the left, UP = 1 moves one row up, RIGHT = 2 moves one column to the right, and DOWN = 3 moves one row down.

Cell Validation: canStandOn(row, col)

Before any move is accepted, the engine checks whether the destination cell is a legal standing position. A cell passes this check only when both conditions are true:
  1. The coordinates are within the 8×8 boundary (isInside returns true).
  2. The base maze value at that position equals 1 (open, not a wall).
canStandOn(row, col) {
  return this.isInside(row, col) && this.baseMaze[row][col] === 1;
}
If either condition fails the cell is treated as impassable, exactly like a wall.

Computing Valid Actions: validActions()

validActions() returns an array of all action integers that would move the player onto a legal cell from the current position. It iterates over the four action constants, applies each delta, and keeps only those where canStandOn returns true for the resulting coordinates:
validActions(cell = null) {
  const row = cell ? cell[0] : this.state.row;
  const col = cell ? cell[1] : this.state.col;

  return [LEFT, UP, RIGHT, DOWN].filter((action) => {
    const [dr, dc] = ACTION_DELTAS[action];
    const nextRow = row + dr;
    const nextCol = col + dc;
    return this.canStandOn(nextRow, nextCol);
  });
}
An empty array means the player is fully surrounded by walls or boundaries — a "blocked" state.

Applying a Move: updateState(action)

updateState(action) is the core movement function. It records the current cell as visited, evaluates the action, and sets the resulting game mode:
updateState(action) {
  let { row, col } = this.state;
  let mode = this.state.mode;

  if (this.canStandOn(row, col)) {
    this.visited.add(this.key(row, col));
  }

  const validActions = this.validActions();

  if (!validActions.length) {
    mode = "blocked";
  } else if (validActions.includes(action)) {
    const [dr, dc] = ACTION_DELTAS[action];
    row += dr;
    col += dc;
    mode = "valid";
  } else {
    mode = "invalid";
  }

  this.state = { row, col, mode };
}
The three outcomes map directly to the game’s mode system:
OutcomeConditionMode setPosition changes?
Successful moveAction is in validActions()"valid"Yes
Wall / boundary hitAction not in validActions() but list is non-empty"invalid"No
Fully trappedvalidActions() returns []"blocked"No

Game Modes

The state.mode field describes what happened on the most recent turn. The engine uses four named string modes throughout its reward and status logic:
ModeWhen it is set
"start"Initial state after reset() is called
"valid"The last action moved the player to a new open cell
"invalid"The last action pointed at a wall or outside the grid
"blocked"There are no valid actions from the current cell

Observing the Board: observe()

observe() returns a flat 64-element array — a serialized snapshot of the current board state. The maze values (0 or 1) are preserved as-is, except the player’s current cell, which is overwritten with 0.5 to distinguish it from both walls and normal open cells:
observe() {
  const canvas = cloneMaze(this.baseMaze);
  canvas[this.state.row][this.state.col] = 0.5;
  return canvas.flat();
}
This format mirrors the environment observation used in the original deep Q-learning project, where the flattened array was fed directly into the neural network as the state input.

Creating and Stepping Through a TreasureMaze

import { TreasureMaze, DOWN, RIGHT } from "./treasure-maze.js";
import { createRandomMaze } from "./treasure-maze.js";

// Generate a fresh randomized board
const { maze } = createRandomMaze();

// Create the environment — player starts at [0,0], target at [7,7]
const env = new TreasureMaze(maze);

// Inspect the initial observation (64-element flat array, player cell = 0.5)
console.log(env.observe());

// Take a step downward
const result = env.act(DOWN);
console.log(result.state);       // { row: 1, col: 0, mode: "valid" }
console.log(result.reward);      // -0.04 (valid step)
console.log(result.totalReward); // -0.04 (accumulated)
console.log(result.status);      // "not_over"

// Attempt an invalid move (into a wall)
const blocked = env.act(RIGHT);
console.log(blocked.state.mode); // "invalid"
console.log(blocked.reward);     // -0.75
The act(action) method is the public entry point for all moves. It calls updateState(), then getReward(), accumulates totalReward, evaluates gameStatus(), and returns all five values together so the caller never needs to invoke internal methods directly.

Build docs developers (and LLMs) love