def test_2normalize(self): logger.info("test_2normalize") rows = [1, 0, 3, 2, 1] gs = GameState(rows) self.assertEqual(gs.get_rows(), rows) self.assertEqual(str(gs), "[10321]") self.assertFalse(gs.is_normalized()) p = gs.normalize() self.assertTrue(gs.is_normalized()) self.assertEqual(gs.get_rows(), [0, 1, 1, 2, 3]) gs.denormalize(p) self.assertEqual(gs.get_rows(), rows)
def solve(game_state: GameState, level: int) -> (GameMove or None, int): """Compute the next move. :param game_state: a valid game_state :param level: the smartness level, must be in 0..2. :return: result[0] the game-move result[1] game continues, int in [-1, 0, 1, 2, 3] -1 : "You won". Occurs when input game_state contains exactly 1 match. In this case, result[0] == None. In all other cases result[0] != None. 0 : "I won". Occurs when game_state after making the move specified by result[0] contains exactly 1 match. 1 : game continues -- no further information 2 : game continues -- you have a safe strategy to win. 3 : game continues -- your opponent (i.e. I) has a safe strategy to win :raise: Error, if level invalid. """ Error.check(0 <= level <= 2, "level must be an integer in 0..2") rows = game_state.get_rows() # sub functions def random_move() -> GameMove: """Choose randomly one of the possible moves.""" non_zeros = [k for k in range(5) if rows[k] > 0] # all indices with value > 0 row_index = rand.choice(non_zeros) # choose such index max_n = min( 3, rows[row_index]) # max number of matches to be taken at this index if len( non_zeros ) == 1: # special case: only this row has matches --> must not make_move all max_n = min(max_n, rows[row_index] - 1) match_count = rand.randint( 1, max_n) # choose number of matches at this index return GameMove(row_index, match_count) def most_first() -> GameMove: """Choose a row with the most matches and take as many matches as possible.""" p = game_state.normalize() sorted_rows = game_state.get_rows() match_count = min(3, sorted_rows[4]) # max number of matches if sorted_rows[ 3] == 0: # special case: only this row has matches --> must not take all match_count = min(match_count, sorted_rows[4] - 1) row_index = p(4) return GameMove(row_index, match_count) def best_move() -> (GameMove, int): """Choose best possible move, if several exist, choose one randomly. Return also the winning flag of the resulting node. """ p = game_state.normalize() node = current_tree().find(game_state) _game_move, _winning = node.select_move() _game_move.row_index = p(_game_move.row_index) return _game_move, _winning # todo: new intermediate level between 1 and 2: start randomly, switch to best when more than half of # the matches have been taken. # main body continued # init to "you won" game_move = None game_continues = -1 # not "you won" if sum(rows) > 1: # choose an algorithm winning = 0 if level == 0: game_move = random_move() elif level == 1: game_move = most_first() else: game_move, winning = best_move() assert 1 <= game_move.match_count <= min(3, rows[game_move.row_index]) # check whether I won or game continues assert sum(rows) - game_move.match_count > 0 if sum(rows) - game_move.match_count > 1: game_continues = 1 if winning == 1: game_continues = 2 elif winning == -1: game_continues = 3 else: # I won game_continues = 0 # you won else: assert sum(rows) == 1 return game_move, game_continues