class OptimalPlaySimulationTests(unittest.TestCase):
	def setUp(self):
		self.action_selector = OptimalActionSelector()
	def tearDown(self):
		pass

	def testExpectedScoreOfThreeDieThrow(self):
		state = SearchState(0, 8, 2, 1, 0)
		expected_score = self.action_selector.expected_score(state)

		state = SideDiceState({DieFace.Ray: 8, DieFace.Cow: 2})
		num_runs = 100000
		scores = [
			(key, sum(1 for _ in iter))
			for key, iter in itertools.groupby(
				sorted(play_turn(self.action_selector, ini_side_dice = state)
				for _ in range(num_runs))
			)
		]
		simulated_score = sum(score * count for score, count in scores) / num_runs
		print(scores, simulated_score, expected_score)
		self.assertAlmostEqual(expected_score, simulated_score, delta = 0.01)

	def testExpectedScoreOfThreeDieThrow2(self):
		state = SearchState(4, 4, 2, 1, 0)
		expected_score = self.action_selector.expected_score(state)

		state = SideDiceState({ DieFace.Tank: 4, DieFace.Ray: 4, DieFace.Chicken: 2 })
		num_runs = 100000
		scores = [
			(key, sum(1 for _ in iter))
			for key, iter in itertools.groupby(
				sorted(play_turn(self.action_selector, ini_side_dice = state)
				for _ in range(num_runs))
			)
		]
		simulated_score = sum(score * count for score, count in scores) / num_runs
		print(scores, simulated_score, expected_score)
		self.assertAlmostEqual(expected_score, simulated_score, delta = 0.01)

	def testSimulator(self):
		state = SideDiceState({ DieFace.Ray: 9, DieFace.Cow: 2})
		num_runs = 100000
		scores = [
			(key, sum(1 for _ in iter))
			for key, iter in itertools.groupby(
				sorted(play_turn(self.action_selector, ini_side_dice = state)
				for _ in range(num_runs))
			)
		]
		print(scores)
		avg_score = sum(score * count for score, count in scores) / num_runs
		print(avg_score)
		self.assertAlmostEqual(avg_score, (314 / 3) / 36, delta = 0.01)
