ブログトップ

今日から始めるゲーム統計学

tsubame30.exblog.jp

かつては麻雀およびエロゲのデータを統計解析して遊んでました。今では日本酒に夢中です。

【麻雀雑談】今日から始める天鳳牌譜解析 with Ruby

私自身プログラムも統計学も達者ではないですが、
自分なりに手を動かしている身として提供できることもあるのかなぁ、と思い、
今日は、私がどのように統計をとっているかを書いてみようと思います。





●手順1、牌譜の書式化

天鳳Win版を打っていて、かつ牌譜データを残している方は、牌譜ファイルmj.logがたまっていると思います。
mj.logのまま解析する方法もあるらしいですが、私には難しすぎるので、この牌譜データを書式化します。

やり方は、天鳳Win版を起動し、メニュー>牌譜解析スクリプトを開きます。
右上に「書式変換」があるので、それをクリック。

d0279358_2358549.png


「すべての牌譜をファイルに保存」を押すと、My Tenhou>log内にある全ての牌譜を書式化し、
txtファイルとして保存することができます。

例えばASAPINさんの天鳳位昇段戦の東1はこんな風に書式化されます。
Gはツモ(Get)、Rはリーチなど、だいたい直感的にわかると思います。

---------------------------------------------------------
===== 天鳳 L0000 鳳南喰赤 開始 2011/02/04 http://tenhou.net/0/?log=2011020417gm-00a9-0000-b67fcaa3&tw=1 =====
持点25000 [1]-ron-/七段/男 R2135 [2]ASAPIN/十段/男 R2260 [3]うきでん/七段/男 R2018 [4]超ヒモリロ/九段/男 R2198
東1局 0本場(リーチ0) -ron- -1000 ASAPIN 3000 うきでん -1000 超ヒモリロ -1000
流局
[1東]1m1m2m4m5M5m9m1p4p2s4s北発
[2南]5m7m9m5P5p6p6p7p9p2s3s7s8s
[3西]1m2m4m7m8m8m5p8p2s3s5s8s白
[4北]2m6m2p2p7p8p4s6s8s東南北発
[表ドラ]南 [裏ドラ]
* 1G9s 1d1p 2G3p 2d9p 3G4m 3d白 4G発 4d2m 1G7s 1d北 2G4p 2d9m 3G9p 3d8s
* 4G4s 4d南 1G8m 1d5m 2G3m 2d7m 3G2m 3d5s 4G5m 4d東 1G北 1D北 2G4p 2d5m
* 3G中 3D中 4G東 4D東 1G西 1D西 2G白 2D白 3G9s 3D9s 4G8p 4d8s 1G6s 1d2m
* 2G8s 2d3m 3G8m 3d7m 4G6m 4d北 1G3s 1d8m 2G南 2d7s 3G2s 3d5p 4G1p 4d5m
* 1G4m 1D4m 2G2p 2R 2d南 3G6s 3d9p 4C7p8p 4d6m 1G6m 1d9m 2G4p 2D4p 3G5s
* 3d8m 4G5s 4d6m 1G3p 1d9s 2G7s 2D7s 3G1p 3d8m 4G7m 4D7m 1G9m 1D9m 2G7p
* 2D7p 3G中 3d8m 4G西 4d1p 1G中 1D中 2G6p 2D6p 3G1m 3d1p 4G3m 4D3m 1G7m
* 1D7m 2G5p 2D5p 3G6p 3d中 4G西 4d8p 1G東 1D東 2G6m 2D6m 3G白 3d8p 4G9p
* 4D9p 1G3p 1d4p 2G9m 2D9m 3G3m 3D3m 4G1s 4d2p 1G発 1d7s 2G8p 2D8p 3G5S
* 3d白 4G1s 4d2p 1G1s 1d6m 2G9s 2D9s 3G1s 3d2m 4G9p 4D9p 1G3s 1d5M 2G東
* 2D東 3G4s 3d2m

---------------------------------------------------------


●手順2、プログラムを書く

得られたテキストファイルを読み込ませ解析を行わせるべく、プログラムを書きます。
しれっと書いてますが、実作業はほぼこれに尽きます。

私はプルグラム言語にRubyを使っています。というかRuby以外はさっぱりです(多分珍しい)。
Rubyは他のプログラム言語と比べ、かなり直感的に書けるようで、
また言語処理に強いとのことで、書式化した牌譜の解析にはかなり向いているようです(他を知らないので伝聞形)。

私の場合は、情報が専門の友人にお願いして仕込んでもらいました。
やはり、一から理解するのは難しくはあったのですが、
友人からサンプルスクリプトを書いてもらってから格段に理解が進むようになりました。


そんなわけで、今回は「流局率を求めるスクリプト」を例を挙げてみようと思います。


---------------------------------------------------------
#流局率を調べよう。

all_count = 0
#全局数を示す変数all_countに0代入。
ryu_count = 0
#流局した局数を示す変数ryu_countに0代入。
---------------------------------------------------------

「#」はコメントアウトです。
Rubyプログラム的には、その行の「#」以後は基本的に無視されますので、
どういう意図の命令なのかを忘れないように書き残しておきます。
よくサボって後々酷い目にあいます。

方針としては、全ての局数と、流局した局の数がわかれば流局率が求められるので、それを数えます。

そこで、全ての局数と、流局した数を入れる「変数」を定義します。
変数の定義、というとかなり大仰ですが、「=」を使うだけで簡単に定義できます。



---------------------------------------------------------
f = open◆("paihu.txt")
f.each{|line|
  if(line =~ /流局/) then
ryu_count += 1
 end
#「流局」があれば、流局数+1

 if(line =~ /本場/) then
all_count += 1
end
#「本場」があれば、全局数+1

}
f.close
#「paihu.txt」を閉じる。

---------------------------------------------------------

※f = open()の形はブログに記事を投稿する上では、セキュリティ上使えないとエキサイトさんに弾かれたので、間に◆を挟んでいます。
実際に使う際には消してください。



牌譜を読み込ませる処理は↓この部分であり、牌譜解析のメインの部分です。

f = open◆("paihu.txt")
f.each{|line|
 処理1
 処理2
 ……
}


詳しい説明は省きますが、オチとしては「paihu.txtを開いて、行ごとに読み込んで」っていう命令です。
paihu.txtの第1行目が、変数「line」に入っており、この状態で、処理1、2を実行します。
その後、paihu.txtの第2行目について同様の処理をし、
paihu.txtの第3行目について同様の処理をし……を、paihu.txtが終わるまで続けます。



方針としては、流局した局を数えたいわけですが、重要なのは何をもってその局が流局したかということです。
先ほどのASAPINさんの昇段戦テキストを見ますと上から4行目に『流局』と書いてあるように、
流局した局については「流局」とはっきりかいてあります。
ですので、「流局」と書かれた部分を探して数えさせればいいわけです。


そこで「"流局"と書いてあるかどうか」で条件分岐をさせて
書いてある場合は、流局数を1つ増やす処理を行わせています。

  if(line =~ /流局/) then
ryu_count += 1
 end


↑この部分ですね。

if文は条件分岐です。これさえあれば何でもできます(過言だけど、そこまで過言でもないはず)。
条件式に該当するときのみ、then~endまでの処理を行います。


今回条件式に入っているこの形、
AAA =~ /BBB/
これは『正規表現』と呼ばれるものです。
詳しく書くと難しいので今回は「おまじない」程度に考えてください。

これだけで分厚い1冊の本が書けるくらい底の深いエッセンスがある分野らしいです。


また、全体の局数も必要です。
牌譜ファイルテキストには、局ごとに必ず
 ●X局 Y本場 (各プレイヤーの収支)
を記載する行があるので、この「本場」の部分を拾わせています。

 if(line =~ /本場/) then
all_count += 1
 end

↑この部分ですね。


---------------------------------------------------------
ryu_rate = ryu_count.to_f / all_count
#流局率を示す変数ryu_rateに、流局数÷全局数を代入

print("全局数:", all_count, "\n")
print("流局数:", ryu_count, "\n")
print("流局率:", ryu_rate, "\n")
#全局数、流局数、流局率、試合数を表示。

---------------------------------------------------------

数えた流局数と全局数をもとに、割り算をして流局率を求めました。
こんな感じで変数同士でそのまま四則計算ができます。

ここで気になるのは「.to_f」です。
Rubyは、特に指定しない限り、整数÷整数は整数を返すので、
どんな牌譜を解析しても、このままだと0(0%)か1(100%)を返してしまいます。
そのあたりの有効数字のようなものを「.to_f」で指定してあげています。



こんなコードを書いて、コマンドプロンプト上で起動させると……

d0279358_0174560.jpg


流局率を求めることができました。

ちなみに上の方が特上の牌譜、下は鳳南の牌譜です。
しっかりオリる分、鳳凰の方が流局しやすいようです。
だいたいこんな感じで牌譜解析をしています。


これは、あくまで私はこうやってるという一例です。
詳しいRubyのリファレンスは、私が書くよりも優秀なものがゴロゴロあるので、ググってみていただければと。







しかし、今回のこの流局率を出すアルゴリズム、本当のところはちょっと正しくありません。
その良くない部分は、条件分岐に潜んでいます。


「流局」と書いてあるなら流局数+1とさせていますが、
仮にプレイヤー名に「流局」という字を含むプレイヤーがいれば、当然ながらそれも拾うので、正確な値とはズレてきます。
同様にプレイヤー名に「本場」という字を含んでいれば、これまたズレます。


ですから、本当に欲しい情報を拾おうと思ったら、条件分岐を上手に調整し、
要らないものを拾わず、かつ取りこぼしのないようにしなければなりません。
このあたりがプログラムの難しいところです。

しかし、多少荒っぽくても目安にするには十分だったりもするので、
色々やってみると面白いかなと。



かなり荒っぽい解説でしたが、何かの参考になればと思います。

需要はあまりないかもしれませんが、
気が向いたら「鳳凰牌譜の取得の仕方」編もお送りしようかなと思ってます。
[PR]
by tsubame30 | 2013-03-01 00:22 | 麻雀雑談 | Comments(0)