aboutsummaryrefslogtreecommitdiff
path: root/logic.py
diff options
context:
space:
mode:
Diffstat (limited to 'logic.py')
-rw-r--r--logic.py391
1 files changed, 391 insertions, 0 deletions
diff --git a/logic.py b/logic.py
new file mode 100644
index 0000000..01956ee
--- /dev/null
+++ b/logic.py
@@ -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