### Install and Run Validation Script Source: https://riichi.dev/docs/validation Commands to install dependencies and execute the validation bot script. ```bash pip install websockets python validate_bot.py ``` -------------------------------- ### MJAI Bot-to-Server Response Examples Source: https://riichi.dev/docs/protocol Examples of valid JSON responses for various game actions, each including the required request_id. ```json {"type":"dahai","actor":0,"pai":"3m","tsumogiri":true,"request_id":42} {"type":"chi","actor":1,"target":0,"pai":"5m","consumed":["4m","6m"],"request_id":42} {"type":"reach","actor":0,"request_id":42} {"type":"hora","actor":0,"target":2,"pai":"5m","request_id":42} {"type":"none","request_id":42} ``` -------------------------------- ### Start kyoku event Source: https://riichi.dev/docs/protocol Sent at the start of each round, providing initial state and the bot's hand. ```json { "type": "start_kyoku", "bakaze": "E", "dora_marker": "2p", "kyoku": 1, "honba": 0, "kyotaku": 0, "oya": 0, "tehais": [ ["1m","3m","5m","7p","9s",...], ["?","?","?","?","?",...], ["?","?","?","?","?",...], ["?","?","?","?","?",...] ] } ``` -------------------------------- ### Connect Bot via WebSocket Source: https://riichi.dev/docs/local-testing Establishes a WebSocket connection to the game server and handles game events like start, action requests, and end game. Requires a valid bot token in the authorization headers. ```python import asyncioimport jsonimport websocketsfrom riichienv import Observationasync def bot(url: str): async with websockets.connect(url, additional_headers=HEADERS) as ws: my_seat = None agent = MyAgent() while True: msg = json.loads(await ws.recv()) match msg["type"]: case "start_game": my_seat = msg.get("id", 0) case "request_action": # Deserialize the observation from the server obs = Observation.deserialize_from_base64( msg["observation"] ) action = agent.act(obs) resp = json.loads(action.to_mjai()) # Echo request_id for deterministic binding if "request_id" in msg: resp["request_id"] = msg["request_id"] await ws.send(json.dumps(resp)) case "end_game": breakTOKEN = "your-bot-token-here"HEADERS = {"Authorization": f"Bearer {TOKEN}"}asyncio.run(bot("wss://game.riichi.dev/ws/validate")) ``` -------------------------------- ### Start game event Source: https://riichi.dev/docs/protocol Sent at the beginning of a game to assign the bot's seat index. ```json {"type":"start_game","id":0} ``` -------------------------------- ### request_action JSON structure Source: https://riichi.dev/docs/protocol Example of the JSON payload sent by the server when it is the player's turn to act. ```json { "type": "request_action", "request_id": 42, "time": {"grace_ms": 3000, "bank_ms": 15000, "deadline_ms": 18000}, "possible_actions": [ {"type": "dahai", "pai": "1m"}, {"type": "dahai", "pai": "3m"}, {"type": "reach"}, {"type": "hora"}, {"type": "none"} ], "observation": "eyJwbGF5ZXJfaWQiOjAs..." } ``` -------------------------------- ### GET /status Source: https://riichi.dev/docs/protocol Retrieves the current server status. ```APIDOC ## GET /status ### Description Retrieves the current server status as a JSON object. ### Method GET ### Endpoint /status ``` -------------------------------- ### WebSocket Mortal Agent Implementation Source: https://riichi.dev/docs/mortal A complete example of a Mortal agent processing server events over WebSocket and responding with MJAI actions. ```python import asyncioimport jsonimport websocketsfrom riichienv import Observationfrom model import load_modelclass MortalAgent: def __init__(self, player_id: int): self.player_id = player_id # Initialize your libriichi.mjai.Bot or equivalent self.model = load_model(player_id, "./mortal_v4.pth") def act(self, obs): resp = None for event in obs.new_events(): resp = self.model.react(event) action = obs.select_action_from_mjai(resp) assert action is not None, "Mortal must return a legal action" return actionasync def run_mortal_bot(url: str, token: str): headers = {"Authorization": f"Bearer {token}"} agent = None async with websockets.connect(url, additional_headers=headers) as ws: while True: msg = json.loads(await ws.recv()) match msg["type"]: case "start_game": seat = msg["id"] agent = MortalAgent(seat) case "request_action": obs = Observation.deserialize_from_base64(msg["observation"]) action = agent.act(obs) resp = json.loads(action.to_mjai()) # Echo request_id for deterministic binding if "request_id" in msg: resp["request_id"] = msg["request_id"] await ws.send(json.dumps(resp)) case "end_game": break case _: # Mortal consumes these events through obs.new_events(), # so no direct response is needed here. passasyncio.run( run_mortal_bot( url="wss://game.riichi.dev/ws/ranked", token="YOUR_BOT_TOKEN", )) ``` -------------------------------- ### Run a Local Riichi Simulation Source: https://riichi.dev/docs/local-testing Demonstrates initializing a local environment and running a full game loop with an agent. ```python import jsonfrom riichienv import RiichiEnvagent = MyAgent()# Create a game environment# game_mode: 1 = East-only, 2 = East-South (hanchan)# seed: fixed seed for reproducibility (optional)env = RiichiEnv(game_mode=2, seed=42)# Get initial observations for all playersobservations = env.get_observations()while not env.done(): # Find the player who needs to act for pid in range(4): obs = observations[pid] actions = obs.legal_actions() if actions: # Your agent decides the action action = agent.act(obs) observations = env.step(action) break# Game finished — check resultsprint("Scores:", env.scores())print("Ranks:", env.ranks())# Full MJAI log is availablefor event in env.mjai_log: print(json.dumps(event, ensure_ascii=False)) ``` -------------------------------- ### RiichiEnv(game_mode, seed) Source: https://riichi.dev/docs/local-testing Initializes a local Riichi Mahjong game environment for simulation. ```APIDOC ## RiichiEnv(game_mode, seed) ### Description Initializes a new game environment for local simulation. Supports East-only (1) or East-South (2) game modes. ### Parameters - **game_mode** (int) - Required - 1 for East-only, 2 for East-South (hanchan). - **seed** (int) - Optional - Fixed seed for reproducibility. ### Methods - **get_observations()** - Returns initial observations for all players. - **step(action)** - Executes an action and returns updated observations. - **done()** - Returns True if the game is finished. - **scores()** - Returns the final scores of the players. - **ranks()** - Returns the final ranks of the players. - **mjai_log** - Property containing the full MJAI event log. ``` -------------------------------- ### Connect to Ranked Matches Source: https://riichi.dev/docs/ranked Establishes a WebSocket connection to the ranked matchmaking server using a bot token for authentication. ```APIDOC ## WebSocket wss://game.riichi.dev/ws/ranked ### Description Connects a validated bot to the ranked matchmaking queue. The connection requires an Authorization header containing a valid bot token. ### Method WebSocket Connection ### Endpoint wss://game.riichi.dev/ws/ranked ### Headers - **Authorization** (string) - Required - Bearer ### Response #### Error Response - **error** (string) - Description of the failure (e.g., "Token verification failed: Bot is inactive") ``` -------------------------------- ### CONNECT wss://game.riichi.dev/ws/validate Source: https://riichi.dev/docs/validation Connects the bot to the validation server to initiate the automated validation game. The bot must be in a 'pending' state to use this endpoint. ```APIDOC ## CONNECT wss://game.riichi.dev/ws/validate ### Description Initiates a validation game for a new bot. The server spawns three internal tsumogiri bots and runs an East-only game where the user's bot plays at seat 0. ### Endpoint `wss://game.riichi.dev/ws/validate` ### Parameters #### Headers - **Authorization** (string) - Required - Bearer token generated from the Bots page. ### Response #### Success Response (validation_result event) - **type** (string) - "validation_result" - **passed** (boolean) - true #### Failure Response (validation_result event) - **type** (string) - "validation_result" - **passed** (boolean) - false - **reason** (string) - The cause of failure (e.g., "disconnected" or "penalized (illegal action)") ``` -------------------------------- ### WebSocket /ws/ranked Source: https://riichi.dev/docs/protocol Establishes a WebSocket connection for ranked play. ```APIDOC ## WebSocket /ws/ranked ### Description Connects to the ranked game server. Requires an active bot and authentication. ### Endpoint /ws/ranked ### Parameters #### Headers - **Authorization** (string) - Required - Bearer BOT_TOKEN ``` -------------------------------- ### Connect to Ranked WebSocket Source: https://riichi.dev/docs/ranked Use the ranked endpoint with your bot token in the Authorization header to initiate a connection. ```text wss://game.riichi.dev/ws/ranked Authorization: Bearer ``` -------------------------------- ### Implement a basic Agent class Source: https://riichi.dev/docs/local-testing Define an Agent class with an act method that processes game observations and returns a valid action. The agent should handle win conditions, riichi declarations, and default discards. ```python from riichienv import Action, ActionType, Observationclass MyAgent: """Your bot agent. Implement act() to return an Action.""" def act(self, obs: Observation) -> Action: actions = obs.legal_actions() # Win if possible for a in actions: if a.action_type == ActionType.Tsumo or a.action_type == ActionType.Ron: return a # Declare riichi if possible for a in actions: if a.action_type == ActionType.Riichi: return a # Default: discard the drawn tile (tsumogiri) for a in actions: if a.action_type == ActionType.Discard: return a # Pass on claims return Action(ActionType.Pass) ``` -------------------------------- ### Implement MortalAgent with RiichiEnv Source: https://riichi.dev/docs/mortal This snippet demonstrates the standard MortalAgent structure using RiichiEnv, where the agent processes new MJAI events and selects a legal action. ```python from riichienv import RiichiEnv, Action, GameRulefrom model import load_modelclass MortalAgent: def __init__(self, player_id: int): self.player_id = player_id # Initialize your libriichi.mjai.Bot or equivalent self.model = load_model(player_id, "./mortal_v4.pth") def act(self, obs) -> Action: resp = None for event in obs.new_events(): resp = self.model.react(event) action = obs.select_action_from_mjai(resp) assert action is not None, "Mortal must return a legal action" return actionenv = RiichiEnv(game_mode="4p-red-half", rule=GameRule.default_tenhou())agents = {pid: MortalAgent(pid) for pid in range(4)}obs_dict = env.reset()while not env.done(): actions = {pid: agents[pid].act(obs) for pid, obs in obs_dict.items()} obs_dict = env.step(actions)print(env.scores(), env.ranks()) ``` -------------------------------- ### Connect to Validation Endpoint Source: https://riichi.dev/docs/validation Use the WebSocket URL and Authorization header to initiate the validation process. ```text wss://game.riichi.dev/ws/validate Authorization: Bearer ``` -------------------------------- ### request_action Source: https://riichi.dev/docs/protocol The request_action event is sent by the server when it is the client's turn to act. The client must respond with one of the provided possible_actions, echoing the request_id. ```APIDOC ## request_action ### Description This event notifies the client that it is their turn to perform an action. The client must process the observation and return a valid action from the possible_actions list. ### Request Body - **type** (string) - The event type, always "request_action". - **request_id** (integer) - A unique identifier for the request that must be echoed in the response. - **time** (object) - Time budget details including grace_ms, bank_ms, and deadline_ms. - **possible_actions** (array) - A list of legal MJAI-format actions. - **observation** (string) - A base64-encoded string representing the current game state. ### Request Example { "type": "request_action", "request_id": 42, "time": {"grace_ms": 3000, "bank_ms": 15000, "deadline_ms": 18000}, "possible_actions": [ {"type": "dahai", "pai": "1m"}, {"type": "dahai", "pai": "3m"}, {"type": "reach"}, {"type": "hora"}, {"type": "none"} ], "observation": "eyJwbGF5ZXJfaWQiOjAs..." } ``` -------------------------------- ### WebSocket /ws/validate Source: https://riichi.dev/docs/protocol Establishes a WebSocket connection for bot validation. ```APIDOC ## WebSocket /ws/validate ### Description Connects to the validation server for pending bots. Requires authentication. ### Endpoint /ws/validate ### Parameters #### Headers - **Authorization** (string) - Required - Bearer BOT_TOKEN ``` -------------------------------- ### Minimal Tsumogiri Bot Implementation Source: https://riichi.dev/docs/validation A basic Python bot that discards the drawn tile, sufficient to pass the validation game. ```python import asyncioimport jsonimport websocketsasync def bot(url: str): async with websockets.connect(url, additional_headers=HEADERS) as ws: my_seat = None last_tsumo = None while True: msg = json.loads(await ws.recv()) match msg["type"]: case "start_game": my_seat = msg.get("id", 0) case "tsumo": pai = msg.get("pai") if pai and pai != "?": last_tsumo = pai case "request_action": if last_tsumo: resp = { "type": "dahai", "actor": my_seat, "pai": last_tsumo, "tsumogiri": True, } else: resp = {"type": "none"} # Echo request_id for deterministic binding if "request_id" in msg: resp["request_id"] = msg["request_id"] await ws.send(json.dumps(resp)) last_tsumo = None case "end_game": breakTOKEN = "your-bot-token-here"HEADERS = {"Authorization": f"Bearer {TOKEN}"}asyncio.run(bot("wss://game.riichi.dev/ws/validate")) ``` -------------------------------- ### Observation.deserialize_from_base64(data) Source: https://riichi.dev/docs/local-testing Deserializes a base64-encoded observation string received from the game server. ```APIDOC ## Observation.deserialize_from_base64(data) ### Description Converts a base64-encoded string from a 'request_action' message into an Observation object. ### Parameters - **data** (string) - Required - The base64-encoded observation string. ### Methods - **legal_actions()** - Returns a list of valid actions. - **new_events()** - Returns a list of new MJAI event strings. - **to_dict()** - Returns the full observation as a dictionary. - **encode_extended_win_projection()** - Returns feature encoding for neural networks. - **encode_discard_history_decay()** - Returns discard history features for neural networks. ### Properties - **hand** (list[int]) - Current hand tiles in 136-format. - **player_id** (int) - The seat number (0-3). ``` -------------------------------- ### Deserialize Observation Data Source: https://riichi.dev/docs/local-testing Shows how to deserialize base64-encoded observation strings received from the server into usable objects. ```python # In a request_action message from the server:# {# "type": "request_action",# "request_id": 42,# "time": {"grace_ms": 3000, "bank_ms": 15000, "deadline_ms": 18000},# "possible_actions": [...],# "observation": "base64-encoded-string..."# }from riichienv import Observation# Deserialize the observationobs = Observation.deserialize_from_base64(msg["observation"])# Available methods:obs.legal_actions() # -> list[Action] — valid actions to choose fromobs.new_events() # -> list[str] — new MJAI events (JSON strings)obs.hand # -> list[int] — current hand tiles (136-format)obs.player_id # -> int — your seat number (0-3)obs.to_dict() # -> dict — full observation as a dictionary# For neural network bots:obs.encode_extended_win_projection() # -> feature encodingobs.encode_discard_history_decay() # -> discard history features ``` -------------------------------- ### Handle Connection Errors Source: https://riichi.dev/docs/ranked The server returns this JSON error if the provided token is invalid or the bot is not active. ```json {"error":"Token verification failed: Bot is inactive"} ``` -------------------------------- ### Bot-to-Server Response Protocol Source: https://riichi.dev/docs/protocol Describes the structure and requirements for sending MJAI-format JSON responses to the server, including mandatory request_id echoing. ```APIDOC ## Bot-to-Server Response ### Description Bots respond to server requests by sending a single MJAI-format JSON event. It is strongly recommended to echo the `request_id` from the original request to ensure proper binding. ### Event Types - **dahai**: actor, pai (tsumogiri defaults to false) - **chi**: actor, target, pai, consumed (consumed: 2 tiles from hand) - **pon**: actor, target, pai, consumed (consumed: 2 tiles from hand) - **ankan**: actor, consumed (consumed: all 4 tiles) - **kakan**: actor, pai, consumed (Extend existing pon to kan) - **reach**: actor (Discard follows as separate dahai) - **hora**: actor (Server determines tsumo vs ron) - **none**: Pass on a claim opportunity ### Request Example {"type":"dahai","actor":0,"pai":"3m","tsumogiri":true,"request_id":42} ### Request_id Semantics - **Echo current request_id**: Bound to that request and processed. - **Echo older request_id**: Discarded as stale. - **Echo unknown/future request_id**: Protocol violation (chombo). - **No request_id**: Bound by arrival order. ``` -------------------------------- ### Reach declaration event Source: https://riichi.dev/docs/protocol Indicates a riichi declaration. ```json {"type":"reach","actor":0} ``` -------------------------------- ### Meld declaration events Source: https://riichi.dev/docs/protocol Events for chi, pon, and kan declarations. ```json {"type":"chi","actor":1,"target":0,"pai":"5m","consumed":["4m","6m"]} {"type":"pon","actor":2,"target":0,"pai":"E","consumed":["E","E"]} {"type":"ankan","actor":0,"consumed":["1s","1s","1s","1s"]} ``` -------------------------------- ### Tsumo event Source: https://riichi.dev/docs/protocol Indicates a player has drawn a tile. ```json {"type":"tsumo","actor":0,"pai":"3m"} ``` -------------------------------- ### Hora declaration event Source: https://riichi.dev/docs/protocol Indicates a win declaration. ```json {"type":"hora","actor":0,"target":1,"pai":"5m"} ``` -------------------------------- ### Action acknowledgement event Source: https://riichi.dev/docs/protocol Confirms the server's processing of a bot's action, including status and time bank usage. ```json {"type":"action_ack","request_id":42,"status":"accepted", "elapsed_ms":850,"bank_consumed_ms":0,"bank_ms":15000} ``` ```json {"type":"action_ack","request_id":42,"status":"defaulted", "action":{"type":"dahai","pai":"7s","tsumogiri":true}, "elapsed_ms":18001,"bank_consumed_ms":15000,"bank_ms":0} ``` -------------------------------- ### Dahai event Source: https://riichi.dev/docs/protocol Indicates a player has discarded a tile. ```json {"type":"dahai","actor":0,"pai":"3m","tsumogiri":true} ``` -------------------------------- ### Decode observation for 3-player games Source: https://riichi.dev/docs/protocol Use the Observation3P class to deserialize the base64-encoded observation field for 3-player game states. ```python from riichienv import Observation3P# msg is the parsed request_action JSON objectobs = Observation3P.deserialize_from_base64(msg["observation"]) actions = obs.legal_actions() state = obs.to_dict() ``` -------------------------------- ### Decode observation for 4-player games Source: https://riichi.dev/docs/protocol Use the Observation class to deserialize the base64-encoded observation field for 4-player game states. ```python from riichienv import Observation# msg is the parsed request_action JSON objectobs = Observation.deserialize_from_base64(msg["observation"]) actions = obs.legal_actions() state = obs.to_dict() ``` -------------------------------- ### End game event Source: https://riichi.dev/docs/protocol Sent when the game is over, containing final scores. ```json {"type":"end_game","scores":[30000,25000,20000,25000]} ``` -------------------------------- ### Chombo Error Response Source: https://riichi.dev/docs/protocol The JSON structure returned by the engine when an illegal action is performed or the message cannot be parsed. ```json {"type":"ryukyoku","reason":"Error: Illegal Action by Player N","deltas":[ ... ]} ``` -------------------------------- ### Validation Result Event Payloads Source: https://riichi.dev/docs/validation The server sends these JSON objects to indicate the outcome of the validation game. ```json {"type":"validation_result","passed":true} ``` ```json {"type":"validation_result","passed":false,"reason":"disconnected"} ``` ```json {"type":"validation_result","passed":false,"reason":"penalized (illegal action)"} ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.