Skip to content

Instantly share code, notes, and snippets.

@suzuki-kei
Created May 2, 2022 10:28
Show Gist options
  • Save suzuki-kei/e5de0fb484efd20d0327c3848b2cf167 to your computer and use it in GitHub Desktop.
Save suzuki-kei/e5de0fb484efd20d0327c3848b2cf167 to your computer and use it in GitHub Desktop.
"""
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