diff --git a/ai_agent_tutorials/ai_tic_tac_toe_game/.gitignore b/ai_agent_tutorials/ai_tic_tac_toe_game/.gitignore
new file mode 100644
index 0000000..ed8ebf5
--- /dev/null
+++ b/ai_agent_tutorials/ai_tic_tac_toe_game/.gitignore
@@ -0,0 +1 @@
+__pycache__
\ No newline at end of file
diff --git a/ai_agent_tutorials/ai_tic_tac_toe_game/README.md b/ai_agent_tutorials/ai_tic_tac_toe_game/README.md
new file mode 100644
index 0000000..7e8681f
--- /dev/null
+++ b/ai_agent_tutorials/ai_tic_tac_toe_game/README.md
@@ -0,0 +1,103 @@
+# Agent Tic Tac Toe
+
+This example shows how to build an interactive Tic Tac Toe game where AI agents compete against each other. The application showcases how to:
+- Coordinate multiple AI agents in a turn-based game
+- Use different language models for different players
+- Create an interactive web interface with Streamlit
+- Handle game state and move validation
+- Display real-time game progress and move history
+
+## Features
+- Multiple AI models support (GPT-4, Claude, Gemini, etc.)
+- Real-time game visualization
+- Move history tracking with board states
+- Interactive player selection
+- Game state management
+- Move validation and coordination
+
+### 1. Create a virtual environment
+
+```shell
+python3 -m venv .venv
+source .venv/bin/activate
+```
+
+### 2. Install dependencies
+
+```shell
+pip install -r ai_agent_tutorials/ai_tic_tac_toe_game/requirements.txt
+```
+
+### 3. Export API Keys
+
+The game supports multiple AI models. Export the API keys for the models you want to use:
+
+```shell
+# Required for OpenAI models
+export OPENAI_API_KEY=***
+
+# Optional - for additional models
+export ANTHROPIC_API_KEY=*** # For Claude models
+export GOOGLE_API_KEY=*** # For Gemini models
+export GROQ_API_KEY=*** # For Groq models
+```
+
+### 4. Run the Game
+
+```shell
+streamlit run ai_agent_tutorials/ai_tic_tac_toe_game/app.py
+```
+
+- Open [localhost:8501](http://localhost:8501) to view the game interface
+
+## How It Works
+
+The game consists of three agents:
+
+1. **Master Agent (Referee)**
+ - Coordinates the game
+ - Validates moves
+ - Maintains game state
+ - Determines game outcome
+
+2. **Two Player Agents**
+ - Make strategic moves
+ - Analyze board state
+ - Follow game rules
+ - Respond to opponent moves
+
+## Available Models
+
+The game supports various AI models:
+- GPT-4o (OpenAI)
+- GPT-o3-mini (OpenAI)
+- Gemini (Google)
+- Llama 3 (Groq)
+- Claude (Anthropic)
+
+## Game Features
+
+1. **Interactive Board**
+ - Real-time updates
+ - Visual move tracking
+ - Clear game status display
+
+2. **Move History**
+ - Detailed move tracking
+ - Board state visualization
+ - Player action timeline
+
+3. **Game Controls**
+ - Start/Pause game
+ - Reset board
+ - Select AI models
+ - View game history
+
+4. **Performance Analysis**
+ - Move timing
+ - Strategy tracking
+ - Game statistics
+
+## Support
+
+Join our [Discord community](https://agno.link/discord) for help and discussions.
diff --git a/ai_agent_tutorials/ai_tic_tac_toe_game/agents.py b/ai_agent_tutorials/ai_tic_tac_toe_game/agents.py
new file mode 100644
index 0000000..6be0dc8
--- /dev/null
+++ b/ai_agent_tutorials/ai_tic_tac_toe_game/agents.py
@@ -0,0 +1,165 @@
+"""
+Tic Tac Toe Battle
+---------------------------------
+This example shows how to build a Tic Tac Toe game where two AI agents play against each other.
+The game features a referee agent coordinating between two player agents using different
+language models.
+
+Usage Examples:
+---------------
+1. Quick game with default settings:
+ referee_agent = get_tic_tac_toe_referee()
+ play_tic_tac_toe()
+
+2. Game with debug mode off:
+ referee_agent = get_tic_tac_toe_referee(debug_mode=False)
+ play_tic_tac_toe(debug_mode=False)
+
+The game integrates:
+ - Multiple AI models (Claude, GPT-4, etc.)
+ - Turn-based gameplay coordination
+ - Move validation and game state management
+"""
+
+import sys
+from pathlib import Path
+from textwrap import dedent
+from typing import Tuple
+
+from agno.agent import Agent
+from agno.models.anthropic import Claude
+from agno.models.google import Gemini
+from agno.models.groq import Groq
+from agno.models.openai import OpenAIChat
+
+project_root = str(Path(__file__).parent.parent.parent.parent)
+if project_root not in sys.path:
+ sys.path.append(project_root)
+
+
+def get_model_for_provider(provider: str, model_name: str):
+ """
+ Creates and returns the appropriate model instance based on the provider.
+
+ Args:
+ provider: The model provider (e.g., 'openai', 'google', 'anthropic', 'groq')
+ model_name: The specific model name/ID
+
+ Returns:
+ An instance of the appropriate model class
+
+ Raises:
+ ValueError: If the provider is not supported
+ """
+ if provider == "openai":
+ return OpenAIChat(id=model_name)
+ elif provider == "google":
+ return Gemini(id=model_name)
+ elif provider == "anthropic":
+ if model_name == "claude-3-5-sonnet":
+ return Claude(id="claude-3-5-sonnet-20241022", max_tokens=8192)
+ elif model_name == "claude-3-7-sonnet":
+ return Claude(
+ id="claude-3-7-sonnet-20250219",
+ max_tokens=8192,
+ )
+ elif model_name == "claude-3-7-sonnet-thinking":
+ return Claude(
+ id="claude-3-7-sonnet-20250219",
+ max_tokens=8192,
+ thinking={"type": "enabled", "budget_tokens": 4096},
+ )
+ else:
+ return Claude(id=model_name)
+ elif provider == "groq":
+ return Groq(id=model_name)
+ else:
+ raise ValueError(f"Unsupported model provider: {provider}")
+
+
+def get_tic_tac_toe_players(
+ model_x: str = "openai:gpt-4o",
+ model_o: str = "openai:o3-mini",
+ debug_mode: bool = True,
+) -> Tuple[Agent, Agent]:
+ """
+ Returns an instance of the Tic Tac Toe Referee Agent that coordinates the game.
+
+ Args:
+ model_x: ModelConfig for player X
+ model_o: ModelConfig for player O
+ model_referee: ModelConfig for the referee agent
+ debug_mode: Enable logging and debug features
+
+ Returns:
+ An instance of the configured Referee Agent
+ """
+ # Parse model provider and name
+ provider_x, model_name_x = model_x.split(":")
+ provider_o, model_name_o = model_o.split(":")
+
+ # Create model instances using the helper function
+ model_x = get_model_for_provider(provider_x, model_name_x)
+ model_o = get_model_for_provider(provider_o, model_name_o)
+
+ player_x = Agent(
+ name="Player X",
+ description=dedent("""\
+ You are Player X in a Tic Tac Toe game. Your goal is to win by placing three X's in a row (horizontally, vertically, or diagonally).
+
+ BOARD LAYOUT:
+ - The board is a 3x3 grid with coordinates from (0,0) to (2,2)
+ - Top-left is (0,0), bottom-right is (2,2)
+
+ RULES:
+ - You can only place X in empty spaces (shown as " " on the board)
+ - Players take turns placing their marks
+ - First to get 3 marks in a row (horizontal, vertical, or diagonal) wins
+ - If all spaces are filled with no winner, the game is a draw
+
+ YOUR RESPONSE:
+ - Provide ONLY two numbers separated by a space (row column)
+ - Example: "1 2" places your X in row 1, column 2
+ - Choose only from the valid moves list provided to you
+
+ STRATEGY TIPS:
+ - Study the board carefully and make strategic moves
+ - Block your opponent's potential winning moves
+ - Create opportunities for multiple winning paths
+ - Pay attention to the valid moves and avoid illegal moves
+ """),
+ model=model_x,
+ debug_mode=debug_mode,
+ )
+
+ player_o = Agent(
+ name="Player O",
+ description=dedent("""\
+ You are Player O in a Tic Tac Toe game. Your goal is to win by placing three O's in a row (horizontally, vertically, or diagonally).
+
+ BOARD LAYOUT:
+ - The board is a 3x3 grid with coordinates from (0,0) to (2,2)
+ - Top-left is (0,0), bottom-right is (2,2)
+
+ RULES:
+ - You can only place X in empty spaces (shown as " " on the board)
+ - Players take turns placing their marks
+ - First to get 3 marks in a row (horizontal, vertical, or diagonal) wins
+ - If all spaces are filled with no winner, the game is a draw
+
+ YOUR RESPONSE:
+ - Provide ONLY two numbers separated by a space (row column)
+ - Example: "1 2" places your X in row 1, column 2
+ - Choose only from the valid moves list provided to you
+
+ STRATEGY TIPS:
+ - Study the board carefully and make strategic moves
+ - Block your opponent's potential winning moves
+ - Create opportunities for multiple winning paths
+ - Pay attention to the valid moves and avoid illegal moves
+ """),
+ model=model_o,
+ debug_mode=debug_mode,
+ )
+
+ return player_x, player_o
diff --git a/ai_agent_tutorials/ai_tic_tac_toe_game/app.py b/ai_agent_tutorials/ai_tic_tac_toe_game/app.py
new file mode 100644
index 0000000..17faeff
--- /dev/null
+++ b/ai_agent_tutorials/ai_tic_tac_toe_game/app.py
@@ -0,0 +1,261 @@
+import nest_asyncio
+import streamlit as st
+from agents import get_tic_tac_toe_players
+from agno.utils.log import logger
+from utils import (
+ CUSTOM_CSS,
+ TicTacToeBoard,
+ display_board,
+ display_move_history,
+ show_agent_status,
+)
+
+nest_asyncio.apply()
+
+# Page configuration
+st.set_page_config(
+ page_title="Agent Tic Tac Toe",
+ page_icon="🎮",
+ layout="wide",
+ initial_sidebar_state="expanded",
+)
+
+# Load custom CSS with dark mode support
+st.markdown(CUSTOM_CSS, unsafe_allow_html=True)
+
+
+def main():
+ ####################################################################
+ # App header
+ ####################################################################
+ st.markdown(
+ "
Watch Agents play Tic Tac Toe
",
+ unsafe_allow_html=True,
+ )
+
+ ####################################################################
+ # Initialize session state
+ ####################################################################
+ if "game_started" not in st.session_state:
+ st.session_state.game_started = False
+ st.session_state.game_paused = False
+ st.session_state.move_history = []
+
+ with st.sidebar:
+ st.markdown("### Game Controls")
+ model_options = {
+ "gpt-4o": "openai:gpt-4o",
+ "o3-mini": "openai:o3-mini",
+ "claude-3.5": "anthropic:claude-3-5-sonnet",
+ "claude-3.7": "anthropic:claude-3-7-sonnet",
+ "claude-3.7-thinking": "anthropic:claude-3-7-sonnet-thinking",
+ "gemini-flash": "google:gemini-2.0-flash",
+ "gemini-pro": "google:gemini-2.0-pro-exp-02-05",
+ "llama-3.3": "groq:llama-3.3-70b-versatile",
+ }
+ ################################################################
+ # Model selection
+ ################################################################
+ selected_p_x = st.selectbox(
+ "Select Player X",
+ list(model_options.keys()),
+ index=list(model_options.keys()).index("claude-3.7-thinking"),
+ key="model_p1",
+ )
+ selected_p_o = st.selectbox(
+ "Select Player O",
+ list(model_options.keys()),
+ index=list(model_options.keys()).index("o3-mini"),
+ key="model_p2",
+ )
+
+ ################################################################
+ # Game controls
+ ################################################################
+ col1, col2 = st.columns(2)
+ with col1:
+ if not st.session_state.game_started:
+ if st.button("▶️ Start Game"):
+ st.session_state.player_x, st.session_state.player_o = (
+ get_tic_tac_toe_players(
+ model_x=model_options[selected_p_x],
+ model_o=model_options[selected_p_o],
+ debug_mode=True,
+ )
+ )
+ st.session_state.game_board = TicTacToeBoard()
+ st.session_state.game_started = True
+ st.session_state.game_paused = False
+ st.session_state.move_history = []
+ st.rerun()
+ else:
+ game_over, _ = st.session_state.game_board.get_game_state()
+ if not game_over:
+ if st.button(
+ "⏸️ Pause" if not st.session_state.game_paused else "▶️ Resume"
+ ):
+ st.session_state.game_paused = not st.session_state.game_paused
+ st.rerun()
+ with col2:
+ if st.session_state.game_started:
+ if st.button("🔄 New Game"):
+ st.session_state.player_x, st.session_state.player_o = (
+ get_tic_tac_toe_players(
+ model_x=model_options[selected_p_x],
+ model_o=model_options[selected_p_o],
+ debug_mode=True,
+ )
+ )
+ st.session_state.game_board = TicTacToeBoard()
+ st.session_state.game_paused = False
+ st.session_state.move_history = []
+ st.rerun()
+
+ ####################################################################
+ # Header showing current models
+ ####################################################################
+ if st.session_state.game_started:
+ st.markdown(
+ f"{selected_p_x} vs {selected_p_o}
",
+ unsafe_allow_html=True,
+ )
+
+ ####################################################################
+ # Main game area
+ ####################################################################
+ if st.session_state.game_started:
+ game_over, status = st.session_state.game_board.get_game_state()
+
+ display_board(st.session_state.game_board)
+
+ # Show game status (winner/draw/current player)
+ if game_over:
+ winner_player = (
+ "X" if "X wins" in status else "O" if "O wins" in status else None
+ )
+ if winner_player:
+ winner_num = "1" if winner_player == "X" else "2"
+ winner_model = selected_p_x if winner_player == "X" else selected_p_o
+ st.success(f"🏆 Game Over! Player {winner_num} ({winner_model}) wins!")
+ else:
+ st.info("🤝 Game Over! It's a draw!")
+ else:
+ # Show current player status
+ current_player = st.session_state.game_board.current_player
+ player_num = "1" if current_player == "X" else "2"
+ current_model_name = selected_p_x if current_player == "X" else selected_p_o
+
+ show_agent_status(
+ f"Player {player_num} ({current_model_name})",
+ "It's your turn",
+ )
+
+ display_move_history()
+
+ if not st.session_state.game_paused and not game_over:
+ # Thinking indicator
+ st.markdown(
+ f"""
+
+
🔄
+ Player {player_num} ({current_model_name}) is thinking...
+
+
""",
+ unsafe_allow_html=True,
+ )
+
+ valid_moves = st.session_state.game_board.get_valid_moves()
+
+ current_agent = (
+ st.session_state.player_x
+ if current_player == "X"
+ else st.session_state.player_o
+ )
+ response = current_agent.run(
+ f"""\
+Current board state:\n{st.session_state.game_board.get_board_state()}\n
+Available valid moves (row, col): {valid_moves}\n
+Choose your next move from the valid moves above.
+Respond with ONLY two numbers for row and column, e.g. "1 2".""",
+ stream=False,
+ )
+
+ try:
+ import re
+
+ numbers = re.findall(r"\d+", response.content if response else "")
+ row, col = map(int, numbers[:2])
+ success, message = st.session_state.game_board.make_move(row, col)
+
+ if success:
+ move_number = len(st.session_state.move_history) + 1
+ st.session_state.move_history.append(
+ {
+ "number": move_number,
+ "player": f"Player {player_num} ({current_model_name})",
+ "move": f"{row},{col}",
+ }
+ )
+
+ logger.info(
+ f"Move {move_number}: Player {player_num} ({current_model_name}) placed at position ({row}, {col})"
+ )
+ logger.info(
+ f"Board state:\n{st.session_state.game_board.get_board_state()}"
+ )
+
+ # Check game state after move
+ game_over, status = st.session_state.game_board.get_game_state()
+ if game_over:
+ logger.info(f"Game Over - {status}")
+ if "wins" in status:
+ st.success(f"🏆 Game Over! {status}")
+ else:
+ st.info(f"🤝 Game Over! {status}")
+ st.session_state.game_paused = True
+ st.rerun()
+ else:
+ logger.error(f"Invalid move attempt: {message}")
+ response = current_agent.run(
+ f"""\
+Invalid move: {message}
+
+Current board state:\n{st.session_state.game_board.get_board_state()}\n
+Available valid moves (row, col): {valid_moves}\n
+Please choose a valid move from the list above.
+Respond with ONLY two numbers for row and column, e.g. "1 2".""",
+ stream=False,
+ )
+ st.rerun()
+
+ except Exception as e:
+ logger.error(f"Error processing move: {str(e)}")
+ st.error(f"Error processing move: {str(e)}")
+ st.rerun()
+ else:
+ st.info("👈 Press 'Start Game' to begin!")
+
+ ####################################################################
+ # About section
+ ####################################################################
+ st.sidebar.markdown(f"""
+ ### 🎮 Agent Tic Tac Toe Battle
+ Watch two agents compete in real-time!
+
+ **Current Players:**
+ * 🔵 Player X: `{selected_p_x}`
+ * 🔴 Player O: `{selected_p_o}`
+
+ **How it Works:**
+ Each Agent analyzes the board and employs strategic thinking to:
+ * 🏆 Find winning moves
+ * 🛡️ Block opponent victories
+ * ⭐ Control strategic positions
+ * 🤔 Plan multiple moves ahead
+
+ Built with Streamlit and Agno
+ """)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ai_agent_tutorials/ai_tic_tac_toe_game/requirements.txt b/ai_agent_tutorials/ai_tic_tac_toe_game/requirements.txt
new file mode 100644
index 0000000..c275c18
--- /dev/null
+++ b/ai_agent_tutorials/ai_tic_tac_toe_game/requirements.txt
@@ -0,0 +1,238 @@
+# This file was autogenerated by uv via the following command:
+# ./generate_requirements.sh
+agno==1.1.6
+ # via -r cookbook/examples/apps/tic_tac_toe/requirements.in
+altair==5.5.0
+ # via streamlit
+annotated-types==0.7.0
+ # via pydantic
+anthropic==0.47.1
+ # via -r cookbook/examples/apps/tic_tac_toe/requirements.in
+anyio==4.8.0
+ # via
+ # anthropic
+ # groq
+ # httpx
+ # openai
+attrs==25.1.0
+ # via
+ # jsonschema
+ # referencing
+blinker==1.9.0
+ # via streamlit
+build==1.2.2.post1
+ # via pip-tools
+cachetools==5.5.2
+ # via
+ # google-auth
+ # streamlit
+certifi==2025.1.31
+ # via
+ # httpcore
+ # httpx
+ # requests
+charset-normalizer==3.4.1
+ # via requests
+click==8.1.8
+ # via
+ # pip-tools
+ # streamlit
+ # typer
+distro==1.9.0
+ # via
+ # anthropic
+ # groq
+ # openai
+docstring-parser==0.16
+ # via agno
+gitdb==4.0.12
+ # via gitpython
+gitpython==3.1.44
+ # via
+ # agno
+ # streamlit
+google-auth==2.38.0
+ # via google-genai
+google-genai==1.3.0
+ # via -r cookbook/examples/apps/tic_tac_toe/requirements.in
+groq==0.18.0
+ # via -r cookbook/examples/apps/tic_tac_toe/requirements.in
+h11==0.14.0
+ # via httpcore
+httpcore==1.0.7
+ # via httpx
+httpx==0.28.1
+ # via
+ # agno
+ # anthropic
+ # google-genai
+ # groq
+ # ollama
+ # openai
+idna==3.10
+ # via
+ # anyio
+ # httpx
+ # requests
+jinja2==3.1.5
+ # via
+ # altair
+ # pydeck
+jiter==0.8.2
+ # via
+ # anthropic
+ # openai
+jsonschema==4.23.0
+ # via altair
+jsonschema-specifications==2024.10.1
+ # via jsonschema
+markdown-it-py==3.0.0
+ # via rich
+markupsafe==3.0.2
+ # via jinja2
+mdurl==0.1.2
+ # via markdown-it-py
+narwhals==1.28.0
+ # via altair
+nest-asyncio==1.6.0
+ # via -r cookbook/examples/apps/tic_tac_toe/requirements.in
+numpy==2.2.3
+ # via
+ # pandas
+ # pydeck
+ # streamlit
+ollama==0.4.7
+ # via -r cookbook/examples/apps/tic_tac_toe/requirements.in
+openai==1.64.0
+ # via -r cookbook/examples/apps/tic_tac_toe/requirements.in
+packaging==24.2
+ # via
+ # altair
+ # build
+ # streamlit
+pandas==2.2.3
+ # via streamlit
+pathlib==1.0.1
+ # via -r cookbook/examples/apps/tic_tac_toe/requirements.in
+pillow==11.1.0
+ # via
+ # -r cookbook/examples/apps/tic_tac_toe/requirements.in
+ # streamlit
+pip==25.0.1
+ # via pip-tools
+pip-tools==7.4.1
+ # via -r cookbook/examples/apps/tic_tac_toe/requirements.in
+protobuf==5.29.3
+ # via streamlit
+pyarrow==19.0.1
+ # via streamlit
+pyasn1==0.6.1
+ # via
+ # pyasn1-modules
+ # rsa
+pyasn1-modules==0.4.1
+ # via google-auth
+pydantic==2.10.6
+ # via
+ # agno
+ # anthropic
+ # google-genai
+ # groq
+ # ollama
+ # openai
+ # pydantic-settings
+pydantic-core==2.27.2
+ # via pydantic
+pydantic-settings==2.8.0
+ # via agno
+pydeck==0.9.1
+ # via streamlit
+pygments==2.19.1
+ # via rich
+pyproject-hooks==1.2.0
+ # via
+ # build
+ # pip-tools
+python-dateutil==2.9.0.post0
+ # via pandas
+python-dotenv==1.0.1
+ # via
+ # -r cookbook/examples/apps/tic_tac_toe/requirements.in
+ # agno
+ # pydantic-settings
+python-multipart==0.0.20
+ # via agno
+pytz==2025.1
+ # via pandas
+pyyaml==6.0.2
+ # via agno
+referencing==0.36.2
+ # via
+ # jsonschema
+ # jsonschema-specifications
+requests==2.32.3
+ # via
+ # google-genai
+ # streamlit
+rich==13.9.4
+ # via
+ # -r cookbook/examples/apps/tic_tac_toe/requirements.in
+ # agno
+ # streamlit
+ # typer
+rpds-py==0.23.1
+ # via
+ # jsonschema
+ # referencing
+rsa==4.9
+ # via google-auth
+setuptools==75.8.0
+ # via pip-tools
+shellingham==1.5.4
+ # via typer
+six==1.17.0
+ # via python-dateutil
+smmap==5.0.2
+ # via gitdb
+sniffio==1.3.1
+ # via
+ # anthropic
+ # anyio
+ # groq
+ # openai
+streamlit==1.42.2
+ # via -r cookbook/examples/apps/tic_tac_toe/requirements.in
+tenacity==9.0.0
+ # via streamlit
+toml==0.10.2
+ # via streamlit
+tomli==2.2.1
+ # via agno
+tornado==6.4.2
+ # via streamlit
+tqdm==4.67.1
+ # via openai
+typer==0.15.1
+ # via agno
+typing-extensions==4.12.2
+ # via
+ # agno
+ # altair
+ # anthropic
+ # anyio
+ # google-genai
+ # groq
+ # openai
+ # pydantic
+ # pydantic-core
+ # referencing
+ # streamlit
+ # typer
+tzdata==2025.1
+ # via pandas
+urllib3==2.3.0
+ # via requests
+websockets==14.2
+ # via google-genai
+wheel==0.45.1
+ # via pip-tools
diff --git a/ai_agent_tutorials/ai_tic_tac_toe_game/utils.py b/ai_agent_tutorials/ai_tic_tac_toe_game/utils.py
new file mode 100644
index 0000000..ffc97dc
--- /dev/null
+++ b/ai_agent_tutorials/ai_tic_tac_toe_game/utils.py
@@ -0,0 +1,416 @@
+from typing import List, Optional, Tuple
+
+import streamlit as st
+
+# Define constants for players
+X_PLAYER = "X"
+O_PLAYER = "O"
+EMPTY = " "
+
+
+class TicTacToeBoard:
+ def __init__(self):
+ # Initialize empty 3x3 board
+ self.board = [[EMPTY for _ in range(3)] for _ in range(3)]
+ self.current_player = X_PLAYER
+
+ def make_move(self, row: int, col: int) -> Tuple[bool, str]:
+ """
+ Make a move on the board.
+
+ Args:
+ row (int): Row index (0-2)
+ col (int): Column index (0-2)
+
+ Returns:
+ Tuple[bool, str]: (Success status, Message with current board state or error)
+ """
+ # Validate move coordinates
+ if not (0 <= row <= 2 and 0 <= col <= 2):
+ return (
+ False,
+ "Invalid move: Position out of bounds. Please choose row and column between 0 and 2.",
+ )
+
+ # Check if position is already occupied
+ if self.board[row][col] != EMPTY:
+ return False, f"Invalid move: Position ({row}, {col}) is already occupied."
+
+ # Make the move
+ self.board[row][col] = self.current_player
+
+ # Get board state
+ board_state = self.get_board_state()
+
+ # Switch player
+ self.current_player = O_PLAYER if self.current_player == X_PLAYER else X_PLAYER
+
+ return True, f"Move successful!\n{board_state}"
+
+ def get_board_state(self) -> str:
+ """
+ Returns a string representation of the current board state.
+ """
+ board_str = "\n-------------\n"
+ for row in self.board:
+ board_str += f"| {' | '.join(row)} |\n-------------\n"
+ return board_str
+
+ def check_winner(self) -> Optional[str]:
+ """
+ Check if there's a winner.
+
+ Returns:
+ Optional[str]: The winning player (X or O) or None if no winner
+ """
+ # Check rows
+ for row in self.board:
+ if row.count(row[0]) == 3 and row[0] != EMPTY:
+ return row[0]
+
+ # Check columns
+ for col in range(3):
+ column = [self.board[row][col] for row in range(3)]
+ if column.count(column[0]) == 3 and column[0] != EMPTY:
+ return column[0]
+
+ # Check diagonals
+ diagonal1 = [self.board[i][i] for i in range(3)]
+ if diagonal1.count(diagonal1[0]) == 3 and diagonal1[0] != EMPTY:
+ return diagonal1[0]
+
+ diagonal2 = [self.board[i][2 - i] for i in range(3)]
+ if diagonal2.count(diagonal2[0]) == 3 and diagonal2[0] != EMPTY:
+ return diagonal2[0]
+
+ return None
+
+ def is_board_full(self) -> bool:
+ """
+ Check if the board is full (draw condition).
+ """
+ return all(cell != EMPTY for row in self.board for cell in row)
+
+ def get_valid_moves(self) -> List[Tuple[int, int]]:
+ """
+ Get a list of valid moves (empty positions).
+
+ Returns:
+ List[Tuple[int, int]]: List of (row, col) tuples representing valid moves
+ """
+ valid_moves = []
+ for row in range(3):
+ for col in range(3):
+ if self.board[row][col] == EMPTY:
+ valid_moves.append((row, col))
+ return valid_moves
+
+ def get_game_state(self) -> Tuple[bool, str]:
+ """
+ Get the current game state.
+
+ Returns:
+ Tuple[bool, str]: (is_game_over, status_message)
+ """
+ winner = self.check_winner()
+ if winner:
+ return True, f"Player {winner} wins!"
+
+ if self.is_board_full():
+ return True, "It's a draw!"
+
+ return False, "Game in progress"
+
+
+def display_board(board: TicTacToeBoard):
+ """Display the Tic Tac Toe board using Streamlit"""
+ board_html = ''
+
+ for i in range(3):
+ for j in range(3):
+ cell_value = board.board[i][j]
+ board_html += f'
{cell_value}
'
+
+ board_html += "
"
+ st.markdown(board_html, unsafe_allow_html=True)
+
+
+def show_agent_status(agent_name: str, status: str):
+ """Display the current agent status"""
+ st.markdown(
+ f"""
+ 🤖 {agent_name}: {status}
+
""",
+ unsafe_allow_html=True,
+ )
+
+
+def create_mini_board_html(
+ board_state: list, highlight_pos: tuple = None, is_player1: bool = True
+) -> str:
+ """Create HTML for a mini board with player-specific highlighting"""
+ html = ''
+ for i in range(3):
+ for j in range(3):
+ highlight = (
+ f"highlight player{1 if is_player1 else 2}"
+ if highlight_pos and (i, j) == highlight_pos
+ else ""
+ )
+ html += f'
{board_state[i][j]}
'
+ html += "
"
+ return html
+
+
+def display_move_history():
+ """Display the move history with mini boards in two columns"""
+ st.markdown(
+ '📜 Game History
',
+ unsafe_allow_html=True,
+ )
+ history_container = st.empty()
+
+ if "move_history" in st.session_state and st.session_state.move_history:
+ # Split moves into player 1 and player 2 moves
+ p1_moves = []
+ p2_moves = []
+ current_board = [[" " for _ in range(3)] for _ in range(3)]
+
+ # Process all moves first
+ for move in st.session_state.move_history:
+ row, col = map(int, move["move"].split(","))
+ is_player1 = "Player 1" in move["player"]
+ symbol = "X" if is_player1 else "O"
+ current_board[row][col] = symbol
+ board_copy = [row[:] for row in current_board]
+
+ move_html = f"""
+ {create_mini_board_html(board_copy, (row, col), is_player1)}
+
+
Move #{move["number"]}
+
{move["player"]}
+
Position: ({row}, {col})
+
+
"""
+
+ if is_player1:
+ p1_moves.append(move_html)
+ else:
+ p2_moves.append(move_html)
+
+ max_moves = max(len(p1_moves), len(p2_moves))
+ history_content = ''
+
+ # Left column (Player 1)
+ history_content += '
'
+ for i in range(max_moves):
+ entry_html = ""
+ # Player 1 move
+ if i < len(p1_moves):
+ entry_html += p1_moves[i]
+ history_content += entry_html
+ history_content += "
"
+
+ # Right column (Player 2)
+ history_content += '
'
+ for i in range(max_moves):
+ entry_html = ""
+ # Player 2 move
+ if i < len(p2_moves):
+ entry_html += p2_moves[i]
+ history_content += entry_html
+ history_content += "
"
+
+ history_content += "
"
+
+ # Display the content
+ history_container.markdown(history_content, unsafe_allow_html=True)
+ else:
+ history_container.markdown(
+ """
+ No moves yet. Start the game to see the history!
+
""",
+ unsafe_allow_html=True,
+ )
+
+
+CUSTOM_CSS = """
+
+"""