-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathboard.py
More file actions
161 lines (142 loc) · 6.25 KB
/
board.py
File metadata and controls
161 lines (142 loc) · 6.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# minesweeper_solver/board.py
import random
class Cell:
"""Represents a single cell on the Minesweeper board."""
def __init__(self):
self.is_mine = False
self.is_revealed = False
self.is_flagged = False
self.adjacent_mines = 0
class Board:
"""Represents the Minesweeper game board and logic."""
def __init__(self, width, height, num_mines):
if num_mines >= width * height:
raise ValueError("Number of mines cannot be more than or equal to the total number of cells.")
self.width = width
self.height = height
self.num_mines = num_mines
self.grid = [[Cell() for _ in range(width)] for _ in range(height)]
self.mines_placed = False
self.game_over = False
self.win = False
self.revealed_count = 0
self.flags_placed = 0
def _is_valid(self, r, c):
"""Check if coordinates are within board boundaries."""
return 0 <= r < self.height and 0 <= c < self.width
def _get_neighbors(self, r, c):
"""Get valid neighbor coordinates for a cell."""
neighbors = []
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
nr, nc = r + dr, c + dc
if self._is_valid(nr, nc):
neighbors.append((nr, nc))
return neighbors
def _place_mines(self, first_click_r, first_click_c):
"""Place mines randomly, avoiding the first click location."""
mines_to_place = self.num_mines
while mines_to_place > 0:
r = random.randrange(self.height)
c = random.randrange(self.width)
# Ensure mine isn't placed on the first click or where one already exists
if not self.grid[r][c].is_mine and (r != first_click_r or c != first_click_c):
self.grid[r][c].is_mine = True
mines_to_place -= 1
self.mines_placed = True
self._calculate_adjacent_mines()
def _calculate_adjacent_mines(self):
"""Calculate the number of adjacent mines for each non-mine cell."""
for r in range(self.height):
for c in range(self.width):
if not self.grid[r][c].is_mine:
count = 0
for nr, nc in self._get_neighbors(r, c):
if self.grid[nr][nc].is_mine:
count += 1
self.grid[r][c].adjacent_mines = count
def reveal(self, r, c):
"""Reveal a cell. Returns True if a mine was hit, False otherwise."""
if not self._is_valid(r, c) or self.grid[r][c].is_revealed or self.grid[r][c].is_flagged:
return False # No change or invalid move
# First click handling: Place mines *after* the first click
if not self.mines_placed:
self._place_mines(r, c)
cell = self.grid[r][c]
cell.is_revealed = True
self.revealed_count += 1
if cell.is_mine:
self.game_over = True
self.win = False
print(f"BOOM! Hit a mine at ({r}, {c})")
return True # Hit a mine
# Cascade reveal for cells with 0 adjacent mines
if cell.adjacent_mines == 0:
for nr, nc in self._get_neighbors(r, c):
# Important: only reveal neighbours that are not already revealed or flagged
if not self.grid[nr][nc].is_revealed and not self.grid[nr][nc].is_flagged:
self.reveal(nr, nc) # Recursive call
self._check_win_condition()
return False # Did not hit a mine
def flag(self, r, c):
"""Toggle flag on an unrevealed cell."""
if not self._is_valid(r, c) or self.grid[r][c].is_revealed:
return False # Cannot flag revealed cell
cell = self.grid[r][c]
if cell.is_flagged:
cell.is_flagged = False
self.flags_placed -= 1
else:
cell.is_flagged = True
self.flags_placed += 1
# Optionally check win condition based on flags (less common)
# self._check_win_condition()
return True
def _check_win_condition(self):
"""Check if the game has been won."""
# Win if all non-mine cells are revealed
if self.revealed_count == (self.width * self.height) - self.num_mines:
self.game_over = True
self.win = True
print("Congratulations! You cleared the board!")
# Auto-flag remaining mines (optional visual)
for r in range(self.height):
for c in range(self.width):
if self.grid[r][c].is_mine and not self.grid[r][c].is_flagged:
self.grid[r][c].is_flagged = True
self.flags_placed += 1
def display(self, show_mines=False):
"""Display the board state in the console."""
print("\n " + " ".join(map(str, range(self.width)))) # Header
print(" +" + "--" * self.width + "-+")
for r in range(self.height):
print(f"{r}|", end=" ")
for c in range(self.width):
cell = self.grid[r][c]
if self.game_over and cell.is_mine and not show_mines: # Reveal mines on loss
show_mines = True
if cell.is_flagged:
print("F", end=" ")
elif not cell.is_revealed:
if show_mines and cell.is_mine:
print("*", end=" ") # Show mine location if requested (debug/endgame)
else:
print("?", end=" ") # Hidden
elif cell.is_mine:
# Should only happen if show_mines is True during game or after loss
print("*", end=" ")
elif cell.adjacent_mines > 0:
print(cell.adjacent_mines, end=" ")
else:
print(".", end=" ") # Revealed empty cell (0 adjacent mines)
print("|")
print(" +" + "--" * self.width + "-+")
print(f"Mines left: {self.num_mines - self.flags_placed}")
if self.game_over:
print("GAME OVER!")
if self.win:
print("YOU WON!")
else:
print("YOU LOST!")