paloma blog

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

スリーカードポーカーゲームを作りたい 手札を整形する編

着手し始めたスリーカードポーカーゲームですが、今の出力だとまだポーカーの体を成していません。

スリーカードポーカーゲームを作りたい なんとか動く物作れたよ編 - paloma blog

手札の勝敗判定の関係でエースが14、絵柄がそのまま数字になっちゃってます。

masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['D9', 'S14', 'C4'] ['High card!']
Dealer Hand: ['H3', 'C2', 'H10'] ["Dealer can't play"]

Player WIN!
masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['D3', 'C14', 'C11'] ['High card!']
Dealer Hand: ['H13', 'S8', 'H2'] ['High card!']

Player WIN!
masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['H14', 'C13', 'S6'] ['High card!']
Dealer Hand: ['H9', 'D8', 'C10'] ['Straight!']

Dealer WIN!

これでは人様に遊んでいただくときに混乱させてしまいます。
(もう公開しちゃったけど)

そんなわけで今回は手札を整形します。

やること

  • スートの整形
  • エース、絵札の整形

処理の整理

ポーカー系プログラムの流れは大まかに

  1. デッキ作成
  2. ドロー
  3. 手札評価
  4. 勝負

となっていますが、私のプログラムでは数字をそのまま③のランク判定に使用していますので③終了後に変換されるほうが良さそうです。

スートの整形

私のデッキ作成ですが、スートは

self.suit = ['S', 'H', 'D', 'C']

で表現してます。
それぞれの頭文字です。

これをこのまま記号で書き直したら上手いこと動きました。
フラッシュ系のスート判定も問題ありませんでした。

self.suit = ['♠', '♡', '♢', '♣']

スートのUnicodeに対応していないターミナルだとうまく動かないかもです。
(今どきあるか不明ですが)

エース、絵札の整形

デッキ作成してから数字のままの11、12、13、14をJ、Q、K、Aに変換します。

数字はすべての役の判定に使われるので評価処理の前では変えたくありません。
どうせ最後に出力される見た目上の問題なだけなので、大人しく手札オープン時に変更することにしました。

置換は簡単に組み込みのreplaceを使います。

手札はリストで帰ってきますが、リストだと中身を置換できないので一度ループで展開してから別リストに入れなおしました。
下記の関数を作って手札オープン時に組み込んでます。

def Shaping(hand):
    h = []
    for i in hand:
        if '11' in i:
            h.append(i.replace('11', 'J'))
        elif '12' in i:
            h.append(i.replace('12', 'Q'))
        elif '13' in i:
            h.append(i.replace('13', 'K'))
        elif '14' in i:
            h.append(i.replace('14', 'A'))
        else:
            h.append(i)
    return h

差分

差分だけ出してもイマイチかもしれませんが、こんな感じです。
個人的にはgit diffの練習です。

--- a/pokerapp.py
+++ b/pokerapp.py
@@ -10,7 +10,7 @@ class Deck:
 
    def build(self):
        self.value = [x for x in range(2,15)] # Ace is 14
-       self.suit = ['S', 'H', 'D', 'C']
+       self.suit = ['', '', '', '']
 
        for s in self.suit:
            for v in self.value:
@@ -101,6 +101,21 @@ def Match(p1, p2):
        else:
            print('Player WIN!')
 
+def Shaping(hand):
+   h = []
+   for i in hand:
+       if '11' in i:
+           h.append(i.replace('11', 'J'))
+       elif '12' in i:
+           h.append(i.replace('12', 'Q'))
+       elif '13' in i:
+           h.append(i.replace('13', 'K'))
+       elif '14' in i:
+           h.append(i.replace('14', 'A'))
+       else:
+           h.append(i)
+   return h
+
 # main
 def main():
 
@@ -119,9 +134,12 @@ def main():
    p_role = Handcheck(player).get_role()
    d_role = Handcheck(dealer, flag=True).get_role()
 
+   p_show = Shaping(player)
+   d_show = Shaping(dealer)
+
    print("Player Hand: {} {}\nDealer Hand: {} {}\n".format( \
-       player, p_role[0], \
-       dealer, d_role[0] ))
+       p_show, p_role[0], \
+       d_show, d_role[0] ))
 
    Match(p_hand, d_hand)
 

動作

動かすとこんな感じ。

masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['♠4', '♠3', '♠Q'] ['Flash!']
Dealer Hand: ['♣7', '♢4', '♣10'] ["Less than Queen-high. Dealer can't play"]

Player WIN!
masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['♣Q', '♢5', '♠J'] ['High card!']
Dealer Hand: ['♡4', '♡2', '♢K'] ['High card!']

Dealer WIN!
masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['♠9', '♣A', '♠A'] ['One Pair!']
Dealer Hand: ['♡5', '♣8', '♣Q'] ['High card!']

