Skip to content

Instantly share code, notes, and snippets.

@Julien00859
Created May 21, 2017 14:25
Show Gist options
  • Save Julien00859/d94b04b9943439f0a313087967bd6c44 to your computer and use it in GitHub Desktop.
Save Julien00859/d94b04b9943439f0a313087967bd6c44 to your computer and use it in GitHub Desktop.
Démineur
#!/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
#!./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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment