|
| 1 | +""" |
| 2 | +Dots and Boxes - Command Line Implementation |
| 3 | +
|
| 4 | +A classic two-player game where players take turns connecting dots to form boxes. |
| 5 | +The player who completes more boxes wins the game. |
| 6 | +
|
| 7 | +Author: AI Assistant (fixed) |
| 8 | +Date: 2024 (updated) |
| 9 | +""" |
| 10 | + |
| 11 | +import os |
| 12 | +import sys |
| 13 | + |
| 14 | + |
| 15 | +class DotsAndBoxes: |
| 16 | + def __init__(self, rows=3, cols=3): |
| 17 | + """ |
| 18 | + Initialize the game board. |
| 19 | +
|
| 20 | + Args: |
| 21 | + rows (int): Number of rows of boxes |
| 22 | + cols (int): Number of columns of boxes |
| 23 | + """ |
| 24 | + self.rows = rows |
| 25 | + self.cols = cols |
| 26 | + # Grid dimensions: (2*rows + 1) x (2*cols + 1) |
| 27 | + self.grid = [[' ' for _ in range(2 * cols + 1)] for _ in range(2 * rows + 1)] |
| 28 | + self.scores = {'A': 0, 'B': 0} |
| 29 | + self.current_player = 'A' |
| 30 | + self.game_over = False |
| 31 | + |
| 32 | + # Initialize dots |
| 33 | + for i in range(0, 2 * rows + 1, 2): |
| 34 | + for j in range(0, 2 * cols + 1, 2): |
| 35 | + self.grid[i][j] = '•' |
| 36 | + |
| 37 | + # Track completed boxes and drawn lines |
| 38 | + # horizontal_lines: (rows + 1) x cols -> between horizontally adjacent dots |
| 39 | + # vertical_lines: rows x (cols + 1) -> between vertically adjacent dots |
| 40 | + self.completed_boxes = [[False for _ in range(cols)] for _ in range(rows)] |
| 41 | + self.horizontal_lines = [[False for _ in range(cols)] for _ in range(rows + 1)] |
| 42 | + self.vertical_lines = [[False for _ in range(cols + 1)] for _ in range(rows)] |
| 43 | + |
| 44 | + def display_board(self): |
| 45 | + """Display the current state of the game board.""" |
| 46 | + # clear screen |
| 47 | + os.system('cls' if os.name == 'nt' else 'clear') |
| 48 | + print("\n" + "=" * 50) |
| 49 | + print(" DOTS AND BOXES") |
| 50 | + print("=" * 50) |
| 51 | + print(f"Player A: {self.scores['A']} points") |
| 52 | + print(f"Player B: {self.scores['B']} points") |
| 53 | + print(f"Current Player: {self.current_player}") |
| 54 | + print("=" * 50) |
| 55 | + |
| 56 | + for i in range(len(self.grid)): |
| 57 | + row_display = "" |
| 58 | + for j in range(len(self.grid[i])): |
| 59 | + # Handle dots, lines, and box contents |
| 60 | + if i % 2 == 0 and j % 2 == 0: # Dot positions |
| 61 | + row_display += self.grid[i][j] |
| 62 | + elif i % 2 == 0: # Horizontal positions |
| 63 | + # Stored as '-' when drawn |
| 64 | + if self.grid[i][j] == '-': |
| 65 | + row_display += '─' |
| 66 | + else: |
| 67 | + row_display += ' ' |
| 68 | + elif j % 2 == 0: # Vertical positions |
| 69 | + if self.grid[i][j] == '|': |
| 70 | + row_display += '│' |
| 71 | + else: |
| 72 | + row_display += ' ' |
| 73 | + else: # Box centers |
| 74 | + if self.grid[i][j] in ['A', 'B']: |
| 75 | + row_display += self.grid[i][j] |
| 76 | + else: |
| 77 | + row_display += ' ' |
| 78 | + |
| 79 | + # Add spacing between elements |
| 80 | + if j % 2 == 0 and j < len(self.grid[i]) - 1: |
| 81 | + row_display += ' ' |
| 82 | + print(row_display) |
| 83 | + |
| 84 | + print("=" * 50) |
| 85 | + |
| 86 | + def is_valid_move(self, row1, col1, row2, col2): |
| 87 | + """ |
| 88 | + Check if a move is valid. |
| 89 | +
|
| 90 | + Args: |
| 91 | + row1, col1: Coordinates of first dot (0-based, dot coordinates) |
| 92 | + row2, col2: Coordinates of second dot |
| 93 | +
|
| 94 | + Returns: |
| 95 | + bool: True if move is valid, False otherwise |
| 96 | + """ |
| 97 | + # Ensure coordinates are integers and within dot indices |
| 98 | + if not all(isinstance(x, int) for x in (row1, col1, row2, col2)): |
| 99 | + return False |
| 100 | + |
| 101 | + if not (0 <= row1 <= self.rows and 0 <= col1 <= self.cols and |
| 102 | + 0 <= row2 <= self.rows and 0 <= col2 <= self.cols): |
| 103 | + return False |
| 104 | + |
| 105 | + # Convert to grid coordinates |
| 106 | + grid_row1, grid_col1 = row1 * 2, col1 * 2 |
| 107 | + grid_row2, grid_col2 = row2 * 2, col2 * 2 |
| 108 | + |
| 109 | + # Check if connecting adjacent dots (Manhattan distance = 2 in grid coords) |
| 110 | + if abs(grid_row1 - grid_row2) + abs(grid_col1 - grid_col2) != 2: |
| 111 | + return False |
| 112 | + |
| 113 | + # Check if line is already drawn |
| 114 | + if grid_row1 == grid_row2: # Horizontal line |
| 115 | + line_col = min(grid_col1, grid_col2) // 2 |
| 116 | + line_row = grid_row1 // 2 |
| 117 | + if self.horizontal_lines[line_row][line_col]: |
| 118 | + return False |
| 119 | + else: # Vertical line |
| 120 | + line_row = min(grid_row1, grid_row2) // 2 |
| 121 | + line_col = grid_col1 // 2 |
| 122 | + if self.vertical_lines[line_row][line_col]: |
| 123 | + return False |
| 124 | + |
| 125 | + return True |
| 126 | + |
| 127 | + def draw_line(self, row1, col1, row2, col2): |
| 128 | + """ |
| 129 | + Draw a line between two dots and check for completed boxes. |
| 130 | +
|
| 131 | + Args: |
| 132 | + row1, col1: Coordinates of first dot |
| 133 | + row2, col2: Coordinates of second dot |
| 134 | +
|
| 135 | + Returns: |
| 136 | + bool: True if a box was completed (player gets another turn), False otherwise |
| 137 | + """ |
| 138 | + grid_row1, grid_col1 = row1 * 2, col1 * 2 |
| 139 | + grid_row2, grid_col2 = row2 * 2, col2 * 2 |
| 140 | + |
| 141 | + box_completed = False |
| 142 | + |
| 143 | + if grid_row1 == grid_row2: # Horizontal line |
| 144 | + line_col = min(grid_col1, grid_col2) // 2 |
| 145 | + line_row = grid_row1 // 2 |
| 146 | + self.horizontal_lines[line_row][line_col] = True |
| 147 | + |
| 148 | + # Update grid display (place '-' in the middle) |
| 149 | + mid_col = (grid_col1 + grid_col2) // 2 |
| 150 | + self.grid[grid_row1][mid_col] = '-' |
| 151 | + |
| 152 | + # Check for boxes above and below |
| 153 | + if line_row > 0: # Check box above |
| 154 | + if (self.horizontal_lines[line_row - 1][line_col] and |
| 155 | + self.vertical_lines[line_row - 1][line_col] and |
| 156 | + self.vertical_lines[line_row - 1][line_col + 1]): |
| 157 | + self.complete_box(line_row - 1, line_col) |
| 158 | + box_completed = True |
| 159 | + |
| 160 | + if line_row < self.rows: # Check box below |
| 161 | + if (self.horizontal_lines[line_row + 1][line_col] and |
| 162 | + self.vertical_lines[line_row][line_col] and |
| 163 | + self.vertical_lines[line_row][line_col + 1]): |
| 164 | + self.complete_box(line_row, line_col) |
| 165 | + box_completed = True |
| 166 | + |
| 167 | + else: # Vertical line |
| 168 | + line_row = min(grid_row1, grid_row2) // 2 |
| 169 | + line_col = grid_col1 // 2 |
| 170 | + self.vertical_lines[line_row][line_col] = True |
| 171 | + |
| 172 | + # Update grid display (place '|' in the middle) |
| 173 | + mid_row = (grid_row1 + grid_row2) // 2 |
| 174 | + self.grid[mid_row][grid_col1] = '|' |
| 175 | + |
| 176 | + # Check for boxes left and right |
| 177 | + if line_col > 0: # Check box to the left |
| 178 | + if (self.vertical_lines[line_row][line_col - 1] and |
| 179 | + self.horizontal_lines[line_row][line_col - 1] and |
| 180 | + self.horizontal_lines[line_row + 1][line_col - 1]): |
| 181 | + self.complete_box(line_row, line_col - 1) |
| 182 | + box_completed = True |
| 183 | + |
| 184 | + if line_col < self.cols: # Check box to the right |
| 185 | + if (self.vertical_lines[line_row][line_col + 1] and |
| 186 | + self.horizontal_lines[line_row][line_col] and |
| 187 | + self.horizontal_lines[line_row + 1][line_col]): |
| 188 | + self.complete_box(line_row, line_col) |
| 189 | + box_completed = True |
| 190 | + |
| 191 | + return box_completed |
| 192 | + |
| 193 | + def complete_box(self, box_row, box_col): |
| 194 | + """ |
| 195 | + Mark a box as completed for the current player. |
| 196 | +
|
| 197 | + Args: |
| 198 | + box_row: Row index of the box |
| 199 | + box_col: Column index of the box |
| 200 | + """ |
| 201 | + if not self.completed_boxes[box_row][box_col]: |
| 202 | + self.completed_boxes[box_row][box_col] = True |
| 203 | + self.scores[self.current_player] += 1 |
| 204 | + |
| 205 | + # Mark the box with player's initial |
| 206 | + center_row = box_row * 2 + 1 |
| 207 | + center_col = box_col * 2 + 1 |
| 208 | + self.grid[center_row][center_col] = self.current_player |
| 209 | + |
| 210 | + def check_game_over(self): |
| 211 | + """Check if all boxes have been completed.""" |
| 212 | + return all(all(row) for row in self.completed_boxes) |
| 213 | + |
| 214 | + def switch_player(self): |
| 215 | + """Switch to the other player.""" |
| 216 | + self.current_player = 'B' if self.current_player == 'A' else 'A' |
| 217 | + |
| 218 | + def get_winner(self): |
| 219 | + """Determine the winner based on scores.""" |
| 220 | + if self.scores['A'] > self.scores['B']: |
| 221 | + return 'A' |
| 222 | + elif self.scores['B'] > self.scores['A']: |
| 223 | + return 'B' |
| 224 | + else: |
| 225 | + return 'Tie' |
| 226 | + |
| 227 | + def play_turn(self): |
| 228 | + """Handle a single player's turn.""" |
| 229 | + while True: |
| 230 | + try: |
| 231 | + print("\nEnter coordinates for your line (format: row1 col1 row2 col2)") |
| 232 | + print(f"Rows and columns are 0-based dots: row in [0..{self.rows}], col in [0..{self.cols}]") |
| 233 | + print("Example: '0 0 0 1' to connect dot at (0,0) to (0,1)") |
| 234 | + coords = input("Your move: ").strip().split() |
| 235 | + |
| 236 | + if len(coords) != 4: |
| 237 | + print("Please enter exactly 4 numbers separated by spaces.") |
| 238 | + continue |
| 239 | + |
| 240 | + row1, col1, row2, col2 = map(int, coords) |
| 241 | + |
| 242 | + if self.is_valid_move(row1, col1, row2, col2): |
| 243 | + box_completed = self.draw_line(row1, col1, row2, col2) |
| 244 | + |
| 245 | + if not box_completed: |
| 246 | + self.switch_player() |
| 247 | + return True |
| 248 | + else: |
| 249 | + print("Invalid move! Please choose adjacent dots that aren't already connected.") |
| 250 | + |
| 251 | + except ValueError: |
| 252 | + print("Please enter valid integer numbers.") |
| 253 | + except KeyboardInterrupt: |
| 254 | + print("\n\nGame interrupted. Thanks for playing!") |
| 255 | + return False |
| 256 | + |
| 257 | + def play_game(self): |
| 258 | + """Main game loop.""" |
| 259 | + while not self.game_over: |
| 260 | + self.display_board() |
| 261 | + |
| 262 | + if not self.play_turn(): |
| 263 | + return |
| 264 | + |
| 265 | + if self.check_game_over(): |
| 266 | + self.game_over = True |
| 267 | + self.display_board() |
| 268 | + self.display_final_results() |
| 269 | + |
| 270 | + def display_final_results(self): |
| 271 | + """Display final scores and winner announcement.""" |
| 272 | + winner = self.get_winner() |
| 273 | + |
| 274 | + print("\n" + "=" * 50) |
| 275 | + print(" GAME OVER!") |
| 276 | + print("=" * 50) |
| 277 | + print(f"Final Scores:") |
| 278 | + print(f" Player A: {self.scores['A']} points") |
| 279 | + print(f" Player B: {self.scores['B']} points") |
| 280 | + |
| 281 | + if winner == 'Tie': |
| 282 | + print(" It's a tie!") |
| 283 | + else: |
| 284 | + print(f" Player {winner} wins!") |
| 285 | + print("=" * 50) |
| 286 | + |
| 287 | + |
| 288 | +def get_grid_size(): |
| 289 | + """Get grid size from user input.""" |
| 290 | + while True: |
| 291 | + try: |
| 292 | + size = input("Enter grid size (e.g., '3' for 3x3, '4' for 4x4, default is 3): ").strip() |
| 293 | + if not size: |
| 294 | + return 3 |
| 295 | + size = int(size) |
| 296 | + if 2 <= size <= 8: # allow a few more sizes if terminal can handle it |
| 297 | + return size |
| 298 | + else: |
| 299 | + print("Please enter a number between 2 and 8.") |
| 300 | + except ValueError: |
| 301 | + print("Please enter a valid number.") |
| 302 | + |
| 303 | + |
| 304 | +def main(): |
| 305 | + """Main function to run the game.""" |
| 306 | + print("Welcome to Dots and Boxes!") |
| 307 | + print("=" * 50) |
| 308 | + print("Rules:") |
| 309 | + print("- Players take turns connecting adjacent dots") |
| 310 | + print("- Complete a box to score a point and get another turn") |
| 311 | + print("- The player with the most boxes wins") |
| 312 | + print("=" * 50) |
| 313 | + |
| 314 | + try: |
| 315 | + while True: |
| 316 | + grid_size = get_grid_size() |
| 317 | + game = DotsAndBoxes(rows=grid_size, cols=grid_size) |
| 318 | + game.play_game() |
| 319 | + |
| 320 | + # Ask if players want to play again |
| 321 | + play_again = input("\nWould you like to play again? (y/n): ").strip().lower() |
| 322 | + if play_again not in ['y', 'yes']: |
| 323 | + print("Thanks for playing! Goodbye!") |
| 324 | + break |
| 325 | + except KeyboardInterrupt: |
| 326 | + print("\n\nGoodbye!") |
| 327 | + |
| 328 | + |
| 329 | +if __name__ == "__main__": |
| 330 | + main() |
0 commit comments