Player WIN!

もうトランプのゲームと分かりますね。
手札と役がリストで出ちゃうのは直したいです。


というわけで簡単な修正ですが、見た目はポーカーっぽくなりました。
整形により手札判定の処理に跳ねるかと思ってましたが、意外とすんなり出来てよかったです。
しかし本命であるBET処理が残ってます。
完成はいつになることやら…。

リポジトリはこちら

github.com

スリーカードポーカーゲームを作りたい なんとか動く物作れたよ編

ここ数年はGWはキャンプに行っていたのですが、今年の予定地が休場になってしまい家で過ごしました。

家にいるなら勉強しなきゃってことで、考えていたポーカーゲームがなんとか動くものになりましたのでおさらいをします。

ルール

  • デッキはジョーカーなしの52枚
  • 3枚のカードでポーカーを行う
  • 勝負はディーラーと1対1
  • カードの交換はできない
  • 手札を見て勝負か降りるかを決める
    • 今回は実装していません
  • ディーラーはQ以上のハイカードじゃないと勝負できない
    • 強制的にプレーヤーの勝ち
  • 勝敗とは別の配当(ペアプラス)があります
    • 今回は実装していません

手役

3枚なので少ないです。 通常のポーカーと異なり、ストレートのほうがフラッシュより強いです。

  1. ストレートフラッシュ
  2. スリーカード
  3. ストレート
  4. フラッシュ
  5. ワンペア
  6. イカード

の順となります。
同役なら数字が高いほうが勝ちです。

ゲーム作成の方針

  • GW中に動くものを作成する
  • 最低限ゲームの勝敗判別ができること

というわけで最小機能で何とか間に合いました。

環境

メイン機のWSL上で動かしてます。

masashi@DESKTOP-986MNSO:~/game$ lsb_release -a ;uname -a ; python3 --version
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.4 LTS
Release:        16.04
Codename:       xenial
Linux DESKTOP-986MNSO 4.4.0-18362-Microsoft #476-Microsoft Fri Nov 01 16:53:00 PST 2019 x86_64 x86_64 x86_64 GNU/Linux
Python 3.5.2

ゲーム画像

画像というかコンソールでプレイしますのでログになりますが…

$ python3 pokerapp.py

上記ファイルを実行すると、カードが配られプレーヤーとディーラーが勝手に勝負します。
掛け金、ベット、フォールドの選択はできませんw

こんな感じです。

masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['D12', 'C2', 'D10'] ['High card!']
Dealer Hand: ['C7', 'C13', 'H5'] ['High card!']

Dealer WIN!
masashi@DESKTOP-986MNSO:~/game$
masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['S3', 'H4', 'D13'] ['High card!']
Dealer Hand: ['D10', 'D4', 'H6'] ["Dealer can't play"]

Player WIN!
masashi@DESKTOP-986MNSO:~/game$
masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['H10', 'H2', 'D14'] ['High card!']
Dealer Hand: ['H6', 'H3', 'S3'] ['One Pair!']

Dealer WIN!

選択の余地が全くないのでゲームというかシミュレーターっぽくなってしまいましたね。

デッキ作成、シャッフル

トランプゲームのデッキ作成、シャッフルの記事はたくさんありますが、
コピペだけだと面白くないので独自に作ってみました。

各13枚 * 4種類を掛ければ1組作れますね。

ポーカーではエースが一番強いのですが、シンプルに14番に割り振りました。
ストレートの判別が簡単になります。
スートも記号で表現したかったのですが、とりあえず今回は諦めます。

class Deck:

    def __init__(self):
        self.deck = []
        self.build()

    def build(self):
        self.value = [x for x in range(2,15)] # Ace is 14
        self.suit = ['S', 'H', 'D', 'C']

        for s in self.suit:
            for v in self.value:
                self.deck.append(''.join([s, str(v)]))

        return self

    def show(self):
        for c in self.deck:
            print(c)

    def shuffle(self):
        random.shuffle(self.deck)


    def draw(self):
        return self.deck.pop(0)

本件で初めて自作クラスを作成しました。
作ってみて何となく慣れてきましたが、難しいですね。

勉強と思って作りましたが関数だけでも作れたかもしれません。
(まだクラス作成のメリットがあんまり…)

動かしてみるとこんな感じ。

>>> import pokerapp
>>>
>>> card = pokerapp.Deck()
>>> card.show()
S2
S3
S4
S5

…

シャッフル。ちゃんと混ざります。

>>> card.shuffle()
>>> card.show()
D4
C13
H14
C5
S6

…

シャッフル後のデッキのリストを頭から引いていきます。

カードを引く関数は別で作成しました。

def Handout(card):
    hand = []
    for i in range(3):
        hand.append(card.draw())

    return hand

手役判定

