def perform_simulation(self, state): ''' Performs the simulation until legal moves are available. If simulation ends by finding a solution, a root state starting from this simulation is returned ''' solution_tiles_order = [] depth = 0 simulation_root_state = state # in case simulation ends in solution; these states are the solution if len(state.tiles) == 0: print('perform_simulation called with empty tiles') return ALL_TILES_USED, simulation_root_state, solution_tiles_order val = self.val while True: val += 1 if len(state.tiles) == 0: print('solution found in simulation') return ALL_TILES_USED, simulation_root_state, solution_tiles_order valid_moves = SolutionChecker.get_valid_next_moves(state, state.tiles, val=val, colorful_states=self.colorful_states) if not valid_moves: return depth, simulation_root_state, solution_tiles_order next_random_tile_index = random.randint(0, len(valid_moves) -1) success, new_board = SolutionChecker.get_next_turn( state, valid_moves[next_random_tile_index], val, destroy_state=True, colorful_states=self.colorful_states ) self.n_tiles_placed += 1 solution_tiles_order.append(valid_moves[next_random_tile_index]) if success == ALL_TILES_USED: print('grid is full') # no LFB on grid; probably means grid is full solution_tiles_order.append(valid_moves[next_random_tile_index]) return ALL_TILES_USED, simulation_root_state, solution_tiles_order elif success == NO_NEXT_POSITION_TILES_UNUSED: print('no next position with unused tiles') return depth, simulation_root_state, solution_tiles_order elif success == TILE_CANNOT_BE_PLACED: # cannot place the tile. return depth reached return depth, simulation_root_state, solution_tiles_order else: new_tiles = SolutionChecker.eliminate_pair_tiles(state.tiles, valid_moves[next_random_tile_index]) new_state = State(board=new_board, tiles=new_tiles, parent=state) new_state.score = -1 # because no choice is performed for sequent actions state.children.append(new_state) state = new_state depth += 1 return depth, simulation_root_state, solution_tiles_order
def play_using_prediction(nnet, width, height, tiles, grid, n_tiles, dg, predict_move_index=False, verbose=False, scalar_tiles=False): if scalar_tiles: tiles = tiles else: tiles = state_to_tiles_dims(tiles, dg) while True: tiles_left = len(tiles) if tiles_left == 0: print( f"Success: game ended with {tiles_left / ORIENTATIONS} tiles left unplaced." ) return 0 _tiles_ints = SolutionChecker.get_possible_tile_actions_given_grid( grid, tiles) if len(_tiles_ints) == 0 and tiles_left: print( f"game ended with {tiles_left / ORIENTATIONS} tiles left unplaced." ) return tiles_left / ORIENTATIONS np.random.shuffle(_tiles_ints) if scalar_tiles: _tiles = tiles_to_np_array( SolutionChecker.pad_tiles_with_zero_scalars( _tiles_ints, ORIENTATIONS * n_tiles - len(_tiles_ints))) # state = state.squeeze() prediction = nnet.predict([grid, tiles_to_np_array(_tiles)]) else: tiles_in_matrix_shape = dg._transform_instance_to_matrix( _tiles_ints, only_one_orientation=True) tiles_in_matrix_shape = pad_tiles_with_zero_matrices( tiles_in_matrix_shape, n_tiles * ORIENTATIONS - tiles_in_matrix_shape.shape[2], width, height) state = np.dstack((np.expand_dims(grid, axis=2), tiles_in_matrix_shape)) prediction = nnet.predict(state) if not predict_move_index: if len(prediction) == 2: # if we are also predicting v prediction, v = prediction prediction = np.reshape(prediction, (width, height)) # get the probability matrix prediction = get_prediction_masked(prediction, state[:, :, 0]) if verbose: print('-' * 50) print(grid, prediction) print(tiles) if scalar_tiles: _ttiles = tiles else: _ttiles = state_to_tiles_dims(state, dg) solution_tile = get_best_tile_by_prediction( grid, _ttiles, prediction, dg, predict_move_index=predict_move_index) if verbose: print(solution_tile) if solution_tile is None: print( f"game ended with {tiles_left / ORIENTATIONS} tiles left unplaced." ) return tiles_left / ORIENTATIONS success, grid = SolutionChecker.place_element_on_grid_given_grid( solution_tile[0], solution_tile[1], val=1, grid=grid, cols=width, rows=height) if not success: print( f"game ended with {tiles_left / ORIENTATIONS} tiles left unplaced." ) return tiles_left / ORIENTATIONS if scalar_tiles: tiles = [tuple(x) for x in tiles] tiles = SolutionChecker.eliminate_pair_tiles(tiles, solution_tile[0]) return 0
def predict(self, temp=1, N=3000): initial_state = self.state state = self.state available_tiles = state.tiles prev_state = state solution_found = False depth = 0 self.val = 1 while len(state.tiles): tile_placed = False states = [] print(len(state.tiles)) best_score = 0 for i, tile in enumerate(state.tiles): success, new_board = SolutionChecker.get_next_turn(state, tile, self.val, destroy_state=False) self.n_tiles_placed += 1 if success == ALL_TILES_USED: print('solution found!') solution_found = True return initial_state, depth, solution_found if success == TILE_CANNOT_BE_PLACED: # cannot place the tile. this branch will not be considered states.append(None) continue else: tile_placed = True new_tiles = SolutionChecker.eliminate_pair_tiles(state.tiles, tile) new_state = State( board=new_board, tiles=new_tiles, parent=state) state.children.append(new_state) simulation_result, solution_tiles_order = self.perform_simulations(new_state, N=N, record_simulations=False) if simulation_result == ALL_TILES_USED: print('solution found in simulation!') print(tile) solution_found = True # update scores of states found in simulation new_state.score = len(state.tiles) / ORIENTATIONS - 1 _state = new_state.children[0] while _state.children: _state.score = (len(_state.tiles) / ORIENTATIONS) - 1 _state = _state.children[0] if state.tile_placed: self.solution_tiles_order.extend([state.tile_placed] + [tile] + solution_tiles_order) else: self.solution_tiles_order.extend([tile] + solution_tiles_order) return initial_state, depth, solution_found new_state.score = simulation_result if new_state.score > best_score: best_tile = tile best_score = new_state.score new_state.tile_placed = tile state.solution_tiles_order.append(tile) states.append(new_state) if not tile_placed: # no tile was placed, it's a dead end; end game return initial_state, depth, solution_found # PERFORMANCE: # for visualization this can be changed # all tiles will be 1 inside the frame for performance reasons self.val += 1 depth += 1 best_action = get_max_index(states) prev_state = state new_state = states[best_action] print(best_tile, prev_state.tile_placed) if prev_state.tile_placed: self.solution_tiles_order.append(prev_state.tile_placed) state = new_state print('Solution found!') solution_found = True return initial_state, depth, solution_found
def test_eliminate_pair_tiles_when_pair_is_not_present(self): tiles_list = [(1, 8), (2, 1), (1, 1), (1, 1), (2, 1), (1, 1)] with self.assertRaises(ValueError): SolutionChecker.eliminate_pair_tiles(tiles_list, tile_to_remove=(1, 8))
def test_eliminate_pair_tiles_3(self): tiles_list = [(1, 1), (2, 1), (1, 1), (1, 1), (2, 1), (1, 1)] new_tiles = SolutionChecker.eliminate_pair_tiles(tiles_list, tile_to_remove=(1, 1)) self.assertEqual(new_tiles, [(2, 1), (1, 1), (2, 1), (1, 1)])