Overview Refactoring isn't just about making code look pretty; it's about making it maintainable and clear. In this second phase of our Battleship refactor, we focus on moving beyond basic input handling to address the structural decay that often plagues growing projects. By separating the board's internal representation from the game's orchestration logic, we create a system where each component has one job. This approach eliminates the "God Class" problem where a single object knows too much about everything, making the game easier to scale, test, and debug. Prerequisites To get the most out of this tutorial, you should be comfortable with Python basics, specifically classes and methods. You should understand the difference between instance variables and local variables, and have a passing familiarity with list comprehensions and the modulo operator. Key Libraries & Tools * **Python 3.x**: The core programming language used for the implementation. * **Random Module**: A standard Python library used to generate the hidden ship's coordinates. * **List Comprehensions**: A concise Python syntax used for initializing the game grid efficiently. Moving Magic Numbers to Constants Hardcoded values, or "magic numbers," are a maintenance nightmare. If you decide to change your board from 5x5 to 10x10, you don't want to hunt through twenty lines of code to update every instance of the number five. We start by defining clear constants at the top of our module. ```python GUESSES_COUNT = 5 BOARD_SIZE_X = 5 BOARD_SIZE_Y = 5 HIDDEN = "O" SHIP = "S" GUESS = "X" ``` By using uppercase naming conventions, we signal to other developers that these values are configuration points, not variables that should change during runtime. This immediately makes the code more readable—seeing `SHIP` is far more descriptive than seeing a random string `"S"` buried in an `if` statement. Encapsulating the BattleshipBoard The original code mixed game rules with grid management. We solve this by creating a dedicated `BattleshipBoard` class. This class acts as the single source of truth for the game state. Instead of the game logic manually checking nested lists, it asks the board questions like "Is this a ship?" or "Has this spot been guessed?" ```python class BattleshipBoard: def __init__(self, size_x: int, size_y: int): self.grid = [[HIDDEN for _ in range(size_x)] for _ in range(size_y)] self.ship_row = random.randint(0, size_x - 1) self.ship_col = random.randint(0, size_y - 1) self.grid[self.ship_row][self.ship_col] = SHIP def is_ship(self, row: int, col: int) -> bool: return self.grid[row][col] == SHIP def to_string(self, show_ship: bool = False) -> str: rows_string = [] for row in self.grid: row_repr = [HIDDEN if col == SHIP and not show_ship else col for col in row] rows_string.append(" ".join(row_repr)) return "\n".join(rows_string) ``` The `to_string` method is a key design choice. By allowing a `show_ship` toggle, we separate the internal data (the actual grid) from the visual representation (what the player sees). This keeps the logic clean and avoids the need for maintaining two separate "visible" and "hidden" boards. Simplifying Game Logic with Math Managing player turns often involves messy index tracking. We can replace complex conditional blocks with simple math using the modulo operator (`%`). This allows us to rotate through any number of players seamlessly within a single `while` loop. ```python def play_game(player_count: int, board: BattleshipBoard): total_guesses = 0 max_guesses = GUESSES_COUNT * player_count while total_guesses < max_guesses: current_player = (total_guesses % player_count) + 1 # ... turn logic ... total_guesses += 1 ``` This math ensures that `current_player` always cycles correctly (1, 2, 1, 2 for a two-player game) without needing to manually reset a counter. It’s a robust pattern that reduces the surface area for bugs. Syntax Notes * **List Comprehensions**: Used `[[HIDDEN for _ in range(x)] for _ in range(y)]` to avoid reference copying issues that occur with simple list multiplication. * **Double Slash Division (`//`)**: Used for integer division to ensure guess counts remain whole numbers. * **Dunder Methods**: We avoided `__str__` in favor of `to_string` because we needed to pass the `show_ship` argument, which standard string conversion doesn't support. Tips & Gotchas Avoid the temptation to put print statements inside your core logic classes. Notice how `BattleshipBoard` returns strings but doesn't print them. This makes the class portable; you could theoretically use the same board class in a web app or a GUI without changing a line of code because it doesn't care how its data is displayed.
Battleship
Products
Mar 2022 • 1 videos
High activity month for Battleship. ArjanCodes among the most active voices, with 1 videos across 1 sources.
Mar 2022
- Mar 4, 2022