from __future__ import annotations from pathlib import Path import gradio as gr import numpy as np from game import is_terminal, legal_actions, play, winner from mcts import NeuralMCTS from model import MicroZeroNet from safetensors.torch import load_file ARTIFACT = Path(__file__).resolve().parent / "artifacts" / "microzero" MODEL = MicroZeroNet() MODEL.load_state_dict(load_file(ARTIFACT / "model.safetensors")) MODEL.eval() SYMBOLS = {0: ".", 1: "X", -1: "O"} def neural_action( model: MicroZeroNet, board: np.ndarray, player: int, simulations: int, seed: int, ) -> int: search = NeuralMCTS(model, simulations=simulations) policy = search.policy( board, player, temperature=0.0, add_noise=False, rng=np.random.default_rng(seed), ) return int(np.argmax(policy)) def render(board: list[int], message: str) -> str: cells = [SYMBOLS[int(value)] for value in board] rows = [" | ".join(cells[index : index + 3]) for index in range(0, 9, 3)] return "```\n" + "\n---------\n".join(rows) + f"\n```\n{message}" def new_game() -> tuple[list[int], str]: board = [0] * 9 return board, render(board, "You are X. Choose a cell from 0 to 8.") def move(board: list[int], action: int) -> tuple[list[int], str]: array = np.asarray(board, dtype=np.int8) action = int(action) if action not in legal_actions(array): return board, render(board, "That cell is occupied.") array = play(array, action, 1) if is_terminal(array): message = "You win." if winner(array) == 1 else "Draw." return array.tolist(), render(array.tolist(), message) response = neural_action(MODEL, array, -1, simulations=96, seed=7000 + action) array = play(array, response, -1) result = winner(array) if result == -1: message = f"MicroZero played {response} and won." elif is_terminal(array): message = f"MicroZero played {response}. Draw." else: message = f"MicroZero played {response}. Your turn." return array.tolist(), render(array.tolist(), message) with gr.Blocks(title="MicroZero") as demo: gr.Markdown("# MicroZero\nPlay against the neural MCTS agent.") state = gr.State([0] * 9) board = gr.Markdown(render([0] * 9, "You are X. Choose a cell from 0 to 8.")) action = gr.Slider(0, 8, value=4, step=1, label="Your move") with gr.Row(): play_button = gr.Button("Play", variant="primary") reset_button = gr.Button("New game") play_button.click(move, inputs=[state, action], outputs=[state, board]) reset_button.click(new_game, outputs=[state, board]) if __name__ == "__main__": demo.launch()