A high performance modern tetris bot framework written in Python (and eventually) C++ with useful bindings, utilities, and UI for visualizations.
mino_taur provides a python library to create bots for Modern Tetris. Eventually, a port of the core functionality in C++ is planned, allowing bots written in python to leverage the performance of native C++ code under the hood.
Modern Tetris is a vastly different game from the NES-Classic Tetris that most think of when they hear "Tetris". Additions like the SRS Rotation System, 7-Bag Randomizer, and Hold Piece make optimal play unimaginably more complex to compute.
I would highly recommend checking out this 60-second video.
In Classic Tetris, the search space (pretty much) consists of the 10 columns (or fewer depending on piece orientation and width) where a piece can land, multiplied by 4 for each of its orientations. Achieving efficient search in modern tetris introduces a massive amount of new complexity.
I built this framework out of my passion as an avid player of modern tetris and an inability to help myself from tango-ing with that complexity through programming. My goal with mino_taur is to create a blazingly fast platform for creating modern tetris bots in a beginner-friendly language such as python, with the plan to eventually interface with bindings in C++ (a language I'd really like to learn) for performance-critical components.
This project is still incomplete as I need to proceed with the port to C++. However, before that, I've thorougly made sure that the high level implementation is heavily optimized, and by that I mean using efficient data structures and algorithms, and excessive profiling with tools such as py-spy.
The core game simulation uses a column-major bitboard representation for the board (column-major as the dominant operation in tetris is vertically dropping the piece long distances, for more information see core/board.py). Each column is stored as a single 64-bit integer with every bit representing whether a cell is filled or empty, enabling O(1) collision detection and highly efficient operations. I've profiled every hot path in the codebase and optimized accordingly, keeping in mind which portions of the code I plan on porting to C++ someday.
The simulator (core/simulator.py) implements pure functions where all state transformations return new immutable states rather than mutating existing ones. This should be useful once ported to C++ (taking advantage of compiler optimizations for value semantics and copy-on-write). It also makes massively parallel search far simpler for any users who want to implement parallelization into their bot.
In addition to the Simulator which allows you to simulate an action taken from a given game state, we also have:
Explorer: Uses BFS to enumerate all reachable piece placements from a given game statePathfinder: Uses A* search with a DAS-compatible admissible heuristic to find optimal move sequences to reach a target placementCache: A little tool for precomputing and caching future moves before you get there to prevent slowdowns after each action
# Clone the repository
git clone https://github.com/ruelalarcon/mino_taur.git
cd mino_taur
# Install dependencies
pip install -r requirements.txtThis project was tested and built for Python 3.11 or higher
python run_tetris.pyThis starts a playable Tetris game with a WebSocket server which simply broadcasts the game state to connected clients.
Note that this doesn't let clients send actions remotely or anything like that. It's just a one-way broadcast of the game state that a client can read.
Standalone mode (fastest, self-contained simulation):
python run_bot_standalone.py simple_botExternal mode (connects to a running game and sends keyboard inputs):
# First, start the game in a separate terminal
python run_tetris.py
# Then run the bot
python run_bot_external.py simple_botThe bot development workflow is straightforward. Here's a minimal example:
# bots/my_bot.py
from typing import List, Optional
from bot.bot import Bot
from bot.bot_action import BotAction
from bot.explorer import Explorer
from bot.pathfinder import Pathfinder
from core.game_state import GameState
class MyBot(Bot):
def get_action_sequence(self, game_state: GameState) -> Optional[List[BotAction]]:
# 1. Explore all possible placements
placements = Explorer.explore(game_state)
if not placements:
return None
# 2. Evaluate each placement with your scoring function
best_score = float('-inf')
best_placement = None
for state_after, placement in placements:
score = self._evaluate(state_after, placement)
if score > best_score:
best_score = score
best_placement = (state_after, placement)
if best_placement is None:
return None
# 3. Find path to best placement
actions = Pathfinder.find_path(game_state, best_placement[1])
return actions
def _evaluate(self, game_state: GameState, placement) -> float:
# Your evaluation function here
# Consider: height, holes, bumpiness, lines cleared, etc.
return 0.0Run your bot:
python run_bot_standalone.py my_botThe framework takes care of:
- Simulating all possible piece placements (rotations, positions)
- Finding the optimal move sequence to reach your chosen placement
- Handling complex mechanics (wallkicks, spins, hold piece)
- Managing timing and input in external mode
You focus on implementing the bot logic, such as an evaluation-function-design or training a neural network.
GameState: Immutable snapshot of game state including board, current piece, held piece, bag, combo counters, and back-to-back status. Implements __hash__() and __eq__() for use in caching and search algorithms.
Simulator: Pure functional game logic. All methods take a GameState and return a new GameState without mutation. The key method is simulate(game_state, action) which returns (new_state, placement).
Board: Column-major bitboard representation. Each column is a single integer with bits representing cells. Provides fast operations like get_cell(), set_cell(), clear_all_lines(), and distance_to_ground().
Action: Enum defining all possible moves: MOVE_LEFT, MOVE_RIGHT, MOVE_DOWN, ROTATE_CW, ROTATE_CCW, ROTATE_180, DAS_LEFT, DAS_RIGHT, SONIC_DROP, HARD_DROP, HOLD_PIECE.
Placement: Result of a hard drop containing the final piece state, lines cleared, spin detection (T-spin, mini, all-spin), and all-clear flag.
Bot: Abstract base class. Implement get_action_sequence(game_state) -> List[BotAction] to create a bot.
Explorer: BFS-based enumeration of all reachable placements from a game state. Returns a set of (GameState, Placement) tuples representing unique outcomes after hard drop.
Pathfinder: A* search to find the optimal sequence of moves from the current piece state to a target placement. Uses an admissible heuristic and ensures spin requirements match exactly.
BotAction: Wrapper around Action that includes distance metadata (cells moved), used for calculating keyboard hold durations in external bot mode.
Cache: Decorator-based caching system for precomputing bot actions, keyed by immutable game state.
Tetris: Base class providing GUI rendering (pygame), event handling, and game loop.
TetrisGame: Extends Tetris with WebSocket server for bot connections, input handling (DAS/ARR), and keybind management.
InputHandler: Manages DAS (Delayed Auto Shift), ARR (Auto Repeat Rate), and SDD (Soft Drop Delay) timing for realistic Tetris controls.
WebSocketConnector: Client for connecting to the game's WebSocket server and receiving state updates.
serde.py: Serialization and deserialization of game state for WebSocket transmission.
All settings are centralized in settings.toml:
[game]
board_width = 10
board_height = 40
visible_board_height = 20
[keybinds]
left = "a"
right = "d"
hard_drop = "space"
rotate_clockwise = "up"
# ... more keybinds
[handling]
das = 133 # Delayed Auto Shift (ms)
arr = 10 # Auto Repeat Rate (ms)
sdd = 16 # Soft Drop Delay (ms)
dcd = 0 # DAS Cancel Delay (ms)
[bot.external]
move_delay = 1 # Delay between bot actions (ms)
[websocket]
address = "localhost"
port = 8765# Visualize pathfinding algorithms
python run_path_visualizer.py
# Debug WebSocket messages
python run_ws_listener.py# Profile bot performance
python -m cProfile -o profile.stats bench/test_bot.py
# Profile pathfinder performance
python -m cProfile -o profile.stats bench/test_pathfinder.py
# Analyze results
python -m pstats profile.statsAlternatively, use a program like py-spy to create interactive flamegraphs and profiles.
Two reference implementations demonstrate the framework:
simple_bot.py: Basic bot using the Dellacherie heuristic (landing height, eroded piece cells, row transitions, column transitions, holes, well depth).
random_bot.py: Random bot that selects actions randomly.
The simple bot shows a simple complete workflow: exploration, evaluation, selection, and pathfinding.
The simulator automatically detects spins during hard drop:
All-spin: Triggered when the last action was a rotation AND the piece is immobile (cannot move in any direction).
T-mini: T piece specific - last action was rotation, matches corner configurations, wallkick index < 3, no drop distance.
Spin detection affects back-to-back counters and scoring, rewarding setup and execution of advanced techniques.
This Python implementation is a stepping stone toward a C++ rewrite. The planned architecture will:
- Port performance-critical components (Simulator, Board, Explorer, Pathfinder) to idiomatic C++
- Provide Python bindings for seamless integration
- Write proper documentation once the C++ integration is finalized