Fabfibシミュレータfor研究
Fabfibというゲームの研究用の(プレイ用でない)シミュレータを2017年くらいから書いています。プレイをしたい場合はFabfibの購入をよろしくお願いします。
Fabfibとは
自身のポートフォリオサイトに詳細を書いています。
動機
宣言(call)捨て札(discard)およびダウト(doubt)のそれぞれの戦略について興味を持ちました。
どこまで書いたか
自分がプレイする時にどのような戦略を採用しているかを参考に、それぞれに基本的な戦略を定義しました。それを元にゲームを回すところまでを書きました。
何を使ったか
依存している外部ライブラリはnumpyのみで、他はPythonの標準ライブラリとなっています。Pythonは3系です。
pip3 install numpy
なぜnumpyを使ったかと言えば、numpyの行列で戦況を格納すれば、分析するときに便利だと考えたからです。
Call Strategy
前の宣言よりも低い宣言は無効です。いつも前の宣言よりも高い手札を引けるとは限らないので、Call Strategyでは、状況に応じて嘘の宣言をすることが求められます。
「捨てた札を引き直した結果、より高い数字の札を引いた。」と見せかける戦略を基本的な戦略として採用しています。
問題は、どのくらい高い数字に見せかけるかです。これを数理的に考えると、ポアソン分布に基づく乱数のような構造をしていると考え、採用しました。
例えば、4を捨てて6を引いたと見せかけるより、9を引いたと見せかける方が心理的な障壁が高いと考えられます。確率を考えるとより低い数字を引く確率が高いとは言えませんが、次の人になんとか受け取ってもらおうという心理を考えると、一定の合理性があるように感じます。
採用したロジックはこんな感じです。
class BasicLogic(CallStrategy):
def __init__(self, Number, DiscardCount, Hand):
super().__init__(Number, DiscardCount, Hand)
def call_check(self):
"""Basic call_check method
>>> import numpy as np
>>> RegularCase = BasicLogic(431,1,np.array([6,3,1]))
>>> print(RegularCase.call_check() == 631)
True
>>> IregularCase = BasicLogic(831,1,np.array([6,3,1]))
>>> print(631 < IregularCase.call_check() <= 999)
True
>>> NineCase = BasicLogic(995,1,np.array([9,9,4]))
>>> print(995 < NineCase.call_check() <= 999)
True
>>> AllChange = BasicLogic(654,3,np.array([4,3,1]))
>>> print(654 < AllChange.call_check() <= 999)
True
"""
EvaluatedValue = super().evaluate_hand(self.Hand)
# if real hand's value is weak,
# Calling fake number based on Game's State
if self.Number >= EvaluatedValue:
# Leaves to change is based on NonNines Count of
# Game's Number or DiscardCount.
ToCall = np.array([int(i) for i in str(self.Number)])
ToCall = np.sort(ToCall) # ascending order
NonNines = len(np.where(ToCall != 9)[0])
LeavesToChange = min(self.DiscardCount, NonNines)
for i in range(0, LeavesToChange - 1):
ToCall[i] = np.random.poisson(lam=4)
if ToCall[i] > 9:
ToCall[i] = 9
ToCall[LeavesToChange - 1] += 1
ToCall[LeavesToChange - 1] += np.random.poisson(lam=0.2)
if ToCall[LeavesToChange - 1] > 9:
ToCall[LeavesToChange - 1] = 9
return super().evaluate_hand(np.sort(ToCall)[::-1])
else:
return EvaluatedValue
Discard Strategy
受け取った手札からどれを捨てるのかを決定する戦略です。
基本的な戦略として採用しているのはシンプルな戦略で、まず6より小さい数字は全て捨て、もし一番大きい数字と一番小さい数字の差が4未満であれば、9以外を全部捨てます。
例えば876という手札であれば、全て捨てて9を1枚以上引けば良いので、その確率にかけます。まずまずの順当な戦略と言えると思います。
採用したロジックはこんな感じです。
class BasicLogic(DiscardStrategy):
"""Players on BasicLogic strategy discard ...
less then 6, because a high possibility to draw any of 5-9.
If the difference of Max and Min is less then 4, other then nine.
To raise the posibility of 9.
"""
def __init__(self, Hand):
super().__init__(Hand)
self.Max = np.max(Hand)
self.Min = np.min(Hand)
def discard_check(self):
"""Basic discard check logic
>>> import numpy as np
>>> CloseCase = BasicLogic(np.array([6,5,4]))
>>> CloseCase.discard_check()
array([5, 4, 6])
"""
self.less_then(6)
if((self.Max - self.Min) < 4):
self.less_then(9)
return self.Discard
def less_then(self, Number):
self.Discard = np.append(self.Discard, self.Hand[self.Hand < Number])
self.Hand = self.Hand[self.Hand >= Number]
Doubt Strategy
宣言に対してダウトを仕掛ける戦略です。ダウトを仕掛ける場合、ダメージを受けるリスクを負うので、ダウトを仕掛ける際は慎重になります。嘘をついてる可能性が高いと判断しても、宣言されている数字が低ければ受けとることがあり得るでしょう。
つまりダウトを仕掛ける判断には、宣言されている数字が重要になります。以下の式を用いてダウト指数とし、状況を加味してそれを補正する方法を採用しました。
採用したロジックはこんな感じです。
class BasicLogic(DoubtStrategy):
def __init__(self, Call, DiscardCount):
super().__init__(Call, DiscardCount)
def doubt_check(self):
"""Basic doubt_check
>>> import numpy as np
>>> TwoDiscardCase = BasicLogic(992,2)
>>> type(TwoDiscardCase.doubt_check())
<class 'bool'>
>>> ThreeDiscardCase = BasicLogic(982,3)
>>> type(ThreeDiscardCase.doubt_check())
<class 'bool'>
"""
DoubtIndex = 0.0
DoubtIndex += (np.random.rand() * 0.2 + 0.8) * \
(((self.Call - 700) / 300) ** 3)
if self.DiscardCount == 2:
DoubtIndex += 0.1
if self.Call >= 990:
DoubtIndex += 0.6
if self.DiscardCount == 3:
DoubtIndex += 0.3
if self.Call >= 900:
DoubtIndex += 0.3
if DoubtIndex > 0.85:
self.Doubt = True
return self.Doubt
動かしかた
numpyがインストールされていることを前提とします。cloneします。
git clone https://github.com/say-ya-sigma/fabfib.git
cd fabfib
game.py
で動かします。引数はゲーム数です。
python3 game.py 10