agents.base_agent
1import json 2import logging 3from typing import Any, Dict, List, Optional, Tuple, Union, cast 4 5import websockets 6 7logging.basicConfig(level=logging.INFO, format="%(asctime)s - AGENT - %(levelname)s - %(message)s") 8 9 10class BaseBSAgent: 11 """ 12 Abstract base class for Battleship agents. 13 14 Subclasses MUST implement the deliberate() method to define their 15 decision-making logic. 16 """ 17 18 def __init__(self, server_uri: str = "ws://localhost:8765") -> None: 19 """ 20 Initializes the agent with the server URI. 21 22 Args: 23 server_uri (str): The WebSocket URI of the Battleship server. 24 """ 25 self.server_uri: str = server_uri 26 self.player_id: Optional[int] = None 27 self.board_size: Optional[int] = None 28 29 async def run(self) -> None: 30 """ 31 Connects to the server and enters the main message loop. 32 """ 33 try: 34 async with websockets.connect(self.server_uri) as websocket: 35 await websocket.send(json.dumps({"client": "agent"})) 36 37 async for message in websocket: 38 if not isinstance(message, str): 39 continue 40 41 data = cast(Dict[str, Any], json.loads(message)) 42 43 msg_type = str(data.get("type", "")) 44 if msg_type == "setup": 45 self.player_id = cast(Optional[int], data.get("player_id")) 46 self.board_size = cast(Optional[int], data.get("size")) 47 logging.info(f"Connected! Assigned Player {self.player_id}") 48 49 elif msg_type == "state": 50 current_turn = data.get("current_turn", -1) 51 my_ships_val = data.get("my_ships", []) 52 my_shots_val = data.get("my_shots", []) 53 valid_actions_val = data.get("valid_actions", []) 54 55 if ( 56 isinstance(current_turn, int) 57 and isinstance(my_ships_val, list) 58 and isinstance(my_shots_val, list) 59 and isinstance(valid_actions_val, list) 60 and self.player_id is not None 61 and current_turn == self.player_id 62 ): 63 # Type narrowing for my_ships, my_shots, valid_actions 64 my_ships = cast(List[List[Union[int, str]]], my_ships_val) 65 my_shots = cast(List[List[int]], my_shots_val) 66 valid_actions = cast(List[List[int]], valid_actions_val) 67 68 # Pass the partial state to the subclass logic 69 target_coord = await self.deliberate(my_ships, my_shots, valid_actions) 70 71 if target_coord is not None: 72 await websocket.send( 73 json.dumps( 74 { 75 "action": "fire", 76 "x": target_coord[0], 77 "y": target_coord[1], 78 } 79 ) 80 ) 81 82 elif msg_type == "game_over": 83 logging.info(f"Round Over: {data.get('message', 'Unknown result')}") 84 logging.info("Waiting for next round to start...") 85 86 except Exception as e: 87 logging.error(f"Connection lost: {e}") 88 89 async def deliberate( 90 self, 91 my_ships: List[List[Union[int, str]]], 92 my_shots: List[List[int]], 93 valid_actions: List[List[int]], 94 ) -> Optional[Union[List[int], Tuple[int, int]]]: 95 r""" 96 Determines the next target to fire upon. 97 98 MUST be implemented by subclasses. 99 100 Args: 101 my_ships: 10x10 grid with your ship names or 0 for water. 102 $M_{ships} \in \{0, \text{'Carrier'}, \dots\}^{10 \times 10}$ 103 my_shots: 10x10 grid with your shot history (0=unknown, 1=miss, 2=hit). 104 $M_{shots} \in \{0, 1, 2\}^{10 \times 10}$ 105 valid_actions: List of [x, y] coordinates you haven't shot at yet. 106 $A = \{(x, y) \mid M_{shots}[y][x] = 0\}$ 107 108 Returns: 109 A list or tuple of [x, y] representing the target coordinate. 110 """ 111 raise NotImplementedError("Subclasses must implement deliberate()")
11class BaseBSAgent: 12 """ 13 Abstract base class for Battleship agents. 14 15 Subclasses MUST implement the deliberate() method to define their 16 decision-making logic. 17 """ 18 19 def __init__(self, server_uri: str = "ws://localhost:8765") -> None: 20 """ 21 Initializes the agent with the server URI. 22 23 Args: 24 server_uri (str): The WebSocket URI of the Battleship server. 25 """ 26 self.server_uri: str = server_uri 27 self.player_id: Optional[int] = None 28 self.board_size: Optional[int] = None 29 30 async def run(self) -> None: 31 """ 32 Connects to the server and enters the main message loop. 33 """ 34 try: 35 async with websockets.connect(self.server_uri) as websocket: 36 await websocket.send(json.dumps({"client": "agent"})) 37 38 async for message in websocket: 39 if not isinstance(message, str): 40 continue 41 42 data = cast(Dict[str, Any], json.loads(message)) 43 44 msg_type = str(data.get("type", "")) 45 if msg_type == "setup": 46 self.player_id = cast(Optional[int], data.get("player_id")) 47 self.board_size = cast(Optional[int], data.get("size")) 48 logging.info(f"Connected! Assigned Player {self.player_id}") 49 50 elif msg_type == "state": 51 current_turn = data.get("current_turn", -1) 52 my_ships_val = data.get("my_ships", []) 53 my_shots_val = data.get("my_shots", []) 54 valid_actions_val = data.get("valid_actions", []) 55 56 if ( 57 isinstance(current_turn, int) 58 and isinstance(my_ships_val, list) 59 and isinstance(my_shots_val, list) 60 and isinstance(valid_actions_val, list) 61 and self.player_id is not None 62 and current_turn == self.player_id 63 ): 64 # Type narrowing for my_ships, my_shots, valid_actions 65 my_ships = cast(List[List[Union[int, str]]], my_ships_val) 66 my_shots = cast(List[List[int]], my_shots_val) 67 valid_actions = cast(List[List[int]], valid_actions_val) 68 69 # Pass the partial state to the subclass logic 70 target_coord = await self.deliberate(my_ships, my_shots, valid_actions) 71 72 if target_coord is not None: 73 await websocket.send( 74 json.dumps( 75 { 76 "action": "fire", 77 "x": target_coord[0], 78 "y": target_coord[1], 79 } 80 ) 81 ) 82 83 elif msg_type == "game_over": 84 logging.info(f"Round Over: {data.get('message', 'Unknown result')}") 85 logging.info("Waiting for next round to start...") 86 87 except Exception as e: 88 logging.error(f"Connection lost: {e}") 89 90 async def deliberate( 91 self, 92 my_ships: List[List[Union[int, str]]], 93 my_shots: List[List[int]], 94 valid_actions: List[List[int]], 95 ) -> Optional[Union[List[int], Tuple[int, int]]]: 96 r""" 97 Determines the next target to fire upon. 98 99 MUST be implemented by subclasses. 100 101 Args: 102 my_ships: 10x10 grid with your ship names or 0 for water. 103 $M_{ships} \in \{0, \text{'Carrier'}, \dots\}^{10 \times 10}$ 104 my_shots: 10x10 grid with your shot history (0=unknown, 1=miss, 2=hit). 105 $M_{shots} \in \{0, 1, 2\}^{10 \times 10}$ 106 valid_actions: List of [x, y] coordinates you haven't shot at yet. 107 $A = \{(x, y) \mid M_{shots}[y][x] = 0\}$ 108 109 Returns: 110 A list or tuple of [x, y] representing the target coordinate. 111 """ 112 raise NotImplementedError("Subclasses must implement deliberate()")
Abstract base class for Battleship agents.
Subclasses MUST implement the deliberate() method to define their decision-making logic.
19 def __init__(self, server_uri: str = "ws://localhost:8765") -> None: 20 """ 21 Initializes the agent with the server URI. 22 23 Args: 24 server_uri (str): The WebSocket URI of the Battleship server. 25 """ 26 self.server_uri: str = server_uri 27 self.player_id: Optional[int] = None 28 self.board_size: Optional[int] = None
Initializes the agent with the server URI.
Args: server_uri (str): The WebSocket URI of the Battleship server.
30 async def run(self) -> None: 31 """ 32 Connects to the server and enters the main message loop. 33 """ 34 try: 35 async with websockets.connect(self.server_uri) as websocket: 36 await websocket.send(json.dumps({"client": "agent"})) 37 38 async for message in websocket: 39 if not isinstance(message, str): 40 continue 41 42 data = cast(Dict[str, Any], json.loads(message)) 43 44 msg_type = str(data.get("type", "")) 45 if msg_type == "setup": 46 self.player_id = cast(Optional[int], data.get("player_id")) 47 self.board_size = cast(Optional[int], data.get("size")) 48 logging.info(f"Connected! Assigned Player {self.player_id}") 49 50 elif msg_type == "state": 51 current_turn = data.get("current_turn", -1) 52 my_ships_val = data.get("my_ships", []) 53 my_shots_val = data.get("my_shots", []) 54 valid_actions_val = data.get("valid_actions", []) 55 56 if ( 57 isinstance(current_turn, int) 58 and isinstance(my_ships_val, list) 59 and isinstance(my_shots_val, list) 60 and isinstance(valid_actions_val, list) 61 and self.player_id is not None 62 and current_turn == self.player_id 63 ): 64 # Type narrowing for my_ships, my_shots, valid_actions 65 my_ships = cast(List[List[Union[int, str]]], my_ships_val) 66 my_shots = cast(List[List[int]], my_shots_val) 67 valid_actions = cast(List[List[int]], valid_actions_val) 68 69 # Pass the partial state to the subclass logic 70 target_coord = await self.deliberate(my_ships, my_shots, valid_actions) 71 72 if target_coord is not None: 73 await websocket.send( 74 json.dumps( 75 { 76 "action": "fire", 77 "x": target_coord[0], 78 "y": target_coord[1], 79 } 80 ) 81 ) 82 83 elif msg_type == "game_over": 84 logging.info(f"Round Over: {data.get('message', 'Unknown result')}") 85 logging.info("Waiting for next round to start...") 86 87 except Exception as e: 88 logging.error(f"Connection lost: {e}")
Connects to the server and enters the main message loop.
90 async def deliberate( 91 self, 92 my_ships: List[List[Union[int, str]]], 93 my_shots: List[List[int]], 94 valid_actions: List[List[int]], 95 ) -> Optional[Union[List[int], Tuple[int, int]]]: 96 r""" 97 Determines the next target to fire upon. 98 99 MUST be implemented by subclasses. 100 101 Args: 102 my_ships: 10x10 grid with your ship names or 0 for water. 103 $M_{ships} \in \{0, \text{'Carrier'}, \dots\}^{10 \times 10}$ 104 my_shots: 10x10 grid with your shot history (0=unknown, 1=miss, 2=hit). 105 $M_{shots} \in \{0, 1, 2\}^{10 \times 10}$ 106 valid_actions: List of [x, y] coordinates you haven't shot at yet. 107 $A = \{(x, y) \mid M_{shots}[y][x] = 0\}$ 108 109 Returns: 110 A list or tuple of [x, y] representing the target coordinate. 111 """ 112 raise NotImplementedError("Subclasses must implement deliberate()")
Determines the next target to fire upon.
MUST be implemented by subclasses.
Args: my_ships: 10x10 grid with your ship names or 0 for water. $M_{ships} \in {0, \text{'Carrier'}, \dots}^{10 \times 10}$ my_shots: 10x10 grid with your shot history (0=unknown, 1=miss, 2=hit). $M_{shots} \in {0, 1, 2}^{10 \times 10}$ valid_actions: List of [x, y] coordinates you haven't shot at yet. $A = {(x, y) \mid M_{shots}[y][x] = 0}$
Returns: A list or tuple of [x, y] representing the target coordinate.