class OptimalPlayUnitTests(unittest.TestCase):
    def setUp(self):
        self.action_selector = OptimalActionSelector(consider_win_score=True)

    def tearDown(self):
        pass

    def testExpectedScoreOfOneDieThrow(self):
        state = SearchState(0, 10, 2, 1, 0)
        expected_score = self.action_selector.expected_score(state)
        self.assertAlmostEqual(expected_score, (2 * 3 + 4 * 2) / 6)

    def testExpectedScoreOfOneDieThrow2(self):
        state = SearchState(0, 10, 2, 2, 0)
        expected_score = self.action_selector.expected_score(state)
        self.assertAlmostEqual(expected_score, (1 * 6 + 5 * 2) / 6)

    def testExpectedScoreOfOneDieThrow3(self):
        state = SearchState(5, 5, 2, 1, 0)
        expected_score = self.action_selector.expected_score(state)
        self.assertAlmostEqual(expected_score, (2 * 3 + 3 * 2 + 1 * 0) / 6)

    def testExpectedScoreOfOneDieThrow4(self):
        state = SearchState(0, 9, 3, 2, 0)
        expected_score = self.action_selector.expected_score(state)
        self.assertAlmostEqual(expected_score, (1 * 7 + 5 * 3) / 6)

    def testExpectedScoreOfOneDieThrow5(self):
        state = SearchState(0, 10, 2, 1, 0)
        expected_score = self.action_selector.expected_score(state)
        self.assertAlmostEqual(expected_score, (2 * 3 + 4 * 2) / 6)

    def testExpectedScoreOfTwoDieThrow1(self):
        state = SearchState(0, 9, 2, 1, 0)
        expected_score = self.action_selector.expected_score(state)
        self.assertAlmostEqual(expected_score, (314 / 3) / 36)

    def testExpectedScoreOfTwoDieThrow2(self):
        state = SearchState(3, 3, 5, 2, 0)
        expected_score = self.action_selector.expected_score(state)
        self.assertAlmostEqual(expected_score, (10 * 1 + 9 * 8 + 5 * 12 +
                                                (5 + 2 / 3) * 8 + 0 * 7) / 36)

    def testExpectedScoreOfTwoDieThrow3(self):
        # Similar to previous, but in this case, best move is to pass
        state = SearchState(2, 2, 7, 2, 0)
        expected_score = self.action_selector.expected_score(state)
        self.assertAlmostEqual(expected_score, 7)

    def testExpectedScoreOfTwoDieThrow_NoAsserts1(self):
        state = SearchState(5, 6, 0, 0, 0)
        # Should not fail any assert built-in asserts
        expected_score = self.action_selector.expected_score(state)

    def testExpectedScoreOfTwoDieThrow_NoAsserts2(self):
        state = SearchState(0, 8, 3, 2, 0)
        # Should not fail any assert built-in asserts
        expected_score = self.action_selector.expected_score(state)

    def testExpectedScoreOfFourDie(self):
        state = SearchState(5, 4, 0, 0, 0)
        # Should not fail any assert built-in asserts
        expected_score = self.action_selector.expected_score(state)

    def testExpectedScoreOfFiveDie(self):
        state = SearchState(5, 0, 3, 2, 0)
        # Should not fail any assert built-in asserts
        expected_score = self.action_selector.expected_score(state)

    def testShouldStopWhenWinningEvenWhenOddsAreGood(self):
        state = TurnState(side_dice=SideDiceState({
            DieFace.Ray: 5,
            DieFace.Cow: 2
        }))

        self.assertTrue(self.action_selector.should_stop(state, 1))
        self.assertTrue(self.action_selector.should_stop(state, 2))
        self.assertFalse(self.action_selector.should_stop(state, 3))
        self.assertFalse(self.action_selector.should_stop(state))

    def testDieChoiceDependsOnWinScore1(self):
        # State with four different choices depending on win score
        state = TurnState(side_dice=SideDiceState({DieFace.Tank: 2}),
                          throw=DiceThrow({
                              DieFace.Ray: 2,
                              DieFace.Cow: 2,
                              DieFace.Human: 3,
                              DieFace.Chicken: 4
                          }))

        self.assertEqual(DieFace.Chicken,
                         self.action_selector.select_die(state))
        self.assertEqual(DieFace.Ray,
                         self.action_selector.select_die(state, 1))
        self.assertEqual(DieFace.Cow,
                         self.action_selector.select_die(state, 2))
        self.assertEqual(DieFace.Human,
                         self.action_selector.select_die(state, 3))

    def testDieChoiceDependsOnWinScore2(self):
        # State where choice deviates only for a couple of higher win scores.
        # It is more typical that there is a deviation for lower win scores up until a limit.
        state = TurnState(side_dice=SideDiceState({DieFace.Tank: 1}),
                          throw=DiceThrow({
                              DieFace.Ray: 4,
                              DieFace.Cow: 1,
                              DieFace.Human: 3,
                              DieFace.Chicken: 4
                          }))

        self.assertEqual(DieFace.Ray, self.action_selector.select_die(state))
        self.assertEqual(DieFace.Ray,
                         self.action_selector.select_die(state, 3))

        self.assertEqual(DieFace.Chicken,
                         self.action_selector.select_die(state, 4))
        self.assertEqual(DieFace.Chicken,
                         self.action_selector.select_die(state, 5))

        score_ray0 = self.action_selector.expected_score(
            SearchState(1, 4, 0, 0, 0))
        score_ray4 = self.action_selector.expected_score(
            SearchState(1, 4, 0, 0, 4))
        score_kip0 = self.action_selector.expected_score(
            SearchState(1, 0, 4, 1, 0))
        score_kip4 = self.action_selector.expected_score(
            SearchState(1, 0, 4, 1, 4))

        self.assertGreater(score_ray0, score_kip0)
        self.assertGreater(score_kip4, score_ray4)
        self.assertGreaterEqual(score_ray0, score_ray4)
        self.assertGreaterEqual(score_kip0, score_kip4)

    def testDieChoiceDependsOnWinScore3(self):
        # State where choice deviates for low and high, but not medium scores
        state = TurnState(side_dice=SideDiceState({DieFace.Tank: 1}),
                          throw=DiceThrow({
                              DieFace.Ray: 5,
                              DieFace.Cow: 1,
                              DieFace.Human: 2,
                              DieFace.Chicken: 4
                          }))

        self.assertEqual(DieFace.Chicken,
                         self.action_selector.select_die(state))
        self.assertEqual(DieFace.Chicken,
                         self.action_selector.select_die(state, 4))
        self.assertEqual(DieFace.Chicken,
                         self.action_selector.select_die(state, 5))
        self.assertEqual(DieFace.Chicken,
                         self.action_selector.select_die(state, 6))

        self.assertEqual(DieFace.Ray,
                         self.action_selector.select_die(state, 1))
        self.assertEqual(DieFace.Ray,
                         self.action_selector.select_die(state, 2))
        self.assertEqual(DieFace.Ray,
                         self.action_selector.select_die(state, 3))
        self.assertEqual(DieFace.Ray,
                         self.action_selector.select_die(state, 7))
        self.assertEqual(DieFace.Ray,
                         self.action_selector.select_die(state, 8))
