# Quoridor

## Game logic

<details>

<summary>Game Logic code</summary>

```python
BOARD_WIDTH = 9
BOARD_HEIGHT = 9

def init_game(game_settings):
    position1 = {
        'row': 0, 
        'column': 4
        }
    position2 = {
        'row': BOARD_HEIGHT - 1, 
        'column': 4
        }

    state = {
        'player_turn_idx': 0,
        'player_positions': [position1, position2],
        'walls': [],
        'remaining_wall_count': [10, 10]
        }
    return state

def move_up(state):
    return __move_helper(state, __can_move_up, "up")

def move_down(state):
    return __move_helper(state, __can_move_down, "down")

def move_left(state):
    return __move_helper(state, __can_move_left, "left")

def move_right(state):
    return __move_helper(state, __can_move_right, "right")

def place_horizontal_wall(state, row, column):
    new_state = __place_wall_helper(state, row, column, True)
    return new_state

def place_vertical_wall(state, row, column):
    new_state = __place_wall_helper(state, row, column, False)
    return new_state

def get_game_result(state):
    if state['player_positions'][0]['row'] == BOARD_HEIGHT - 1:
        return {
            "game_result": "Winner",
            "winner_idx": 0
        }
    if state['player_positions'][1]['row'] == 0:
        return {
            "game_result": "Winner",
            "winner_idx": 1
        }
    return {
        "game_result": "NoWinnerYet"
    }

def __place_wall_helper(state, row, column, is_horizontal):
    player_idx = state['player_turn_idx']
    if state['remaining_wall_count'][player_idx] <= 0:
        raise InvalidActionError("no more walls to place")
    if row < 0 or row >= BOARD_HEIGHT - 1 or \
    column < 0 or column >= BOARD_WIDTH - 1:
        raise InvalidActionError("can't place wall there")
    for w in state['walls']:
        if w['row'] == row and w['column'] == column and w['is_horizontal'] != is_horizontal:
            raise InvalidActionError("overlapping with another wall")
        if w['is_horizontal'] and is_horizontal and w['row'] == row:
            if abs(w['column'] - column) <= 1:
                raise InvalidActionError("overlapping with another wall")
        if not w['is_horizontal'] and not is_horizontal and w['column'] == column:
            if abs(w['row'] - row) <= 1:
                raise InvalidActionError("overlapping with another wall")

    state['walls'].append({
        'row': row, 
        'column': column, 
        'is_horizontal': is_horizontal
        })

    if not __has_path_to(state, BOARD_HEIGHT - 1, state['player_positions'][0]):
        state['walls'].pop() # Just clean up so we don't modify the state accidentally
        raise InvalidActionError("wall seals the path to the top")
    if not __has_path_to(state, 0, state['player_positions'][1]):
        state['walls'].pop() # Just clean up so we don't modify the state accidentally
        raise InvalidActionError("wall seals the path to the bottom")

    state["player_turn_idx"] = (state["player_turn_idx"] + 1) % 2
    state['remaining_wall_count'][player_idx] -= 1
    return state

def __move_helper(state, direction_func, direction_str):
    player_idx = state['player_turn_idx']
    other_player_idx = (player_idx + 1) % 2
    player_position = state['player_positions'][player_idx]
    other_player_position = state['player_positions'][other_player_idx]
    wall_set = set([(w['row'], w['column'], w['is_horizontal']) for w in state['walls']])
    
    move_coordinate = direction_func(wall_set, player_position['row'], player_position['column'])
    if not move_coordinate:
        raise InvalidActionError("cannot move " + direction_str)
    if move_coordinate == (other_player_position['row'], other_player_position['column']):
        move_more = direction_func(wall_set, move_coordinate[0], move_coordinate[1])
        if not move_more:
            raise InvalidActionError("cannot move " + direction_str + " over other player")
        state['player_positions'][player_idx]['row'] = move_more[0]
        state['player_positions'][player_idx]['column'] = move_more[1]
    else:
        state['player_positions'][player_idx]['row'] = move_coordinate[0]
        state['player_positions'][player_idx]['column'] = move_coordinate[1]
    state['player_turn_idx'] = other_player_idx
    return state

def __can_move_up(wall_set, r, c):
    if (r, c, True) not in wall_set and \
        (r, c - 1, True) not in wall_set and \
            r + 1 < BOARD_HEIGHT:
        return (r + 1, c)
    return None

def __can_move_down(wall_set, r, c):
    if (r - 1, c, True) not in wall_set and \
            (r - 1, c - 1, True) not in wall_set and \
            r - 1 >= 0:
        return (r - 1, c)
    return None

def __can_move_left(wall_set, r, c):
    if (r, c - 1, False) not in wall_set and \
            (r - 1, c - 1, False) not in wall_set and \
            c - 1 >= 0:
        return (r, c - 1)
    return None

def __can_move_right(wall_set, r, c):
    if (r, c, False) not in wall_set and \
            (r - 1, c, False) not in wall_set and \
            c + 1 < BOARD_WIDTH:
        return (r, c + 1)
    return None

def __has_path_to(state, target_row, position):
    """
    Does a dfs search from position to target. return true if there is a path
    """
    visited = set()
    stack = [(position['row'], position['column'])]
    wall_set = set([(w['row'], w['column'], w['is_horizontal']) for w in state['walls']])
    while len(stack) > 0:
        node = stack.pop()
        if node[0] == target_row:
            return True
        if node in visited:
            continue
        r, c = node
        visited.add(node)
        neighbors = [__can_move_up(wall_set, r, c), 
            __can_move_down(wall_set, r, c), 
            __can_move_right(wall_set, r, c),
            __can_move_left(wall_set, r, c)]
        for n in neighbors:
            if n:
                stack.append(n)
    return False
```

</details>

## Game state schema

```protobuf
message State {
  // Required field to indicate the player who should be making the next move
  // Values = 0 or 1
  int32 player_turn_idx = 1;

  // Array of player positions, of length 2
  repeated Position player_positions = 2;

  // These are the walls that have been placed on the board.
  repeated Wall walls = 3;

  // Array of length 2, storing the number of wall pieces remaining for each player
  // that can still be placed onto the board.
  repeated int32 remaining_wall_count = 4;
}
```

```protobuf
/*
Walls have a length of 2. 
A horizontal wall in <row 0, column 0> blocks player piece at both <row 0, column 0> and <row 0, column 1> from going up.
A vertical wall in <row 0, column 0> blocks player piece at both <row 0, column 0> and <row 1, column 0> from going right.
Both horizontal and vertical walls can be placed between rows 0-7 and columns 0-7 inclusive. 
*/
message Wall {
  int32 row = 1;
  int32 column = 2;
  bool is_horizontal = 3;
}
```

```protobuf
message Position {
  int32 row = 1;
  int32 column = 2;
}
```

## Action schema

```javascript
move_up()
move_down()
move_left()
move_right()
place_horizontal_wall(int row, int column)
place_vertical_wall(int row, int column)
```

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


---

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