Skip to content

Commit f56a563

Browse files
authored
Merge pull request #72 from UrekMaz/dot_and_boxes
dot and boxes
2 parents 89fb7ab + c70fbe8 commit f56a563

1 file changed

Lines changed: 330 additions & 0 deletions

File tree

Python/Dots_Boxes/Dots_Boxes.py

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
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

Comments
 (0)