tictactoe was taken from https://gist.github.com/qianguigui1104/edb3b11b33c78e5894aad7908c773353
# To install 
```bash
pip install game-ai-client==0.1.0
```
# Usage of the library
import via a single client
```python
from game_sdk.mcts_client import AIGameClient
```
# Things that must be provided:
You keep full control over your game logic.
To use the framework, you only need to implement:
A players list
A legal moves function
An apply-move function for the AI

- Players list:
```python
players = [
    {"id": "P1", "type": "human",  "symbol": "X"},
    {"id": "P2", "type": "ai_mcts","symbol": "O"},
]
```
- Legal moves function
Return all legal moves for the current player, as a list of dicts:
```python
def compute_legal_moves(board, players, current_player_symbol):
    player_index = next(i for i, p in enumerate(players)
                        if p["symbol"] == current_player_symbol)
    moves = []
    for r in range(len(board)):
        for c in range(len(board[0])):
            if board[r][c].strip() == "":
                moves.append({
                    "id": f"PLACE_{r}_{c}",         # string id is recommended
                    "player_index": player_index,   # index in players[]
                    "type": "PLACE_MARK",
                    "position": {"row": r, "col": c},
                })
    return moves
```
- Apply-move function for AI (the only required “hook”)
this is the only function that is needed from your game
```python
from typing import Dict, Any, List
from game_sdk.state_utils import build_generic_state

State = Dict[str, Any]
Move = Dict[str, Any]

def apply_move_my_game(state: State, move: Move) -> State:
    players: List[Dict[str, Any]] = state["players"]

    # --- decode board from generic state ---
    board_info = state["board"]
    int_board = board_info["cells"]              # 2D ints
    symbol_map = {0: " "}
    for idx, p in enumerate(players):
        symbol_map[idx + 1] = p["symbol"]

    board = [[symbol_map[v] for v in row] for row in int_board]

    # --- apply the move ---
    r = move["position"]["row"]
    c = move["position"]["col"]
    player_index = move["player_index"]
    symbol = players[player_index]["symbol"]
    board[r][c] = symbol

    # --- next player ---
    next_player_index = (player_index + 1) % len(players)
    next_symbol = players[next_player_index]["symbol"]

    # --- terminal & result: YOUR logic here ---
    # is_terminal_and_winner() should be implemented by you and return:
    #   (finished: bool, winner_symbol: Optional[str])
    finished, winner_symbol = is_terminal_and_winner(board)

    result_map = None
    if finished:
        # winner_to_results() should return (result_str, result_map)
        # e.g. result_map = {"P1": 1.0, "P2": -1.0}
        _, result_map = winner_to_results(winner_symbol, players)

    move_count = state["extra"].get("move_count", 0) + 1
    legal_moves = [] if finished else compute_legal_moves(board, players, next_symbol)

    # --- build and return the new state ---
    return build_generic_state(
        game_id=state["game_id"],
        board=board,
        players=players,
        current_player_symbol=next_symbol,
        move_count=move_count,
        finished=finished,
        legal_moves=legal_moves,
        result=result_map,
    )
```
# Building a state for the AI / logging
On each turn, describe the current position using build_generic_state:
```python
state = build_generic_state(
    game_id="my_game_id",
    board=board,                        # 2D list of symbols, e.g. [["X"," ","O"], ...]
    players=players,
    current_player_symbol=current_sym,  # whose turn it is
    move_count=move_count,
    finished=False,                     # or True if you know it’s over
    legal_moves=compute_legal_moves(board, players, current_sym),
    result=None,                        # for terminal state: {player_id: score}
)
```
# Using AIGameClient in your loop

Create client and start match:
```python
client = AIGameClient(
    game_id="my_game_id",
    api_key="demo-key",
    apply_move_fn=apply_move_my_game,
)

match_id = client.start_match(players=players, metadata={"mode": "casual"})

```

Each turn:
```python
while not finished:
    current_sym = ...                 # your turn logic
    legal_moves = compute_legal_moves(board, players, current_sym)

    state = build_generic_state(
        game_id="my_game_id",
        board=board,
        players=players,
        current_player_symbol=current_sym,
        move_count=move_count,
        finished=False,
        legal_moves=legal_moves,
    )

    current_player = next(p for p in players if p["symbol"] == current_sym)

    if current_player["type"] == "ai_mcts":
        # ---- AI turn ----
        client.send_state(match_id, state)
        move = client.best_move(match_id, iterations=800)
    else:
        # ---- human turn ----
        move = get_human_move_somehow(legal_moves)

    # Apply move in your game
    apply_move_on_real_board(board, move, current_sym)
    
    # log the move 
    client.log_move(match_id=match_id, state=state, move=move)

    # Update finished / winner using your own logic
    finished, winner_symbol = is_terminal_and_winner(board)
    move_count += 1
```

End the match:
```python
result_str, result_map = winner_to_results(winner_symbol, players)

final_state = build_generic_state(
    game_id="my_game_id",
    board=board,
    players=players,
    current_player_symbol=current_sym,
    move_count=move_count,
    finished=True,
    legal_moves=[],
    result=result_map,
)

client.end_match(
    match_id=match_id,
    result=result_str,
    final_state=final_state,
)

```