本当はビット演算とかして判定したかったのですが、
3枚しかないので大人しくバラして判定することにしました。

対戦型なので、役にランク値を持たせて上下を決める流れになります。 役判定の1つ目の戻り値から値だけ抜いて、判定のためにリスト化してます。

valueを抜いただけだと辞書型で返ってしまうので上下の判定ができません。
リスト型は万能ですねえ。

ディーラーはQハイカードより下の役は負けになるので、そのフラグ建てが苦労しました。

  • 手札チェック

同役の場合はカードの数での勝負になるので戻り値がハンドランクと手役の最大値の2つを返しています。

class Handcheck:

    def __init__(self, hand, flag=False):
        self.hand = hand
        self.flag = flag

        if self.flag == True:
            self.player = Judge(self.hand, dealer=True)
        else:
            self.player = Judge(self.hand)

    def get_role(self):
        return list(self.player[0].keys()), self.player[1]

    def result(self):
        return list(self.player[0].values()), self.player[1]
  • 役判定

手札の値をバラしてから役の判定をしています。
1つの関数で完結できました。
3枚で行うゲームだからできる技ですね。

ストレートの処理が大変そうだと思ってましたが、ソートすれば簡単ですね。

def Judge(hands, dealer=False):

    # Devide hands into suits and numbers
    suit = [ hands[0][0] , hands[1][0] , hands[2][0] ]
    rank = [int(x[1:]) for x in hands]

    # Sort of numbers for role evaluation
    rank.sort()

    # Evaluation of hand
    if len(set(suit)) == 1 and rank[1] == rank[0] + 1 and rank[2] == rank[1] + 1:
        return {'Straight Flash': 6}, max(rank)
    elif len(set(rank)) == 1:
        return {'Three of a kind!': 5}, max(rank)
    elif rank[1] == rank[0] + 1 and rank[2] == rank[1] + 1:
        return {'Straight!': 4}, max(rank)
    elif len(set(suit)) == 1:
        return {'Flash!': 3}, max(rank)
    elif len(set(rank)) == 2:
        return {'One Pair!': 2}, max(rank)
    elif max(rank) < 12 and dealer == True:
        return {'Dealer can\'t play': 0}, max(rank)
    elif max(rank):
        return {'High card!': 1}, max(rank)

勝負判定

戻り値の値から上下関係を決めるだけです。 同役同士の引き分けはプレイヤーの勝ちにしました。 GTAオンライン内のルールに則って作成しています。公式ルールはどうかわかりません。

def Match(p1, p2):

    if p1[0] > p2[0]:
        print('Player WIN!')
    elif p1[0] < p2[0]:
        print('Dealer WIN!')
    # Compare number in case draw rank hand
    elif p1[0] == p2[0]:
        if p1[1] > p2[1]:
            print('Player WIN!')
        elif p1[1] < p2[1]:
            print('Dealer WIN!')
        # Player wins in case of draw
        else:
            print('Player WIN!')

メイン処理

ポーカーゲームの流れに沿って関数を呼び出すだけです。
処理が簡潔に書ける分、クラスや関数を作るメリットはありますね。

チップを掛ける処理を作っていないので、今時点ではカードを引くだけのゲームになってます…。

def main():

    # Ante

    # Game start
    card = Deck()
    card.shuffle()

    player = Handout(card)
    dealer = Handout(card)

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

    p_role = Handcheck(player).get_role()
    d_role = Handcheck(dealer, flag=True).get_role()

    print("Player Hand: {} {}\nDealer Hand: {} {}\n".format( \
        player, p_role[0], \
        dealer, d_role[0] ))

    Match(p_hand, d_hand)

    # bet, fold

    # pay off

今後の展開

一回手を付けたからにはもう少しゲームらしくしたいですよね。 というわけでタスクです。

  • 手札の清書
    • スートを記号(♠,♥,♦,♣)に変換する
    • エース、絵柄を文字(A,K,Q,J)に変換する
  • チップの処理追加
    • アンティ(場代)
    • アプラス
    • ベット or フォールド
    • 払い戻し
  • シミュレータの作成
    • 出やすい役の統計、勝率とか
    • 作れたらGTAオンラインで実践します
  • テストコード作成
    • 今回は自分で何回も動かしました
  • GUIゲーム化

あとは適宜コードのリファクタリングですね。
どう書くのがいいコードなのかはまだまだ経験積まないとわからないです。

完走した感想

いや、完走はしてないのですがとりあえずポーカーゲームっぽい動くものが作れました。

これにかかったのは30時間くらいですかね。
GWの4~6まで3日まるまる費やすつもりでしましたが、ゲームやったりお酒飲んだりで思ったより手を付けられませんでした。
家には誘惑がいっぱいです。

