Skip to content

Instantly share code, notes, and snippets.

@MinusKelvin
Last active May 11, 2025 14:42
Show Gist options
  • Save MinusKelvin/de519f7e922a1c0821c3ac55de10384c to your computer and use it in GitHub Desktop.
Save MinusKelvin/de519f7e922a1c0821c3ac55de10384c to your computer and use it in GitHub Desktop.
Tetris Bot Protocol Minimum Viable Product

Tetris Bot Protocol (TBP)

Tetris-playing computer programs have recently become more popular with the development of Zetris (mat1jaczyyy), a port of MisaMino (misakamm) to the official Tetris game Puyo Puyo Tetris. Since then, new programs have been developed such as Cold Clear (MinusKelvin), Tetras (traias), Wataame (ameyasu), and Hikari (SoRA_X7). Currently, all of these independently solve the problem of interfacing with the host game. This duplicates a lot of work between these projects, especially since this interfacing code is generally not made public due to concerns about such programs being used to cheat in online Tetris games. This repository aims to specify a common interface for communication between a Tetris-playing program and a Tetris frontend, similar to how the Universal Chess Interface solves a similar problem for Chess.

Overview

A bot conforming to the TBP exists as a standalone executable that is executed by the frontend and communicates over standard input and output. Each message is JSON terminated by a line break. On the web, a conforming bot is a script that can be executed as a Web Worker and communicates using the postMessage API. JSON is chosen as the data format because it is ubiquitous, human readable, and easily allows the protocol to be extended in the future. Every message consists of an object with a type attribute indicating its type. Messages with an unrecognized type should be ignored.

This document specifies the minimum viable product of the TBP, as well as the minimum subset a conforming bot must implement. This version of the TBP can only be used with bots playing guideline-conforming Tetris games, but extensions will allow this limitation to be lifted in the future.

Messages (Frontend -> Bot)

rules

The rules message tells the bot what game rules are in place. Currently, this message is empty, and will be extended later. The bot must respond with either the ready message, if the bot can play with those rules, or error if it cannot. The standard guideline rules are the default; 10x40 board, pieces spawn in the North orientation at x=4, roughly y=19, hold is allowed, the Super Rotation System is used, etc. Since many Tetris games have slightly different peculiarities, the defaults are not precisely specified.

start

The start message tells the bot to begin calculating from the specified position. This message must be sent before asking the bot for a move.

Attribute Description
hold Either a pieces if hold is filled or null if hold is empty.
queue A list of pieces. Example: ["S", "Z", "O"]
combo The number of consecutive line clears that have been made.
back_to_back A boolean indicating back to back status.
board A list of 40 lists of 10 board cells.

A board cell can be either null to indicate that it is empty, or a string indicating which piece was used to fill it, or "G" for garbage cells.

stop

The stop message tells the bot to stop calculating.

suggest

The suggest message tells the bot to suggest some next moves in order of preference. It is only valid to send this message if the bot is calculating. The bot must reply as quickly as possible with a suggestion message containing suggested moves.

play

The play message tells the bot to advance its game state as if the specified move was played and begin calculating from that position. It is only valid to send this message if the bot is calculating. Whether a hold was performed is inferred from the type of piece placed.

Attribute Description
move The move, as specified in the suggestion message.

new_piece

The new_piece message informs the bot that a new piece has been added to the queue. This message is generally paired with a play message.

Attribute Description
piece A piece. Example: "T"

quit

The quit message tells the bot to exit.

Messages (Bot -> Frontend)

error

The error message informs the frontend that the bot cannot play with the specified rules.

ready

The ready message tells the frontend that the bot understands the specified game rules and is ready to receive a position to calculate from.

info

The info message must be sent by the bot to advertise to the frontend what TBP features it supports. Once the frontend receives this message, it can inform the bot of the game rules using the rules message.

Attribute Description
name A string, the human-friendly name of the bot. Example: "Cold Clear"
version A string version identifier. Example: "Gen 14 proto 8"
author A string identifying the author of the bot. Example: "SoRA_X7"
features A list of supported features.

suggestion

The suggestion message is sent in response to a suggest message. It informs the frontend of what moves the bot wishes to make in order of preference. The frontend should play the most preferred valid move. If no moves are valid, the bot forfeits and the frontend should tell the bot to stop calculation. Whether a hold should be performed is inferred from the type of piece to be placed.

Attribute Description
moves A list of moves in order of preference.

A move is an object with the following attributes:

Attribute Description
location A piece location specifying where the piece should be placed.
spin One of "none", "mini", or "full" indicating whether a spin should be performed.

A piece location is an object with the following attributes:

Attribute Description
type The type of piece this is. Example: "I"
orientation The rotation state of the piece. Spawn orientation is "north", after a clockwise rotation "east", etc.
x The x coordinate of the center of the piece, with 0 being the coordinate of the leftmost column.
y The y coordinate of the center of the piece, with 0 being the coordinate of the bottommost row.

The center of the piece is defined according to SRS true rotation. The piece centers for the L, J, T, S, and Z pieces are the stationary points of basic rotation in SRS. The center of the O piece in the north orientation is the bottom-left mino; for east, top-left mino; for south, the top-right mino; for west, the bottom-right mino. The center of the I piece in the north orientation is the middle-left mino; for east, the middle-top mino; for south, the middle-right mino; for west, the middle-bottom mino.

@MinusKelvin
Copy link
Author

