Browse Source

initial commit, far too late

master
mryouse 6 months ago
parent
commit
1b7343b3a7
4 changed files with 972 additions and 1 deletions
  1. +11
    -1
      README.md
  2. +391
    -0
      logic.py
  3. +327
    -0
      ouija.py
  4. +243
    -0
      sweepy.py

+ 11
- 1
README.md View File

@@ -1,3 +1,13 @@
# sweepy

minesweeper in python with curses
minesweeper in python with curses

## features
- save/resume game
- 2 size boards
- ~x~ pReTtYcOlOrS ~x~

## coming soon?
- custom boards
- local multiplayer
- scoring?

+ 391
- 0
logic.py View File

@@ -0,0 +1,391 @@
import random

# 8x8_10
# 16x16_40
# 16x30_99

# under variables
UNDER_DEFAULT = 0
UNDER_BOMB = -1

# over variables
OVER_DEFAULT = '_'
OVER_FLAGGED = 'x'
OVER_UNCOVERED = 'u'
OVER_MOVES = [OVER_FLAGGED, OVER_UNCOVERED]

# board variables
BOARD_COVERED = '_'
BOARD_FLAGGED = '!'
BOARD_BOMB_UNCOVERED = 'x'
BOARD_BOMB_COVERED = '-'
BOARD_BOMB_FLAGGED = '+'

DELIMITER = '|'

# get a random seed value
def get_seed():
return random.randint(1,1000000)

class MinesweeperLogic:

# field is the bombs and the values
# board is uncovered/flagged
UNDER = []
OVER = []

# other variables
WIDTH = 0
HEIGHT = 0
BOMBS = 0
SEED = 0
MOVES = []

def do_all_moves(self):
for move in self.MOVES:
self.do_move(int(move[0]), int(move[1]), move[2])

def load(self, filename):
# get a handle to the global variables
#global WIDTH, HEIGHT, BOMBS, SEED, MOVES

# grab the lines from the file
with open(filename, 'r') as fil:
istr = fil.readline().strip()
moves = fil.readlines()

# the first line must have 4 numeric values
try:
ilist = [int(i) for i in istr.split('|')]
except ValueError as e:
return False
if len(ilist) == 4:

# set up global variables
self.WIDTH = ilist[0]
self.HEIGHT = ilist[1]
self.BOMBS = ilist[2]
self.SEED = ilist[3]

self.setup()

# validate moves
for m in moves:
vm = self.validate_move(m.strip())
if not vm:
return False
#self.MOVES.append(vm)
self.do_move(vm[0], vm[1], vm[2])

# if all the moves are valid, do them
#self.do_all_moves()
#for move in self.MOVES:
#self.do_move(int(move[0]), int(move[1]), move[2])

return True
return False

def save(self, filename=None):
# set a filename, if missing
if filename is None:
filename = "{}.sweepy".format(str(self.SEED))

with open(filename, "w") as fn:

# write the header information
fn.write("{}|{}|{}|{}\n".format(str(self.WIDTH),
str(self.HEIGHT),
str(self.BOMBS),
str(self.SEED)))

# write the moves
for move in self.MOVES:
fn.write("{}\n".format(str(move)))

def new_game(self, width=8, height=8, bombs=10):
self.WIDTH = width
self.HEIGHT = height
self.BOMBS = bombs
self.setup()
self.do_first_move()

# handle interactive highlighting
def get_first_cell(self):
for y, col in enumerate(self.OVER):
for x, cell in enumerate(col):
if cell == OVER_DEFAULT:
return y, x

def get_covered_cells(self):
out = []
for y, col in enumerate(self.OVER):
for x, cell in enumerate(col):
if cell == OVER_DEFAULT:
out.append([y,x])
return out

def closest(self, y, x):
covered_cells = self.get_covered_cells()
for point in covered_cells:
if point[0] == y and point[1] > x:
return point[0], point[1]
elif point[0] > y:
return point[0], point[1]
return covered_cells[0][0], covered_cells[0][1]

def get_closest_cell(self, y, x):
# set some defaults that will be overwritten
min_off = self.WIDTH + self.HEIGHT
ny = -1
nx = -1

# go through the directions
ry, rx, ro = self.get_right(y, x)
if 0 < ro < min_off:
min_off = ro
ny = ry
nx = rx

dy, dx, do = self.get_down(y, x)
if 0 < do < min_off:
min_off = do
ny = dy
nx = dx

ly, lx, lo = self.get_left(y, x)
if 0 < lo < min_off:
min_off = lo
ny = ly
nx = lx

uy, ux, uo = self.get_up(y, x)
if 0 < uo < min_off:
min_off = uo
ny = uy
nx = ux

# if we found something, return it
if min_off < self.WIDTH + self.HEIGHT:
return ny, nx
else:
return self.get_first_cell()

def get_left(self, y, x):
col = self.OVER[y]
for ix in range(1,x+1):
adj_x = x - ix
if col[adj_x] in (OVER_DEFAULT, OVER_FLAGGED):
return y, adj_x, abs(adj_x - x)
return y, x, 0

def get_right(self, y, x):
col = self.OVER[y]
for ix in range(x+1,len(col)):
if col[ix] in (OVER_DEFAULT, OVER_FLAGGED):
return y, ix, abs(ix - x)
return y, x, 0

def get_up(self, y, x):
row = [self.OVER[i][x] for i in range(len(self.OVER))]
for iy in range(1,y+1):
adj_y = y - iy
if row[adj_y] in (OVER_DEFAULT, OVER_FLAGGED):
return adj_y, x, abs(adj_y - y)
return y, x, 0

def get_down(self, y, x):
row = [self.OVER[i][x] for i in range(len(self.OVER))]
for iy in range(y+1, len(row)):
if row[iy] in (OVER_DEFAULT, OVER_FLAGGED):
return iy, x, abs(iy - y)
return y, x, 0

def do_first_move(self):
for y, col in enumerate(self.UNDER):
for x, cell in enumerate(self.UNDER[y]):
if cell == UNDER_DEFAULT:
self.do_move(y, x, OVER_UNCOVERED)
return

def validate_move(self, move):

if type(move) is list:
mlist = move
else:
mlist = move.split(DELIMITER)

try:
if not 0 <= int(mlist[0]) < self.WIDTH:
return None
mlist[0] = int(mlist[0])

if not 0 <= int(mlist[1]) < self.HEIGHT:
return None
mlist[1] = int(mlist[1])

if mlist[2] in OVER_MOVES:
return mlist
except ValueError as e:
return None

# returns true if move is successful/valid
# returns false otherwise
def do_move(self, col, row, move, propagated=False):

# if the cell hasn't been uncovered, try to uncover it
if self.OVER[col][row] == OVER_DEFAULT:
if move == OVER_FLAGGED:
if not propagated:
self.MOVES.append("{}|{}|{}".format(str(col), str(row), move))
self.OVER[col][row] = move
elif move == OVER_UNCOVERED:

# uncover the targeted cell
if not propagated:
self.MOVES.append("{}|{}|{}".format(str(col), str(row), move))
self.OVER[col][row] = move

# if the uncovered cell is the default,
# uncover neighboring cells
if self.UNDER[col][row] == UNDER_DEFAULT:
neighbors = self.get_valid_neighbors(col, row)
for n in neighbors:
n.extend(move) # add the move to the neighbor
vm = self.validate_move(n)
if vm and self.UNDER[vm[0]][vm[1]] != UNDER_BOMB:
self.do_move(vm[0], vm[1], vm[2], True)
elif self.OVER[col][row] == OVER_FLAGGED and move == OVER_FLAGGED:
if not propagated:
self.MOVES.append("{}|{}|{}".format(str(col), str(row), move))
self.OVER[col][row] = OVER_DEFAULT
else:
return False
def setup(self):

# grab a handle to the global variables
if self.SEED == 0:
self.SEED = get_seed()

# initialize with the width
# use None objects, as these will be ultimately be lists
self.UNDER = [None] * self.WIDTH
self.OVER = [None] * self.WIDTH

# add the height rows (use the defaults)
for i in range(self.WIDTH):
under_row = [UNDER_DEFAULT] * self.HEIGHT
self.UNDER[i] = under_row

over_row = [OVER_DEFAULT] * self.HEIGHT
self.OVER[i] = over_row

# generate and place the bombs
random.seed(self.SEED)
for i in range(self.BOMBS):
while True:
w = random.randint(0, self.WIDTH - 1)
h = random.randint(0, self.HEIGHT - 1)
if self.UNDER[w][h] == UNDER_DEFAULT:
self.UNDER[w][h] = UNDER_BOMB
break
# calculate the values next to the bombs
for w in range(self.WIDTH):
for h in range(self.HEIGHT):
if self.UNDER[w][h] == UNDER_BOMB:
neighbors = self.get_valid_neighbors(w, h)
for n in neighbors:
if self.UNDER[n[0]][n[1]] != UNDER_BOMB:
self.UNDER[n[0]][n[1]] += 1

def get_valid_neighbors(self, col, row):

# calculate the values
col_left = col - 1
col_right = col + 1
row_up = row - 1
row_down = row + 1

# calculate which directions we can go
left = True if col_left >= 0 else False
right = True if col_right < self.WIDTH else False
up = True if row_up >= 0 else False
down = True if row_down < self.HEIGHT else False

# valid neighbors
vns = []
if left and up:
vns.append([col_left, row_up])
if left:
vns.append([col_left, row])
if left and down:
vns.append([col_left, row_down])
if right and up:
vns.append([col_right, row_up])
if right:
vns.append([col_right, row])
if right and down:
vns.append([col_right, row_down])
if up:
vns.append([col, row_up])
if down:
vns.append([col, row_down])

return vns

def has_won(self):
# win condition: all non-bombs are uncovered
for w in range(0,self.WIDTH):
for h in range(0,self.HEIGHT):
if self.UNDER[w][h] != UNDER_BOMB and self.OVER[w][h] != OVER_UNCOVERED:
return False
return True

def has_lost(self):
# lose condition: at least one bomb is uncovered
for w in range(0,self.WIDTH):
for h in range(0,self.HEIGHT):
if self.UNDER[w][h] == UNDER_BOMB and self.OVER[w][h] == OVER_UNCOVERED:
return True
return False

def get_board(self):
# this will be what the user sees

lost = self.has_lost()

out = []
for c in range(self.WIDTH):
col = self.OVER[c]
inner = []
for r in range(self.HEIGHT):
row = col[r]

under_cell = self.UNDER[c][r]

if row == OVER_UNCOVERED and under_cell >= 0:
inner.append(str(under_cell))
elif row == OVER_UNCOVERED:
inner.append(BOARD_BOMB_UNCOVERED)
elif row == OVER_FLAGGED:
if lost and under_cell == UNDER_BOMB:
inner.append(BOARD_BOMB_FLAGGED)
else:
inner.append(BOARD_FLAGGED)
else:
if lost and under_cell == UNDER_BOMB:
inner.append(BOARD_BOMB_COVERED)
else:
inner.append(BOARD_COVERED)
out.append(inner)
return out

def get_flag_count(self):
total = 0
for col in self.OVER:
for cell in col:
if cell == OVER_FLAGGED:
total += 1
return total

+ 327
- 0
ouija.py View File

@@ -0,0 +1,327 @@
import curses
from functools import reduce
from enum import Enum
import math

class Ouija:

class Align(Enum):
LEFT = 0
RIGHT = 1
CENTER = 2

DEFAULT_VERT_EDGE = "|"
DEFAULT_HORIZ_EDGE = "~"
DEFAULT_CORNER = ":"
DEFAULT_HORIZ_PAD = 1
DEFAULT_VERT_PAD = 0
DEFAULT_HORIZ_MARGIN = 0
DEFAULT_VERT_MARGIN = 0
DEFAULT_UNIFORM_WIDTH = True
DEFAULT_ALIGN = Align.LEFT

class _OuijaColor:
DEFAULT = -1
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
CYAN = 6
WHITE = 7
ORANGE = 9
YELLOW = 11

def __init__(self, fg=DEFAULT, bg=DEFAULT):
self.fg = fg
self.bg = bg

def color_pair(self):
# start with an offset of 1 (can't do negatives)
out = 1

# hacky bitwise operator for different fg/bg colors
white_bg = 16
white_fg = 32
black_fg = 64

if self.bg == self.DEFAULT:
return out + self.fg
elif self.bg == self.WHITE:
return out + white_bg + self.fg
elif self.fg == self.WHITE:
return out + white_fg + self.bg
else:
return out + black_fg + self.bg

COLORS = {
"DEFAULT" : _OuijaColor(),
"BLACK" : _OuijaColor(_OuijaColor.BLACK),
"RED" : _OuijaColor(_OuijaColor.RED),
"GREEN" : _OuijaColor(_OuijaColor.GREEN),
"BLUE" : _OuijaColor(_OuijaColor.BLUE),
"CYAN" : _OuijaColor(_OuijaColor.CYAN),
"WHITE" : _OuijaColor(_OuijaColor.WHITE),
"ORANGE" : _OuijaColor(_OuijaColor.ORANGE),
"YELLOW" : _OuijaColor(_OuijaColor.YELLOW),

"RED_ON_WHITE" : _OuijaColor(_OuijaColor.RED, _OuijaColor.WHITE),
"GREEN_ON_WHITE" : _OuijaColor(_OuijaColor.GREEN, _OuijaColor.WHITE),
"BLUE_ON_WHITE" : _OuijaColor(_OuijaColor.BLUE, _OuijaColor.WHITE),
"CYAN_ON_WHITE" : _OuijaColor(_OuijaColor.CYAN, _OuijaColor.WHITE),
"ORANGE_ON_WHITE" : _OuijaColor(_OuijaColor.ORANGE, _OuijaColor.WHITE),
"YELLOW_ON_WHITE" : _OuijaColor(_OuijaColor.YELLOW, _OuijaColor.WHITE),

"WHITE_ON_BLACK" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.BLACK),
"WHITE_ON_RED" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.RED),
"WHITE_ON_GREEN" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.GREEN),
"WHITE_ON_BLUE" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.BLUE),
"WHITE_ON_CYAN" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.CYAN),
"WHITE_ON_ORANGE" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.ORANGE),
"WHITE_ON_YELLOW" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.YELLOW),

"BLACK_ON_WHITE" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.WHITE),
"BLACK_ON_RED" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.RED),
"BLACK_ON_GREEN" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.GREEN),
"BLACK_ON_BLUE" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.BLUE),
"BLACK_ON_CYAN" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.CYAN),
"BLACK_ON_ORANGE" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.ORANGE),
"BLACK_ON_YELLOW" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.YELLOW),
}

def _setup_curses_colors():
# initialize colors for curses
curses.start_color()
curses.use_default_colors()

for c in Ouija.COLORS.keys():
color = Ouija.COLORS[c]
curses.init_pair(color.color_pair(), color.fg, color.bg)

def __init__(self, stdscr, y, x):
self.stdscr = stdscr
self.y = y
self.x = x

# setup default styles
self.style()

# setup the curses colors
Ouija._setup_curses_colors()

