0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

日本語プログラミング言語「なでしこ」Advent Calendar 2021

Day 8

なでしこさんでマルバツゲームを作るよ! ③ ~人対コンピューターで遊べるようにする~

Last updated at Posted at 2021-12-07

コンピューターが打てるようにする

 コンピューターがいかにして着手を決定するかとゆう問題以前に、コンピューターも打つということはまず、今はマウスのイベントの中に入っている、局面の記録やマルバツの描画などのもろもろは、別関数に独立させなきゃなりません。
 どこに打つか決まったら、後はやること大体一緒なので、マウスのイベントからとコンピューター側の着手からと、同じ関数を呼んでやるわけですね。
 そして、人対人の時と同様に描画して勝敗判定をした後は、コンピューターの手番だったらCOM着手を呼ぶ。
 イベント側は、コンピューターの手番だったら何もしないで戻るようにする。
 みたいな?

定数 [先攻,後攻]=[0,1]。
変数 [人間,COM]=[先攻,後攻]。

ゲーム画面をマウス押した時には
    もし、ゲーム中がいいえならば、戻る。
    もし、手番がCOMならば、戻る。
    列=(マウスX/マス幅)を整数変換。
    行=(マウスY/マス幅)を整数変換。
    番号=行*3+列。
    番号へ打つ。
ここまで。

●(番号へ)打つ
    もし、局面[番号]がアキでなければ、戻る。
    局面[番号]に手番を代入。
    番号に手番をマルバツ描画。

    0.1秒後には //chromeで描画より先にダイアログが上がっちゃうの防ぐため
        勝敗判定して結果に代入。
          もし、結果が継続ならば、
            手数=手数+1。手番=手数%2。
            もし、手番がCOMならば、
                COM着手。
            ここまで。
        違えば、
            結果で終局。
        ここまで。
    ここまで。
ここまで。

コンピューターが打つ

 肝心のCOM着手の中身を考えていきます。

乱数で打つ

 ああっ、石を投げないで~w

●COM着手
  8の乱数へ打つ。
ここまで。

 これだけでも、とりあえず出来たことは出来たんですが、めっちゃくちゃ弱いとかいう以前にちょっと問題が。
 終局が近付くにつれて考える時間が長くなっていっちゃう。
 打てないところを選択しては、やり直しを繰り返しているんだよね。(マウスで置けないところをクリックした時と同様、乱数で得たマスがアキじゃなかった時には、もっかいCOM着手を呼ぶようにしています)

着手可能マスを調べる

 折角局面を記録してるんだから、アキになっているマスを調べて、その中からランダムに選ぶようにすればムダが省けるわけです。

●COM着手
    着手可能マス=着手可能マス確認。
    着手可能マス[(着手可能マスの要素数)の乱数]へ打つ。
ここまで。

●着手可能マス確認
    着手可能マスは空配列。
    局面を反復
        もし、対象がアキならば、着手可能マスに対象キーを配列追加。
    ここまで。
    着手可能マスで戻る。
ここまで。

 OKですね! めっちゃくちゃ弱いってコトを除けば・・・www

もうちょっと強くしてみる

 こうゆうゲームは、相手が強すぎてもツラいけど、弱すぎても面白くないよね。
 人間の考えで言うと、三目同じ記号が並んだら勝ちなわけだから、負けないためには相手の記号が二目並んだら当然止めますよね。
 そして逆に、自分の記号が二目並んでいたとしたら、三目にして勝たないわけがありません。
 とゆうわけで、勝敗判定のパターンを使って、その列に同じ記号が2つあってもう一箇所がアキだったらそこに置くようにしてみる。
 こんな感じ?