@mat1jaczyyy I think you misunderstand the role that this interface is supposed to play. This is a communication protocol between a decision-maker and a frontend. The handling of things like menu navigation and RNG prediction are unrelated to this communication and thus out of scope for the TBI. I think my usage of the word "game" may have confused you, but that's why this is a draft. I'll change the language to use "frontend" instead.

@MinusKelvin
Copy link
Author

MinusKelvin commented Mar 17, 2021

@ExileLord Most of those limitations I plan to address later with extensions, since they are not strictly required to get a bot to play a game and some will require considerable design effort. This is part of the reason why I chose JSON as the message format; those features of a specific game such as rotation system, randomizer, spin detection, incoming garbage, etc. can be specified in addition to the required stuff without breaking bots or frontends that don't understand the extension.

The rotation system is presumed to be SRS

While this is something I plan to address via an extension, the SRS assumption seems to be a problem, but not for a reason you described. I'm thinking a NES Tetris bot using this interface would use a rotation system extension to specify that it supports NRS and only NES RS, but a frontend that does not implement the rotation system extension would assume that SRS is supported since that's what's assumed here, and the incompatibility would be undiscoverable by the frontend (the bot would figure it out when NRS is not specified when the game starts).

It should be possible to know if a move is possible.

This is tricky. Whether a move is possible depends on quite a few things, which makes it tricky to calculate accurately, and a bot would like to do this for every move it considers during its search; this is obviously not something that should be done.

@SoRA-X7
Copy link

SoRA-X7 commented Mar 17, 2021

I'm sorry if my English is unclear or not understandable

As the other guys said, there are quite a few things we want to know. I think it should be possible for bots to send some queries to the interface to know what the current board state, gravity, opponent's board state, game rules (like combo table, garbage blocking, ...), etc. Each bots asks TBI only information which they need to perform calculation. The "extensions" you're saying can also be accessed via queries.

My idea is: TBI should work as a local GraphQL API server. You can ask all the information you need at once with GraphQL. The return format is JSON. This API style works as described below ↓↓

  • Bots (client) can post queries to ask the information they need at any time
  • TBI (server) have to reply these queries, but it can be ignored if it is not supported as an extension
  • Bots (client) have to post mutations to specify what move they have chosen when they're asked to provide move by TBI (Bot => Frontend messaging part)

Frontend => Bot messaging is kinda yikes with this client-server model, but the method you shown is fair enough imo.

It seems also a problem that when there are multiple bots, it is impossible to distinct who is the sender of the "suggestion" message the interface received. We should provide the bots specific IDs (integer), and all the messages should have "id" attribute to know who is the supposed receiver/sender.

@MinusKelvin
Copy link
Author

TBI is supposed to be driven by the frontend; the bot is supposed to be entirely reactive.

It seems also a problem that when there are multiple bots, it is impossible to distinct who is the sender of the "suggestion" message the interface received.

I don't see how this is a problem; one process is one bot, if you run multiple bots you simply launch multiple processes. The processes don't share input or output streams, so there's no way to confuse what messages are being sent/received from different bots.

@XDEmer0r-L0rd-360-G0d-SlayerXD
Copy link

XDEmer0r-L0rd-360-G0d-SlayerXD commented Mar 19, 2021

As I understand it, someone needs to make a unique interface program for each game that exposes the TBI to make bot programs plug'n'play. Kinda with a Game <-> interface <-> TBI <-> bot structure where the TBI is basically the protocol/connection for the interface and the bot to talk. The interface being able to make choices while the bot does nothing but take parameters and do calculations. Due to this disconnect between the bot and game, it is up to the TBI to pass all data the interface provides to it.
Piggy backing off of ExileLord a bit, I think the interface should have the ability to query the bot for information the bot cares about while sending the bot game settings, and game state. As to whether the TBI passes all relevant game settings or just the game name + game mode, is left up to you guys to decide if the interface or bot should have to figure out these settings. I personally think it should be the interfaces job to hold and pass all the minor details to bot when it asks for them. It will be the bot's job to deal with missing parameters.

I don't like rotation system being a start attribute because it's information only needed once and feels a bit out of place. I suggest adding two new messages from the frontend, capabilities to learn what info the bot wants, and setup to change the game rules/parameters. To compliment these new messages, the bot should be able to send a new one too. online should come before ready to indicate its responsiveness but lack of supplied game settings. I'm thinking of a flow where B launches -> (B->F)online -> (F->B)capabilities -> (B->F)capabilities -> (F->B)setup -> (B->F)ready -> (F->B)start.

In all games there should be a minimum amount of data that needs to be sent. For setup we need to check for compatibility with board size, mino shapes, mino bag randomization, and rotation system. For start data, we need board state, at least one queue pieces (active piece), hold status (holding piece, empty, or unavailable). The rest should be able to be inferred for a poor, but working experience. More info should always be better.

Hope I managed to make this coherent. Obviously everything is up for change.

@MinusKelvin
Copy link
Author

@XDEmer0r-L0rd-360-G0d-SlayerXD I like the setup and capabilities messages, although I think I'll rename them and simplify the startup flow a bit. It'd be more like B launches -> (B->F)info -> (F->B)rules -> (B->F)ready or error -> (F->B)start. The info message would carry everything the ready message currently does, and the new ready message probably won't carry any data. The rules message would carry the rotation system and any other details about game rules that the bot might need or care about. Then the bot has an opportunity to tell the frontend that it doesn't know how to play with that ruleset.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment