MiHaJong AI用スクリプト説明

Copyright (c) 2008-2013, 2019-2021 MihailJP

はじめに

MiHaJongでは、バージョン1.7より、AIの思考ルーチンをLuaスクリプトで書くことができます。 この文書では、MiHaJong用に追加された要素を説明しています。 Lua言語の基本的なことは、別途Lua言語のリファレンスマニュアルなどをご覧ください。

AIスクリプトファイルの置き場所

Vista 以降をお使いの場合で C:\Program Files 以下にインストールした場合、 AIスクリプトファイルはは C:\Users\(ユーザー名)\AppData\Roaming\MiHaJong\ai に置かれます。 そうでない場合、インストール先のフォルダーに ai というサブフォルダーがありますのでそこにスクリプトを置きます。

半荘を始めるときに規定のフォルダーから *.lua ファイルが検索され、 見つかったスクリプトファイルの中からランダムに選択されてロードされます。

使用可能な標準ライブラリ

MiHaJongでは、基本ライブラリ(コルーチンライブラリを含む)のみがオープンさ れています。

MiHaJong用スクリプトで定義しなければならない関数

MiHaJongで使用するLuaスクリプトでは、次の関数をグローバル環境に定義しなければなりません。 定義されていない場合、そのスクリプトを無効とみなしツモ切りを行います。

ontsumo(gametbl)

ツモ番になるごとに、(山から1枚自摸った状態で)この関数が呼ばれます。この関数は引数を1つ取り、2つの値を返します。

引数 gametbl は、卓の情報を示すテーブルで、軽量ユーザーデータとメソッドが格納されています。

1番目の返り値は、捨て牌の付帯情報(暗槓する、リーチする……)を表す番号です。 これについては、後述する mihajong.DiscardType を参照してください。

2番目の返り値は、手牌の番号を表すインデックスで、1から14で指定します。 14を指定すると、ツモ切りしたことになります(副露してもこれは変わりません)。 存在しない牌のインデックスを指定した場合、多牌でアガリ放棄となります。

なお、1番目の返り値に mihajong.DiscardType.Agarimihajong.DiscardType.Kyuushu を指定した場合 捨牌が不要ですので、この場合は2番目の返り値を nil にしてもかまいません。

ondiscard(gametbl)

誰かの打牌があるごとに、この関数が呼ばれます。この関数は引数を1つ取り、1つの値を返します。

引数 gametbl は、卓の情報を示すテーブルで、軽量ユーザーデータとメソッドが格納されています。

返り値は鳴きの種類を表す番号です。これについては、後述する mihajong.Call を参照してください。

MiHaJong本体から呼び出されるその他の関数

MiHaJongで使用するLuaスクリプトでは、前述した必須の関数のほか、次の関数が呼ばれることがあります。 定義されていない場合、何もしなかったものとみなされます(スクリプト自体が無効とみなされたりはしません)。

onkakan(gametbl)

誰かの加槓があると、この関数が呼ばれます。引数・返り値は ondiscard と同様です。 この関数は、槍槓を和了るために定義されています。

onankan(gametbl)

誰かの暗槓があると、この関数が呼ばれます。引数・返り値は ondiscard と同様です。 この関数は、国士無双を槍槓で和了るために定義されています。

init(gametbl)

局が開始され、ephemeralテーブル(後述)が初期化されると呼ばれます。 引数はontsumoondiscardと同様です。戻り値はありません。

ephemeralテーブルにサブテーブルが必要な場合などは、この関数内で初期化を行なってください。

予約されている名前

MiHaJongで使用するLuaスクリプトでは、グローバル環境における次の名前が将来の拡張のために予約されています。

ontsumoondiscard の引数について

ontsumo()ondiscard()の引数には、卓の状態が渡されます。 引数は1つのテーブルで、次のフィールド、メソッドが含まれています。

gametbl.addr

卓情報のアドレスを示す軽量ユーザーデータです。

gametbl.playerid

自分自身を表すプレイヤー番号です。

gametbl:movetile(from, to)

インデックスfromの位置の牌をtoに移動します。返り値はありません。

鳴きたい牌を指定するとき、toを1にして使います。

gametbl:evaluate(tsumoflag, [tiletbl])

現在の手が和了になっているなら、その点数を調べます。 gametbl:gethand()形式の引数を指定した場合、その手牌で点数を計算します。

tsumoflagには、ツモ和了として計算するならtrue、ロン和了として計算す るならfalseを指定します。

返り値は以下のフィールドが含まれるテーブルとなります。

ismahjong フィールド

和了形になっているならtrue、そうでなければfalseが格納されます。

なお、このフィールドの値がfalseの場合、ほかのフィールドは存在しません。

isvalid フィールド

縛りを満たしているならtrue、そうでなければfalseが格納されます。

fu フィールド

評価結果の符が格納されます。

han フィールド

評価結果の合計翻が格納されます。例えば、「三色・ツモ・ドラ1」で あれば4です。

mangan フィールド

評価結果の合計満貫が格納されます。例えば、役満なら4、ダブル役満なら8です。

points フィールド

評価結果の基本点数が格納されます。これは、子がツモ和了した時に他の子が支払う点数に相当します。 また、100点単位の切り上げは行われません。例えば、30符1翻の「ゴミ手」であれば240、満貫であれば2000となります。

gametbl:getactiveplayer()

現在のツモ番のプレイヤー番号を1~4(三人打ちの場合は1~3)の数値で返します。

gametbl:getbakaze()

場風牌を返します。返り値は mihajong.Tile.Wind テーブルの定数のどれかです (白入ありのルールでは mihajong.Tile.Dragon テーブルの値になることもあります)。

gametbl:getbasepoint()

原点(返し点)を返します。

gametbl:getchip([playerid])

playeridで指定したプレイヤー(省略した、またはnilを指定した場合は自分自身)のチップの収支を返します。 但し、チップ無しのルールの場合はnilを返します。

gametbl:getcurrentdiscard()

現在の捨牌を表すテーブルで返します。ontsumo()関数内では意味を持ちません。 このテーブルは次のフィールドをもちます。

tile フィールド

牌の種類を表す番号が入ります。

red フィールド

赤ドラかどうかを示す番号が入ります。

gametbl:getdeckleft()

山牌の残り枚数を返します(王牌は数えません)。

gametbl:getdeposit()

現在の供託点棒の数を返します。

gametbl:getdiscard([playerid])

playeridで指定したプレイヤー(省略した、またはnilを指定した場合は自分自身)捨牌をテーブルで返します。 このテーブルはそれぞれの捨牌の情報がサブテーブルとして順番に格納されており、これには次のフィールドがあります。

tile フィールド

牌の種類を表す番号が入ります。

red フィールド

赤ドラかどうかを示す番号が入ります。

through フィールド

ツモ切りした牌であればtrueが、そうでなければfalseが入ります。

riichi フィールド

リーチ宣言牌であればtrueが、そうでなければfalseが入ります。

taken フィールド

鳴かれた牌であればtrueが、そうでなければfalseが入ります。

gametbl:getdorainfo()

牌の種類ごとにドラになっているかどうか(なっている場合は何重にドラになっているかも)を格納したテーブルを返します。 このテーブルのキーは mihajong.Tile に定義された数値で、キーに関連付けられる値は、 例えばドラではないときは0、ダブルドラになっているときは2……という数値になります。

なお、槓ドラ(裏ドラはもちろん含まれません)のほか、永田町ルールにおけるサイコロによるドラも含まれます。

注意! この関数で返されるテーブルはキーが飛び飛びの数値になっています。

注意! Luaの言語仕様では、0はtrueに評価されます。ある牌がドラではないことを判定したいならば、

(gametbl:getdorainfo())[mihajong.Tile.Wind.East] == 0

のようにします。

gametbl:getdoukasen()

導火線(自摸牌の位置)にあたるプレイヤーの番号を1~4(三人打ちの場合は1~3)の数値で返します。 導火線なしの時はnilを返します。

gametbl:getflower([playerid])

playeridで指定したプレイヤー(省略した、またはnilを指定した場合は自分自身)の花牌、 または三麻の抜きドラの数を返します。

gametbl:gethand([playerid])

自分の(playeridを指定した場合は指定したプレイヤー、但し他家を指定する場合はオープン立直している場合に限る)手牌をテーブルで返します。 このテーブルは1~14のインデックスを持ちますが、自摸牌のインデックスは常に14となります。 このため、門前でないときはsparse な配列になることがあります。 存在する牌の位置には牌の情報を格納したサブテーブルが入り、牌が存在しない位置はnilとなります。

親の配牌時は自摸牌に関係なく理牌された状態で1~14に牌のデータが格納されています。 また、鳴いた直後の捨牌についてはインデックス14がnilとなっています。

tile フィールド

牌の種類を表す番号が入ります。

red フィールド

赤ドラかどうかを示す番号が入ります。

gametbl:getjikaze()

自風牌を返します。返り値は mihajong.Tile.Wind テーブルの定数のどれかです。

gametbl:getmeld([playerid])

playeridで指定したプレイヤー(省略した、またはnilを指定した場合は自分自身)の鳴き面子を格納したテーブルで返します。 このテーブルは鳴いた回数に応じて1~4のインデックスを持ちます。門前かつ暗槓も行なっていない場合は空のテーブルが返ります。 個別の鳴き面子の情報はサブテーブルで表され、次のフィールドが含まれます。

tile フィールド

牌の種類を表す番号が入ります。ただし明順子(チー)の場合、一番数字の低い牌が入ります。

red フィールド

それぞれの牌について赤ドラかどうかを示す番号を格納した配列が入ります。

type フィールド

面子の種類(順子、刻子……)を示す番号が入ります。

gametbl:getopenwait()

牌の種類ごとにオープンリーチの待ち牌になっているかどうかを格納したテーブルを返します。 このテーブルのキーは mihajong.Tile に定義された数値で、キーに関連付けられる値は、 その牌がオープンリーチの待ち牌になっていればtrue、そうでなければfalseになります。

注意! この関数で返されるテーブルはキーが飛び飛びの数値になっています。

gametbl:getpreviousdiscard()

この関数は返り値が2つあります。鳴いた直後の捨牌である場合、直前に鳴いた牌の種類を表す番号を返します。 この関数は、食い変えにならないようにするための判定に使用できます。

1番目の返り値は鳴いた牌の現物を表す番号で、2番めは(チーの直後である場合のみ)筋食い変えになるような牌の番号です。

該当する牌がなければ、nilが返ります。

gametbl:getrank([playerid])

playeridで指定したプレイヤー(省略した、またはnilを指定した場合は自分自身)の順位を返します。

gametbl:getround()

現在の局番号を返します。 これは、東1局を1とし、東2局を2、以下同様にして北4局が16となり、返り東1局が17(白入ありのルールでは白1局が17、 以下同様にして中4局が28、返り東1局が29)とした通し番号です。

なお、三人打ちでは4、8、12(以下同様)が欠番となります(四麻・三麻問わず南1局は5になるということ)。

東場のみを行うルールの場合、東5局は5、東6局は6……のように、「東n局」のnの部分がそのまま返されます。

gametbl:getrule(ruletag)

ruletagで指定された文字列に対応するルール設定を返します。

gametbl:getscore([playerid])

playeridで指定したプレイヤー(省略した、またはnilを指定した場合は自分自身)の持ち点を返します。

滅多に発生しないことですが、箱下ありの青天井ルールであまりに絶対値の大きい点数の場合、正確な値が取得できない場合があります。

gametbl:getseentiles()

牌の種類ごとに場に見えている枚数を格納したテーブルを返します。 このテーブルのキーは mihajong.Tile に定義された数値です。

注意! この関数で返されるテーブルはキーが飛び飛びの数値になっていま す。

gametbl:getshanten([tiletbl][, type])

現在の向聴数を返します。gametbl:gethand()形式の引数を指定した場合、その手牌の向聴数を計算します。

typeを指定した場合、和了形式を限定した向聴数を返します。typeには、mihajong.AgariTypeのフィールドのどれかを指定します。

聴牌になっている場合は0となり、和了っている場合は-1が返されます。

門前でないのに七対子に対する向聴数を求めようとした場合などそのような和了り方が不可能な場合や、 南北戦争なしのルールで南北戦争に対する向聴数を求めようとした場合などそのような和了り方がルール上認められていない場合、 返り値はnilになります。

gametbl:gettenpaistat([tiletbl])

聴牌に関する情報をテーブルで返します。gametbl:gethand()形式の引数を指定した場合、その手牌について計算します。 このテーブルには次のフィールドがあります。

isfuriten フィールド

フリテンであればtrueが、そうでなければfalseが入ります。

total フィールド

待ち牌の残り枚数が入ります。

kinds フィールド

待ち牌の種類数(2面待ちなら2、3面待ちなら3のように)が入ります。

bytile フィールド

牌ごとに次の情報を格納したサブテーブルです。このテーブルのキーは mihajong.Tile に定義された数値です。

gametbl:gettilecontext()

手牌の中のある牌について、面子を構成しているかどうか判断するための情報を格納したテーブルを返します。 インデックスは gametbl:gethand() の返り値と同様で、次のフィールドを持つサブテーブルが格納されています。

isexistent フィールド

そのインデックスの牌が存在すればtrue、そうでなければfalseが格納されます。

formstriplet フィールド

刻子をなしている牌ならtrue、そうでなければfalseが格納されます。

formssequence フィールド

順子をなしている牌ならtrue、そうでなければfalseが格納されます。

canformquad フィールド

手の内に4枚あるならtrue、そうでなければfalseが格納されます。

formspair フィールド

対子をなしている牌ならtrue、そうでなければfalseが格納されます。

formsryanmen フィールド

両面搭子をなしている牌ならtrue、そうでなければfalseが格納されます。

formskanchan フィールド

嵌搭子をなしている牌ならtrue、そうでなければfalseが格納されます。

formspenchan フィールド

辺搭子をなしている牌ならtrue、そうでなければfalseが格納されます。

gametbl:gettilerisk()

手牌の中のそれぞれの牌について、安牌かどうか判断するための情報を格納したテーブル返します。 インデックスは gametbl:gethand() の返り値と同様で、次のフィールドを持つサブテーブルが格納されています。

issameasprevious フィールド

合わせ打ちになる牌ならtrue、そうでなければfalseが格納されます。

isdora フィールド

ドラの現物ならtrue、そうでなければfalseが格納されます。

isdorasuji フィールド

ドラ筋にあたる牌ならtrue、そうでなければfalseが格納されます。

isdorasoba フィールド

ドラそば(=ドラの跨ぎ筋)の牌ならtrue、そうでなければfalseが格納されます。

isnochance フィールド

ノーチャンスの牌ならtrue、そうでなければfalseが格納されます。

isonechance フィールド

ワンチャンスの牌ならtrue、そうでなければfalseが格納されます。

isneverdiscarded フィールド

生牌ならtrue、そうでなければfalseが格納されます。

isseenfour フィールド

場に4枚見えているならtrue、そうでなければfalseが格納されます。

kamicha/toimen/shimocha フィールド

上家、対面、下家に対し通りそうかどうかを判断する材料を提供します。 これはサブテーブルであり、次のようなブール値のフィールドを持っています。

なお、三人打ちで存在しないプレイヤーの位置についてはフィールド自体がありません(空テーブルではなくnilとなります)。

gametbl:gettilesinhand()

牌の種類ごとに手牌に持っている枚数を格納したテーブルを返します。 このテーブルのキーは mihajong.Tile に定義された数値です。

注意! この関数で返されるテーブルはキーが飛び飛びの数値になっていま す。

gametbl:gettsumibou()

現在の積み棒の数を返します。

gametbl:getwareme()

割れ目(開門の位置)にあたるプレイヤーの番号を1~4(三人打ちの場合は1~3)の数値で返します。 割れ目なしの時はnilを返します。

gametbl:getyakuhaiwind()

それぞれの風牌について役牌になっているかどうかを格納したテーブルを返します。 これはEastSouthWestNorthのフィールドをもち、その牌が役牌になっていればtrueが、そうでなければfalseが設定されています。

gametbl:isabovebase([player])

playerには調べたいプレイヤーの番号を指定します。 nilが渡された場合または省略された場合は、自身の状態を調べます。

浮いている(持ち点が返し点以上)ならtrueを、そうでなければfalseを返します。

gametbl:isankanallowed()

リーチ後に暗槓出来る状態ならtrueを、そうでなければfalseを返します。

なお、この関数はリーチしていない状態でも使用できますが、その場合は意味をなしません。

