# Generals

## Game logic

<details>

<summary>Game Logic code</summary>

```python
import random

def init_game(game_settings):
    width = 10
    height = 10
    mountain_count = 10
    tower_count = 2

    board_list = []
    for i in range(mountain_count):
        board_list.append(_build_tile('mountain', 0))
    for i in range(tower_count):
        board_list.append(_build_tile('tower', 40))
    board_list.append(_build_tile('king', 1, 0))
    board_list.append(_build_tile('king', 1, 1))
    for i in range(width*height - len(board_list)):
        board_list.append(_build_tile('regular', 0, None))

    board = _make_2d_board(board_list, width, height)
    return {
        "player_turn_idx": 0,
        "player1_total_count": 1,
        "player2_total_count": 1,
        "board": board,
        "round": 0,
        "board_width": width,
        "board_height": height
    }

def _build_tile(tile_type, count, tile_player_idx = None, is_visible = True):
    return {
        'type': tile_type,
        'tile_player_idx': tile_player_idx,
        'count': count,
        'is_visible': is_visible
    }

def _make_2d_board(board_list, width, height):
    random.shuffle(board_list)
    index = 0
    board = []
    for r in range(height):
        row = []
        for c in range(width):
            row.append(board_list[index])
            index += 1
        board.append(row)
    return board

def _is_out_of_bound(row, column, board):
    if row < 0 or row >= len(board):
        return True
    if column < 0 or column >= len(board[row]):
        return  True
    return False

def move(state, row, column, direction, move_half):
    board = state['board']
    if _is_out_of_bound(row, column, board):
        raise InvalidActionError("Start position is out of bound")
    start_piece = board[row][column]
    player_idx = state['player_turn_idx']
    if start_piece["tile_player_idx"] != player_idx:
        raise InvalidActionError("Cannot move from a tile you do not control")

    end_row = row
    end_column = column
    if direction == "UP":
        end_row -= 1
        if end_row < 0:
            raise InvalidActionError("Moving out of bound")
    elif direction == "DOWN":
        end_row += 1
        if end_row >= len(board):
            raise InvalidActionError("Moving out of bound")
    elif direction == "LEFT":
        end_column -= 1
        if end_column < 0:
            raise InvalidActionError("Moving out of bound")
    elif direction == "RIGHT":
        end_column += 1
        if end_column >= len(board[row]):
            raise InvalidActionError("Moving out of bound")
    else:
        raise InvalidActionError("Direction should be UP, DOWN, LEFT, or RIGHT")

    
    end_piece = board[end_row][end_column]
    number_to_move = int(start_piece["count"]/2) if move_half else start_piece["count"] - 1
    number_to_move = max(number_to_move, 0)

    if end_piece["type"] != "mountain":
        start_piece["count"] -= number_to_move
        if end_piece["tile_player_idx"] == None:
            end_piece["count"] -= number_to_move
        elif end_piece["tile_player_idx"] == start_piece["tile_player_idx"]:
            end_piece["count"] += number_to_move
        else:
            end_piece["count"] -= number_to_move

        if end_piece["count"] < 0:
            end_piece["count"] = -end_piece["count"]
            end_piece["tile_player_idx"] = player_idx

    state = _auto_update(state)
    state = _recompute_count(state)
    return state

def _recompute_count(state):
    player1_count = 0
    player2_count = 0
    for row in state["board"]:
        for piece in row:
            if piece["tile_player_idx"] == 0:
                player1_count += piece["count"]
            if piece["tile_player_idx"] == 1:
                player2_count += piece["count"]
    state["player1_total_count"] = player1_count
    state["player2_total_count"] = player2_count
    return state

def do_nothing(state):
    state = _auto_update(state)
    state = _recompute_count(state)
    return state

def _auto_update(state):
    player_idx = state['player_turn_idx']
    if player_idx == 0:
        state["player_turn_idx"] = 1
        return state
    state["player_turn_idx"] = 0
    

    if state["round"] % 25 == 0:
        for row in state["board"]:
            for piece in row:
                if piece["tile_player_idx"] != None:
                    piece["count"] += 1 
    else:
        for row in state["board"]:
            for piece in row:
                piece_type = piece["type"]
                if (piece_type == "tower" or piece_type == "king") and piece["tile_player_idx"] != None:
                    piece["count"] += 1 

    state["round"] += 1
    return state

def get_game_result(state):
    has_player1 = False
    has_player2 = False
    for row in state["board"]:
        for piece in row:
            if piece["tile_player_idx"] == 0:
                if piece["type"] == "king":
                    has_player1 = True
            if piece["tile_player_idx"] == 1:
                if piece["type"] == "king":
                    has_player2 = True
    if has_player1 and not has_player2:
        return {
            "game_result": "Winner",
            "winner_idx": 0
        }
    if has_player2 and not has_player1:
        return {
            "game_result": "Winner",
            "winner_idx": 1
        }
    if state["round"] > 500:
        if state["player1_total_count"] == state["player2_total_count"]:
            return {
                "game_result": "Draw"
            }
        elif state["player1_total_count"] > state["player2_total_count"]:
            return {
                "game_result": "Winner",
                "winner_idx": 0
            }
        else:
            return {
                "game_result": "Winner",
                "winner_idx": 1
            }
    return {
            "game_result": "NoWinnerYet"
        }

def _get_visible(state, player_idx):
    initial_visible = set()
    visible = set()
    board = state["board"]
    for row_idx, row in enumerate(board):
        for column_idx, piece in enumerate(row):
            if piece["tile_player_idx"] == player_idx:
                visible.add((row_idx, column_idx))
                initial_visible.add((row_idx, column_idx))
    for r, c in initial_visible:
        for i in [-1, 0, 1]:
            for j in [-1, 0, 1]:
                if not _is_out_of_bound(r + i, c + j, board):
                    visible.add((r + i, c + j))
    return visible


def get_player_states(state):
    player_states = []
    for i in range(2):
        visible_coordinates = _get_visible(state, i)
        player_board = []
        for row_idx, row in enumerate(state["board"]):
            player_board.append([])
            for column_idx, piece in enumerate(row):
                if (row_idx, column_idx) in visible_coordinates:
                    player_board[row_idx].append(piece)
                elif piece["type"] == "mountain":
                    player_board[row_idx].append(piece)
                else:
                    player_board[row_idx].append(_build_tile('regular', 0, None, False))
        
        player_states.append({
            "player_turn_idx": state["player_turn_idx"],
            "board": player_board,
            "round": state["round"],
            "board_width": state["board_width"],
            "board_height": state["board_height"],
            "player1_total_count": state["player1_total_count"],
            "player2_total_count": state["player2_total_count"]
        })
    return player_states
```

</details>

## Game state schema

```protobuf
message State {
  int32 player_turn_idx = 1;
  int32 player1_total_count = 2;
  int32 player2_total_count = 3;
  repeated Tile board = 4 [packed = true];
  int32 round = 5;
  int32 board_width = 6;
  int32 board_height = 7;
}
```

```protobuf
message Tile {
    string tile_type = 1;
    int32 tile_player_idx = 2;
    int32 count = 3;
    bool is_visible = 4;
}
```

## Action schema

```javascript
move(int row, int column, string direction, bool move_half)
do_nothing()
```

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


---

# 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/generals.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.
