def get_adj_coords(self, coord): """Returns a list of coordinates adjacent to coordinate coord""" adj_coords = [] if not coord.x == 0: adj_coords.append(coord_class.Coordinate(coord.x - 1, coord.y)) if not coord.x == self.num_rows - 1: adj_coords.append(coord_class.Coordinate(coord.x + 1, coord.y)) if not coord.y == 0: adj_coords.append(coord_class.Coordinate(coord.x, coord.y - 1)) if not coord.y == self.num_cols - 1: adj_coords.append(coord_class.Coordinate(coord.x, coord.y + 1)) return adj_coords
def generate_coord_boards(): """Generates a 2D coordinate board and its transpose. These are used when verifying solutions and creating random boards. """ self.coord_board = [] for x in range(self.num_rows): coord_list = [] for y in range(self.num_cols): coord_list.append(coord_class.Coordinate(x, y)) self.coord_board.append(coord_list) self.transpose_coord_board = [list(l) for l in zip(*self.coord_board)]
def __init__(self, config): """Initializes the LightUpPuzzle class. Where config is a Config object for the light up puzzle problem. """ def generate_coord_boards(): """Generates a 2D coordinate board and its transpose. These are used when verifying solutions and creating random boards. """ self.coord_board = [] for x in range(self.num_rows): coord_list = [] for y in range(self.num_cols): coord_list.append(coord_class.Coordinate(x, y)) self.coord_board.append(coord_list) self.transpose_coord_board = [ list(l) for l in zip(*self.coord_board) ] def generate_random_board(): """Randomly generates a solvable board. Solvable boards are generated by iteratively placing black squares (with probability dictated by the configuration file) and required bulbs around each square. Bulbs are also placed randomly around the board (not neighboring black squares). All bulbs are then removed, leaving a board with at least one solution. This function should only be called in __init__ """ self.black_squares = {} bulbs = set([]) if int(self.config.settings["override_random_board_dimensions"]): self.num_rows = int(self.config.settings["override_num_rows"]) self.num_cols = int(self.config.settings["override_num_cols"]) else: min_dimension = int( self.config.settings["min_random_board_dimension"]) max_dimension = int( self.config.settings["max_random_board_dimension"]) self.num_rows = random.randint(min_dimension, max_dimension) self.num_cols = random.randint(min_dimension, max_dimension) generate_coord_boards() # Create a list of shuffled coordinates used in assigning black squares & bulbs shuffled_coords = [] for row in self.coord_board: for coord in row: shuffled_coords.append(coord) random.shuffle(shuffled_coords) # Assign black squares & bulbs to the board for coord in shuffled_coords: if not coord in bulbs: if random.random() <= float( self.config.settings["black_square_placement_prob"] ): # Place a black square adj_coord_list = self.get_adj_coords(coord) num_placed_bulbs = 0 # Compute the random max value for this black square max_value = random.choices( list( range( 0, int(self.config. settings["adj_value_dont_care"]) + 1)), [ int(n) for n in self.config. settings["black_square_value_weights"].split( ',') ])[0] if max_value == int( self.config.settings["adj_value_dont_care"]): # Always place a black square with value adj_value_dont_care self.black_squares[coord] = max_value else: # Put a placeholder black square to ensure the maximum amount of bulbs can be placed self.black_squares[coord] = int( self.config.settings["adj_value_dont_care"]) # Place bulbs around the square, if allowed for adj_coord in adj_coord_list: if num_placed_bulbs < max_value and self.place_bulb( adj_coord, bulbs): num_placed_bulbs += 1 # Account for black square placements with value zero if num_placed_bulbs == 0 and len([ c for c in self.get_adj_coords(coord) if c in bulbs ]): # Place a adj_value_dont_care black square to preserve the bulb placement validity self.black_squares[coord] = int( self.config.settings["adj_value_dont_care"] ) else: # Update the real black square value to match the number of adjacent bulbs self.black_squares[coord] = num_placed_bulbs elif random.random() <= float( self.config.settings["bulb_placement_prob"]): # Attempt to place a bulb self.place_bulb(coord, bulbs) self.black_squares = {} self.config = config if int(self.config.settings["generate_uniform_random_puzzle"]): # Generate random initial board state generate_random_board() else: # Read initial board state with open(self.config.settings["input_file_path"], 'r') as input_file: # Read line 0 (number of columns) self.num_cols = int(input_file.readline()) # Read line 1 (number of rows) self.num_rows = int(input_file.readline()) # Read line 2 to eof (coordinates of black squares and their adjacency values) for row in input_file: black_square_data = [int(i) for i in row.split()] self.black_squares[coord_class.Coordinate( black_square_data[1] - 1, black_square_data[0] - 1)] = black_square_data[2] # Generate coordinate versions of the board generate_coord_boards()
def visualize_pretty(self, bulbs): """Prints the board in a pretty way.""" format = { 0: "\x1b[0;37;40m0 \x1b[0m", 1: "\x1b[0;37;40m1 \x1b[0m", 2: "\x1b[0;37;40m2 \x1b[0m", 3: "\x1b[0;37;40m3 \x1b[0m", 4: "\x1b[0;37;40m4 \x1b[0m", 5: "\x1b[0;37;40m \x1b[0m", 'LIT': "\x1b[1;33;43m \x1b[0m", 'BULB': "\x1b[1;33;43m[]\x1b[0m", 'NOT_LIT': "\x1b[5;30;47m \x1b[0m" } board = [['_' for col in range(self.num_cols)] for row in range(self.num_rows)] # Create and populate set of shined squares self.shined_squares = set([]) for bulb_coord in bulbs: # Create a list of adjacency lists - used for determining where the bulb shines adj_coord_lists = [] adj_coord_lists.append( self.coord_board[bulb_coord.x][:bulb_coord.y] [::-1]) # Row from this column to the left adj_coord_lists.append(self.coord_board[bulb_coord.x] [bulb_coord.y + 1:]) # Row from this column to the right adj_coord_lists.append(self.transpose_coord_board[ bulb_coord.y][:bulb_coord.x][::-1]) # Column from this row up adj_coord_lists.append(self.transpose_coord_board[ bulb_coord.y][bulb_coord.x + 1:]) # Column from this row down for coord_list in adj_coord_lists: for coord in coord_list: if coord in self.black_squares: break # Shine cannot propagate any further elif coord in bulbs: # Redundant check for bulb on bulb shining # Nullify the fitness of this board self.shined_squares = set([]) return False else: self.shined_squares.add(coord) # Ensure bulbs count as shined squares for bulb_coord in bulbs: self.shined_squares.add(bulb_coord) tmp_coords = set([]) for x in range(self.num_rows): for y in range(self.num_cols): tmp_coords.add(coord_class.Coordinate(x, y)) # Print the board for coord, value in self.black_squares.items(): board[coord.x][coord.y] = format[value] tmp_coords.remove(coord) for coord in self.shined_squares: board[coord.x][coord.y] = format['LIT'] if coord in tmp_coords: tmp_coords.remove(coord) for coord in bulbs: board[coord.x][coord.y] = format['BULB'] if coord in tmp_coords: tmp_coords.remove(coord) for coord in tmp_coords: board[coord.x][coord.y] = format['NOT_LIT'] for row in board: for item in row: print(item, end='') print() print()
def get_random_coord(self): """Returns a random coordinate ranging in the space (num_cols, num_rows).""" return coord_class.Coordinate(random.randint(0, self.num_rows - 1), random.randint(0, self.num_cols - 1))