-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsolver.py
More file actions
166 lines (141 loc) · 7.99 KB
/
solver.py
File metadata and controls
166 lines (141 loc) · 7.99 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
162
163
164
165
166
# minesweeper_solver/solver.py
import random
from board import Board # Assuming board.py is in the same directory or accessible
class Solver:
def __init__(self, board: Board):
self.board = board
def _get_neighbors_states(self, r, c):
"""Helper to get counts of neighbor states (hidden, flagged, revealed)."""
hidden_neighbors = []
flagged_count = 0
revealed_neighbors_values = [] # Store value of revealed neighbours
for nr, nc in self.board._get_neighbors(r, c):
neighbor_cell = self.board.grid[nr][nc]
if neighbor_cell.is_flagged:
flagged_count += 1
elif not neighbor_cell.is_revealed:
hidden_neighbors.append((nr, nc))
else: # Is revealed and not flagged
revealed_neighbors_values.append(neighbor_cell.adjacent_mines)
return hidden_neighbors, flagged_count, revealed_neighbors_values
def find_safe_moves(self):
"""
Implements the basic Minesweeper logic:
1. If a revealed cell with number N has N flagged neighbours,
all other hidden neighbours are safe.
2. If a revealed cell with number N has N hidden neighbours (and 0 flagged),
all those hidden neighbours must be mines.
Returns lists of coordinates: (safe_to_reveal, safe_to_flag)
"""
safe_to_reveal = set()
safe_to_flag = set()
for r in range(self.board.height):
for c in range(self.board.width):
cell = self.board.grid[r][c]
# Only apply logic around revealed, numbered cells that are not 0
if cell.is_revealed and not cell.is_mine and cell.adjacent_mines > 0:
hidden_neighbors, flagged_count, _ = self._get_neighbors_states(r, c)
num_unknown = len(hidden_neighbors)
if num_unknown > 0:
# Rule 1: All mines accounted for by flags -> reveal remaining hidden
if cell.adjacent_mines == flagged_count:
for nr, nc in hidden_neighbors:
# Check again it's not flagged (could be flagged by another rule run)
if not self.board.grid[nr][nc].is_flagged:
safe_to_reveal.add((nr, nc))
# Rule 2: Remaining hidden cells must equal remaining mines -> flag them all
if num_unknown == cell.adjacent_mines - flagged_count:
for nr, nc in hidden_neighbors:
# Check again it's not already flagged
if not self.board.grid[nr][nc].is_flagged:
safe_to_flag.add((nr, nc))
return list(safe_to_reveal), list(safe_to_flag)
def make_move(self):
"""
Decides the next move:
1. Apply basic logic to find guaranteed safe reveals/flags.
2. If no guaranteed moves, make a random guess among hidden, unflagged cells.
Returns the action taken: ('reveal', r, c), ('flag', r, c), ('guess', r, c), or ('none',)
"""
if self.board.game_over:
return ('none',)
# --- Initial Move ---
if not self.board.mines_placed:
# Always start with a random guess if board is empty
r, c = random.randrange(self.board.height), random.randrange(self.board.width)
print(f"Solver: Making initial guess at ({r}, {c})")
hit_mine = self.board.reveal(r, c)
return ('reveal', r, c, hit_mine) # Return if mine was hit
# --- Logical Moves ---
made_logical_move = True
while made_logical_move: # Keep applying logic until no more obvious moves
made_logical_move = False
safe_to_reveal, safe_to_flag = self.find_safe_moves()
# Prioritize flagging as it can enable reveals
if safe_to_flag:
r, c = safe_to_flag[0] # Apply one flag at a time
if not self.board.grid[r][c].is_flagged: # Check again, state might change
print(f"Solver: Logic found mine at ({r}, {c}). Flagging.")
self.board.flag(r, c)
made_logical_move = True
# Don't return yet, continue applying logic loop
# return ('flag', r, c) # Old: return immediately
# If no flags found, check for safe reveals
elif safe_to_reveal:
r, c = safe_to_reveal[0] # Apply one reveal at a time
# Check again, state might change (e.g., could have been flagged by logic)
if not self.board.grid[r][c].is_revealed and not self.board.grid[r][c].is_flagged:
print(f"Solver: Logic found safe cell at ({r}, {c}). Revealing.")
hit_mine = self.board.reveal(r, c) # Should always be False here
made_logical_move = True
# Don't return yet, continue applying logic loop
# return ('reveal', r, c, hit_mine) # Old: return immediately
# If a move was made in this iteration, loop again to see if it unlocked more logic
if not made_logical_move:
break # Exit the logic loop if stable
# --- Guessing Move ---
# If we reach here, no more logical moves were found in the last pass
# Check if we already made a move in the loop (avoids guessing if logic succeeded)
if not safe_to_flag and not safe_to_reveal:
# Find all hidden, un-flagged cells
possible_guesses = []
for r in range(self.board.height):
for c in range(self.board.width):
if not self.board.grid[r][c].is_revealed and not self.board.grid[r][c].is_flagged:
possible_guesses.append((r, c))
if not possible_guesses:
# This case shouldn't happen if win/loss condition is checked correctly
# but handle it just in case. Could mean all are flagged.
print("Solver: No logical moves and no cells to guess. Stuck or game finished?")
self._check_unflagged_mines() # Attempt to win if only mines left unrevealed
if not self.board.game_over:
print("Solver: Still stuck. No further action.")
return ('none',)
# Make a random guess
r, c = random.choice(possible_guesses)
print(f"Solver: No logical moves found. Guessing at ({r}, {c})...")
hit_mine = self.board.reveal(r, c)
return ('guess', r, c, hit_mine)
else:
# If logic *did* succeed in the last pass (even if the lists are now empty),
# signal that an action was taken, but don't guess yet. Let the main loop run again.
print("Solver: Completed a logical step.")
# Find out what the last logical action actually *was* (tricky without returning earlier)
# Let's simplify: just return 'logic_step' and main loop will call again
return ('logic_step',)
def _check_unflagged_mines(self):
"""Check if the only remaining hidden cells are mines and flag them to win."""
hidden_unflagged_count = 0
expected_mines_left = self.board.num_mines - self.board.flags_placed
coords_to_flag = []
for r in range(self.board.height):
for c in range(self.board.width):
if not self.board.grid[r][c].is_revealed and not self.board.grid[r][c].is_flagged:
hidden_unflagged_count += 1
coords_to_flag.append((r,c))
if hidden_unflagged_count == expected_mines_left > 0:
print("Solver: Assuming remaining hidden cells are mines and flagging them.")
for r,c in coords_to_flag:
self.board.flag(r,c)
# Re-check win condition after flagging
self.board._check_win_condition()