Sample gameplay of an end-to-end trained agent playing tetris.
Training statistics of the agent.
The easiest way to see the model in action is to check out the pre-recorded gifs of the agent playing Tetris. You can find them in the gifs directoy. The gifs are names with the training step of the agent at which they're recorded.
Once you clone/download the repository, run the following to replicate training. Note that uv handles dependency installation, so you don't need to install any dependencies manually. If you do not have uv, consult this guide or run wget -qO- https://astral.sh/uv/install.sh | sh if you're on a Unix system.
uv run train_dqn.pyTraining takes significant time (on a server-grade GPU, this ranges from couple of hours to 1 day), so if you just want to try the model out, you can run the following to load a pre-trained model and play a game of Tetris:
uv run test_dqn.py --gifUsing --gif will save a gameplay by the agent to gifs/test_episode.gif. Or, you can use --ansi option to print out gameplays and evaluation statistics to the terminal. Note that the evaluation script uses the default reward function, which is different from the one used in training, so the scores may not be comparable. The model I trained is uploaded in the final_checkpoints directory, and is used as the default model checkpoint to evaluate in the test script.
Tetris-RL/
├── gifs/ # pre-recorded gifs
├── logs/ # training logs
├── final_checkpoints/ # final model checkpoints
├── dqn_agent.py # DQN & agent class implementation
├── train_dqn.py # training script
├── test_dqn.py # testing script that loads a model and plays a game
├── visualize_logs.py # script to visualize training logs
├── train.sh # bash script to run training with SLURM
├── pyproject.toml # uv project file
├── uv.lock # uv lock file
There are many attempts at training Tetris-playing agents (like this, or this). The key observation is that training on raw observations --- just what the human sees --- does not work. Projects that demonstrate impressive results are usually a comnbination of sophisticated feature engineering, search (e.g. MCTS), and/or action grouping. The starting goal of this project is to explore how far we could get with none of the above, with only a basic model-free RL algorithm. The key characteristics of the setup are follows:
- end-to-end: unlike common RL tetris agents, the model receives the board state as input and has to produce an action as output. There are no human-enginerred features or action grouping.
- full-fledged tetris: the agent has access the the entire action space, including hard drop and holding etc.
Implementationally, the following are also important:
- vector envs: I used multiple parallel environments to speed up rollouts during training
- replay buffer: Instead of generating all training samples on the fly, I implemented a replay buffer, which is standard in DQN training. The buffer stores past samples and gradually updates them, and training batches are sampled from the buffer as opposed to simulated.
- logging: I implemented a minimal logger that saves key training stats as a csv file, which is later used for visualization
The model I'm training is a simple neural network. Since the model receives a board state as input, which is visual information, the model is a 3-layer CNN followed by a 3-layer MLP. See dqn_agent.py for details.
I initially tried training the model with DQN, which optimizes for the Bellman equation loss
where
where
where the action train method of dqn_agent.py.
Running a full training takes ~20h on a V100 GPU. Due to the computational cost, I did not do a full-fledged hyperparameter search (which would require running full-length experiments). Instead, I heuristically tried the below combinations and settled on the ones that seems to work best during early training.
- Reward function: the reward function turns out the be the most important factor: balancing reward values for different outcomes encourages drastically different behaviors. Crucially, if the reward of dropping pieces is too high compared to the reward for staying alive, the model quickly learns to "suicide" by hard-dropping pieces and ending the game early, and does not escape from the strategy.
- Game over:
-1 - Line clear:
1 - Piece drop:
0.05 - Staying alive:
0.05
- Game over:
- Replay Buffer size
100000
- Model size
- 2 hidden layers, each of 128, ReLU activation
- Learning rate
-
2e-3, after trying1e-4,5e-3, and1e-2
-
- Batch size
-
256, after trying32,128, and512
-
- Epsilon decay
-
0.9999, after trying0.999,0.99999, and0.999999. Epsilon starts at1.0and decays to0.05over the course of training.
-
-
$\tau$ for target network update0.005
- Update frequency
-
2, after trying1,2, and4
-
We see that with the above setup, the model significantly outperforms a random policy, and certainly "learns" to play Tetris in some important sense: for example, it often is able to clear a line, and knows, to some extent, to avoid the board filling up and use/release blocks in hold. However, it clearly hasn't really grasped the core idea of the game: continuously clearing lines. It often succeeds at clearing the first line, but performance seems to degrade as gameplay goes on, suggesting that there is still a bootstrapping problem. Furthermore, the model quickly dies at the end and training reward plateaus mid-training, suggesting that DDQN with the current setup is not enough to learn the game.

