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.
GameExperience.py solves one of the most important practical problems in training a deep Q-learning agent: the instability that comes from learning only from the most recent move. Without a memory buffer, the neural network is trained on a stream of highly correlated, sequential experiences — each step is almost identical to the one before it, which causes the model to overfit to recent behavior and forget what it learned earlier. GameExperience breaks this pattern by storing (state, action, reward, next_state, game_over) tuples in a rolling buffer and drawing random samples from that buffer at training time. The result is a more stable, less forgetful agent.
What Experience Replay Does
In a standard online training setup, the network would update its weights immediately after every single step, using only the latest experience. Experience replay defers and diversifies training:- Every step is stored as an episode tuple in a memory list.
- Training batches are drawn by randomly sampling from the full memory — not just the last step.
- Temporal correlation between consecutive steps is broken because random samples are unlikely to all come from adjacent moments in the same episode.
- Older experiences remain in the pool and can be reused across many training iterations, making every stored episode more valuable.
This technique was central to DeepMind’s original DQN paper (2013/2015). Even in a small maze project, the benefit is visible: without replay, the agent oscillates and forgets; with replay, it converges more reliably.
__init__(self, model, max_memory=100, discount=0.95)
The constructor wires together the neural network, the memory buffer, and the discount hyperparameter.
| Parameter | Default | Description |
|---|---|---|
model | (required) | The Keras neural network. Must have an output layer with one unit per action (4 for this maze). |
max_memory | 100 | Maximum number of episodes to hold in the buffer. When full, the oldest entry is deleted to make room for the newest. |
discount | 0.95 | The γ (gamma) factor. Controls how much weight future rewards receive relative to immediate rewards. |
self.num_actions is read directly from model.output_shape[-1], so the class automatically adapts if the network architecture changes.
remember(episode)
Appends a completed step to the memory list and trims the oldest entry if the buffer has exceeded max_memory.
episode is a five-element list:
| Index | Name | Type | Description |
|---|---|---|---|
[0] | envstate | ndarray (1, 64) | Flattened maze state before the action |
[1] | action | int | One of LEFT=0, UP=1, RIGHT=2, DOWN=3 |
[2] | reward | float | Reward received from TreasureMaze.get_reward() |
[3] | envstate_next | ndarray (1, 64) | Flattened maze state after the action |
[4] | game_over | bool | True if the episode ended (win or lose) |
del self.memory[0]) means the buffer always reflects the most recent max_memory steps. Very old experiences are eventually discarded, which prevents the agent from training on maze configurations that are no longer representative of its current skill level.
predict(envstate)
A thin wrapper around the Keras model’s predict call, used internally by get_data().
model.predict(envstate) returns a 2D array of shape (1, num_actions). The [0] index strips the outer batch dimension, returning a flat 1D array of num_actions Q-values — one for each possible direction. The calling code can then index directly into this array to get Q(s, LEFT), Q(s, UP), etc.
get_data(data_size=10)
This is the method the training loop calls to produce a batch of inputs and targets for the neural network. It randomly samples data_size episodes from memory and computes updated Q-value targets using the Bellman equation.
Step-by-step breakdown
env_sizeis read from the first stored episode to know how wide the input layer must be (64 for an 8×8 maze).data_sizeis capped atmem_sizeso the method never requests more samples than exist in memory.inputsandtargetsare pre-allocated as zero-filled NumPy arrays of shape(data_size, env_size)and(data_size, num_actions)respectively.- Random sampling —
np.random.choice(range(mem_size), data_size, replace=False)picksdata_sizeunique episode indices. This is the randomness that breaks temporal correlation. targets[i] = self.predict(envstate)— The target array for episodeiis initialized from the current network’s own predictions. This means only the action that was actually taken will have its target overwritten. All other actions’ Q-values remain unchanged, preventing spurious gradient updates for actions never taken from this state.Q_sa— The maximum Q-value predicted for the next state. This is the agent’s current best estimate of the value of being inenvstate_next.- Terminal update — If
game_overisTrue, the target for the taken action is simplyreward(no future discounting, because there is no future). - Non-terminal update — Otherwise, the target is
reward + discount * Q_sa, the Bellman update that ties immediate reward to expected future value.
The key insight: only
targets[i, action] is modified. Every other column in targets[i] comes from the network’s own predictions, so the loss for unvisited actions in this state is always zero. The network is only corrected for what it actually did.The Bellman Target at a Glance
| Symbol | Value / Source | Meaning |
|---|---|---|
reward | From TreasureMaze.get_reward() | Immediate feedback for the step |
self.discount | 0.95 (default) | How much future rewards are discounted per step |
Q_sa | max(predict(envstate_next)) | Best future value the agent currently estimates |
No Direct Equivalent in the Browser Demo
The browser demo does not include a replay buffer or a neural network. TheCompetitiveAgent in the demo computes its move on every step using BFS (breadth-first search), finding the shortest unblocked path from its current position to the treasure. This is equivalent to what a fully-trained Q-network would do when epsilon is zero and it always exploits its learned values — but it skips the training process entirely.