# helper method to access style variables
def style(self,
vert_edge=DEFAULT_VERT_EDGE,
horiz_edge=DEFAULT_HORIZ_EDGE,
corner=DEFAULT_CORNER,
horiz_pad=DEFAULT_HORIZ_PAD,
vert_pad=DEFAULT_VERT_PAD,
horiz_margin=DEFAULT_HORIZ_MARGIN,
vert_margin=DEFAULT_VERT_MARGIN,
uniform_width=DEFAULT_UNIFORM_WIDTH,
align=DEFAULT_ALIGN):
self.vert_edge = vert_edge
self.horiz_edge = horiz_edge
self.corner = corner
self.horiz_pad = horiz_pad
self.vert_pad = vert_pad
self.horiz_margin = horiz_margin
self.vert_margin = vert_margin
self.uniform_width = uniform_width
self.align = align

# setup the tiles
def setup_tiles(self, ts):
if type(ts) != list:
return False
self.tiles = {}
for t in ts:
if type(t) != Tile:
return False
self.tiles[t.in_val] = t
print(str(self.tiles))
return True

def calc_cell_width(self, cell_value):
edges = 2 if self.vert_edge is not None else 0
return len(cell_value) + (2 * self.horiz_pad) + edges

def calc_row_width(self, row):
width = 0
for cell in row:

# if there's a cooresponding display value, use its length
# if not, just take the width directly
if cell in self.tiles:
width += self.calc_cell_width(self.tiles[cell].out_val)
else:
width += self.calc_cell_width(cell)

# if there's no margin, we double counted some dividers
if self.horiz_margin == 0:
width -= len(row) - 1
elif self.horiz_margin > 1:
width += (self.horiz_margin - 1) * (len(row) - 1)

return width

# draw the board, optionally highlighting a cell
def draw_board(self, board, hy=-1, hx=-1):

# calculate the longest row
max_row_length = max([len(row) for row in board])

if self.uniform_width:
# get the widest possible cell
out_vals = [i.out_val for i in self.tiles.values()]
max_cell_width = max([len(x) for x in out_vals])

# the max row width is the longest row of the widest cells
max_row_width = self.calc_row_width(["." * max_cell_width] * max_row_length)
else:
max_row_width = max([self.calc_row_width(row) for row in board])

# if specified, make every key as wide as the widest key
if self.uniform_width:
out_vals = [i.out_val for i in self.tiles.values()]
max_width = max([len(x) for x in out_vals])

# keep track of y-coordinate, and board y-index
cur_y = self.y
iy = 0

# loop through all columns in board
for col in board:

if self.uniform_width:
cur_row_width = self.calc_row_width(["." * max_cell_width] * len(col))
else:
cur_row_width = self.calc_row_width(col)
# keep track of x-coordinate, and board x-index
cur_x = self.x
ix = 0

# loop through all cells in the column
for cell in col:

tile = self.tiles[cell]

if self.uniform_width:
cell_width = self.calc_cell_width("." * max_cell_width)
else:
cell_width = self.calc_cell_width(tile.out_val)

target_x = cur_x
if self.align == Ouija.Align.RIGHT:
target_x += max_row_width - cur_row_width
if self.align == Ouija.Align.CENTER:
target_x += math.floor(max_row_width / 2) - math.floor(cur_row_width / 2)


# create the format string with the appropriate width
target_width = max_width if self.uniform_width else len(tile.out_val)
fmt_string = "{:^" + str(target_width) + "}"

# add A_STANDOUT if the cell needs to be highlighted
target_style = tile.style
if hx == ix and hy == iy:
target_style = target_style | curses.A_STANDOUT

# draw the key
#self.draw_rect_key(cur_y, cur_x, fmt_string.format(tile.out_val), tile.color, target_style)
self.draw_rect_key(cur_y, target_x, fmt_string.format(tile.out_val), tile.color, target_style)

# update the x-coordinate, based on width, padding, and margin
# if there's a vertical edge, add an additional position
#cur_x += target_width + (2 * self.horiz_pad) + self.horiz_margin
cur_x += cell_width - 1 + self.horiz_margin