gametbl:isdoujunfuriten()

同巡内フリテンか、リーチ後見逃しによるフリテンであればtrueを、そうでなければfalseを返します。

現物フリテンかどうか調べるには、gametbl:gettenpaistat()を使用します。

gametbl:isfinalround()

オーラスまたは延長戦(半荘戦であれば西入以降)であればtrueを、そうでなければfalseを返します。

gametbl:isfirstdraw()

鳴きのない第1ツモ(親については配牌直後)であればtrueを、そうでなければfalseを返します。

gametbl:isippatsu([player])

playerには調べたいプレイヤーの番号を指定します。nilが渡された場合または省略された場合は、自身の状態を調べます。

立直一発が成立しうる状態ならtrueを、そうでなければfalseを返します。 但し、一発なしのルールの時はnilを返します。

gametbl:iskansanjunqualified([player])

playerには調べたいプレイヤーの番号を指定します。nilが渡された場合または省略された場合は、自身の状態を調べます。

3巡目に槓をしている場合はtrueを、そうでなければfalseを返します。 但し、槓三巡なしのルールの時はnilを返します。

gametbl:iskyuushu()

九種九牌の条件を満たしていればtrueを、そうでなければfalseを返します。 但し、九種九牌で流せないルールの時はnilを返します。

gametbl:ismenzen([player])

playerには調べたいプレイヤーの番号を指定します。nilが渡された場合または省略された場合は、自身の状態を調べます。

門前であればtrueを、そうでなければfalseを返します。

gametbl:isopenriichideclared([player])

playerには調べたいプレイヤーの番号を指定します。nilが渡された場合または省略された場合は、自身の状態を調べます。

オープン立直しているならtrueを、そうでなければfalseを返します。

gametbl:ispenultimateround()

ラス前(半荘戦であれば南3局、東風戦であれば東3局)であればtrueを、そうでなければfalseを返します。

gametbl:isrenpaitenhohqualified()

この関数は2つのブール値を返します。

1つめの返り値は戻牌天和の条件を満たしているかどうかで、満たしているならtrueを、そうでなければfalseを返します。

2つめの返り値はその局で戻牌天和の条件を満たしたことがあるかどうかで、 満たしたことがないならtrueを、そうでなければfalseを返します。

gametbl:isriichideclared([player])

playerには調べたいプレイヤーの番号を指定します。nilが渡された場合または省略された場合は、自身の状態を調べます。

立直しているならtrueを、そうでなければfalseを返します。

gametbl:isshisanbuda()

十三不塔になっていればtrueを、そうでなければfalseを返します。 但し、十三不塔なしのルールの時はnilを返します。

gametbl:isshisibuda()

十三無靠(十四不塔)になっていればtrueを、そうでなければfalseを返します。 但し、十三無靠(十四不塔)なしのルールの時はnilを返します。

gametbl:isshokanqualified([player])

playerには調べたいプレイヤーの番号を指定します。nilが渡された場合または省略された場合は、自身の状態を調べます。

1巡目に槓をしている場合はtrueを、そうでなければfalseを返します。但し、初槓なしのルールの時はnilを返します。

gametbl:issumaroallowed([player])

playerには調べたいプレイヤーの番号を指定します。nilが渡された場合または省略された場合は、自身の状態を調べます。

四馬路和了のための事前条件(満貫以上の和了)を満たしている場合trueを、そうでなければfalseを返します。 但し、四馬路自体がないルールの時はnilを返します。

gametbl:isyakitori([player])

playerには調べたいプレイヤーの番号を指定します。nilが渡された場合または省略された場合は、自身の状態を調べます。

指定したプレイヤーが焼き鳥ならtrueを、そうでなければfalseを返します。 但し、焼き鳥なしのルールの時はnilを返します。

mihajongテーブル

MiHaJongで使用する(現在のシャンテン数を求める、などといった)関数、 あるいは(暗槓する、リーチするといった捨牌の付帯情報の番号といった)エイリアスが、 mihajongテーブルに格納されています。

注意! これらのサブテーブルはすべてプロキシテーブルになっているため、 単に pairs(mihajong.Tile.Wind)としただけでは正しいイテレータが返りません。 正しいイテレータを取得するには、

