# Blokus

## Game logic

<details>

<summary>Game Logic code</summary>

```python
def init_game(game_settings, ref_data):
    player_count = int(game_settings["Player Count"])
    required_positions = [
        {
            "positions": [{"x": 0, "y": 0}]
        },
        {
            "positions": [{"x": 19, "y": 0}]
        }, 
        {
            "positions": [{"x": 19, "y": 19}]
        },
        {
            "positions": [{"x": 0, "y": 19}]
        }
    ]
    
    return {
        "player_hands": [list(range(len(ref_data["piece_templates"]))) for i in range(4)],
        "game_board": [-1]*(20*20),
        "players_unavailable_spots": [{
            "unavailable_spots": [0]*(20*20)
        } for i in range(4)],
        "scores": [0]*player_count,
        "skips": 0,
        "required_positions": required_positions,
        "player_turn_idx": 0,
        "player_idx_to_play": 0,
        "player_idx_to_play_fourth_color": 0 if player_count == 3 else None
    }

def place_piece(state, ref_data, piece_idx, orientation_idx, offset_x, offset_y):
    player_idx = state["player_idx_to_play"]
    if piece_idx not in state["player_hands"][player_idx]:
        raise InvalidActionError("piece_idx not in the player's hand")

    if orientation_idx < 0 or orientation_idx >= len(ref_data["piece_templates"][piece_idx]["all_orientations"]):
        raise InvalidActionError("orientation_idx out of bound")
    piece = ref_data["piece_templates"][piece_idx]["all_orientations"][orientation_idx]

    board = state["game_board"]
    positions, sides, corners = _convert_pos_to_coordinate_tuples(piece, offset_x, offset_y)
    required_positions = state["required_positions"][player_idx]["positions"]
    required_positions = set([(pos["x"], pos["y"]) for pos in required_positions])

    is_required_position_met = False
    for x, y in positions:
        if x < 0 or y < 0 or x >= 20 or y >= 20:
            raise InvalidActionError("Placing this piece here goes out of the board")
        if board[_coordinate_to_index(x, y)] != -1:
            raise InvalidActionError("Piece overlaps with another on the board")
        if (x, y) in required_positions:
            is_required_position_met = True
    if not is_required_position_met:
        raise InvalidActionError("Piece must be placed in one of the required positions (check if it touches the corner of another one of your piece)")

    for x, y in sides:
        if x < 0 or y < 0 or x >= 20 or y >= 20:
            continue
        if board[_coordinate_to_index(x, y)] == player_idx:
            raise InvalidActionError("The side of this piece touches another one of your own pieces")

    # Place the piece and update all the other helper fields
    for x, y in positions:
        index = _coordinate_to_index(x, y)
        board[index] = player_idx
        for unavailable_spots in state["players_unavailable_spots"]:
            unavailable_spots["unavailable_spots"][index] = 1

    for x, y in sides:
        if x < 0 or y < 0 or x >= 20 or y >= 20:
            continue
        state["players_unavailable_spots"][player_idx]["unavailable_spots"][_coordinate_to_index(x, y)] = 1

    for x, y in corners:
        if x >= 0 and y >= 0 and x < 20 and y < 20:
            state["required_positions"][player_idx]["positions"].append({"x": x, "y": y})

    state["player_hands"][player_idx].remove(piece_idx)
    state = _sanitize_required_positions(state, player_idx)
    state["game_board"] = board
    state["skips"] = 0
    state = _update_score(state, len(positions))
    state = _update_player_turns(state)
    return state

def pass_turn(state, ref_data):
    state["skips"] += 1
    state = _update_player_turns(state)
    return state

def get_game_result(state, ref_data):
    if state["skips"] == len(state["scores"]):
        reversed_scores = state["scores"][::-1] # if scores are equal, last player to move wins
        return {
            "game_result": "Winner",
            "winner_idx": len(reversed_scores) - 1 - reversed_scores.index(max(reversed_scores))
        }
    return {
            "game_result": "NoWinnerYet"
        }

def _convert_pos_to_coordinate_tuples(piece, offset_x, offset_y):
    positions = [(pos["x"] + offset_x, pos["y"] + offset_y) for pos in piece["pos"]]
    sides = [(side["x"] + offset_x, side["y"] + offset_y) for side in piece["side"]]
    corners = [(corner["x"] + offset_x, corner["y"] + offset_y) for corner in piece["corner"]]
    return positions, sides, corners

def _coordinate_to_index(x, y):
    return x + y*20

def _sanitize_required_positions(state, player_idx):
    required_positions = state["required_positions"]
    players_unavailable_spots = state["players_unavailable_spots"]
    board = state["game_board"]

    new_required_positions = []
    for i in range(4):
        required = required_positions[i]
        unavailable_spots = players_unavailable_spots[i]["unavailable_spots"]
        new_required = []
        for pos in required["positions"]:
            index = _coordinate_to_index(pos["x"], pos["y"])
            if unavailable_spots[index] == 1:
                continue # this spot is no longer available, so it is not required
            
            if board[index] == -1:
                new_required.append(pos)
        new_required_positions.append({
            "positions": new_required
            })
    state["required_positions"] = new_required_positions
    return state

def _update_score(state, score):
    num_of_players = len(state["scores"])
    if num_of_players == 2 or num_of_players == 4:
        state["scores"][state["player_idx_to_play"] % num_of_players] += score
    elif num_of_players == 3:
        if state["player_idx_to_play"] != 3:
            state["scores"][state["player_idx_to_play"]] += score
    
    return state

def _update_player_turns(state):
    state["player_idx_to_play"] = (state["player_idx_to_play"] + 1) % 4 # colors always increment

    num_of_players = len(state["scores"])
    if num_of_players == 2 or num_of_players == 4:
        state["player_turn_idx"] = state["player_idx_to_play"] % num_of_players
    elif num_of_players == 3:
        if state["player_idx_to_play"] == 3:
            state["player_turn_idx"] = state["player_idx_to_play_fourth_color"]
            state["player_idx_to_play_fourth_color"] = (state["player_idx_to_play_fourth_color"] + 1) % 3
        else:
            state["player_turn_idx"] = state["player_idx_to_play"]
    
    return state
```

</details>

## Game state schema

```protobuf
message State {
  repeated int32 player_hands = 1;
  repeated int32 game_board = 2;
  repeated UnavailableSpots players_unavailable_spots = 3;
  int32 player_turn_idx = 4;
  repeated int32 scores = 5;
  int32 skips = 6;
  repeated RequiredPositions required_positions = 7;
  int32 player_idx_to_play = 8;
  int32 player_idx_to_play_fourth_color = 9;
}
```

```protobuf
message Orientation {
  repeated Position pos = 1;
  repeated Position side = 2;
  repeated Position corner = 3;
}
```

```protobuf
message PieceTemplate {
  repeated Orientation all_orientations = 1;
}
```

```protobuf
message RefData {
  repeated PieceTemplate piece_templates = 1;
}
```

```protobuf
message RequiredPositions {
  repeated Position positions = 1;
}
```

```protobuf
message Position {
  int32 x = 1;
  int32 y = 1;
}
```

```protobuf
message UnavailableSpots {
  repeated int32 unavailable_spots = 1;
}
```

## Action schema

```javascript
place_piece(int piece_idx, int orientation_idx, int offset_x, int offset_y)
pass_turn()
```

[Start building now](https://botpot.ai/game/blokus)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.botpot.ai/games/explore/blokus.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