# update the board x-index
ix += 1

# update the y-coordinate, based on padding and margin
# if there's a horizontal edge, add an additional position
cur_y += 1 + (2 * self.vert_pad) + self.vert_margin
if self.horiz_edge is not None:
cur_y += 1

# update the board x-index
iy += 1

# draw a rectangular key, with the top corner at the given (y, x),
# the specified text, color, and style
def draw_rect_key(self, y, x, text, color, style):
# calculate the inner width, including padding
iw = len(text) + (2 * self.horiz_pad)

# outer width and horizontal offset are defaults
ow = iw
h_off = 0
v_off = 1

# if we have a vertical edge, adjust the outer width and horizontal offset
if self.vert_edge is not None:
ow += 2
h_off += 1

# create format strings for the inner and outer widths
owf = "{:^" + str(ow) + "}"
iwf = "{:^" + str(iw) + "}"

# calculate how tall each cell should be,
# and where in the cell the text should appear
max_y = y + 2 + (2 * self.vert_pad)
mid_y = math.ceil((max_y - y) / 2)

# draw the horizontal edges, if defined
if self.horiz_edge is not None:

# top edge begins at (y, x),
# is 'iw' long,
# centered within the 'ow' length
self.stdscr.addstr(y, x, owf.format(self.horiz_edge * iw))

# bottom edge takes into account vertical offset
# and vertical padding
self.stdscr.addstr(y + (2 * v_off) + (2 * self.vert_pad), x, owf.format(self.horiz_edge * iw))

# draw the vertical edges, if defined
if self.vert_edge is not None:

# loop through each row we need to draw
for iy in range(y + 1, max_y):
self.stdscr.addstr(iy, x, self.vert_edge)
self.stdscr.addstr(iy, x + ow - 1, self.vert_edge)

# draw the corners, if defined
if self.corner is not None:
self.stdscr.addstr(y, x, self.corner)
self.stdscr.addstr(y, x + ow - 1, self.corner)
self.stdscr.addstr(max_y, x, self.corner)
self.stdscr.addstr(max_y, x + ow - 1, self.corner)

# draw the inside
for iy in range(y + 1, max_y):
# if we're at the mid-point of the cell, draw the text
# otherwise, don't draw anything
target_text = text if iy == (y + mid_y) else ""
self.stdscr.addstr(iy, x + h_off, iwf.format(target_text), style | curses.color_pair(color.color_pair()))

class Tile:

def __init__(self, in_val, out_val, color=Ouija.COLORS["DEFAULT"], style=0):
self.in_val = in_val
self.out_val = out_val
self.color = color
self.style = style

def __str__(self):
return "[{},{},{}]".format(self.in_val, self.out_val, str(self.color))

# helper function, not sure where it goes yet
def biggest_cell(board):
return max([max(list(map(lambda x: len(x), row))) for row in board])


+ 243
- 0
sweepy.py View File

@@ -0,0 +1,243 @@
#!/usr/bin/env python3

import curses
from logic import MinesweeperLogic
from ouija import Ouija, Tile
from pathlib import Path
import os

HOME = Path(os.environ["HOME"])
SWEEPY_ROOT = ".sweepy"
MY_SWEEPY_ROOT = HOME / SWEEPY_ROOT
MY_SWEEPY_WON = MY_SWEEPY_ROOT / "won"
MY_SWEEPY_LOST = MY_SWEEPY_ROOT / "lost"

def generate_tiles():