pairs(getmetatable(mihajong.Tile.Wind).__index)

のようにします。

mihajong.DiscardType

暗槓する、リーチするといった捨牌の付帯情報を表す数値を格納したテーブルです。 これらは、determine_discard 関数の戻り値として、「return mihajong.DiscardType.Riichi, 7」のように使用します。 このテーブルには、以下のフィールドが格納されています。

※1 オープンリーチなしの時に mihajong.DiscardType.OpenRiichi を指定した場合、mihajong.DiscardType.Riichi を指定したものとみなされます。

※2 和了れない状況で mihajong.DiscardType.Agari を指定した場合、チョンボになります。

なおこの他に切断されたリモートプレイヤーが返すを特殊なコードも内部的には存在しますが、このスクリプト仕様からそれは省かれています。

mihajong.Call

チー、ポンといった鳴きの種類を表す数値を格納したテーブルです。 これらは、determine_call 関数の戻り値として、「return mihajong.Call.Ron」のように使用します。 このテーブルには、以下のフィールドが格納されています。

※1 和了れない状況で mihajong.Call.Ron を指定した場合、チョンボになります。

mihajong.MeldType

明刻、暗槓といった鳴き面子の種類を表す数値を格納したテーブルです。 このテーブルには、以下のフィールドが格納されています。

なお、暗順子、暗刻子を表すコードも内部的には存在しますが、 役判定処理にのみ使われており、AIインターフェイスに出てくることはありません。 そのため、このスクリプト仕様からもそれらのコードは省かれています。

mihajong.AgariType

七対子、国士無双といった和了の形式を表す数値を格納したテーブルです。 これは、gametbl:getshanten()関数の引数に使用します。 このテーブルには、以下のフィールドが格納されています。

mihajong.Tile

一索、五筒といった牌の種類を表す数値を格納したテーブルです。 このテーブルには、以下のフィールドが格納されています。

mihajong.DoraColor

赤ドラかどうかを表す数値を格納したテーブルです。 このテーブルには、以下のフィールドが格納されています。

mihajong.gametbl

determine_discarddetermine_call の引数として渡されるテーブルのプロトタイプです。詳細は前述しています。

mihajong.random

MiHaJongのLuaインタプリタではmathライブラリがオープンされていないので、 乱数が必要な場合は mihajong.random() 関数を使います。 C++11の <random> ライブラリで実装されていて、 Lua標準ライブラリの math.random (これはC言語のrand()です)よりも高品位な乱数が得られます。

この関数は本物の乱数ではなくあくまで擬似乱数ですが、MiHaJongのシステム側で乱数の種は予測不能な値に設定されます。

なお、この関数は引数の与え方によって異なる挙動を示します。

mihajong.random()mihajong.random(nil)mihajong.random(nil, nil)

mihajong.random を引数なしで呼び出した場合、半開区間 [0, 1) の連続一様乱数を返します。

mihajong.random(n)mihajong.random(n, nil)

mihajong.random を1つの整数の引数で呼び出した場合、 閉区間 [1, n] の離散一様乱数を返します(返り値は整数になります)。 たとえば n == 6 の場合、サイコロを1つ振るのと同じになります。

mihajong.random(mean, var)

mihajong.random を2つの引数で呼び出した場合で、2つ目の引数が正の場合、 平均 μ == mean、分散 σ^2 == var の正規乱数を返します。

mihajong.random(n, -faces)

mihajong.random を2つの引数で呼び出した場合で、2つ目の引数が負の場合、 |faces| 面のサイコロを n 個振った出目の合計を返します。

mihajong.gametype

MiHaJongの種別を表す文字列です。意味は次のとおりです。

mihajong.version

MiHaJongのバージョンです。このテーブルには、以下のフィールドが格納されています。 また、tostring(mihajong.version)とすると、"1.7.0"のような文字列が返りますす。

mihajong.say(message)

この関数は廃止されました。互換性のために残されています。

ephemeralテーブル

これは空のテーブルで、AI作者が望むものを何でも格納することができます。これらは、毎局開始時にリセットされます。 プレイヤーごとに別々のLua環境が用意されているので、混線することはありません。

半荘中ずっと保持したい情報は、ephemeralテーブルの「外の」グローバル環境に保持します。