自作言語、最初で最後のプログラム
問題の自作言語ですが、とりあえずマルバツゲームを開発してみて、標準入出力内でひと通りの事はできるという事は解りました。
とはいえ、
- そもそも右結合にしてしまった事
- 簡約がグダグダなのでクロージャが扱えない
- リストと値の扱いが曖昧なまま力技で実装した関係でおかしな挙動をする
等といった理由から、まったく使い物にならないという事がわかったので、とりあえずこの言語の開発は今後凍結しようと思います。
実際に自作言語を使ってゲーム作りをする中、「簡約」「正格/非正格表現」といった、今まで字面だけ理解していた言葉の意味がちゃんと理解できたので、わりと得たものは大きかった気がします。
件のマルバツゲームは次のURLからダウンロードできます。興味があったら遊んでみてください。
こちらがソースファイル:
http://ux.getuploader.com/ToyFunctionalLanguage/download/11/tictactoe.zip
最新のインタプリタじゃないと実行できないです:
http://ux.getuploader.com/ToyFunctionalLanguage/download/10/TFL.zip
tictactoeフォルダをインタプリタと同じフォルダに置いた後、以下のように実行できます。※マルバツゲームは先手・後手ともに最善を尽くす引き分けになるゲームです。なので基本的に勝てないです。後日、アルゴリズムに不備を発見しました。ので、ちゃんと勝ち手があります。ゲームとしてはこのくらいのほうが・・・
$ ./tfl.jar tictactoe 1|2|3 ーーーーー 4|5|6 ーーーーー 7|8|9 Your turn : 9 1|2|3 ーーーーー 4|5|6 ーーーーー 7|8|O Enemy turn : 5 1|2|3 ーーーーー 4|X|6 ーーーーー 7|8|O Your turn : 3 1|2|O ーーーーー 4|X|6 ーーーーー 7|8|O Enemy turn : 6 1|2|O ーーーーー 4|X|X ーーーーー 7|8|O Your turn : 4
ソースコードは下のようになってます。
言語仕様が酷いので見た目がぐちゃぐちゃですが・・・
initial.tfl
##initial; ##tictactoe/outstr.tfl; ##tictactoe/cpu.tfl; ##tictactoe/obj.tfl; ##tictactoe/io.tfl; ##tictactoe/decision.tfl; ##tictactoe/util.tfl; main = GAME01 INITBORD;
cpu.tfl
---------------------- -- COMの思考処理 ; --各升目の利き手テーブル ; -- get関数の不具合回避、各リストの末尾にダミーのリストを入れておく。 ; dummyField = [-1,-1,-1] ; enableFields = [ [ [0,1,2] , [0,3,6] , [0,4,8] , dummyField ] , [ [0,1,2] , [1,4,7] , dummyField ] , [ [0,1,2] , [2,5,8] , [2,4,6] , dummyField ] , [ [3,4,5] , [0,3,6] , dummyField ] , [ [3,4,5] , [1,4,7] , [0,4,8] , [2,4,6] , dummyField ] , [ [3,4,5] , [2,5,8] , dummyField ] , [ [6,7,8] , [0,3,6] , [2,4,6] , dummyField ] , [ [6,7,8] , [1,4,7] , dummyField ] , [ [6,7,8] , [2,5,8] , [0,4,8] , dummyField ] , dummyField ]; --盤面 b において、利き手テーブル k の全ての要素に、プレイヤー p のフィールドが x 以上ある数。 ; _cntPlayer = b -> k -> p -> x -> len -> if`(>_[1,len]) { 0 } { +[`adequate (getEnableState car k b) p x, `_cntPlayer b (cdr k) p x -[len,1] ] }; cntPlayer = b -> k -> p -> x -> _cntPlayer b k p x `length k; --盤面 b において、プレイヤー p から見た座標 i のポイントを計算 ; _calcPoint = b -> p -> k -> +[ *[`cntPlayer b k p 2 , 10000], *[`cntPlayer b k `getEnemy p 2 , 1000] *[`cntPlayer b k p 1 , 100] `cntPlayer b k `getEnemy p 10, `length k]; calcPoint = b -> p -> i -> if`(`not eq[`get b i,0]) { 0 } { _calcPoint b p get enableFields i }; --盤面 b とプレイヤー p から最善手を計算する ; _calcCOM = b -> p -> i -> max -> maxIdx -> if`(>[i,8]) { maxIdx } { ( point -> _calcCOM b p +[i,1] `big max point `if`(>[max,point]) { maxIdx } { i } ) `calcPoint b p i }; calcCOM = b -> p -> _calcCOM b p 0 0 0;
cpu.tfl
---------------------- -- 勝敗判定 ; -- 勝敗判定用のラインテーブル ; lineTable = [ [0,1,2],[3,4,5],[6,7,8], [0,3,6],[1,4,7],[2,5,8], [0,4,8],[2,4,6],[-1,-1,-1]]; --盤面 b に 0 が残っていればtrue ; searchZero = b -> eq[0,* b]; -- 利き手 k に盤面 b の状態を適用し、すべて同じ値だったら(一列揃っていたら)その値を、そうでなければ 0 を返す ; checkFieldLine = k -> b -> (l -> if`(eq l) { car l } { 0 }) getEnableState k b; -- 盤面 b の勝敗判定、0 は継続 -1 でdrow ; _checkDecision = b -> i -> if`(eq[8,i]) { 0 } { (r -> if`(eq[0,r]) {_checkDecision b +[i,1]} { r } ) `checkFieldLine (get lineTable i) b }; checkDecision = b -> (d -> if`(eq[d,0]) { if`(`searchZero b) { 0 } { -1 } } { d } ) `_checkDecision b 0;
io.tfl
---------------------- -- IO処理 ; GAME01 = bord -> GAME02 bord `PLAYER1; GAME02 = bord -> p -> if`(eq[p,`PLAYER1]) { OutStr along[ getOutBord bord , "\nYour turn : " ] {GAME03_1} bord p } { (i -> OutStr along [ getOutBord bord , "\nEnemy turn : " , fromNumToIntStr +[i,1] , "\n"] {GAME04} bord p i) `calcCOM bord p }; --プレイヤーの入力 ; GAME03_1 = bord -> p -> InStr {GAME03_2} bord p; GAME03_2 = bord -> p -> s -> GAME04 bord p -[fromStrToNum s,1]; --設置、判定 ; GAME04 = bord -> p -> i -> GAME05 (set bord i p) p; GAME05 = bord -> p -> (d -> if`(eq[d,0]) { GAME02 bord `getEnemy p } { OutStr getOutBord bord GAME06_1 d} ) `checkDecision bord; --勝敗判定 ; GAME06_1 = p -> if`(eq[p,-1]) { OutStr "\nDrow!!" } { GAME06_2 p }; GAME06_2 = p -> if`(eq[p,`PLAYER1]) { OutStr "\nYou win!" } { OutStr "\nYou lose ... " };
obj.tfl
---------------------- -- ボードやプレイヤー等のオブジェクト定義等 ; --プレイヤー ; PLAYER1 = 1; PLAYER2 = 2; --空の盤面 ; INITBORD = makelist 8; --相手プレイヤーを返す ; getEnemy = p -> if`(eq[p,`PLAYER1]) { PLAYER2 } { PLAYER1 };
outstr.tfl
---------------------- -- 出力用文字列作成 ; -- 盤面のインデックスから表示用の全角数値に変換 ; idxToEm = i -> get ["1","2","3","4","5","6","7","8","9"] i; -- ステータスによって、各手番を表す文字を返却する ; getPlayerChar = s -> if`(eq[s,1]) { "O" } { "X" }; -- 盤面のステータス、インデックスから、出力用全角文字一文字を返す ; getOutChar = s -> i -> if`(eq[s,0]) { idxToEm i } { getPlayerChar s }; -- 盤面出力処理本体 ; getOutBord = b -> along [ getOutChar `get b 0 0 , "|" , getOutChar `get b 1 1, "|" , getOutChar `get b 2 2 , "\n" , "ーーーーー\n" , getOutChar `get b 3 3 , "|" , getOutChar `get b 4 4, "|" , getOutChar `get b 5 5 , "\n" , "ーーーーー\n" , getOutChar `get b 6 6 , "|" , getOutChar `get b 7 7, "|" , getOutChar `get b 8 8 ];
util.tfl
---------------------- -- 共通関数 ; --リスト内に指定した値が指定数以上ある場合にTRUEを返す ; adequate = l -> v -> c -> >_[`count l v,c]; --利き手リスト k に盤面 b の状態を割り振る ; getEnableState = k -> b -> if`(isnil cdr k) { get b car k } { ++[`get b car k,getEnableState (cdr k) b };
開発作業は割と楽しかったので、機会があったらもっとちゃんと使える関数型言語を作ってみたいと思います。