Enoder commited on
Commit
9403e91
·
verified ·
1 Parent(s): c9245ec

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +213 -0
app.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ demineur.py
4
+ Version simple en terminal (ligne de commande).
5
+
6
+ Commandes :
7
+ r i j -> révéler la case (ligne i, colonne j) (1-based)
8
+ f i j -> poser/enlever un drapeau sur la case
9
+
10
+ Exemple:
11
+ r 3 4
12
+ f 2 1
13
+ """
14
+
15
+ import random
16
+ import sys
17
+ from typing import List, Set, Tuple
18
+
19
+ # ---------- Configuration par défaut ----------
20
+ DEFAULT_ROWS = 9
21
+ DEFAULT_COLS = 9
22
+ DEFAULT_MINES = 10
23
+
24
+ # ---------- Génération du plateau ----------
25
+ def create_board(rows: int, cols: int, mines: int):
26
+ if mines >= rows * cols:
27
+ raise ValueError("Le nombre de mines doit être inférieur au nombre total de cases.")
28
+ # board: -1 = mine, otherwise number of adjacent mines
29
+ board = [[0 for _ in range(cols)] for _ in range(rows)]
30
+ # placement aléatoire des mines
31
+ all_positions = [(r, c) for r in range(rows) for c in range(cols)]
32
+ mine_positions = set(random.sample(all_positions, mines))
33
+ for (r, c) in mine_positions:
34
+ board[r][c] = -1
35
+ # calcul des voisins
36
+ for r in range(rows):
37
+ for c in range(cols):
38
+ if board[r][c] == -1:
39
+ continue
40
+ count = 0
41
+ for dr in (-1, 0, 1):
42
+ for dc in (-1, 0, 1):
43
+ if dr == 0 and dc == 0:
44
+ continue
45
+ nr, nc = r + dr, c + dc
46
+ if 0 <= nr < rows and 0 <= nc < cols and board[nr][nc] == -1:
47
+ count += 1
48
+ board[r][c] = count
49
+ return board, mine_positions
50
+
51
+ # ---------- Affichage ----------
52
+ def display(rows:int, cols:int, revealed: Set[Tuple[int,int]], flags: Set[Tuple[int,int]], board: List[List[int]], show_mines=False):
53
+ # entêtes colonnes
54
+ header = " " + " ".join(f"{c+1:2}" for c in range(cols))
55
+ sep = " +" + "---"*cols + "+"
56
+ print(header)
57
+ print(sep)
58
+ for r in range(rows):
59
+ row_cells = []
60
+ for c in range(cols):
61
+ pos = (r, c)
62
+ if pos in flags:
63
+ cell = " F"
64
+ elif pos in revealed:
65
+ val = board[r][c]
66
+ if val == -1:
67
+ cell = " *" # mine
68
+ elif val == 0:
69
+ cell = " " # vide
70
+ else:
71
+ cell = f" {val}"
72
+ else:
73
+ if show_mines and board[r][c] == -1:
74
+ cell = " *"
75
+ else:
76
+ cell = " #"
77
+ row_cells.append(cell)
78
+ print(f"{r+1:2} |" + "".join(row_cells) + " |")
79
+ print(sep)
80
+
81
+ # ---------- Découverte en cascade ----------
82
+ def flood_fill(board: List[List[int]], start_r:int, start_c:int, rows:int, cols:int, revealed: Set[Tuple[int,int]]):
83
+ stack = [(start_r, start_c)]
84
+ while stack:
85
+ r, c = stack.pop()
86
+ if (r,c) in revealed:
87
+ continue
88
+ revealed.add((r,c))
89
+ if board[r][c] == 0:
90
+ for dr in (-1, 0, 1):
91
+ for dc in (-1, 0, 1):
92
+ nr, nc = r + dr, c + dc
93
+ if 0 <= nr < rows and 0 <= nc < cols and (nr,nc) not in revealed:
94
+ if board[nr][nc] != -1:
95
+ stack.append((nr,nc))
96
+
97
+ # ---------- Vérifications de victoire ----------
98
+ def check_victory(rows:int, cols:int, mines:int, revealed:Set[Tuple[int,int]]):
99
+ # victoire si toutes les cases non-mines sont révélées
100
+ return len(revealed) == rows*cols - mines
101
+
102
+ # ---------- Analyse entrée utilisateur ----------
103
+ def parse_command(s: str):
104
+ parts = s.strip().split()
105
+ if len(parts) != 3:
106
+ return None
107
+ cmd = parts[0].lower()
108
+ if cmd not in ("r", "f"):
109
+ return None
110
+ try:
111
+ i = int(parts[1])
112
+ j = int(parts[2])
113
+ return (cmd, i, j)
114
+ except ValueError:
115
+ return None
116
+
117
+ # ---------- Jeu principal ----------
118
+ def run_game(rows:int, cols:int, mines:int):
119
+ board, mine_positions = create_board(rows, cols, mines)
120
+ revealed: Set[Tuple[int,int]] = set()
121
+ flags: Set[Tuple[int,int]] = set()
122
+ lost = False
123
+
124
+ print(f"\nBienvenue au Démineur ! {rows}x{cols}, mines: {mines}")
125
+ print("Commandes: 'r i j' pour révéler, 'f i j' pour poser/retirer un drapeau. Coordonnées 1-based (ex: r 3 4).")
126
+ while True:
127
+ display(rows, cols, revealed, flags, board if lost else False)
128
+ if lost:
129
+ print("💥 BOOM ! Vous avez touché une mine. Partie terminée.")
130
+ display(rows, cols, revealed.union(mine_positions), flags, board, show_mines=True)
131
+ break
132
+ if check_victory(rows, cols, mines, revealed):
133
+ print("🎉 Félicitations — vous avez gagné ! Toutes les cases sûres ont été révélées.")
134
+ display(rows, cols, revealed, flags, board, show_mines=True)
135
+ break
136
+
137
+ s = input("Entrée (r/f i j) > ").strip()
138
+ if s.lower() in ("quit", "exit", "q"):
139
+ print("Au revoir !")
140
+ break
141
+ parsed = parse_command(s)
142
+ if not parsed:
143
+ print("Commande invalide — ex: 'r 3 4' ou 'f 2 1'.")
144
+ continue
145
+ cmd, i, j = parsed
146
+ # convertir en 0-based
147
+ r, c = i - 1, j - 1
148
+ if not (0 <= r < rows and 0 <= c < cols):
149
+ print("Coordonnées hors plage.")
150
+ continue
151
+ pos = (r, c)
152
+ if cmd == "f":
153
+ if pos in revealed:
154
+ print("Impossible de poser un drapeau sur une case déjà révélée.")
155
+ else:
156
+ if pos in flags:
157
+ flags.remove(pos)
158
+ print(f"Drapeau retiré en ({i},{j}).")
159
+ else:
160
+ flags.add(pos)
161
+ print(f"Drapeau posé en ({i},{j}).")
162
+ elif cmd == "r":
163
+ if pos in flags:
164
+ print("Case marquée d'un drapeau — retirez le drapeau avant de révéler.")
165
+ continue
166
+ if pos in revealed:
167
+ print("Case déjà révélée.")
168
+ continue
169
+ if board[r][c] == -1:
170
+ # perdu
171
+ lost = True
172
+ revealed.add(pos)
173
+ continue
174
+ # si case sans voisins, on fait flood fill, sinon on révèle uniquement
175
+ if board[r][c] == 0:
176
+ flood_fill(board, r, c, rows, cols, revealed)
177
+ else:
178
+ revealed.add(pos)
179
+
180
+ # ---------- Interface simple pour lancer ----------
181
+ def main():
182
+ # possibilité de passer dimensions en argument: python demineur.py rows cols mines
183
+ rows = DEFAULT_ROWS
184
+ cols = DEFAULT_COLS
185
+ mines = DEFAULT_MINES
186
+ if len(sys.argv) >= 4:
187
+ try:
188
+ rows = int(sys.argv[1])
189
+ cols = int(sys.argv[2])
190
+ mines = int(sys.argv[3])
191
+ except Exception:
192
+ print("Usage: python demineur.py [rows cols mines]")
193
+ return
194
+ elif len(sys.argv) == 2:
195
+ # si un seul argument, on l'interprète comme taille carrée
196
+ try:
197
+ rows = cols = int(sys.argv[1])
198
+ except Exception:
199
+ print("Usage: python demineur.py [rows cols mines]")
200
+ return
201
+
202
+ # borne simple
203
+ if rows <= 0 or cols <= 0 or mines <= 0:
204
+ print("Les valeurs doivent être positives.")
205
+ return
206
+ if mines >= rows*cols:
207
+ print("Trop de mines pour la taille de la grille.")
208
+ return
209
+
210
+ run_game(rows, cols, mines)
211
+
212
+ if __name__ == "__main__":
213
+ main()