tiles = []
tiles.append(Tile("0", ""))
tiles.append(Tile("1", "1", Ouija.COLORS["DEFAULT"], curses.A_BOLD))
tiles.append(Tile("2", "2", Ouija.COLORS["BLUE"], curses.A_BOLD))
tiles.append(Tile("3", "3", Ouija.COLORS["RED"], curses.A_BOLD))
tiles.append(Tile("4", "4", Ouija.COLORS["GREEN"], curses.A_BOLD))
tiles.append(Tile("5", "5", Ouija.COLORS["ORANGE"], curses.A_BOLD))
tiles.append(Tile("!", "#", Ouija.COLORS["YELLOW"]))
tiles.append(Tile("_", "."))
tiles.append(Tile("x", "x", Ouija.COLORS["BLACK_ON_YELLOW"]))
tiles.append(Tile("-", ".", Ouija.COLORS["BLACK_ON_YELLOW"]))
tiles.append(Tile("+", "#", Ouija.COLORS["BLACK_ON_YELLOW"]))
return tiles

def env_setup():
if not MY_SWEEPY_ROOT.exists():
MY_SWEEPY_ROOT.mkdir()
if not MY_SWEEPY_WON.exists():
MY_SWEEPY_WON.mkdir()
if not MY_SWEEPY_LOST.exists():
MY_SWEEPY_LOST.mkdir()

def game_loop(stdscr, ob, ml):

cur_y, cur_x = ml.get_first_cell()

while(True):

if ml.has_won() or ml.has_lost():
msg = "You won!" if ml.has_won() else "You lost :("
base_fn = MY_SWEEPY_WON if ml.has_won() else MY_SWEEPY_LOST

stdscr.addstr(4, 3, "{} [n]ew game, [r]eplay, or [q]uit?".format(msg))
ob.draw_board(ml.get_board())

fn = "{}.sweepy".format(ml.SEED)
root_fn = MY_SWEEPY_ROOT / fn
fn_fq = MY_SWEEPY_WON / fn
if root_fn.exists():
root_fn.unlink()
ml.save(str(fn_fq))

while(True):

ik = stdscr.getkey()
if ik == 'n':
# clear the line
stdscr.move(4, 3)
stdscr.clrtoeol()

ml = MinesweeperLogic()
ml.new_game()
cur_y, cur_x = ml.get_first_cell()
break

elif ik == 'r':
stdscr.move(4, 3)
stdscr.clrtoeol()
ml.new_game()
cur_y, cur_x = ml.get_first_cell()
break
elif ik == 'q':
exit()

game_head(stdscr, ml)
game_foot(stdscr)
ob.draw_board(ml.get_board(), cur_y, cur_x)
in_key = stdscr.getkey()

# uncover a cell using the enter key
if in_key == "\n":
ml.do_move(cur_y, cur_x, "u")
if ml.get_first_cell():
cur_y, cur_x = ml.get_closest_cell(cur_y, cur_x)
#cur_y, cur_x = ml.closest(cur_y, cur_x)

# flag a cell using the "f" key
elif in_key == "f":
ml.do_move(cur_y, cur_x, "x")
if ml.get_first_cell():
cur_y, cur_x = ml.get_closest_cell(cur_y, cur_x)
#cur_y, cur_x = ml.closest(cur_y, cur_x)

# save a game (and exit) using the "s" key
elif in_key == "s":
ob.draw_board(ml.get_board())
fn = "{}.sweepy".format(ml.SEED)
root_fn = MY_SWEEPY_ROOT / fn
ml.save(str(root_fn))
stdscr.addstr(4, 3, "Game saved!")
stdscr.getch()
exit()

# start a new game with the "n" key
elif in_key == "n":
ob.draw_board(ml.get_board())
stdscr.addstr(4, 3, "new game? (y/n) ")
choice = stdscr.getkey()
stdscr.move(4, 3)
stdscr.clrtoeol()

if choice == 'y':
ml = MinesweeperLogic()
ml.new_game()
cur_y, cur_x = ml.get_first_cell()

# reset the current game with the "r" key
elif in_key == "r":
ob.draw_board(ml.get_board())
stdscr.addstr(4, 3, "reset the current game? (y/n) ")
choice = stdscr.getkey()
stdscr.move(4, 3)
stdscr.clrtoeol()

if choice == 'y':
ml.new_game()
cur_y, cur_x = ml.get_first_cell()

# quit without saving using the "q" key
elif in_key == "q":
ob.draw_board(ml.get_board())
stdscr.addstr(4, 3, "Quit without saving? (y/n) ")
choice = stdscr.getkey()
if choice == 'y':
exit()
else:
stdscr.move(4, 3)
stdscr.clrtoeol()

# navigation
elif in_key == "KEY_LEFT":
cur_y, cur_x, cost = ml.get_left(cur_y, cur_x)
elif in_key == "KEY_RIGHT":
cur_y, cur_x, cost = ml.get_right(cur_y, cur_x)
elif in_key == "KEY_UP":
cur_y, cur_x, cost = ml.get_up(cur_y, cur_x)
elif in_key == "KEY_DOWN":
cur_y, cur_x, cost = ml.get_down(cur_y, cur_x)
elif in_key == "\t":
cur_y, cur_x = ml.closest(cur_y, cur_x)

def game_head(stdscr, ml):
stdscr.addstr(1, 3, "~ sweepy ~")
stdscr.addstr(2, 3, "{} bombs, seed: {}".format(str(ml.BOMBS),str(ml.SEED)))
stdscr.addstr(3, 3, "flags: {:>2}".format(str(ml.get_flag_count())))
stdscr.refresh()

def game_foot(stdscr):
stdscr.addstr(24, 3, "u/d/l/r/tab navigation")
stdscr.addstr(25, 3, "enter: uncover cell")
stdscr.addstr(26, 3, "'f': (un)flag cell")
stdscr.addstr(27, 3, "'n': new game")
stdscr.addstr(28, 3, "'r': reset game")
stdscr.addstr(29, 3, "'s': save and quit game")
stdscr.addstr(30, 3, "'q': quit game (without saving)")

def main(stdscr):

stdscr.clear()
curses.curs_set(False)

env_setup()

# blocks are 2 tall and 4 wide
ml = MinesweeperLogic()
board = Ouija(stdscr, 6, 5)
board.style(corner=None, horiz_edge="=")

if not board.setup_tiles(generate_tiles()):
return False

# print some info
stdscr.addstr(1, 3, "~ sweepy ~")
stdscr.addstr(2, 3, "[n]ew game, or [r]esume a game")

# initial loop
while(True):
in_key = stdscr.getkey()

if in_key == "q":
exit()
elif in_key == "n":
stdscr.addstr(3, 3, "choose a size")
stdscr.addstr(4, 3, "[0] 8 x 8 (10 bombs)")
stdscr.addstr(5, 3, "[1] 16 x 8 (25 bombs)")
while True:
choice = stdscr.getkey()
if choice == '0':
ml.new_game(8, 8, 10)
break
elif choice == '1':
ml.new_game(8, 16, 25)
break
else:
stdscr.addstr(6, 3, "invalid choice, choose again")
break
elif in_key == "l":
stdscr.addstr(3, 3, "what game do you want to load? ")
fn = stdscr.getstr()
stdscr.addstr(4, 3, fn)
stdscr.getch()
ml.load(fn.decode('utf-8'))
break
elif in_key == "r":
current_games = sorted(MY_SWEEPY_ROOT.glob("*.sweepy"))
stdscr.addstr(3, 3, "there are " + str(len(current_games)) + " games in progress")
for idx, game in enumerate(current_games):
stdscr.addstr(4 + idx, 3, "[{}] {}".format(str(idx), str(game.name)))

if len(current_games) != 0:
stdscr.addstr(4 + len(current_games), 3, "choose a game")
while True:
r_key = stdscr.getkey()
if r_key.isdigit() and int(r_key) in range(len(current_games)):
r_game = current_games[int(r_key)]
ml.load(str(r_game))
break
else:
stdscr.addstr(4 + len(current_games), 3, "invalid choice, choose a game")
break

stdscr.clear()
game_loop(stdscr, board, ml)

curses.wrapper(main)

Loading…
Cancel
Save