# Battleship

## Game logic

<details>

<summary>Game Logic code</summary>

```python
def init_game(game_settings):
    state = {
        'phase': "PLACE_SHIP",
        'player_info': [
            {
                "ships": [],
                "sunken_ship_sizes": [],
                "shots_at_this_player": []
            },
            {
                "ships": [],
                "sunken_ship_sizes": [],
                "shots_at_this_player": []
            }
        ],
        'player_turn_idx': 0
        }
    return state

def place_ships(state, ships):
    """
    <ships> should be an array of 2+3+3+4+5 = 17 coordinates expressed as object {'x': x, 'y': y}. Elements 0~1 represent the first ship of size 2. Elements 2~4 represent the second ship of size 3, etc..., until elements 12~16 represent the fifth ship of size 5.
    """
    BOARD_WIDTH = 10
    BOARD_HEIGHT = 10

    ships = [(int(coord["x"]), int(coord["y"])) for coord in ships]
    player_idx = state["player_turn_idx"]
    other_player_idx = (player_idx + 1) % 2

    if len(state['player_info'][player_idx]['ships']) > 0:
        raise InvalidActionError("Ships already placed.")
    if __are_ships_valid(ships):
        __place_ships(state, player_idx, ships)

    
    if len(state['player_info'][other_player_idx]['ships']) > 0:
        state['phase'] = "FIRE_SHOT"

    state["player_turn_idx"] = other_player_idx
    return state

def fire_shot(state, x, y):
    BOARD_WIDTH = 10
    BOARD_HEIGHT = 10
    x = int(x)
    y = int(y)
    if state['phase'] == "PLACE_SHIP":
        raise InvalidActionError("Still waiting for ships to be placed.")
    if x < 0 or x >= BOARD_WIDTH or y < 0 or y >= BOARD_HEIGHT:
        raise InvalidActionError("Out of bound.")

    player_idx = state["player_turn_idx"]
    other_player_idx = (player_idx + 1) % 2

    other_player_info = state["player_info"][other_player_idx]
    
    target_positions = [(pos['x'], pos['y']) for ship in other_player_info['ships'] for pos in ship['positions']]
    shots_fired = set([(shot['x'], shot['y']) for shot in other_player_info['shots_at_this_player']])

    if (x, y) in shots_fired:
        raise InvalidActionError("Already placed a shot at this location.")
    if (x, y) in target_positions:
        did_hit = True
    else:
        did_hit = False

    state['player_info'][other_player_idx]['shots_at_this_player'].append({
        'x': x,
        'y': y, 
        'did_hit': did_hit
        })
    shots_fired.add((x, y))
    state['player_info'][other_player_idx]['sunken_ship_sizes'] = __get_sunken_ships(shots_fired, state['player_info'][other_player_idx]['ships'])
    state["player_turn_idx"] = other_player_idx
    return state

def __get_sunken_ships(shots, ships):
    sunken_ships = []
    for ship in ships:
        is_sunk = True
        for pos in ship['positions']:
            if (pos['x'], pos['y']) not in shots:
                is_sunk = False
                continue
        if is_sunk:
            sunken_ships.append(len(ship['positions']))
    return sunken_ships


def __place_ships(state, player_idx, ships):
    ship_models = [__make_ship_model(ships[0:2]), __make_ship_model(ships[2:5]), __make_ship_model(ships[5:8]
        ), __make_ship_model(ships[8:12]), __make_ship_model(ships[12:17])]
    state['player_info'][player_idx]['ships'] = ship_models

def __make_ship_model(coordinates):
    ship_positions = [{'x': coord[0], 'y': coord[1]} for coord in coordinates]
    return {'positions': ship_positions}

def __are_ships_valid(ships):
    if len(ships) != 17:
        raise InvalidActionError("Ships should be a list of 17 coordinates representing ships of sizes 2, 3, 3, 4, 5.")
    if len(set(ships)) != 17:
        raise InvalidActionError("Ships are overlapping.")
    if (__is_valid_ship(ships[0:2]) and 
        __is_valid_ship(ships[2:5]) and 
        __is_valid_ship(ships[5:8]) and 
        __is_valid_ship(ships[8:12]) and 
        __is_valid_ship(ships[12:17])):
        return True
    return False

def __is_valid_ship(ship):
    """
    Verify if <ship> is a list of adjacent tuples in a straight line that are within the bounds of the board.
    """
    BOARD_WIDTH = 10
    BOARD_HEIGHT = 10
    for x, y in ship:
        if x < 0 or x >= BOARD_WIDTH or y < 0 or y >= BOARD_HEIGHT:
            raise InvalidActionError("Out of bound.")
    xs = [coordinate[0] for coordinate in ship]
    ys = [coordinate[1] for coordinate in ship]
    diff_x = max(xs) - min(xs)
    diff_y = max(ys) - min(ys)
    if diff_x == 0: # vertical ship
        adjacent_coordinates = sorted(ys)
    elif diff_y == 0:
        adjacent_coordinates = sorted(xs)
    else:
        raise InvalidActionError("Ship has to be vertical or horizontal.")
    for i in range(len(adjacent_coordinates) - 1):
        if adjacent_coordinates[i+1] - adjacent_coordinates[i] != 1:
            raise InvalidActionError("Ship coordinates aren't consecutive.")
    return True

def get_game_result(state):
    if len(state['player_info'][0]['sunken_ship_sizes']) == 5:
        if len(state['player_info'][1]['sunken_ship_sizes']) == 5:
            return {
                "game_result": "Draw"
            }
        else:
            return {
                "game_result": "Winner",
                "winner_idx": 1
            }
    elif len(state['player_info'][1]['sunken_ship_sizes']) == 5 and state['player_turn_idx'] == 0:
        return {
            "game_result": "Winner",
            "winner_idx": 0
        }
    return {
        "game_result": "NoWinnerYet"
    }

def get_player_states(state):
    player_states = []
    for i in range(2):
        player_info =  [
            {
                "ships": state['player_info'][0]["ships"] if i == 0 else [],
                "sunken_ship_sizes": state['player_info'][0]["sunken_ship_sizes"],
                "shots_at_this_player": state['player_info'][0]["shots_at_this_player"]
            },
            {
                "ships": state['player_info'][1]["ships"] if i == 1 else [],
                "sunken_ship_sizes": state['player_info'][1]["sunken_ship_sizes"],
                "shots_at_this_player": state['player_info'][1]["shots_at_this_player"]
            }
        ]
        player_states.append({
            'player_info': player_info,
            'phase': state['phase'],
            'player_turn_idx': state['player_turn_idx']
            })
    return player_states
```

</details>

## Game state schema

```protobuf
message State {
  enum Phase {
    PLACE_SHIP = 0;
    FIRE_SHOT = 1;
  }

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

  // This will be length 2 for the two players. The player can only see their own ships
  // but will have information on the other fields such as the shots and sunken ships.
  repeated PlayerInfo player_info = 2;

  // The game starts off with PLACE_SHIP phase. Once both players placed their ships,
  // the phase changes to FIRE_SHOT until one of the player's ships are all sunk.
  Phase phase = 3;
}
```

```protobuf
message PlayerInfo {
  // Initially length 0. Once the ships are placed, this will be length 5.
  // The ships will have Coordinate lengths of 2, 3, 3, 4, 5.
  repeated Ship ships = 1;

  // Opponents fire shots at this player. Once a ship is sunk, it gets added
  // to this list, which stores the length of the ship that was sunk. This 
  // array will be size 0 to 5, and the game is over when all ships are sunk.
  repeated int32 sunken_ship_sizes = 2;

  // Stores all the shots at this player.
  repeated Shot shots_at_this_player = 3;
}
```

```protobuf
message Ship {
  repeated Coordinate positions = 1;
}
```

```protobuf
message Shot {
  int32 x = 1;
  int32 y = 2;
  bool did_hit = 3;
}
```

```protobuf
message Coordinate {
  int32 x = 1;
  int32 y = 2;
}
```

## Action schema

```javascript
/* 
Takes in a list of length 2+3+3+4+5 = 17 coordinates
Elements 0~1 represent the first ship of size 2. 
Elements 2~4 represent the second ship of size 3, etc..., 
until elements 12~16 represent the fifth ship of size 5.
*/
place_ships(list<coordinate> ships)

// The action needed for the second phase of the game
fire_shot(int x, int y)
```

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


---

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