パターンを反復
    変数[a,b,c]=対象。
    もし、(局面[b]≠アキ)かつ(局面[b]=局面[c])かつ(局面[a]=アキ)ならば、
        aへ打って戻る。
    違えば、もし、(局面[a]≠アキ)かつ(局面[a]=局面[c])かつ(局面[b]=アキ)ならば、
        bへ打って戻る。
    違えば、もし、(局面[a]≠アキ)かつ(局面[a]=局面[b])かつ(局面[c]=アキ)ならば、
        cへ打って戻る。
    ここまで。
ここまで。

 あんまり賢そうでもない;
 しかも、どちらのリーチもなかったら? ・・・やっぱ乱数でwww
 でも、これだけで結構強くなりましたよ。遊べるレベル☆

ここまでのコード

 とりあえず、遊べるようになりました!(え)
 開始時に、先攻後攻はランダムで決め、完全に乱数で打つレベル0「よわい」と、二目を止めるレベル1「ふつう」を選択出来るようにしています。
 0か1かを入力して選択してください。
 「弱い」と「普通」じゃなくて、「めちゃくちゃ弱い」と「弱い」だろうとゆう指摘はナシでお願いします。
 ついでに、終局したら先攻後攻を入れ替えて続けることが出来るようにしています。

#---宣言-----
定数 ゲーム画面=描画中キャンバス。
定数 マス幅=100。
定数 [アキ,マル,バツ]=[-1,0,1]
変数 [手数,手番]=[0,0]。
変数 局面=空配列。