예제 #3
0
class HumanPlayer:
    def __init__(self, show_hint=False):
        self.hint_generator = OptimalActionSelector() if show_hint else None

    def show_options(self, options):
        items = []
        for option in options:
            items.append("[%s] %s" % (die2key[option], option.name))

        print(" ".join(items))

    def show_hint(self, state: TurnState):
        num_earthling_types = len(state.side_dice.collected_earthlings)
        scores = [(self.hint_generator.expected_score(
            SearchState(state.side_dice[DieFace.Tank],
                        state.side_dice[DieFace.Ray],
                        state.side_dice.num_earthlings + action,
                        num_earthling_types + 1)), action) for action in
                  list(set(
                      state.throw[x] for x in state.selectable_earthlings))]
        if state.throw[DieFace.Ray] > 0:
            scores.append((self.hint_generator.expected_score(
                SearchState(
                    state.side_dice[DieFace.Tank],
                    state.side_dice[DieFace.Ray] + state.throw[DieFace.Ray],
                    state.side_dice.num_earthlings, num_earthling_types)), 0))

        for score, action in sorted(scores, key=lambda x: x[0], reverse=True):
            if action > 0:
                die = next(die for die in state.selectable_earthlings
                           if state.throw[die] == action)
                choice = "%d [%s] Earthling%s" % (action, die2key[die],
                                                  "s" if action > 1 else "")
            else:
                choice = "%d [R] Ray%s" % (state.throw[
                    DieFace.Ray], "s" if state.throw[DieFace.Ray] > 1 else "")
            print("%.3f %s" % (score, choice))

    def select_die(self, state: TurnState):
        options = state.selectable_earthlings
        if state.throw[DieFace.Ray] > 0:
            options.append(DieFace.Ray)

        if self.hint_generator is not None:
            self.show_hint(state)
        else:
            self.show_options(options)

        while True:
            key = input("Your choice : ").upper()
            if key in key2die:
                return key2die[key]

    def should_stop(self, state: TurnState):
        while True:
            choice = input("Continue (Y/N)? : ").upper()
            if choice == "Y" or choice == "N":
                return choice == "N"

    def __str__(self):
        return "HumanPlayer"