関連書籍を読みだけはしていた自作クラスにも初挑戦でしたが、どこの定義がどの変数に当たってるのか初めのうちは苦労しました。 自作クラス作れるとステップアップした気がしますねw

でも関数とクラスの使い分けもまだよくわからないのでこの子を育てて身に着けていこうと思います。

本件のリポジトリです。

github.com

参考サイト

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

プログラミングのステップの1つでゲームを作ってみようというのがありますよね。
私も本業はインフラエンジニアの身ですが、 pythonは2年ほど触っていますのでそろそろこちらにも挑戦しようと思います。

これまで一枚岩のコードはいくつか書いてきましたが、
自身初となる自作関数、インタラクティブ処理の練習にもなりますからね。

何を作るか

入門かどうかは置いておいて「python ゲーム」とかで検索すると以下が多く出てきます。

この中で以下の条件で進められるように考えます。

  • CLIから始められるもの
    • コンソールで理解できるトランプ系かテキスト系になりますね
  • ルールが簡単なもの
    • これは単純に複雑さがコーディング量に比例します(と思います)

そんなわけで私の挑戦するゲームを決めました。
スリーカードポーカーです!

スリーカードポーカーとは

ルールは普通のポーカーと同じように52枚のカードから手役を構築しますが、

  • 3枚のカードでポーカーを行う
  • 対戦はディーラーと1対1

というものになります。

スリーカードポーカーにした理由ですが、

  • 誰も作成していない
    • 需要が無いからかも
  • 5枚ポーカーよりは作成が簡単そう
    • 手役のバリエーションは当然少ないです
  • GTAオンラインでハマっている

からです。

もともとはGTAオンラインのミニゲームで出てきて興味を持ったんですが、
検索しても公式ルールとかが出てこないんですよね。

オンラインカジノ系のサイトではちゃんとルール記載があってプレイできるようなので、
ポーカーの一種として存在はしているのでしょうが謎ですね。

とりあえずこちらの作成を目標にしたいと思います。

ブラックジャックは意外と作成記が出てくるし、
ホールデムやビデオポーカーの類のものは処理がすごく大変そうなので
スリーカードポーカーを初のゲームに選定しました。

GW中の課題としてある程度のものはできたらいいですが…
役判定のところが鬼門そうですね。

ちなみにGTAオンライン

f:id:paloma69:20200430222336p:plain

GTAオンラインのスリーカードポーカーはカジノアップデートからできるようになりました。
ポーカーの醍醐味である他プレイヤーとの駆け引きの要素は無いのですが、シンプルながらなかなかどうしてハマります。

一人称視点だと本当にカジノでやっている気分が味わえます。

f:id:paloma69:20200430222723p:plain

GTAオンラインでやればいいじゃんて話ですが、ゲーム開始時のロードがすごく長いのでお手軽に遊べる環境が欲しいのも作成理由の1つです。

GTAオンラインマネーのキャッシュフローを可視化したい グラフ化する編

前回

paloma69.hatenablog.com

溜まったデータはこんな感じです。

PS C:\Users\masashi\tools\gta_assets> cat .\data\gtacashflow.csv
2020/04/12 17:15,40000,1081924
2020/04/12 17:30,40000,1081924
2020/04/12 17:45,0,1121924
2020/04/12 18:00,0,1095781
2020/04/12 18:15,0,1060181
2020/04/12 18:30,0,1236818
2020/04/12 18:45,2000,1236818
2020/04/12 19:00,2000,1136818
2020/04/12 19:15,0,1122693
2020/04/12 19:30,0,1239993
2020/04/12 19:45,0,1248918
2020/04/12 20:00,0,1248918
2020/04/12 20:15,0,1248918

グラフ化

私含めたSIer業界()ならExcelでグラフ化するところですが、
私も脱Excelをしていきたいのでコードを書いてグラフに起こします。

pythonのサイトでよく出てくるmatplotlibを使います。
numpyもセットでよく使われますが、今回は使わず前回タンキングしたCSVを使います。

コード

取得したデータから起こします。
現金と銀行預金のデータがありますので、併せられるよう棒グラフにします。

グラフ化はpyplotというメソッドを使います。
凡例もつけられて凄い便利です。

matplotlibの使用例はpyplotがほとんどなんですが、
他の使い方って出てきませんね。
(今度調べてみます)

とりあえずpyplotはデータをリスト化して使うと覚えておけば何とかなりますw

  • view_assets.py
import csv
import matplotlib.pyplot as plt

with open('.\\data\\gtacashflow.csv', 'r') as f:
    Assets = csv.reader(f)
    DATA = list(Assets)

x = list() 
y_cash = list()
y_bank = list()

for a in DATA:
    x.append(a[0])
    y_cash.append(int(a[1]))
    y_bank.append(int(a[2]))

# Create graph as bar, add legend
p1 = plt.bar(x, y_bank, color='blue')
p2 = plt.bar(x, y_cash, bottom=y_bank, color='green')
plt.legend((p1[0],p2[0]),('Bank','Cash'))

# Fine-tune labels
plt.title('GTA Online Asset Flow')
plt.xticks(rotation=45)
plt.ylabel('Assets ($)')
plt.ylim([0,1500000])

plt.show()

実行

PS C:\Users\masashi\tools\gta_assets> python .\view_assets.py

f:id:paloma69:20200413221209p:plain

OK!

なんかそれっぽくないですか?
思ったより増減の幅が少ないので映えませんがいい感じ!

ミッションで稼いではいますが、持っている拠点の日時の固定費の支出もあるので意外とお金貯まりません。

cashが無いのは死ぬと減ってしまうのですぐ銀行に預けるためです。
預けておくと死んでも減りません。
買い物は預金から使うこともできるのでなるべく現金を持たないのがコツですw

で、これって何の役に立つん?

ただのゲーム内通貨のフローをグラフ化しただけなので役には立ちません。

しかし、自キャラのキャッシュフロー見える化できました。

ゲーム内の犯罪会社の出来事ですが、
自分の稼ぐさまを見ることで一応経営者気分が味わえますw

まあ、メインはコードでのグラフ化なのでそれが実装できて嬉しいです。
GUI系の出力ができるとプログラミングの面白さが加速しますね。

結び

役に立たないと言いながらこれを使って自作の支払い集計ツールの可視化もできそうですね。

また、Rockstar社のゲームなら似たようなHPになってると思うのでこの方法で資金系の可視化ができそうです。
Red Dead Redemption 2は未購入ですが、買ったらこれと合わせてやってみます。

本件のネタは1年くらい前に思いついたのですが、やっと実現できました。

APIが動かない、seleniumでこれ以上進めない、で何回も挫折したのですが、
今どき調べれば何かヒットしますね。(ヒントになり得るものでも)
先人の知恵に感謝です。

データタンキング用のブラウザ操作の過程が一番難しくて
今回のグラフ化はどちらかというと簡単でした。

ブラウザの仕組み、認証の仕組みなどインターネットは奥が深くて楽しいです。
いや、深すぎか。


これもGithubリポジトリ作りました。

github.com

まだリポジトリのみですが、随時更新していきます。
グラフの上限値の自動設定や棒グラフのカラーなんかは調整したいですね。

それでは最後に私の愛車、インテヴェーロ コケットのテールを見ながらお別れしましょう。

f:id:paloma69:20200413225850p:plain

参考サイト

GTAオンラインマネーのキャッシュフローを可視化したい データ溜める編

GTAオンライン面白いですよね。
2013年発売のゲームなんですが、未だにアップデートが続いてます。

私も頻度は減りましたがまだログインしてます。

今は運悪く新型コロナウイルスによる非常事態宣言期間内なのでますますログインが増えそうです。

最近のゲームはメーカーのアカウントを作ってオンラインでステータスを確認することができます。
何でも記録に残っちゃいますね。

しかし基本的に現時点のステータスしか確認できません。

GTAオンラインの重要な要素であるお金の推移が分かるようにしたかったので
いっちょ作ってみようと思います。

自アカウントのステータスを取る

Rockster Social Clubで自キャラのステータスを確認できます。

APIが用意されていた様なのですがしばらくメンテされていないようで動きませんでした。
(日本Social Clubにログインすると日本のドメインにリダイレクトされるのでそれがダメなのかも)

rockstar-api.readthedocs.io

となればseleniumの出番ですね。

環境

ノートWindows10でやります。
WSLも入ってますがGUIも見れた方がいいと思いWindowspythonで動かします。

PS C:\Users\masashi\tools\gta_assets> $psversiontable

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


PS C:\Users\masashi\tools\gta_assets> python --version
Python 3.7.2

準備

pythonseleniumはだいぶなれたので今回もこれで行きます。

主な処理

  1. SocialClubにアクセス
  2. ログイン処理
  3. GTAマネーの値をゲット
  4. ファイルに保存

seleniumの使用ブラウザはChromeです。

認証が突破できない、困った

seleniumで起動するとまっさらなChromeが立ち上がります。
当然ブラウザにもアカウントのデータがないのでSoclal Clubサインイン時に画像選択の認証がでます。
ロボット対策のreCAPTCHA認証というらしいです。

これね。

f:id:paloma69:20200412215517p:plain

認証を手動でやるのは本末転倒なので他の方法を考えます。

  1. reCAPTCHAを突破する機能を作る
  2. pyautoguiでブラウザ操作する
  3. seleniumGoogleアカウントを読み込ませる

1を実装が出来たらハッカー級です、今の私には無理…
2は座標の取得が大変です。
無難に3でやることにしました。

さすがというかこの辺のナレッジがたくさんありました。

ブラウザセッションの関係で本当は専用ユーザープロファイルを作った方がいいのですが
このためにわざわざ作るのもなんなのでいつも使ってる自分のプロファイルを使います。

保存ディレクトリはURLにchrome://veisionで確認出来ます。

Chromeはバックエンドでプロセスが動いていてこの状態でselenium起動するとセッション重複でエラーになります。

プロセス終了すればseleniumは動くので今回はChromeが動いていない状態での挙動になります。

コード

  • getgtamoney.py
from selenium import webdriver
import configure
import datetime
import time
import csv
import os

os.chdir(configure.my_path)

URL = 'https://ja.socialclub.rockstargames.com'

options = webdriver.ChromeOptions()
options.add_argument('--user-data-dir=' + configure.chrome_path)
browser = webdriver.Chrome(options=options)

browser.get(URL)
time.sleep(10)

# For Singin Function at Firsttime
def Sitelogin():
    signin_btn = browser.find_element_by_xpath('//*[@id="app-page"]/div[1]/div/div/div/div/div/div[1]/a[1]')
    signin_btn.click()

    email = browser.find_element_by_xpath('//*[@id="app-page"]/div[2]/div[1]/div/div/form/fieldset[1]/span/input')
    email.send_keys(configure.email)

    password = browser.find_element_by_xpath('//*[@id="app-page"]/div[2]/div[1]/div/div/form/fieldset[2]/span[1]/span[1]/input')
    password.send_keys(configure.password)

    login_btn = browser.find_element_by_xpath('//*[@id="app-page"]/div[2]/div[1]/div/div/form/fieldset[3]/div/button')
    login_btn.click()

browser.get('/'.join([URL, 'games/gtav/pc/career/overview/gtaonline']))
time.sleep(3)

getcash = browser.find_element_by_xpath('//*[@id="cash-value"]').text
getbank = browser.find_element_by_xpath('//*[@id="bank-value"]').text

# Change datatype integer
cash = int(getcash[1:].replace(',', ''))
bank = int(getbank[1:].replace(',', ''))

gettime = datetime.datetime.today().strftime('%Y/%m/%d %H:%M')

with open('.\\data\\gtacashflow.csv', 'a', newline='') as f:
    writer = csv.writer(f)
    writer.writerow([gettime, cash, bank])


browser.close()
browser.quit()

コードの解説

自アカウント、環境系はconfigureファイルに外だししてます。

ページ遷移の時にseleniumのwaitを使いたかったのですが、今回はsleepで凌いでます。
エラー処理も特にやってないやっつけです。

--user-data-dirで自分のChromeプロファイルのフォルダを指定してRockster Social Clubにログインしておくことで認証をスキップできます。
def Sitelogin()はログイン用に用意したのですが、今回の方法なら不要でした。
なのでコード内で使っていません。
(いつか消します)

このコードで現在の自キャラの現金と銀行の預金が取れます。

値を取ってきた状態はstr型なので計算出来るようにint型に直してます。

実行はこれだけですね。

PS C:\Users\masashi\tools\gta_assets> python .\getgtamoney.py

取得データのタンキング

これで取得出来るようになったので定刻おきに取得してタンキングします。

取るデータは取得時間とその時の所持金だけなのでcsvに溜めます。

2~3時間ほどプレイします。
効率の良いカーディーラーをメインに稼ぎます。

今回は上記のコードをタスクスケジューラで起動しました。
15分おきに起動しています。
タスクスケジューラ起動の際にファイル保存できるようにosモジュールもインポートしてます。

数時間後

お気に入りマイカーのインテヴェーロ コケット。
シボレー コルベットがモデルですね。

f:id:paloma69:20200412220334p:plain

データはこんな感じで溜まってます。
取得時間、現金、銀行預金のデータです。

PS C:\Users\masashi\tools\gta_assets> cat '.\data\gtacashflow.csv'
2020/04/12 17:15,40000,1081924
2020/04/12 17:30,40000,1081924
2020/04/12 17:45,0,1121924
2020/04/12 18:00,0,1095781
2020/04/12 18:15,0,1060181
2020/04/12 18:30,0,1236818
2020/04/12 18:45,2000,1236818
2020/04/12 19:00,2000,1136818
2020/04/12 19:15,0,1122693
2020/04/12 19:30,0,1239993
2020/04/12 19:45,0,1248918
2020/04/12 20:00,0,1248918
2020/04/12 20:15,0,1248918

いよいよ可視化します!

表計算ソフトを使っても面白くないのでここもpythonを使ってグラフ化してみます。

pythonはライブラリが沢山あるのでグラフ化パッケージもちゃんとあります。

matplotlibというライブラリがメジャーで色々情報があり使いやすそうだったのでこれを使ってみます。

と言うわけで次回に続きます。

参考サイト

コマンドからteratermを起動して一発ログイン 完全版

前回書いた記事の更新版です。
もう去年の話なんですねえ。

paloma69.hatenablog.com

前回の課題

作業手順書簡略化用のCLIログインですが、パスワードを平文で書かないといけないのが欠点でした。

私の環境の作業用端末って最低限のツールしか入っていないので、
Powershellだけで暗号化できるのかと思っていたんですが、いい記事を見つけました。

こいつを使ってパスワードを暗号、複合でセキュアなログインをしたいと思います。

Powershellでの暗号化、複合化

セキュア文字列を復号して平文にする[PowerShell] : バヤシタ

環境

いつものノートPC Windows10です。
どのバージョンから暗号化が使えるのかは調べてません。すいません。

PS C:\Users\masashi\tools> Get-WmiObject Win32_OperatingSystem


SystemDirectory : C:\WINDOWS\system32
Organization    :
BuildNumber     : 18363
RegisteredUser  : user
SerialNumber    : 00330-80000-00000-AA868
Version         : 10.0.18363



PS C:\Users\masashi\tools> $psversiontable

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

.Netのバージョン

Microsoft .NET Framework 4 Client Profile
KB2468871
KB2468871v2
KB2478063
KB2533523
KB2544514
KB2600211
KB2600217
Microsoft .NET Framework 4 Extended
KB2468871
KB2468871v2
KB2478063
KB2533523
KB2544514
KB2600211
KB2600217

仕込み(暗号化)

標準入力で暗号化するパスワードを変数に入れます。
(ファイルやコマンドに書くとばれちゃうのでこの方法がいいと思います)
コマンド内の文字列はただのプロンプトになります。

PS C:\Users\masashi\tools> $passwd = read-host "ssh password" -assecurestring
ssh password: *******

たぶん暗号化されている。
(それ用の変数に入っている?のでどのみち読めません)

PS C:\Users\masashi\tools> $passwd
System.Security.SecureString

仕込み(複合化の準備)

お作法的な手順で複合化します。
Powershellはinsensitiveなので全部小文字で書いちゃいます。

[system.runtime.interopservices.marshal]::が何の文法かわからない…

ですが、ググる .Net系っぽいですね。
Windowsで使われているかもしれませんが、自分で意識して使ったことないです。

複合化は.Netの内部アルゴリズムでやってるということですよね。
今回の手順が必要なのは仕方ないですが、何やってるかはさっぱりですね。
MSのページ見ると一回バイナリ文字列にしてStringオブジェクトがどうのこうのと…
全然わかりません…。

PS C:\Users\masashi\tools> $bstr = [system.runtime.interopservices.marshal]::securestringtobstr($passwd)
PS C:\Users\masashi\tools> $bstr
1624188756616

仕込み(複合化)

$bstrをもう一度変換すると平文に複合化されます。

PS C:\Users\masashi\tools> $ttmpass = [system.runtime.interopservices.marshal]::ptrtostringbstr($bstr)

接続する

今回はサブ機のUbuntuデスクトップにログインしてみます。

前回のTeratem CLI構文 + パスワードの部分に上記の変数を当てます。

PS C:\Users\masashi\tools> & 'C:\Program Files (x86)\teraterm\ttermpro.exe' /ssh 192.168.0.10 /user=masashi /passwd=$ttmpass /auth=password

f:id:paloma69:20200409233440p:plain

OK!

Teratermのウィザードが立ち上がり
マクロのように自動でパスワードが入力されてログインできました!

f:id:paloma69:20200409233459p:plain

本当はGIFで公開したかったのですが、間に合わないので後で載せます。

結び

仕込みが少し面倒ですが、平文を見ることなくパスワード入力ができました。
このまま手順書に書いても大丈夫ですね!

最小限の端末環境前提でWindowsの機能だけでやりましたが、
おとなしくopensslくらいインストールしておいた方がもう少し分かりやすい構文で説明できたと思います。

しかしPowershellでここまで出来るなんて驚きです。
コマンドの豊富さからLinuxが好きだったのですが、Windowsもなかなか馬鹿にできませんね。

でも最近のPowershellは普通にsshも使えるのでこんな回りくどいことしなくてもいいかもw
(ログ取りの問題はありますが)

その他参考サイト

PowerShell でパスワードとかのシークレット文字列を対話入力する

Marshal クラス (System.Runtime.InteropServices) | Microsoft Docs

インストールされている NET Framework セキュリティの更新プログラムと修正プログラムを確認する | Microsoft Docs

今更ながらSQLで2019年の支出まとめ

FY2020も終わりという事で、2019年の総支出を出してみたいと思います。
私の個人的な支出なので2019年1~12月を計算します。(今更ですが)
収入は少なくて恥ずかしいので載せません。

