麻雀AIの作り方(mjaiの使い方)
備忘録も兼ねてます。多少の間違いがあるかも。
対戦サーバの選択
こちら(http://togetter.com/li/230676)も参照。ただし若干古い。
現在は、mjai(http://gimite.net/pukiwiki/index.php?Mjai%20%CB%E3%BF%FDAI%C2%D0%C0%EF%A5%B5%A1%BC%A5%D0)一択と思われる。理由は省略。
とりあえず動くプログラム
こんな感じ。*1
#!/usr/bin/env ruby require "socket" require "json" require "uri" host = "gimite.net" port = 11600 room = "manue-1kyoku" socket = TCPSocket.new(host, port) socket.sync = true id = nil socket.each_line() do |line| $stderr.puts("<-\t%s" % line.chomp()) action = JSON.parse(line.chomp()) case action["type"] when "hello" response = { "type" => "join", "name" => "tsumogiri", "room" => room, } when "start_game" id = action["id"] response = {"type" => "none"} when "end_game" break when "tsumo" if action["actor"] == id response = { "type" => "dahai", "actor" => id, "pai" => action["pai"], "tsumogiri" => true, } else response = {"type" => "none"} end when "error" break else response = {"type" => "none"} end $stderr.puts("->\t%s" % JSON.dump(response)) socket.puts(JSON.dump(response)) end
実際に動かす場合、rubyをインストール、gem install json; ruby tsumogiri.rb。gimite氏のサーバでmjai-manueと1局戦(東1局のみ)ができる。
自分でサーバを立てる
サーバも手元で動かすほうが、ログを見れるので便利。
立てる側
$ sudo gem install mjai mjai-manue $ mjai server --port=11600 --game_type=one_kyoku --room=default --log_dir=./log
でOK.
つなぐ側
上のコードで言うと、
host = "localhost" port = 11600 room = "default"
に変える。
オプションの詳細
-
- game_type=[one_kyoku|tonpu|tonnan] # ゲームのタイプ指定。デフォルトはone_kyoku
- port=56565 # ポート指定。デフォルトは11600
- room=foobar # ルーム名指定。デフォルトはdefault
- repeat # 無限ゲーム。デフォルトはfalse
- games=20 # ゲーム数指定。デフォルトは1
- log_dir=./log # 牌譜出力ディレクトリ指定。必須
ビジュアライザ
ビジュアライザが付属している。
$ mjai convert log/2012-08-18-152342.mjson a.html $ google-chrome a.html # firefoxでは動かないので注意
プロトコルの詳細
基本
こんな感じのjson。
<- { "type": "tsumo", "actor": 1, "pai": "3s" } -> { "type": "dahai", "actor": 1, "pai": "5p", "tsumogiri": false }
ただし、<- : サーバからクライアントへの通信
- > : クライアントからサーバへの通信
サーバから1つメッセージが来たら、ちょうど1つのメッセージを返す。
hello
<- { "type": "hello", "protocol": "mjsonp", "protocol_version": 1 } -> { "type": "join", "name": "wistery_k", "room": "default" }
要するに参加表明。適当。
start_game
<- { "type": "start_game", "id":2, "names": ["manue","manue","wistery_k","manue"]} -> { "type": "none" }
自分のidと対局者の名前が来る。
idは0-indexedで、idが0の人が起家。
idは重要なので保存しておく必要がある。
noneを返す。
start_kyoku
<- {"type": "start_kyoku", "bakaze": "E","kyoku":1, "honba": 0, "kyotaku": 0, "oya": 0, "dora_marker" :"5sr", "tehais": [["?","?","?","?","?","?","?","?","?","?","?","?","?"],["?","?","?","?","?","?","?","?","?","?","?","?","?"],["1m","1m","3m","4m","7p","9p","9p","3s","7s","S","P","F","F"],["?","?","?","?","?","?","?","?","?","?","?","?","?"]] } -> {"type":"none"}
局開始。場風、局数(1-indexed,例えば5は南1局)、本場、供託、親、ドラ表示牌、手牌が来る。
noneを返す。
tsumo
<- { "type": "tsumo", "actor": 2, "pai": "3s" } -> { "type": "dahai", "actor": 2, "pai": "5p", "tsumogiri": false }
<- { "type": "tsumo", "actor": 2, "pai": "3s" } -> { "type": "reach", "actor": 2 }
<- { "type": "tsumo", "actor": 2, "pai": "3s" } -> { "type": "kan", "actor": 2, "pai": "5p", "consumed": ["5p", "5p", "5p"] }
<- { "type": "tsumo", "actor": 2, "pai": "3s" } -> { "type": "hora", "actor": 2, "target": 2, "pai": "3s" }
<- { "type": "tsumo", "actor": 3, "pai": "?" } -> { "type": "none" }
いずれかのプレーヤーがツモ。actorが自分の時はdahaiかreachかreachかhoraを返す。*2
actorが自分以外の場合はnoneを返す。
dahai
<- { "type": "dahai", "actor": 1, "pai": "2s", "tsumogiri": false } -> { "type": "none" }
<- { "type": "dahai", "actor": 1, "pai": "2s", "tsumogiri": false } -> { "type": "pon", "actor": 2, "target": 3, "pai": "2s", "consumed": ["2s", "2s"] }
<- { "type": "dahai", "actor": 1, "pai": "2s", "tsumogiri": false } -> { "type": "kan", "actor": 2, "target": 3, "pai": "2s", "consumed": ["2s", "2s", "2s"] }
<- { "type": "dahai", "actor": 1, "pai": "2s", "tsumogiri": false } -> { "type": "chi", "actor": 2, "target": 3, "pai": "2s", "consumed": ["1s", "3s"] }
<- { "type": "dahai", "actor": 1, "pai": "2s", "tsumogiri": false } -> { "type": "hora", "actor": 2, "target": 1, "pai": "2s" }
<- { "type": "dahai", "actor": 1, "pai": "2s", "tsumogiri": false } -> { "type": "none" }
<- { "type": "dahai", "actor": 2, "pai": "2s", "tsumogiri": false } -> { "type": "none" }
打牌。自分以外の打牌にはpon, kan, chi, hora, noneのいずれか。
自分の打牌にはnone。*3
reach
<- { "type": "reach", "actor": 2 } -> { "type": "dahai", "actor": 2, "pai": "5p", "tsumogiri": false }
<- { "type": "reach", "actor": 1 } -> { "type": "none" }
誰かがリーチ宣言した。それが自分の場合、dahaiを返す。
自分以外の場合、noneを返す。
reach_accepted
<- { "type": "reach_accepted", "actor": 2, "deltas": [0,0,-1000,0], "scores": [22700,27900,19700,28700] } -> { "type": "none" }
リーチが受理された(宣言牌がロンされなかった)。リーチ棒が出て点棒と供託が変更される。
常にnoneを返す。
pon
<- { "type": "pon", "actor": 2, "target": 3, "pai": "S", "consumed": ["S","S"] } -> { "type": "dahai", "actor": 2, "pai": "5p", "tsumogiri": false }
<- { "type": "pon", "actor": 0, "target": 3, "pai": "S", "consumed": ["S","S"] } -> { "type": "none" }
ポン。自分の場合、dahaiを返す。他人の場合、noneを返す。*4
chi
ponと同じ。
kan
ponと同じ。*5
hora
<- { "type": "hora", "actor":0, "target": 1, "pai": "9p", "hora_tehais": ["2p","2p","4p","5pr","6p","9p","9p","3s","4s","5s"], "yakus": [["akadora",1],["bakaze",1]], "fu": 30, "fan": 2, "hora_points": 2000, "deltas": [2000,-2000,0,0], "scores": [27000,24300,23700,25000] } -> { "type": "none" }
誰かの和了。noneを返す。
hora_tehaisで手牌が来るが、鳴きは来ない。自分で覚えてれば知っているはずなので、必要ならそれを使う。
ryukyoku
<- { "type": "ryukyoku", "reason": "fanpai", "tehais": [["?","?","?","?","?","?","?","?","?","?"],["?","?","?","?","?","?","?","?","?","?","?","?","?"],["4m","6m","7p","8p","9p","3s","4s","5s","7s","8s","9s","C","C"],["?","?","?","?","?","?","?","?","?","?","?","?","?"]], "tenpais": [false,false,true,false], "deltas": [-1000,-1000,3000,-1000], "scores": [21700,26900,22700,27700] } -> { "type": "none" }
流局。noneを返す。
end_kyoku
<- { "type": "end_kyoku" } -> { "type": "none" }
局終わり。noneを返す。
end_game
<- { "type": "end_game" }
ゲーム終わり。これは例外的に何も返さないで良い(多分)。
error
まとめ
多分、最低限必要/重要な情報は網羅したはず。
Let's クソゲ!!
お願い
手前味噌気味ですが、、、これ(https://github.com/wistery-k/mjai-silica)を倒したらコメントか何かください
結構頑張ったつもりだけど、どうだろう。。。