Files
cours/university/tp8/board.py
2025-11-17 20:02:47 +01:00

178 lines
4.7 KiB
Python

from random import sample
from typing import List, Tuple
Coord = Tuple[int, int]
Grid = List[List[int]]
GridBool = List[List[bool]]
def gen_grid(n: int, m: int, v: int = 0) -> Grid:
"""
Create a list of list to represent the board
"""
return [[v for _ in range(m)] for _ in range(n)]
def place_mine(grid, n):
h = len(grid)
w = len(grid[0])
total = h * w
indices = sample(range(total), n)
for k in indices:
i = k // w
j = k % w
grid[i][j] = 1
def get_neighbours(coord: Coord, grid: Grid) -> List[Coord]:
"""
Returns all the neighbors of a cell (y, x),
with y being the vertical axis and x the horizontal one.
Includes diagonals, excludes the cell itself.
Returns (y, x) pairs (row index, then column index).
"""
y, x = coord
max_y = len(grid) - 1
max_x = len(grid[0]) - 1
neighbours: List[Coord] = []
for ny in range(y - 1, y + 2):
for nx in range(x - 1, x + 2):
if ny == y and nx == x:
continue # skip the cell itself
if 0 <= ny <= max_y and 0 <= nx <= max_x:
neighbours.append((ny, nx))
return neighbours
def test_mine(grid: Grid, coord: Coord) -> bool:
"""Return True if there is a mine at coord in the grid."""
y, x = coord
return grid[y][x] == 1
def count_near_mines(grid: Grid, coord: Coord) -> int:
"""Return the number of mines surrounding the cell at coord."""
count = 0
nearest_cells = get_neighbours(coord, grid)
for y, x in nearest_cells:
if grid[y][x] == 1:
count += 1
return count
def display_mines(grid: Grid) -> None:
"""Print the complete mine grid: '*' for mines, '-' for empty."""
for row in grid:
for cell in row:
print("*" if cell == 1 else "-", end=" ")
print()
def display_known(grid: Grid, known: GridBool) -> None:
"""
Print what the player currently knows:
- '?' for unknown cells
- number of adjacent mines for known empty cells
- '*' for known mines (optional but usually useful)
"""
for y, row in enumerate(grid):
for x, cell in enumerate(row):
if not known[y][x]:
print("?", end=" ")
else:
if cell == 1:
print("*", end=" ")
else:
mines = count_near_mines(grid, (y, x))
print(mines, end=" ")
print()
def get_coords(known_grid: GridBool) -> Coord:
"""
Get coordinates from the user
"""
max_y = len(known_grid) - 1
max_x = len(known_grid[0]) - 1
guess_x = -1
guess_y = -1
while True:
while not (0 <= guess_x <= max_x):
guess_x = int(input(f"Enter x between 0 and {max_x}: "))
while not (0 <= guess_y <= max_y):
guess_y = int(input(f"Enter y between 0 and {max_y}: "))
if not known_grid[guess_y][guess_x]:
return (guess_y, guess_x)
guess_x = -1
guess_y = -1
def is_end(known_grid: GridBool, grid: Grid) -> bool:
"""
return True if all cells has been discovered
"""
for y, row in enumerate(known_grid):
for x, known in enumerate(row):
if not test_mine(grid, (y, x)) and not known:
return False
return True
def linked_cells(grid: Grid, known_grid: GridBool, coord: Coord) -> list[Coord]:
"""
Return all cells that must be revealed when the player opens `coord`,
assuming:
- `coord` is not a mine,
- `coord` is currently unknown,
- we must emulate Minesweeper expansion.
Rules:
- If `coord` has at least one adjacent mine: return only [coord].
- If `coord` has zero adjacent mines:
* reveal the entire connected region of zero-mine cells,
* plus all numbered border cells around that region.
The function returns coordinates only; it does not modify `known_grid`.
"""
y0, x0 = coord
frontier: list[Coord] = [coord]
visited: set[Coord] = set()
result: list[Coord] = []
while frontier:
cy, cx = frontier.pop()
if (cy, cx) in visited:
continue
visited.add((cy, cx))
mines_around = count_near_mines(grid, (cy, cx))
result.append((cy, cx))
if mines_around == 0:
for ny, nx in get_neighbours((cy, cx), grid):
if (ny, nx) not in visited and not known_grid[ny][nx]:
frontier.append((ny, nx))
return result
if __name__ == "__main__":
grid = gen_grid(7, 5, 0)
place_mine(grid, 10)
known = [[False for _ in range(5)] for _ in range(7)]
known[1][1] = True
display_mines(grid)
print()
display_known(grid, known)