Download app.py from Enoder/MineChess: direct link, hf CLI and curl.
- Browser
- Download file 4.97 kB
-
https://huggingface.co/spaces/Enoder/MineChess/resolve/main/app.py
- Command line
-
hf download hf://spaces/Enoder/MineChess/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Enoder/MineChess/resolve/main/app.py
4.97 kB
| import random | |
| import gradio as gr | |
| FILES = "abcdefgh" | |
| RANKS = "12345678" | |
| # Pièces Unicode | |
| PIECES = { | |
| ("W", "p"): "♙", ("B", "p"): "♟", | |
| ("W", "t"): "♖", ("B", "t"): "♜", | |
| ("W", "c"): "♘", ("B", "c"): "♞", | |
| ("W", "f"): "♗", ("B", "f"): "♝", | |
| ("W", "R"): "♕", ("B", "R"): "♛", | |
| ("W", "k"): "♔", ("B", "k"): "♚", | |
| } | |
| def in_bounds(x, y): | |
| return 0 <= x < 8 and 0 <= y < 8 | |
| def initial_board(): | |
| b = [[None for _ in range(8)] for _ in range(8)] | |
| white_row = ['t', 'c', 'f', 'R', 'k', 'f', 'c', 't'] | |
| for x, p in enumerate(white_row): | |
| b[x][0] = ('W', p) | |
| b[x][1] = ('W', 'p') | |
| black_row = ['t', 'c', 'f', 'R', 'k', 'f', 'c', 't'] | |
| for x, p in enumerate(black_row): | |
| b[x][7] = ('B', p) | |
| b[x][6] = ('B', 'p') | |
| return b | |
| def sq_to_coords(sq): | |
| sq = sq.lower() | |
| return FILES.index(sq[0]), RANKS.index(sq[1]) | |
| def coords_to_sq(x, y): | |
| return FILES[x] + RANKS[y] | |
| def generate_mines(board, density=0.15): | |
| empty = [(x, y) for x in range(8) for y in range(8) if board[x][y] is None] | |
| n = max(1, int(len(empty) * density)) | |
| mines = set(random.sample(empty, n)) | |
| return mines, set(), set() | |
| def count_adjacent_mines(x, y, mines): | |
| return sum((nx, ny) in mines for nx in range(x-1, x+2) for ny in range(y-1, y+2) | |
| if (nx, ny) != (x, y) and in_bounds(nx, ny)) | |
| def render_board(board, mines, revealed, exploded, hints): | |
| grid = [] | |
| for y in range(7, -1, -1): | |
| row = [] | |
| for x in range(8): | |
| if (x, y) in exploded: | |
| cell = "💥" | |
| elif board[x][y]: | |
| cell = PIECES[board[x][y]] | |
| elif (x, y) in revealed: | |
| cell = str(hints.get((x, y), 0)) if hints.get((x, y), 0) > 0 else "·" | |
| else: | |
| cell = "⬜" if (x + y) % 2 else "⬛" | |
| row.append(cell) | |
| grid.append(row) | |
| return "\n".join(" ".join(row) for row in grid) | |
| def apply_move(board, mines, revealed, exploded, hints, move, turn): | |
| from_sq, to_sq = move | |
| fx, fy = sq_to_coords(from_sq) | |
| tx, ty = sq_to_coords(to_sq) | |
| piece = board[fx][fy] | |
| if not piece or piece[0] != turn: | |
| return board, "Coup invalide" | |
| if not in_bounds(tx, ty): | |
| return board, "Coup hors du plateau" | |
| # Déplacement | |
| board[fx][fy] = None | |
| if (tx, ty) in mines: | |
| exploded.add((tx, ty)) | |
| text = f"💥 {from_sq}->{to_sq} : Mine ! Pièce détruite." | |
| else: | |
| board[tx][ty] = piece | |
| revealed.add((tx, ty)) | |
| count = count_adjacent_mines(tx, ty, mines) | |
| hints[(tx, ty)] = count | |
| text = f"{from_sq}->{to_sq} (zone sûre, {count} mines autour)" | |
| return board, text | |
| def new_game(density): | |
| board = initial_board() | |
| mines, revealed, exploded = generate_mines(board, density) | |
| hints = {} | |
| state = dict(board=board, mines=mines, revealed=revealed, | |
| exploded=exploded, hints=hints, turn="W", log=["Nouvelle partie"]) | |
| return render_board(board, mines, revealed, exploded, hints), "\n".join(state["log"]), state | |
| def play_move(move_input, state): | |
| if not state: | |
| return "Pas de partie", "Commence une nouvelle partie", None | |
| move_input = move_input.strip().lower() | |
| if len(move_input) != 4: | |
| return render_board(**state), "Format invalide (ex: e2e4)", state | |
| from_sq, to_sq = move_input[:2], move_input[2:] | |
| board, text = apply_move(state["board"], state["mines"], state["revealed"], | |
| state["exploded"], state["hints"], (from_sq, to_sq), state["turn"]) | |
| state["board"] = board | |
| state["log"].append(text) | |
| state["turn"] = "B" if state["turn"] == "W" else "W" | |
| return render_board(board, state["mines"], state["revealed"], state["exploded"], state["hints"]), "\n".join(state["log"]), state | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# ♟️ ChessMine — Échecs + 💣 Démineur") | |
| gr.Markdown("Deux joueurs alternent leurs coups. Si une pièce marche sur une mine 💥 elle explose, sinon la case révèle le nombre de mines autour 🔢.") | |
| density = gr.Slider(0.05, 0.4, value=0.15, label="Densité de mines") | |
| start = gr.Button("Nouvelle partie") | |
| board_box = gr.Textbox(label="Plateau", lines=10, interactive=False) | |
| move_in = gr.Textbox(label="Coup (ex: e2e4)") | |
| play = gr.Button("Jouer") | |
| log_box = gr.Textbox(label="Journal", lines=8, interactive=False) | |
| state = gr.State() | |
| start.click(new_game, inputs=[density], outputs=[board_box, log_box, state]) | |
| play.click(play_move, inputs=[move_in, state], outputs=[board_box, log_box, state]) | |
| gr.Markdown("**Règles :**\n- Entrez vos coups en format `e2e4`.\n- Si la case cible contient une 💣 → explosion !\n- Sinon, un nombre indique combien de mines se trouvent autour.\n- Les pièces utilisent les symboles Unicode standards ♙♟♖♜♘♞♗♝♕♛♔♚.") | |
| if __name__ == "__main__": | |
| demo.launch() | |