paloma blog

NWエンジニアやってます。主に自宅環境のお遊びを書きます。Pythonもちょっと。タイトルは好きなカクテルから。

pythonのPillowライブラリで画像の切り貼り 切り抜き編

前回行ったスリーカードポーカーの実践結果ですが、
キャプチャファイルから画像を切り貼りしたのでその手順を残しておきます。

50枚の結果を、ある部分だけ1枚にまとめたかったのですが、
手動でコピペは面倒くさそうだったのでこれもpythonで解決することにしました。

今回使ったライブラリは画像処理ライブラリのPillowです。

準備

環境

ゲームのキャプチャファイルの関係でメインWindows機で行います。

> $psversiontable

Name                           Value
----                           -----
PSVersion                      5.1.18362.752
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.18362.752
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

ゲーミングPCなので余計なツールは入れないようにしていますが、今回Pillowのためにpythonを入れました。

> python --version
Python 3.8.3
Pillowインスト―ル
> python -m pip install Pillow

特に仮想環境作る必要もないので元のpythonに入れます。
バージョンは7.2.0です。

> python -m pip list
Package    Version
---------- -------
Pillow     7.2.0
pip        19.2.3
setuptools 41.2.0
WARNING: You are using pip version 19.2.3, however version 20.1.1 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.

また、50枚のキャプチャファイルは編集用フォルダにおいて置くものとします。

今回は単発作業のためターミナルで作業します。
最初に以下のモジュールを読み込んでおきます。

>>> import os
>>> from PIL import Image
>>> import glob # 後で使います

切り取り編

ファイルの切り取り

まずは試しに1つだけ切り取る練習です。

ファイル出力

出力する方法はいくつかありますが、osモジュールのlistdir関数にしました。
作業フォルダに対象のファイルしかないので。

>>> os.listdir()
['Grand Theft Auto V 2020-06-27 14-13-01_Moment(10).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(11).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(12).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(13).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(14).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(15).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(16).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(17).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(18).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(19).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(2).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(20).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(21).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(22).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(23).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(24).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(25).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(26).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(27).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(28).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(29).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(3).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(30).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(31).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(32).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(33).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(34).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(35).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(36).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(37).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(38).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(39).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(4).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(40).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(41).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(42).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(43).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(44).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(45).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(46).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(47).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(48).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(49).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(5).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(50).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(51).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(6).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(7).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(8).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(9).jpg']

HDサイズでちゃんと取れてますね。

>>> file = os.listdir()[0]
>>> im = Image.open(file)
>>> im
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=1920x1080 at 0xB85B38>
>>> im.show()

f:id:paloma69:20200714223626p:plain

切り取り

特定の範囲だけ切り取りたいのでcrop関数を使います。

:param box: The crop rectangle, as a (left, upper, right, lower)-tuple.

ヘルプを見るとx座標始点、y座標始点、x座標終点、y座標終点の順で書けばいいようですね。

右上のチップ表記を切り取るのですが、200 * 120のサイズがあれば良さそうなので下記で切り取ります。

>>> x, y = imfile.size # yは使いません
>>> chip = im.crop((x-200, 0, x, 120))
>>> chip.show()

f:id:paloma69:20200714223650p:plain

いい感じに切り取れました。
あとは同じ手順を繰り返すだけです。

ファイルを日付順にソート

os.listdir()はどういう順に格納されるかわかりませんが、キャプチャした順では無い様です。
ファイル名前はキャプチャツールの仕様で昇順で採番されますが、sorted関数ではバージョニングソートはされないようです。(調べられてませんがオプションはあるかも)
ここは他のケースにも対応すべく作成日時でソートします。

osモジュールのstat関数でファイル作成日付のパラメータが整数で取れるようです。

>>> os.stat(file).st_mtime
1594538628.4300964

全ファイルのこのパラメータを取得して昇順で並び替えればいいですね。

というわけで以下を実行。
ループでどうにかしようとしましたが、lambdaを使ってすっきり書かれている方がいたのでサイトを参考にしました。

>>> filelist = sorted(os.listdir(), key=lambda f: os.stat(f).st_mtime)
>>> filelist
['Grand Theft Auto V 2020-06-27 14-13-01_Moment(2).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(3).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(4).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(5).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(6).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(7).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(8).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(9).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(10).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(11).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(12).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(13).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(14).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(15).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(16).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(17).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(18).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(19).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(20).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(21).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(22).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(23).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(24).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(25).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(26).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(27).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(28).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(29).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(30).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(31).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(32).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(33).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(34).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(35).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(36).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(37).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(38).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(39).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(40).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(41).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(42).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(43).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(44).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(45).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(46).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(47).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(48).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(49).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(50).jpg', 'Grand Theft Auto V 2020-06-27 14-13-01_Moment(51).jpg']

OKです。さっきと並び順変わってますね。

ループで全ファイルを切り取り

上のfilelistをループでまわして各ファイルを切り取って保存します。
新規イメージファイルを作成して直接貼り付けてもいいんですが、切り分けのため作業をばらします。

n = 1
for a in filelist:
    imfile = Image.open(a)
    x, y = imfile.size
    cropfile = imfile.crop((x - 200, 0, x, 120))
    cropfile.save(''.join(['result_', str(n), '.png']))
    n += 1

これをターミナルに流します。

>>> for a in filelist:
...     imfile = Image.open(a)
...     x, y = imfile.size
...     cropfile = imfile.crop((x - 200, 0, x, 120))
...     cropfile.save(''.join(['result_', str(n), '.png']))
...     n += 1
...
できました!

新しいファイルが出来てしまったので、検索しやすくするためにglobを使います。

>>> glob.glob('result*.png')
['result_1.png', 'result_10.png', 'result_11.png', 'result_12.png', 'result_13.png', 'result_14.png', 'result_15.png', 'result_16.png', 'result_17.png', 'result_18.png', 'result_19.png', 'result_2.png', 'result_20.png', 'result_21.png', 'result_22.png', 'result_23.png', 'result_24.png', 'result_25.png', 'result_26.png', 'result_27.png', 'result_28.png', 'result_29.png', 'result_3.png', 'result_30.png', 'result_31.png', 'result_32.png', 'result_33.png', 'result_34.png', 'result_35.png', 'result_36.png', 'result_37.png', 'result_38.png', 'result_39.png', 'result_4.png', 'result_40.png', 'result_41.png', 'result_42.png', 'result_43.png', 'result_44.png', 'result_45.png', 'result_46.png', 'result_47.png', 'result_48.png', 'result_49.png', 'result_5.png', 'result_50.png', 'result_6.png', 'result_7.png', 'result_8.png', 'result_9.png']

こんな感じでチップだけ切り抜いたファイルがたくさん作れました。

f:id:paloma69:20200714224448p:plain

あとはこれを張り付けていくだけです。

貼り付けまで書きたかったのですが、長くなりそうなので分割します。
次回全ファイルを一枚に貼り付け編です。

使った関数のメモ

  • ライブラリインポート
from PIL import Image
  • 画像ファイル読み込み
imfile = Image.open('filename')
  • 読み込みサイズの取得
imfile.size
  • 画像の範囲を指定して切り取り
cropfile = imfile.crop((x - 200, 0, x, 120)) # x座標始点、y座標始点、x座標終点、y座標終点
  • 画像の保存
cropfile.save('filename')

参考サイト

ディレクトリ内のファイルリストを取得する – Pythonプログラミング物語

スリーカードポーカーで勝つことはできるのか 実践編

GTAオンラインでプレイしてみるまで存在も知らなかったスリーカードポーカーですが
一応自作ゲーム、シミュレータまで作成しました。

存在を知らしめた大元であるGTAオンラインで勝てるのか試したいと思います。

50回の勝負にします。 シミュレータの結果はこんな感じ。

masashi@PC-ubuntu:~/Three-card-poker$ python3 simulater.py 
You are first given $1,000.
Three card poker simulator start.
The simulator trials 50 times.
You'll bet ante $10 and pair plus $10 all the time.

=== Probabliry of winning or losing ===

Player 34 wins. (68.0%)
Dealer 16 wins. (32.0%)
Draw 0 times. (0.0%)

=== Percentage of Players all hands ===

High card!            70.0%
One Pair!             18.0%
Flash!                 8.0%
Straight!              4.0%

=== Percentage of Players win(34 times) hands ===

High card!          61.765%
One Pair!           23.529%
Flash!              11.765%
Straight!            2.941%

You finally got $2,230
masashi@PC-ubuntu:~/Three-card-poker$ for a in {1..10} ; do python3 simulater.py | tail -1 ; done 
You finally got $1,230
You finally got $1,540
You finally got $1,940
You finally got $1,750
You finally got $1,520
You finally got $2,430
You finally got $1,910
You finally got $1,400
You finally got $1,620
You finally got $1,620

本当にこの通り勝てるんならウハウハもんですね。

いざ勝負!

ダイヤモンドカジノにやってきました。

開始前のチップは17495です。
増やして帰ることはできるんでしょうか。

f:id:paloma69:20200712201328j:plain

シミュレータと同様にアンティ、ペアプラスを10ずつ賭けます。

結果

50回分のサマリです。
負けた時は賭けチップ分がそのまま引かれています。

左上から右下への順で張り付けてます。

f:id:paloma69:20200712201557p:plain

勝負後のチップは16995。
チップが500減ってしまいました…。

勝敗のまとめはこんな感じです。

f:id:paloma69:20200712202444p:plain

勝率50%ですが、今回は27回負けでした。
アプラスボーナスは25%で出る計算ですが、今回10回なので20%でした。
まあ今回の試行回数的にブレるのは仕方ないですね。

なぜ負けたのか…コードに大切な処理が抜けていた

ゲームしていてチップの減るときを見ていたのですが、
コードを見返したらチップを賭けた分を引く処理が抜けてました…。

  1. チップを賭ける
  2. カードのシャッフル
  3. 手札を配る
  4. 勝負する
  5. 払い戻し

の処理順なのですが、1の分を引く処理が抜けていました。
負けた時にチップを引く処理はあるのですが、勝った時は引かれずそのまま払い戻しも貰うという処理になっていました。
そりゃ勝てるよ...。

なぜ負けたか、ではなくて負けるべくして負けた感じですね。

修正

部分だけ切り抜いてもわかりづらいですが、賭けた分引く処理を追加。

diff --git a/pokerapp.py b/pokerapp.py
index 72c421b..ddf0e00 100644
--- a/pokerapp.py
+++ b/pokerapp.py
@@ -314,6 +314,9 @@ def main(chip):
        pay_ante = Payoff(bet_ante, p_hand[0]).ante_bonus()
        pay_pairplus = Payoff(bet_pairplus, p_hand[0]).pairplus_bonus()
 
+       # Reduce bet chips
+       chip = chip - (bet_ante + bet_play + bet_pairplus)
+
        # To reuse the chips for main loop
        global total
 
diff --git a/simulater.py b/simulater.py
index 9f13ad3..3888795 100644
--- a/simulater.py
+++ b/simulater.py
@@ -39,6 +39,9 @@ def Trials(chips):
                match_list.append(match_result)
                hand_list.append(hand_result)
 
+               # Reduce bet chips
+               chips = chips - (ante + bet_chip + pp)
+
                # pay off
                pays = pokerapp.Liquidation(match_result, dealer_hand, ante, bet_chip)

改めてシミュレーション

チップが0以下になったらループ抜けるようにして再度シミュレーションしてみます。

masashi@PC-ubuntu:~/Three-card-poker$ python3 simulater.py 
You are first given $1,000.
Three card poker simulator start.
The simulator trials 50 times.
You'll bet ante $10 and pair plus $10 all the time.

=== Probabliry of winning or losing ===

Player 27 wins. (54.0%)
Dealer 23 wins. (46.0%)
Draw 0 times. (0.0%)

=== Percentage of Players all hands ===

High card!            70.0%
One Pair!             24.0%
Straight!              4.0%
Flash!                 2.0%

=== Percentage of Players win(27 times) hands ===

High card!          51.852%
One Pair!           37.037%
Straight!            7.407%
Flash!               3.704%

You finally got $190
masashi@PC-ubuntu:~/Three-card-poker$ for a in {1..10} ; do python3 simulater.py | tail -1 ; done 
You finally got $-40
You finally got $-10
You finally got $-100
You finally got $970
You finally got $220
You finally got $360
You finally got $-10
You finally got $230
You finally got $-230
You finally got $400

普通に負ける時があります。
まあ、ギャンブルってこんなもんですよね。

一旦チップが0以下になったら止める処理を入れたのですが、マイナス表記になってしまってますね。
手持ち以上賭けられないように修正しないと。

検証結果: うまい話は無い

期待をもって挑んだ実践ですが、やはり楽に儲ける方法は無いようです。
地道に稼ぎましょう。

むすび

最初に今回の部分のコードをきっちり書けていれば、今回の検証は発生しなかったと思います。
とんでもないデバッグ方法になってしまいましたが、重大なバグを潰せたので良しとしますw

しかし本当に勝つことは出来ないのでしょうか。
オリるケースも考慮してシミュレータ組み直したいですね。

リポジトリ

github.com

2020年上半期見た映画メモ & twitter取得ツール改修

気づけばもう7月に入ってしまいました。

今年はコロナ騒ぎですが、私は4月からリモートワーク含んだ外出自粛生活が始まりました。

上半期の半分は自粛生活ということになりますね。

とは言っても、もともとインドアな人間なのであまり生活に変化はありません。
映画も変わらず週一本ペースで見ています。

というわけで上半期見た映画を振り返ります。

以前作ったtwitter投稿ツール & 取得ツールでいつでも振り返ることができます。

さあ上半期を振り返りましょう

ツールの仕様上原題で申し訳ないですが、見た映画です。
有名なtweepyと言うライブラリを使って取得してます。

(python3) masashi@PC-ubuntu:~/movietweet$ python gettw.py 
2020-07-05 00:12:09 The Green Hornet
2020-06-28 21:20:11 Charlie's Angels: Full Throttle
2020-06-27 22:21:16 Charlie's Angels
2020-06-14 22:47:09 Girls Trip
2020-06-07 08:46:58 Miami Vice
2020-05-30 22:57:37 You Don't Mess with the Zohan
2020-05-23 23:24:04 The Magic of Belle Isle
2020-05-16 23:08:08 Indiana Jones and the Temple of Doom
2020-05-09 23:00:40 Pixels
2020-05-03 22:49:06 The House
2020-04-25 23:01:03 Next
2020-04-18 23:28:44 Molly's Game
2020-04-04 22:59:26 Lowriders
2020-03-28 23:31:46 Pain &amp; Gain
2020-03-21 23:34:21 American Made
2020-03-14 23:05:17 Donnie Brasco
2020-03-08 00:17:50 Midnight Run
2020-02-23 22:16:40 Reservoir Dogs

あら、1月分が入ってないですね。
apiのcount値を増やしても変わらずです。

pageの値を増やしたら取得することができました。
countとpageはどう違うんですかね?
...と思ったら自分で書いてました。
countだけだと200件までしか取れないようです。

とりあえず5ページ分取るようにしたのでしばらくは大丈夫そうです。

(python3) masashi@PC-ubuntu:~/movietweet$ python gettw.py 
2020-07-05 00:12:09 The Green Hornet
2020-06-28 21:20:11 Charlie's Angels: Full Throttle
2020-06-27 22:21:16 Charlie's Angels
2020-06-14 22:47:09 Girls Trip
2020-06-07 08:46:58 Miami Vice
2020-05-30 22:57:37 You Don't Mess with the Zohan
2020-05-23 23:24:04 The Magic of Belle Isle
2020-05-16 23:08:08 Indiana Jones and the Temple of Doom
2020-05-09 23:00:40 Pixels
2020-05-03 22:49:06 The House
2020-04-25 23:01:03 Next
2020-04-18 23:28:44 Molly's Game
2020-04-04 22:59:26 Lowriders
2020-03-28 23:31:46 Pain &amp; Gain
2020-03-21 23:34:21 American Made
2020-03-14 23:05:17 Donnie Brasco
2020-03-08 00:17:50 Midnight Run
2020-02-23 22:16:40 Reservoir Dogs
2020-02-08 23:56:44 Ferris Bueller's Day Off
2020-02-01 23:58:44 Unfinished Business
2020-01-18 22:39:36 Four Brothers
(以降昨年見た作品)

このツールはコピペでパパっと作ってしまったので、あまりマニュアルを読んでません。
pegerもCursor関数でやった方がよさそうな記述がありました。
今後の課題ですね。

差分

diffはこんな感じ。
特に考えず作ってしまいましたが、もう少しループ綺麗にできそうな気がする。

(python3) masashi@PC-ubuntu:~/movietweet$ git diff gettw.py
diff --git a/gettw.py b/gettw.py
index 7e7b545..dc36bab 100644
--- a/gettw.py
+++ b/gettw.py
@@ -3,11 +3,14 @@
 from tweetapi import TWauth
 import datetime
 
-for info in TWauth().user_timeline(count=200):
-    if 'tweet movieinfo' in info.source:
-        Posted = info.created_at + datetime.timedelta(hours=9)
-        Text = info.text[info.text.find('')+1:info.text.find('')]
-        print(Posted, Text)
-    else:
-        pass
+API = TWauth()
+
+for i in range(1, 5):
+    for info in API.user_timeline(count=200, page=i):
+        if 'tweet movieinfo' in info.source:
+            Posted = info.created_at + datetime.timedelta(hours=9)
+            Text = info.text[info.text.find('')+1:info.text.find('')]
+            print(Posted, Text)
+        else:
+            pass

上半期ベスト3

せっかく出したので私の上半期ベスト3を発表します。

  1. Pixels (2015年)
    宇宙人がゲームキャラの姿で攻めてくる話です。
    私はSaints rowというシリーズのゲームが大好きなのですが、
    話のぶっ飛び方といい挿入歌といい同じ波長を感じましたw
    大統領が自ら出陣するのもイイ!w
  2. Molly's Game (2017年)
    元スキージャンププレイヤーのモリーという女性が裏カジノを開く話です。
    前の雇い主のノウハウを盗んで、独立して場を立てていく流れがかっこいいです。
    大事なところで諦めないスポーツマンスピリッツがよかったですね。
  3. Reservoir Dogs (1992年)
    クエンティン・タランティーノ監督の映画です。
    パルプフィクションが大好きなのでずっと見たかった映画です。
    内容的には銀行強盗グループの後日談です。
    仲間が揉めてっちゃうんだよなあ。
    気弱なキャラが多い(気がする)スティーブ・ブシェミですが、この映画では強気キャラですw

むすび

2年位前から見た映画の感想をローカルWikiに書いていて、昨年twitterに投稿する形に変えました。
映画の概要は自動で投稿される様にしたので、あとは簡単な感想を書いて視聴記録として残してます。

感想だけ書けばよくなったので、見た後にすぐ投稿・感想を書く様になりました。
お酒飲みながら見てるので記憶が曖昧な時もありますがw

下半期も面白い映画たくさん見たいですね。
ナルコスのメキシコ編もそろそろ見なきゃ。

リポジトリ

github.com

スリーカードポーカーゲームを作りたい unittest編

最近ずっと触っているスリーカードポーカーゲームですが、スクリプトを動かし動かしで作成してきました。
そろそろテストコードも書いてみようということでunittest編です。

私はNW機器の構築が本業なので単体テストというものがずっとピンと来なかったんですよね。 NW機器の単体テストはコマンドで機器のパラメータ取って終わりというものが多いと思います。
(私はteraterm macroでパパっと)

NWの試験はEnd間で疎通を取ってみるほうがバグ出しがし易いですからね。

でもスクリプトやプログラムだと関数の部品ごとに動くかどうか確認することを単体テストと言うんですね。
なるほどねー。

どこまで試験するかはよく課題に上がると思いますが、どこまでやりましょう。
一応動かしてはいるのでとりあえず関数の所だけにしましょう。

単体テストは組み込みのunittestを使ってみます。

unittestの使い方

いろんなサイト見たところ、テストしたい関数を作って戻り値を書いておけばいい様です。
assertEqualでチェックしてるものが多いですね。

でも私のコードでは問題が。

  • returnがない処理
    • カードデッキを作ってシャッフルする関数があるんですがこの場合は何が正になるんでしょうか。
      type classって何が戻ってるのかよくわからないです。
>>> card = pokerapp.Deck()
>>> type(card)
<class 'pokerapp.Deck'>
>>> card.shuffle()
>>> type(card)
<class 'pokerapp.Deck'>
  • 戻り値が変わる場合
    • ポーカーなのでカードを配る関数があります。
      もちろん毎回手札が変わるのですが、これはどうしたら…。
      と思ったら正規表現で判定できそうです。

試験範囲

とりあえず

  • デッキ作成
  • カード配る
  • 手役判定
  • 手札勝負判定
  • ボーナス払い戻し

くらいのテストを作っておきましょう。

デッキ作成

トランプを作ってシャッフルするだけなので戻り値がありません。(たぶん)
どう試験するんだと思ったらassert使わなくてもokになりました。

これは正常に処理されたからOKということになるのかな。

masashi@PC-ubuntu:~/Three-card-poker$ cat test_pokerapp.py 
import unittest
import pokerapp

class TestPokerApp(unittest.TestCase):

    def setUp(self):
        self.card = pokerapp.Deck()
        
    def test_createdeck(self):
        self.card.shuffle()

if __name__ == '__main__':
    unittest.main() 

setUpは各テストごとに呼ばれるので各テストでデッキを作る様に作成してます。
これも継承というか、変数使いまわせるんでしょうか。
でもインスタンス作ってるわけじゃないしね。

とりあえずOKが返ってきました。

masashi@PC-ubuntu:~/Three-card-poker$ python3 test_pokerapp.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
カード配り、役判定

配りから役判定まで書きます。判定の戻り値はタプルで役ランク、手札の数字が返ります。

([1], [2, 5, 11])みたいな感じね。

戻り値がこの形になるよう正規表現を書けばいいですね。
assertRegexという関数で判定できるみたいです。

また、get_roleで役名が返るのでこれも判定しましょう。

役名は全部"One pair!"みたいな形なので".+¥!"でいいでしょう。

同じ形でディーラーの判定も作ります。
ディーラーは手札によっては降りる処理が入ります。

   def test_player(self):
        player = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()
        p_role = pokerapp.Handcheck(player).get_role()

        self.assertRegex(str(p_hand), '\(\[\d\], \[(\d*, ){2}\d*\]\)')
        self.assertRegex(str(p_role), '.+\!')

    def test_dealer(self):
        dealer = pokerapp.Handout(self.card)

        d_hand = pokerapp.Handcheck(dealer, flag=True).result()
        d_role = pokerapp.Handcheck(dealer, flag=True).get_role()

        self.assertRegex(str(d_hand), '\(\[\d\], \[(\d*, ){2}\d*\]\)')
        self.assertRegex(str(d_role), '.+\!|.+')
手札勝負

プレイヤー、ディーラーの手札を比較します。 戻り値は

  • 0 : プレイヤー勝利
  • 1 : ディーラー勝利
  • 2 : 引き分け

です。

orが使えて助かりました。

   def test_match(self):
        player = pokerapp.Handout(self.card)
        dealer = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()
        d_hand = pokerapp.Handcheck(dealer, flag=True).result()

        match = pokerapp.Match(p_hand, d_hand)
        self.assertEqual(match, 0 or 1 or 2)
ボーナス払い戻し

役から賭け金 * n倍の払い戻しをします。
これも数字が返るだけなので"¥d+"でいいですね。

   def test_payoff(self):
        player = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()

        pay_ante = pokerapp.Payoff(10, p_hand[0]).ante_bonus()
        pay_pairplus = pokerapp.Payoff(10, p_hand[0]).pairplus_bonus()

        self.assertRegex(str(pay_ante), '\d+')
        self.assertRegex(str(pay_pairplus), '\d+')

動かしてみる

全体はこんな感じです。
重複コードが多いですが、こんなもんなんでしょうか。

  • test_poketapp.py
import unittest
import pokerapp

class TestPokerApp(unittest.TestCase):

    def setUp(self):
        self.card = pokerapp.Deck()
        
    def test_createdeck(self):
        self.card.shuffle()

    def test_player(self):
        player = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()
        p_role = pokerapp.Handcheck(player).get_role()

        self.assertRegex(str(p_hand), '\(\[\d\], \[(\d*, ){2}\d*\]\)')
        self.assertRegex(str(p_role), '.+\!')

    def test_dealer(self):
        dealer = pokerapp.Handout(self.card)

        d_hand = pokerapp.Handcheck(dealer, flag=True).result()
        d_role = pokerapp.Handcheck(dealer, flag=True).get_role()

        self.assertRegex(str(d_hand), '\(\[\d\], \[(\d*, ){2}\d*\]\)')
        self.assertRegex(str(d_role), '.+\!|.+')

    def test_match(self):
        player = pokerapp.Handout(self.card)
        dealer = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()
        d_hand = pokerapp.Handcheck(dealer, flag=True).result()

        match = pokerapp.Match(p_hand, d_hand)
        self.assertEqual(match, 0 or 1 or 2)

    def test_payoff(self):
        player = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()

        pay_ante = pokerapp.Payoff(10, p_hand[0]).ante_bonus()
        pay_pairplus = pokerapp.Payoff(10, p_hand[0]).pairplus_bonus()

        self.assertRegex(str(pay_ante), '\d+')
        self.assertRegex(str(pay_pairplus), '\d+')


if __name__ == '__main__':
    unittest.main() 

詳細がわかりませんが一応エラーなく完了しました!

masashi@PC-ubuntu:~/Three-card-poker$ python3 test_pokerapp.py 
.....
----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK

テストコード書いてみて思ったこと

関数が動くだけでOKにはなりますが、より精度の高いテストをしようとすると
期待する戻り値を正確に理解していないといけませんね。

コードも本体プログラムと似たものになりましたが、プログラムとテストは表裏一体なんですかねぇ。

今後について

一応OKは出ましたが、もともと本体スクリプトで動いていたものをテストコードとして置き換えただけなので、
これが正しいものなのかはわかりません。

また、エラー判定等を考慮していないのでこの辺の対策を進めたいですね。

  • エラー時の処理のしかた
  • 対話型プログラムの試験方法

など。

でも本体プログラムを書き換えてもテストコードが通ればOKという点では1つ手間が減った気がします。

参考サイト

unittest --- ユニットテストフレームワーク — Python 3.8.3 ドキュメント

リポジトリ

github.com

スリーカードポーカーで勝つことはできるのか

最近このコードばかりいじっています。
シミュレータにチップを賭ける処理を追加したのでやってみましょう。

ここで言う勝つとはカジノへ行ってチップを増やして帰ってくることです。

その前にシミュレータ修正

賭け金勝負する前に勝敗判定部分が3箇所も間違っていました。

  • 判定にいれる手役のミス

手札判定後の値を比較しなければいけないのですが手札をそのまま判定の関数に突っ込んでいました。
正しい比較ができずに判定がバラけてたというわけです。
修正部分の抜粋です。

+               player_hand = pokerapp.Handcheck(player).result() #これをMatchに入れないといけなかった
+               dealer_hand = pokerapp.Handcheck(dealer, flag=True).result() #これをMatchに入れないといけなかった
+
+               match_result = pokerapp.Match(player_hand, dealer_hand)
+

-           player = pokerapp.Handout(deck)
-           dealer = pokerapp.Handout(deck)
+               chips = chips + pays + ante_b + pp_b
 
-           match_result = pokerapp.Match(player, dealer)
  • 判定処理のミス

同役の場合数字が高いカードを持っている方の勝ちですが、後半一部誤ってました。

@@ -133,7 +133,7 @@ def Match(p1, p2):
                                return 0
                        elif p1[1][1] < p2[1][1]:
                                return 1
-                       elif p1[1][1] == p2[1][1]: # こっちが正しい
+                       elif p1[1][1] == p2[1][2]:
                                if p1[1][0] > p2[1][0]:
                                        return 0
                                elif p1[1][0] < p2[1][0]:
  • そもそも引き分け処理がなかった

勝ち負け判定は払い戻し金の処理に効いてくるのですが、その中に引き分け判定が無かったので追加。
そりゃ引き分け0回になりますよね。

if match == 0 and d_hand[0][0] == 0:
    refund_bet = bet * 1
    refund_ante = ante * 2
    return refund_bet + refund_ante
elif match == 0:
    refund_bet = ante * 2
    refund_ante = bet * 2
    return refund_bet + refund_ante
elif match == 1:
    # Forfelt the bet
    refund_bet = 0
    refund_ante = 0
    # Convert negative
    return -((bet * 2) + ante)
#ここがなかった
elif match == 2:
    return 0

正しい結果はこちらです。
改めて50万回まわしてみました。

$ python3 simulater.py 
Three card poker simulator start.
The simulator trials 500,000 times.

=== Probabliry of winning or losing ===

Player 271650 wins. (54.3%)
Dealer 227995 wins. (45.6%)
Draw 355 times. (0.1%)

=== Percentage of Players all hands ===

High card!          74.686%
One Pair!           16.919%
Flash!               4.985%
Straight!            2.974%
Three of a kind!     0.234%
Straight Flash!      0.202%

=== Percentage of Players win(271,650 times) hands ===

High card!          59.368%
One Pair!           25.843%
Flash!               8.636%
Straight!            5.355%
Three of a kind!     0.427%
Straight Flash!      0.371%

引き分けちゃんと出てますね。
判定も修正したので五分五分の勝負では無くなってしまった…?

出る役の比率は同じですが、引き分けが入ったことにより勝った時の役の比率も変わりました。
ペアの割合が増えてハイカードが減りましたね。

賭け金処理を追加

いよいよ本題の賭け金のある状態でシミュレートしてみます。

勝率約50%、ペアプラスの付く率25%でリターン率75%です。
勝つためにはオリも要りますが今回全ツッパでやってみましょう。

賭け金の一連処理

手持ちチップ、ベット金額の処理と勝敗による払い戻しを作ります。

チップ処理

ループ入る前に賭け金を定義して本体(pokerapp.py)と同じように書きます。
勝敗後に勝敗の払い戻しと各ボーナスの払い戻しを足します。

+ante = 10
+pp = 10
+
+def Trials(chips):
        for _ in range(trial):
 
+               deck = pokerapp.Deck()
+               deck.shuffle()
+
+               player = pokerapp.Handout(deck)
+               dealer = pokerapp.Handout(deck)
+
+               hand_result = pokerapp.Handcheck(player).get_role()[0][0]
+               player_hand = pokerapp.Handcheck(player).result()
+               dealer_hand = pokerapp.Handcheck(dealer, flag=True).result()
+
+               match_result = pokerapp.Match(player_hand, dealer_hand)
+
+               # bonus
+               ante_b = pokerapp.Payoff(ante, player_hand[0]).ante_bonus()
+               pp_b = pokerapp.Payoff(pp, player_hand[0]).pairplus_bonus()
+
+               if match_result == 0:
+                       win_list.append(hand_result)
+               elif match_result == 2: #引き分けはボーナス無し
+                       ante_b = 0
+                       pp_b = 0
+               match_list.append(match_result)
+               hand_list.append(hand_result)
+
+               # pay off
+               pays = Liquidation(match_result, dealer_hand, ante, pp)
 
+               chips = chips + pays + ante_b + pp_b

+       return chips
払い戻し

本体のはメイン処理内にベタ書きですが、シミュレータは関数化しました。
内容はさっきの引き分け処理と同じです。

def Liquidation(match, d_hand, ante, bet):
    if match == 0 and d_hand[0][0] == 0:
        refund_bet = bet * 1
        refund_ante = ante * 2
        return refund_bet + refund_ante

    elif match == 0:
        refund_bet = ante * 2
        refund_ante = bet * 2
        return refund_bet + refund_ante

    elif match == 1:
        # Forfelt the bet
        refund_bet = 0
        refund_ante = 0
        # Convert negative
        return -((bet * 2) + ante)

    elif match == 2:
        return 0

これがうまく動けば本体にも採用します。

勝負!

実際何十万回もプレイするのは現実的ではないので100回にしてみます。

手持ちは$1000でアンティ、ペアプラスは$10ずつ賭けます。

$ python3 simulater.py 
You are first given $1,000.
Three card poker simulator start.
The simulator trials 100 times.
You'll bet ante $10 and pair plus $10 all the time.

=== Probabliry of winning or losing ===

Player 56 wins. (56.0%)
Dealer 44 wins. (44.0%)
Draw 0 times. (0.0%)

=== Percentage of Players all hands ===

High card!            77.0%
One Pair!             15.0%
Straight!              4.0%
Flash!                 4.0%

=== Percentage of Players win(56 times) hands ===

High card!          64.286%
One Pair!           21.429%
Straight!            7.143%
Flash!               7.143%

You finally got $2430

増えてる!

今度は詳細を出してもう一回。
printで出してますが、別ファイルに保存したいですね。
左からプレイヤー手札、勝敗、チップ、払い戻し、アンティボーナス、ペアプラスボーナスです。

You are first given $1,000.
Three card poker simulator start.
The simulator trials 100 times.
You'll bet ante $10 and pair plus $10 all the time.
High card! 1 970 -30 0 0 # ハイカードで負けて賭け金$30没収でのこり$970
High card! 1 940 -30 0 0
High card! 1 910 -30 0 0
High card! 1 880 -30 0 0
High card! 0 910 30 0 0
High card! 0 940 30 0 0
One Pair! 0 1000 40 0 20 # ワンペアで勝って配当とペアプラスボーナスで$1000に戻った
High card! 0 1030 30 0 0
High card! 1 1000 -30 0 0
High card! 0 1030 30 0 0
One Pair! 0 1090 40 0 20
High card! 1 1060 -30 0 0
High card! 1 1030 -30 0 0
High card! 1 1000 -30 0 0
One Pair! 1 990 -30 0 20
High card! 1 960 -30 0 0
Straight! 0 1070 30 10 70
High card! 1 1040 -30 0 0
Flash! 0 1130 40 0 50
High card! 0 1170 40 0 0
High card! 1 1140 -30 0 0
High card! 1 1110 -30 0 0
High card! 1 1080 -30 0 0
High card! 0 1110 30 0 0
High card! 0 1150 40 0 0
Flash! 0 1230 30 0 50
One Pair! 0 1290 40 0 20
High card! 1 1260 -30 0 0
High card! 1 1230 -30 0 0
High card! 1 1200 -30 0 0
High card! 0 1230 30 0 0
High card! 0 1260 30 0 0
High card! 1 1230 -30 0 0
One Pair! 0 1290 40 0 20
High card! 1 1260 -30 0 0
High card! 1 1230 -30 0 0
High card! 0 1270 40 0 0
High card! 1 1240 -30 0 0
High card! 1 1210 -30 0 0
High card! 1 1180 -30 0 0
One Pair! 0 1240 40 0 20
High card! 0 1280 40 0 0
High card! 1 1250 -30 0 0
High card! 1 1220 -30 0 0
One Pair! 0 1270 30 0 20
Flash! 0 1350 30 0 50
High card! 0 1380 30 0 0
High card! 1 1350 -30 0 0
High card! 1 1320 -30 0 0
High card! 1 1290 -30 0 0
High card! 1 1260 -30 0 0
High card! 1 1230 -30 0 0
One Pair! 0 1280 30 0 20
High card! 0 1320 40 0 0
High card! 1 1290 -30 0 0
High card! 0 1320 30 0 0
High card! 1 1290 -30 0 0
High card! 0 1330 40 0 0
High card! 1 1300 -30 0 0
High card! 0 1330 30 0 0
High card! 0 1370 40 0 0
One Pair! 1 1360 -30 0 20
High card! 0 1400 40 0 0
High card! 0 1440 40 0 0
High card! 0 1470 30 0 0
High card! 0 1500 30 0 0
High card! 1 1470 -30 0 0
High card! 0 1500 30 0 0
High card! 1 1470 -30 0 0
High card! 1 1440 -30 0 0
High card! 1 1410 -30 0 0
High card! 1 1380 -30 0 0
High card! 0 1420 40 0 0
High card! 1 1390 -30 0 0
High card! 0 1420 30 0 0
High card! 0 1450 30 0 0
High card! 1 1420 -30 0 0
High card! 0 1450 30 0 0
One Pair! 0 1510 40 0 20
High card! 1 1480 -30 0 0
High card! 0 1510 30 0 0
Straight! 0 1630 40 10 70
One Pair! 0 1690 40 0 20
Flash! 0 1780 40 0 50
High card! 0 1810 30 0 0
High card! 0 1850 40 0 0
High card! 1 1820 -30 0 0
High card! 1 1790 -30 0 0
High card! 0 1820 30 0 0
High card! 1 1790 -30 0 0
High card! 1 1760 -30 0 0
High card! 1 1730 -30 0 0
High card! 0 1760 30 0 0
High card! 1 1730 -30 0 0
High card! 0 1760 30 0 0
Straight! 0 1870 30 10 70
High card! 1 1840 -30 0 0
High card! 1 1810 -30 0 0
High card! 0 1850 40 0 0
High card! 1 1820 -30 0 0

…

チップ増えて終了で勝ててますね!
アプラスの配当が貢献してますね。
この詳細見るとLiquidation関数もうまく動いてそうです。

では100回勝負を100セットやったら平均どれくらい貰えるのでしょうか。

$ for a in {1..100} ; do python3 simulater.py | tail -1 ; done | awk -F '$' '{sum+=$NF} END {print sum/NR}'
2522

$1000が約2.5倍になるという結果が出ました。
嬉しい結果ですがホントかなあ。

シミュレーションできたところで実践

シミュレータでは全ツッパでも勝ち越すことができました。
ではGTAオンラインのスリーカードポーカーでこの検証が本当か試してみたいと思います。

というわけで次回実践です。

その他反省

シミュレータの賭け機能部分は全部作ってからcommitしようと思っていたので振り返ると差分探しが大変でした。
機能毎か、ちょっとずつcommitしたほうがいいのか悩みどころですね。

進捗が見える分ちょっとずつcommitの方がいいかな。

リポジトリ

github.com

スリーカードポーカーの手札の確率を計算してみる

スリーカードポーカーゲーム作成PJですが、あとはunittestを書けばひと段落です。
でもその前にシミュレータを改良しました。

改良点

  • 引き分けの処理を追加した
    • カジノだとベット額そのままで次のゲームに行くようなのですが、一勝負ごとに払い戻しの処理にしているのでベット額がそのまま返ってくるようにしました
  • プレイヤーが勝った手札の比率も集計した
    • 勝ったうちの手札の比率も知りたかったので出してみました。

実行

前回と同じ50万回試行します。

$ python3 simulater.py 
Three card poker simulator start.
The simulator trials 500,000 times.

=== Probabliry of winning or losing ===

Player 250265 wins.
Dealer 249735 wins.
Draw 0 times.

=== Percentage of Players all hands ===

High card!          74.592%
One Pair!           17.021%
Flash!                4.98%
Straight!            2.966%
Three of a kind!     0.239%
Straight Flash!      0.202%

=== Percentage of Players win(250,265 times) hands ===

High card!          74.515%
One Pair!           17.023%
Flash!               4.982%
Straight!             3.04%
Three of a kind!     0.235%
Straight Flash!      0.206%

なんと勝った時の比率も全体の結果と変わりませんでした!
上手くできてるもんですねぇ。

しかし引き分けが出ませんね。
どこか間違えたかな?
ディーラー勝負時かつ同役かつ同数字の場合だから出ないか…。

確率を計算してみる

組み合わせ

シミュレータ作る前に研究すべきことだと思いますが、比率は本当なのか確率を計算してみましょう。
高校ぶりに数学をやりますw

  • スリーカードポーカーの手札の組み合わせ

ジョーカーなしの52枚から3枚引くので{}_{52}  C _3 \通りですね。
せっかくなのでpythonで計算しましょう。

itertoolsのcombinationsを使います。

>>> val = set(itertools.combinations(range(52),3))
>>> len(val)
22100

22100通りです。
3枚だけでも結構組み合わせがありますね。

確率

役の組み合わせから22100を割れば出せますね。
確率の低いものから出してみます。

  • ストレートフラッシュ

1種類のスートでA,2,3 ~ 12,13,A までの組み合わせです。12通りあります。
スートが4種類なので48通り。

>>> 48 / 22100
0.0021719457013574662

で0.21%です。

  • スリーカード

同じ数字3枚です。
4種類のスートから3枚引く組み合わせは{}_4 C _2 \

>>> len(set(itertools.combinations(range(4),3)))
4

数字が13まであるので4×13で52通りです。

>>> 52 / 22100
0.002352941176470588

で0.23%です。

  • ストレート

3枚連続した数字です。
4種のスートを問わず3枚引くので43で64通り。
ストレートのパターンは12通りなので64×12で768通り。
ストレートフラッシュのパターンが重複してるので768 - 48で720通りです。

>>> 720 / 22100
0.03257918552036199

で3.25%です。

  • フラッシュ

1種類のスートから3枚引くので{}_{13} C _3 \通り。

>>> len(set(itertools.combinations(range(13),3)))
286

スートは4種類あるから286×4で1144通り。
これもストレートフラッシュの重複を抜いて1144 - 48で1096通りです。

>>> 1096 / 22100
0.049592760180995475

で4.95%です。

  • ワンぺア

同じ数2枚の役ですね。

同じ数は4枚あるのでペアの組み合わせは{}_4 C _2 \通り。

>>> len(set(itertools.combinations(range(4),2)))
6

3枚目の手札は何でもいいので上の4枚を除いて48通り

>>> 6 * 48
288

数字は1~13まであるので

>>> 288 * 13
3744
>>> 3744 / 22100
0.16941176470588235

で16.9%ですね。
3枚だと意外と出ないもんですね。

残りはいわゆる役無しなので今までの組み合わせを引いた数を割れば出ます。

イカード以外の役は

>>> 48 + 52 + 720 + 1096 + 3744
5660

で全体から引いて

>>> 22100 - 5660
16440

を全体で割って

>>> 16440 / 22100
0.7438914027149321

で74.3%ですね。

比較する

確率が出たところでシミュレータの結果と比較してみましょう。

計算した確率 シミュレータ(50万回)の確率
ストレートフラッシュ 0.21% 0.202%
スリーカード 0.23% 0.239%
ストレート 3.25% 2.966%
フラッシュ 4.95% 4.98%
ワンペア 16.9% 17.021%
イカード 74.3% 74.592%

ストレートが少しずれてますが、だいたい近似値になってますよね!
ということは私の判定処理、シミュレータの作りは間違ってなかった!

これは嬉しいです。

ちなみに100回だけ試行するとどうなるのか

回数が多いと大数の法則で真の値に近づいていくのですが、100回だとどうなるんでしょうか。

$ python3 simulater.py 
Three card poker simulator start.
The simulator trials 100 times.

=== Probabliry of winning or losing ===

Player 52 wins.
Dealer 48 wins.
Draw 0 times.

=== Percentage of Players all hands ===

High card!            68.0%
One Pair!             20.0%
Flash!                 7.0%
Straight!              3.0%
Three of a kind!       2.0%

=== Percentage of Players win(52 times) hands ===

High card!          71.154%
One Pair!           19.231%
Flash!               7.692%
Three of a kind!     1.923%

やはり回数が少ないと比率はばらけますね。
100回ではストレートフラッシュは出ませんでした。
ストレート3回も出したのに全部負けてますw

むすび

自分で作ったツールと数学の計算結果が合いました。
処理が正しい構造になってたんだと分かって自身が付きますね。

確率計算なんて高校ぶりで、数学は苦手でしたが今回は計算を自動でやってくれるので
取っつきにくさが無かったです。
苦手と思っていたのは計算が面倒なだけだったのかもしれません。

…と言ったものの、確率の答え合わせしたら一部間違えていたのでやはり苦手ですねw

参考サイト

  • ポーカーの確率
  • この他、答え合わせでスリーカードポーカーのサイトも見ました(カジノ系なので伏せます)

参考本

www.amazon.co.jp

リポジトリ

github.com

スリーカードポーカーゲームを作りたい シミュレータ作る編

細かい修正はあるものの前回の記事でゲーム作成は一段落しました。
しかしユニットテストの作成まではこのシリーズとして続けたいと思います。


プログラミングでやってみたかった事の1つはシミュレータの作成です。
疑似ではあるもののとても現実的ではない試行回数でのデータが取れます。

という訳でスリーカードポーカーのシミュレータを作ります。

やりたいこと

勝負を指定回数行い以下の統計を出します。

  • プレーヤーの勝率
  • 手役の確率

コード

試行回数は多いほうがデータの信憑性が増えますので50万回まわしてみます。
本体のコードはゲームとして作ったのでシミュレータコードは別ファイルで作ります。

本体のファイルからクラスをインポートして、ディーラーとの勝負まで似たようなコードを書いて計算処理を書くだけです。
クラスを使いまわせてすごく楽ちんで便利です。
これがオブジェクト指向かー。

  • simulater.py
import pokerapp
import collections

match_list = []
hand_list = []

trial = 500000

def Trials():
    for _ in range(trial):

        deck = pokerapp.Deck()
        deck.shuffle()

        player = pokerapp.Handout(deck)
        dealer = pokerapp.Handout(deck)

        match_result = pokerapp.Match(player, dealer)
        hand_result = pokerapp.Handcheck(player).get_role()[0][0]
        
        match_list.append(match_result)
        hand_list.append(hand_result)

def Wins():
    for w in collections.Counter(match_list).items():
        if w[0] == 0:
            print('Player {} wins.'.format(w[1]))
        else:
            print('Dealer {} wins.'.format(w[1]))

def Hands():

    # Sort percntages in descending order
    hand_dict = collections.Counter(hand_list)
    sorted_hand_dict = sorted(hand_dict.items(), key=lambda x:x[1], reverse=True)

    for hand, prob in sorted_hand_dict:
        # Convert to str to use rjust
        p  = str(round((prob / trial) * 100, 3))

        shaped_hand = hand.ljust(16)
        shaped_propotion = (p + '%').rjust(10)

        print(shaped_hand, shaped_propotion)

def main():

    print('Three card poker simulator start.')
    print('The simulator trials {} times.'.format(trial))

    Trials()

    print('\n=== Probabliry of winning or losing ===\n')

    Wins()

    print('\n=== Percentage of Players hands ===\n')
    
    Hands()

if __name__ == '__main__':
    main()

動かすとこんな感じ

せっかくなので時間も測りましょう。

サブ機のubuntuで作っているのですが、古いマシンなので低スペックです。

CPU: Corei5-2400
メモリ: 4GB

ですw

f:id:paloma69:20200509111830p:plain

  • 実行

50万回のテストで32秒かかりました。
回数に対して遅い…のか?相場がわかりませんw

でも実際のカードやGTA内で検証するより遥かに早いスピードです。

masashi@PC-ubuntu:~/Three-card-poker$ time python3 simulater.py 
Three card poker simulator start.
The simulator trials 500000 times.

=== Probabliry of winning or losing ===

Dealer 249505 wins.
Player 250495 wins.

=== Percentage of Players hands ===

High card!          74.657%
One Pair!           16.926%
Flash!               5.008%
Straight!            2.971%
Three of a kind!     0.249%
Straight Flash!      0.188%

real    0m32.916s
user    0m32.904s
sys 0m0.012s

結果を分析

分析なんて大層な物ではないですが、結果を見てみましょう。

  • 勝率
Dealer 249505 wins.
Player 250495 wins.

勝率は大体5分ですね。
手札は運任せなので大勝ちはできませんね。

本体の勝敗判定をもう少し細かくすればちゃんとしか結果が出そうです。
同役の場合一番高いカードの番号しか見ていないので、3枚目まで見て判定すればちゃんとした勝敗が出ます。

  • 役の分布

試行回数の中で出た役が高い順にソートしてます。
上手くできたもので配当(ペアプラスボーナス)の低い順になってますね。

手役はほとんどハイカードです。
3枚のポーカーと言えど中々揃いません。
スリーカード、ストレートフラッシュはこのゲームでもレアモノですね。

High card!          74.657%
One Pair!           16.926%
Flash!               5.008%
Straight!            2.971%
Three of a kind!     0.249%
Straight Flash!      0.188%

勝ったパターンのみの手役統計を出せばより良かったかもしれません。

チップを賭けていたらどうなるのか

スリーカードポーカーはペアプラスボーナスとアンティボーナスの払い戻しがベットとは別に返ってきますが、チップの賭け方まで意識して計算したらどうなるのでしょうか?

カジノのゲームとして考えるとゲームの勝敗よりチップを増やすのが本質なので、どう賭けたらチップを増やして帰れるのかというのが見えるかもしれません。

と考えたところで今回はここまでしか作成していないので、また続きは次回以降にしたいと思います。