#勝敗判定
定数 パターン=[[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,-2]
変数 ゲーム中=はい。

#コンピューターが打つ
定数 [先攻,後攻]=[0,1]。
変数 [人間,COM]=[先攻,後攻]。
変数 プレイヤー=空配列。
変数 レベル=1。
#-----------------------------------------------

ゲーム開始。

#---イベント-----
ゲーム画面をマウス押した時には
    もし、ゲーム中がいいえならば、戻る。
    もし、手番がCOMならば、戻る。
    列=(マウスX/マス幅)を整数変換。
    行=(マウスY/マス幅)を整数変換。
    番号=行*3+列。
    番号へ打つ。
ここまで。

#---関数----------
●ゲーム開始
    人間=2の乱数。COM=(人間+1)%2。//先攻後攻決め
    対戦相手選択。
    初期化。
    もし、COMが先攻ならば、COM着手。
ここまで。

●対戦相手選択
    「レベル選択。よわい(0)orふつう(1)」を尋ねる。
    レベルはそれを整数変換。。
    もし、レベルが0でなければ、レベルは1。
    「レベル{レベル}です」を言う。
ここまで。

●初期化
    画面描画。
    番号を0から8まで繰り返す。局面[番号]=アキ。。。
    手数=0。手番=0。ゲーム中は、はい。
    プレイヤー[人間]=「人間」。プレイヤー[COM]=「COM」。
ここまで。

●新規ゲーム
    人間=(人間+1)%2。COM=(COM+1)%2。//先攻後攻入れ替え
    初期化。
    もし、COMが先攻ならば、COM着手。
ここまで。

#描画
●画面描画
    全描画クリア。
    4に線太さ設定。黒色に線色設定。
    2回
        [マス幅*回数,0]から[マス幅*回数,マス幅*3]へ線描画。
        [0,マス幅*回数]から[マス幅*3,マス幅*回数]へ線描画。
    ここまで。
ここまで。

●(番号に記号を)マルバツ描画
    定数 [マル中点,マル半径,バツ始点,バツ終点,記号太さ]=[50,30,20,80,10]
    x=番号を3で割った余りにマス幅を掛ける。
    y=番号を3で割って、それの整数部分にマス幅を掛ける。
    「#DD3344」に線色設定。空に塗り色設定。記号太さに線太さ設定。
    もし、記号がマルならば、
        [x+マル中点,y+マル中点]にマル半径の円描画。
    違えば、もし、記号がバツならば、
        [x+バツ始点,y+バツ始点]から[x+バツ終点,y+バツ終点]まで線描画。
        [x+バツ始点,y+バツ終点]から[x+バツ終点,y+バツ始点]まで線描画。
    ここまで。
ここまで。

●(番号へ)打つ
    もし、局面[番号]がアキでなければ、戻る。
    局面[番号]に手番を代入。
    番号に手番をマルバツ描画。

    0.1秒後には //chromeで描画より先にダイアログが上がっちゃうの防ぐため
        勝敗判定して結果に代入。
          もし、結果が継続ならば、
            手数=手数+1。手番=手数%2。
            もし、手番がCOMならば、
                COM着手。
            ここまで。
        違えば、
            結果で終局。
        ここまで。
    ここまで。
ここまで。

#勝敗判定
●勝敗判定
    パターンを反復
        変数[a,b,c]=対象。
        もし、(局面[a]≠アキ)かつ(局面[a]=局面[b])かつ(局面[a]=局面[c])ならば、局面[a]で戻る。
    ここまで。
    もし、手数が8以上ならば、
        引き分けで戻る。
    違えば、
        継続で戻る。
    ここまで。
ここまで。

●(結果で)終局
    もし、結果が引き分けならば、
        「引き分け。」を言う。
    違えばもし、結果>引き分けならば、
        「{プレイヤー[結果]}の勝ち。」を言う。
    ここまで。
    ゲーム中は、いいえ。
    「続ける?」で二択。
    もし、それがはいならば、新規ゲーム。
ここまで。

#コンピューターが打つ
●COM着手
    レベルで条件分岐。
        0ならば、レベル0。。。
        1ならば、レベル1。。。
    ここまで。
ここまで。

#空いてる所へランダムに打つ
●レベル0
    着手可能マス=着手可能マス確認。
    着手可能マス[(着手可能マスの要素数)の乱数]へ打つ。
ここまで。

#同じ記号が2つあったら止める
●レベル1
    パターンを反復
        変数[a,b,c]=対象。
        もし、(局面[b]≠アキ)かつ(局面[b]=局面[c])かつ(局面[a]=アキ)ならば、
            aへ打って戻る。
        違えば、もし、(局面[a]≠アキ)かつ(局面[a]=局面[c])かつ(局面[b]=アキ)ならば、
            bへ打って戻る。
        違えば、もし、(局面[a]≠アキ)かつ(局面[a]=局面[b])かつ(局面[c]=アキ)ならば、
            cへ打って戻る。
        ここまで。
    ここまで。
    レベル0。
ここまで。

●着手可能マス確認
    着手可能マスは空配列。
    局面を反復
        もし、対象がアキならば、着手可能マスに対象キーを配列追加。
    ここまで。
    着手可能マスで戻る。
ここまで。

動作確認

 実際に遊ぶつもりなら、むしろこのくらいがちょうどいいと思うんだよね。

もっと強くしたい!

 考えられることは色々ありますよ。
 例えばこいつ、自分の記号か相手の記号か判別していないので、自分がリーチで勝てる状態でも、先に相手のリーチを発見すると、そっち止めに行ってしまうんだよね~w とか。
 両天秤(ダブルリーチ)にかけれるとこを探すには? とか?
 ていうか狙って両天秤を作るにはどうしたらいいかな? とかとか?
 そうゆうコトを考えて強くなるよね、人間は。

 むしろ、たったの9手なんだから定石を全部教える、とゆうコトも考えられます。
 実際のところマルバツゲームは終局まで定石があってですね、双方が間違わなければ絶対にどちらも勝てず引き分けになるって決まっているので、あんまりコンピューター強くしたら100%人間の勝ちは無くなるのでつまんないですけれどね。

 つまんなくてもいいから最強を目指したい!

つづきます

 とゆうわけで、次回はミニマックス法とゆうものを学んでみたい。

目次

  1. キャンバスにゲーム画面を描画
  2. 人対人で遊べるようにする
  3. 人対コンピューターで遊べるようにする
  4. ミニマックス法を学ぶ
  5. 最強のマルバツゲームできた☆
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?