本シリーズ

家計簿付けるのカッタルイので、支払いはなるべくクレジット払いにしてトラッキング出来るようにしています。
データベースの勉強もかねてDBで計算しようと思い、支出の計算を始めました。

2019年の決済のCSVファイルはインポート済みです。

また、月次・半期事の集計スクリプトがあるんですが、下半期分が上手く動かないので今回は手動でやります。
INSERTをスクリプトでやるようにしたら動かなくなってしまいました。何故だ。

環境

WSL上のUbuntu18.04LTSとsqlite3です。

PS C:\Users\masashi> wsl lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.2 LTS
Release:        18.04
Codename:       bionic
PS C:\Users\masashi> wsl sqlite3 --version
3.22.0 2018-01-22 18:45:57 0c55d179733b46d8d0ba4d88e01a25e10677046ee3da1d5b1581e86726f2alt1

レコード数

出してもあまり意味無い値ですが出してみます。
カード切った回数ですね。

sqlite> SELECT count(*) FROM expenses
   ...> WHERE date BETWEEN '2019/01/01' and '2019/12/31';
406

このDBの中に現金払いの家賃と光熱費も入っているので2 * 12で24引いて、382回お買い物しました。
(where使った方がスマートでした)
1日1.04回の計算です。

総支出

では、昨年使った額は...

sqlite> SELECT sum(payment) FROM expenses
   ...> WHERE date BETWEEN '2019/01/01' and '2019/12/31';
2018217

201万8217円!

派手に使うような生活はしていないのですが、合計してみると意外と使ってる感じですね。

飲み会とかで現金払いもあってDBに突っ込めてない払いもあります。
昨年賃貸の更新もしたから実態は+10万くらいかな。

211万を月割りで17.6万です。
月17なら少なく見えますねw

項目ごと

節約する為にはなるべく固定の支出を減らすのが大切ですね。
ちょっと項目ごとに研鑽してみましょう。

Sweet Kava Kava

お気に入りのバーです。
引っ越してからいく回数が減っちゃいました。
癒しの場所なので無くせません。

sqlite> select sum(payment) from
   ...>  (select * from expenses where date BETWEEN '2019/01/01' and '2019/12/31')
   ...>  where contents = 'スウイ―ト カヴアカヴア';
32060

一年で30000は少ないですね。(節約的にはいいことですが)
引っ越す前は毎月これくらい使ってた気がします。

通信代

固定回線、キャリアの通話のみプラン、MVMO(データのみ)運用を出してみます。

sqlite> select sum(payment) from
   ...>  (select * from expenses where date BETWEEN '2019/01/01' and '2019/12/31')
   ...>  where contents like '%電話%' or contents like 'J:COM%' or contents = 'インターネットイニシアティブ';
117320

使用キャリア、プロバイダモロバレw
月一万行かないくらいですかね。
少なく出来たのはMVNOの貢献が大きいですね。

食費

スーパーで使った金額。
コンビニのsuica払いは別計算です。

sqlite> select sum(payment) from
   ...>  (select * from expenses where date BETWEEN '2019/01/01' and '2019/12/31')
   ...>  where contents = 'イトーヨーカ堂 (サインレス)' or contents = 'イオンリテール';
94567

スーパーは週一ペースなので食費にしては少ないですね。

suica

ほとんどコンビニですが電子マネーとして使えるところはこれで決済してます。

詳細はsuicaから拾わないと追えませんね。
suica用のDBも作成してJOINすればいいのかな。

sqlite> select sum(payment) from
   ...>  (select * from expenses where date BETWEEN '2019/01/01' and '2019/12/31')
   ...>  where contents = 'JR東日本モバイルSuica';
426250
  • 月割り

SQLで四則演算できるから打ってみます。

sqlite> select sum(payment) / 12 from
   ...>  (select * from expenses where date BETWEEN '2019/01/01' and '2019/12/31')
   ...>  where contents = 'JR東日本モバイルSuica';
35520

月3.5万…もう少し減らしたいですね。
コンビニでお酒も買うことが多いので酒屋とかで大瓶などを買うようにすればもう少し減らせそう。

むすび

という訳で2019年支出の振り返りでした。
この手のツールは沢山ありますが、自分で作ってみるのも面白いです。
自分のはカード会社のCSVを入れたらほとんど終わりですw

今回の手動集計では同じコマンドの羅列になってしまったので、いまいち面白くありませんでした。
かっこよく出力できるようにSQL分ももっと勉強しないと。
集計スクリプトはメンテしないとですね。
とりあえず目的は果たせたので良しとします。

今回のDBのレコードは少ないですがSQLは処理も早いし面白いですね。
将来的には家計簿OSSFirefly IIIと連携させて、収支までちゃんと計算するのが目標です。

来期も頑張りましょう!