Created
May 21, 2017 14:25
-
-
Save Julien00859/d94b04b9943439f0a313087967bd6c44 to your computer and use it in GitHub Desktop.
Démineur
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
if [ -z $(which python3) ]; then | |
echo "Please install python3" | |
exit 1 | |
fi | |
if [ -z $(which virtualenv) ]; then | |
echo "Please install python-virtualenv" | |
exit 1 | |
fi | |
if [ -d venv ]; then | |
rm -Rf venv | |
fi | |
cd dirname "${BASH_SOURCE[0]}" | |
virtualenv -p $(which python3) venv | |
./venv/bin/pip install -r requirements.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!./venv/bin/python | |
import curses | |
import numpy as np | |
from random import choice | |
from operator import mul | |
from collections import namedtuple, deque | |
def myenum(name, *tuples): | |
keys, values = zip(*tuples) | |
return namedtuple(name, keys)(*values) | |
class Game: | |
# Values from 0 to 8 are the mine count around | |
flags = myenum("flags", ("NOTHING", 9), ("MINE", 10), ("MAYBE", 11)) | |
def __init__(self, width: int, height: int, mines: int): | |
"""Create a mine field""" | |
# Create minefield without any mine | |
self.mines = np.zeros((height, width), dtype=bool) | |
self.mines_count = mines | |
# Place some mines | |
coordinates = [(x // width, x % width) for x in range(width * height)] | |
for n in range(mines): | |
mine = choice(coordinates) | |
coordinates.remove(mine) | |
self.mines[mine[0]][mine[1]] = True | |
# Create the ground above the mines | |
self.ground = np.empty_like(self.mines, dtype=np.int8) | |
for x in np.nditer(self.ground, op_flags=['writeonly']): | |
x[...] = Game.flags.NOTHING | |
def discover(self, x: int, y: int, newtiles: list) -> int: | |
"""Discover a tile, return number of bomb around or -1 if it's a bomb""" | |
# Mine discovered | |
if self.mines[y][x]: | |
return -1 | |
# Count mines around, discover the tile, return the count | |
count = self.count_around(x, y) | |
newtiles.append((x, y, count)) | |
self.ground[y][x] = count | |
if count > 0: | |
return count | |
# If the count is 0 then discovers the tiles around (while themselves don't gave mine around) | |
zeros = deque([(x, y)]) | |
height, width = self.mines.shape | |
while zeros: | |
x, y = zeros.popleft() | |
for xx in range(max(0, x-1), min(width, x+2)): | |
for yy in range(max(0, y-1), min(height, y+2)): | |
if xx == x and yy == y: | |
continue | |
if self.ground[yy][xx] != Game.flags.NOTHING: | |
continue | |
count = self.count_around(xx, yy) | |
newtiles.append((xx, yy, count)) | |
self.ground[yy][xx] = count | |
if count == 0: | |
zeros.append((xx, yy)) | |
return 0 | |
def count_around(self, x, y) -> int: | |
count = 0 | |
height, width = self.mines.shape | |
for xx in range(max(x - 1, 0), min(x + 2, width)): | |
for yy in range(max(y - 1, 0), min(y + 2, height)): | |
if xx == x and yy == y: | |
continue | |
if self.mines[yy][xx]: | |
count += 1 | |
return count | |
def isover(self) -> bool: | |
count = 0 | |
for x in np.nditer(self.ground): | |
if x <= 8: | |
count += 1 | |
return count + self.mines_count == mul(*self.mines.shape) | |
input("""Keys: | |
- Move: mouse, arrow keys, WASD, ZQSD | |
- Dig: left click, o | |
- Flag: right click, p | |
- Quit: Escape | |
You win when every non-bomb tile is discovered. | |
You loose if you hit a bomb. | |
Resize your terminal to set the diffifulty | |
Press [Enter] to start the game""") | |
def discover(x, y, game, board) -> bool: | |
if game.ground[y][x] > 8: | |
newtiles = [] | |
count = game.discover(x, y, newtiles) | |
if count == -1: | |
board.addstr(y, x, "*") | |
board.refresh(y, x, y+1, x+1, y+1, x+1) | |
return False | |
elif count == 0: | |
for xx, yy, count in newtiles: | |
board.addstr(yy, xx, str(count) if count > 0 else " ") | |
board.refresh(yy, xx, yy+1, xx+1, yy+1, xx+1) | |
else: | |
board.addstr(y, x, str(count)) | |
board.refresh(y, x, y+1, x+1, y+1, x+1) | |
return True | |
def flag(x, y, game, board, flagsmap): | |
tile = game.ground[y][x] | |
if tile > 8: | |
newflag = (tile - 8) % 3 + 9 | |
game.ground[y][x] = newflag | |
board.addstr(y, x, flagsmap[newflag]) | |
board.refresh(y, x, y+1, x+1, y+1, x+1) | |
@curses.wrapper | |
def main(stdscr): | |
curses.mousemask(~0) | |
stdscr.leaveok(0) | |
stdscr.clear() | |
w, h = curses.COLS, curses.LINES | |
# Draw rectangle | |
for x in range(1, w-1): | |
stdscr.addstr(0, x, "═") | |
stdscr.addstr(h-2, x, "═") | |
for y in range(1, h-2): | |
stdscr.addstr(y, 0, "║") | |
stdscr.addstr(y, w-1, "║") | |
stdscr.addstr(0, 0, "╔") | |
stdscr.addstr(0, w-1, "╗") | |
stdscr.addstr(h-2, 0, "╚") | |
stdscr.addstr(h-2, w-1, "╝") | |
stdscr.refresh() | |
msgstatus = curses.newpad(1, w-2) | |
def msgstatus_refresh(): | |
msgstatus.refresh(0, 0, h-1, 0, h-1, w-2) | |
msgstatus_refresh() | |
flagsmap = { | |
Game.flags.NOTHING: "#", | |
Game.flags.MINE: "+", | |
Game.flags.MAYBE: "?" | |
} | |
board = curses.newpad(h - 2, w - 1) | |
for y in range(h - 3): | |
for x in range(w - 2): | |
board.addstr(y, x, flagsmap[Game.flags.NOTHING]) | |
def board_refresh(): | |
board.refresh(0, 0, 1, 1, h - 3, w - 2) | |
board_refresh() | |
game = Game(w - 2, h - 3, w * h // 8) | |
pos = [h // 2, w // 2] | |
while not game.isover(): | |
stdscr.move(*pos) | |
key = stdscr.getch() | |
if key == 27: | |
break | |
elif key in [curses.KEY_LEFT, ord("q"), ord("a")]: | |
if pos[1] > 1: | |
pos[1] -= 1 | |
elif key in [curses.KEY_UP, ord("z"), ord("w")]: | |
if pos[0] > 1: | |
pos[0] -= 1 | |
elif key in [curses.KEY_RIGHT, ord("d")]: | |
if pos[1] < w - 2: | |
pos[1] += 1 | |
elif key in [curses.KEY_DOWN, ord("s")]: | |
if pos[0] < h - 3: | |
pos[0] += 1 | |
elif key == curses.KEY_MOUSE: | |
m = curses.getmouse() | |
pos = [min(h-3, max(1, m[2])), min(w-2, max(1, m[1]))] | |
if m[4] & curses.BUTTON1_CLICKED: | |
if not discover(pos[1]-1, pos[0]-1, game, board): | |
break | |
elif m[4] & curses.BUTTON3_CLICKED: | |
flag(pos[1]-1, pos[0]-1, game, board, flagsmap) | |
elif key == ord("o"): | |
if not discover(pos[1]-1, pos[0]-1, game, board): | |
break | |
elif key == ord("p"): | |
flag(pos[1]-1, pos[0]-1, game, board, flagsmap) | |
if game.isover(): | |
msgstatus.addstr(0, 0, "Congratulation ! Press a key to quit") | |
else: | |
msgstatus.addstr(0, 0, "Game over ! Press a key to quit") | |
msgstatus_refresh() | |
stdscr.getch() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
numpy==1.12.1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment