Created
May 2, 2022 10:28
-
-
Save suzuki-kei/e5de0fb484efd20d0327c3848b2cf167 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
High and Low における戦略別の的中率を求める. | |
背景 | |
---- | |
あるゲーム内のミニゲームに High and Low があった. | |
* 連続的中回数に応じてアイテムを獲得できる. | |
* アイテムの獲得は 1 日 1 回までであり, 毎日リセットされる. | |
* 1 回目にコインを支払えば, 2 回目以降は無料でプレイできる. | |
High または Low を予想する戦略はいくつか考えられる. | |
(A) それまでに開かれたカードを考慮する戦略. | |
的中率は高いが, それまでに開かれたカードを覚えなければならない. | |
(B) 直前に開かれたカードだけ考慮する戦略. | |
的中率は下がるが, 覚えておくことが少ない. | |
ゲームは何回プレイしてもデメリットは無いため, | |
もし (A) と (B) の的中率に大きな違いがないなら, | |
人間にとっては (B) の戦略を採用することが良い選択かもしれない. | |
ゲームのルール | |
-------------- | |
トランプの 1 から 13 のカードを用いる. | |
トランプはシャッフルされ, 伏せられた状態でゲームは開始する. | |
1. 伏せられたトランプの一番上のカードを開く. | |
2. 次のカードの数字が大きいか小さいか予想する. | |
3. 予想が的中すれば 1. に戻り, 外れた場合はゲームを終了する. | |
""" | |
import abc | |
import operator | |
import random | |
def main(): | |
new_players = [Player1, Player2, Player3] | |
success_counts = {i: [] for i in range(0, 13)} | |
simulation_size = 100000 | |
for new_player in new_players: | |
for key, value in simulate(new_player, simulation_size).items(): | |
success_counts[key].append(value) | |
print(f"連続的中数 => [{', '.join([_.__name__ for _ in new_players])}]") | |
for key, value in success_counts.items(): | |
print(f"{key} => {value}") | |
def simulate(new_player: callable, n: int) -> dict[int, int]: | |
""" | |
指定されたプレイヤーアルゴリズムでシミュレーションする. | |
Arguments | |
--------- | |
new_player: callable | |
新しいプレイヤーを生成するファクトリー. | |
n: int | |
シミュレーションの試行回数. | |
Returns | |
------- | |
dict[int, int] | |
{連続的中回数: それが発生した回数} である dict. | |
""" | |
success_counts = {i: 0 for i in range(0, 13)} | |
for i in range(n): | |
game = HighAndLowGame() | |
player = new_player() | |
try: | |
while True: | |
player.guess(game) | |
except GameIsOver: | |
success_counts[game.success_count] += 1 | |
return success_counts | |
class GameIsOver(Exception): | |
""" | |
ゲームが終了していることを意味する例外. | |
""" | |
class HighAndLowGame(object): | |
""" | |
High and Low のゲーム進行を管理する. | |
""" | |
@classmethod | |
def new_cards(self) -> list[int]: | |
""" | |
シャッフルされた新しいカードを生成する. | |
""" | |
cards = list(range(1, 14)) | |
random.shuffle(cards) | |
return cards | |
def __init__(self): | |
""" | |
インスタンスを初期化する. | |
""" | |
# シャッフル済みのカード. | |
self._cards = self.new_cards() | |
# 最後に開かれたカード (self._cards のインデックス). | |
self._index = 0 | |
# High and Low の予想が失敗した場合に True. | |
self._failed = False | |
# 連続的中数. | |
self._success_count = 0 | |
@property | |
def current_card(self) -> int: | |
""" | |
現在開かれているカード. | |
""" | |
return self._cards[self._index] | |
@property | |
def success_count(self) -> int: | |
""" | |
連続的中数. | |
""" | |
return self._success_count | |
def high(self) -> bool: | |
""" | |
次のカードが現在のカードより大きいと予想する. | |
Returns | |
------- | |
bool | |
予想が的中した場合は True, 外れた場合は False. | |
""" | |
return self._guess(operator.gt) | |
def low(self) -> bool: | |
""" | |
次のカードが現在のカードより小さいと予想する. | |
Returns | |
------- | |
bool | |
予想が的中した場合は True, 外れた場合は False. | |
""" | |
return self._guess(operator.lt) | |
def _guess(self, lt_or_gt: callable) -> bool: | |
""" | |
次のカードが現在のカードより大きいか小さいか予想する. | |
Arguments | |
--------- | |
lt_or_gt: callable | |
operator.lt または operator.gt を指定する. | |
次のカードが小さいと予想する場合は operator.lt を指定する. | |
次のカードが大きいと予想する場合は operator.gt を指定する. | |
Returns | |
------- | |
bool | |
予想が的中した場合は True, 外れた場合は False. | |
""" | |
if self._failed or self._index >= len(self._cards) - 1: | |
raise GameIsOver() | |
self._index += 1 | |
if lt_or_gt(self._cards[self._index], self._cards[self._index - 1]): | |
self._success_count += 1 | |
return True | |
else: | |
self._failed = True | |
return False | |
class Player(abc.ABC): | |
""" | |
プレイヤーの抽象クラス. | |
""" | |
@abc.abstractmethod | |
def guess(self, game: HighAndLowGame) -> None: | |
""" | |
High か Low を予想し, | |
game.high() または game.low() を呼び出す. | |
Arguments | |
--------- | |
game: HighAndLowGame | |
ゲームを管理する HighAndLowGame オブジェクト. | |
""" | |
def _guess_randomely(self, game: HighAndLowGame) -> None: | |
""" | |
ランダムに予想する. | |
""" | |
if random.random() < 0.5: | |
game.high() | |
else: | |
game.low() | |
class Player1(Player): | |
""" | |
ランダムに High または Low を選択するプレイヤー. | |
""" | |
def guess(self, game: HighAndLowGame): | |
self._guess_randomely(game) | |
class Player2(Player): | |
""" | |
直前に開かれたカードだけを考慮するプレイヤー. | |
""" | |
def guess(self, game: HighAndLowGame): | |
if game.current_card <= 6: | |
game.high() | |
if game.current_card >= 8: | |
game.low() | |
if game.current_card == 7: | |
self._guess_randomely(game) | |
class Player3(Player): | |
""" | |
過去に開かれたカードを考慮するプレイヤー. | |
""" | |
def __init__(self): | |
self._closed_cards = list(range(1, 14)) | |
def guess(self, game: HighAndLowGame): | |
self._closed_cards.remove(game.current_card) | |
is_lower = lambda card: card < game.current_card | |
lower_cards = list(filter(is_lower, self._closed_cards)) | |
is_higher = lambda card: card > game.current_card | |
higher_cards = list(filter(is_higher, self._closed_cards)) | |
if len(lower_cards) > len(higher_cards): | |
game.low() | |
if len(lower_cards) < len(higher_cards): | |
game.high() | |
if len(lower_cards) == len(higher_cards): | |
self._guess_randomely(game) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment