diff options
Diffstat (limited to 'logic.py')
| -rw-r--r-- | logic.py | 391 |
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 |
