今回は、一人でも遊べるようにコンピュータの思考ルーチンを実装します。
動くものはこちらです。
https://reversi-d1kqojbar.now.sh/
現時点でのソースコードはこちらにあります。
https://github.com/mikehibm/reversi-react/tree/blog-3
さて、コンピュータとの対戦を実現するにあたって工夫する必要があるのは、「思考ルーチンを別スレッドで動かして、UIをブロックしないようにする」ことです。
通常ブラウザ上で動くJavaScriptアプリケーションでは一つのスレッドしか使えないので、時間がかかってCPUパワーを消費する処理を行うとその間UIが固まったりカクカクした感じになったりして操作性が悪くなります。
これを防ぐために、「Web Workers API」を使います。
ReactでWeb Workersを使う方法は、下の記事に書いたとおりです。
この記事で調べた、「WorkerのJSファイルをBlobとして読み込んでからWorkerスレッドを生成する」という方法を使うことにしました。
ひとまず基本的な仕組みを実装することに主眼を置いたので、思考ルーチンの内容は単に「配置可能な座標のなかからランダムに選ぶ」だけの動作になっています。この処理だと実際には一瞬で終わってしまうのでWorkerスレッドを使う意味は全くありません。
Workerスレッドを使う意味があるような、もっとヘビーにCPUを使う思考ルーチンへの改良については、次回以降の記事で書くことにします。
対戦中にコンピュータの番が来たときには、
のように execute()メソッドを呼んで思考ルーチンを起動しています。このexecute()メソッドというのは、JavaScript標準のWorkerクラスを継承して作成した独自クラスで定義したメソッドで、 Workerクラスの postMessage()を呼んだ後のイベントハンドリングを抽象化したものになっています。
思考ルーチンの処理が終わると、戻り値のresult変数にはコンピュータが選択した座標が入っているので、後は人間がクリックした時と同じように store.setStone(result) を呼んでその座標を渡します。そうすると store内部で盤面の状態が適宜更新されてイベントが発行されるので、それを受けて画面の表示が自動的に更新されることになります。
ここまででコンピュータとの対戦機能を実現するための基本的な仕組みを実装することが出来ました。次回はそれなりに強い思考ルーチンを作ることにチャレンジしたいと思います!
React/TypeScriptでリバーシゲームを作る
(1) - ボードの描画と石の配置
https://blog.makotoishida.com/2018/10/reacttypescript.html
(2) - ゲームロジック
https://blog.makotoishida.com/2018/11/reacttypescript-2.html
(3) - 思考ルーチンその1
https://blog.makotoishida.com/2019/01/reacttypescript-3-1.html
(4) - 思考ルーチンその2
https://blog.makotoishida.com/2019/03/reacttypescript-3-2.html
(5) - アニメーション
https://blog.makotoishida.com/2019/03/reacttypescript-5.html
タイトル画面でレベル1〜3が選べるようになっています。 (現段階ではどれを選んでも実際には難易度は同じです。) |
ゲーム画面は前回と同じですが、コンピュータの手番になると自動的に思考ルーチンが呼び出され、 結果が出るとその座標に石を配置します。 |
現時点でのソースコードはこちらにあります。
https://github.com/mikehibm/reversi-react/tree/blog-3
さて、コンピュータとの対戦を実現するにあたって工夫する必要があるのは、「思考ルーチンを別スレッドで動かして、UIをブロックしないようにする」ことです。
通常ブラウザ上で動くJavaScriptアプリケーションでは一つのスレッドしか使えないので、時間がかかってCPUパワーを消費する処理を行うとその間UIが固まったりカクカクした感じになったりして操作性が悪くなります。
これを防ぐために、「Web Workers API」を使います。
ReactでWeb Workersを使う方法は、下の記事に書いたとおりです。
Create React AppでWeb Workerを使うには
この記事で調べた、「WorkerのJSファイルをBlobとして読み込んでからWorkerスレッドを生成する」という方法を使うことにしました。
ひとまず基本的な仕組みを実装することに主眼を置いたので、思考ルーチンの内容は単に「配置可能な座標のなかからランダムに選ぶ」だけの動作になっています。この処理だと実際には一瞬で終わってしまうのでWorkerスレッドを使う意味は全くありません。
Workerスレッドを使う意味があるような、もっとヘビーにCPUを使う思考ルーチンへの改良については、次回以降の記事で書くことにします。
playersフォルダ内のcomputerPlayer1.tsを見ると、thinkProc() という関数があり、そこに思考ルーチンの実態が入っているのが分かります。
---
この thinkProc関数を createWorker というユーティリティ関数に渡すことで thinkWorkerという名前のWorkerスレッドを作成しています。
const thinkWorker = createWorker(thinkProc);
対戦中にコンピュータの番が来たときには、
const result = await thinkWorker.execute({ board: { cells: board.cells } });
思考ルーチンの処理が終わると、戻り値のresult変数にはコンピュータが選択した座標が入っているので、後は人間がクリックした時と同じように store.setStone(result) を呼んでその座標を渡します。そうすると store内部で盤面の状態が適宜更新されてイベントが発行されるので、それを受けて画面の表示が自動的に更新されることになります。
ここまででコンピュータとの対戦機能を実現するための基本的な仕組みを実装することが出来ました。次回はそれなりに強い思考ルーチンを作ることにチャレンジしたいと思います!
React/TypeScriptでリバーシゲームを作る
(1) - ボードの描画と石の配置
https://blog.makotoishida.com/2018/10/reacttypescript.html
(2) - ゲームロジック
https://blog.makotoishida.com/2018/11/reacttypescript-2.html
https://blog.makotoishida.com/2019/01/reacttypescript-3-1.html
(4) - 思考ルーチンその2
https://blog.makotoishida.com/2019/03/reacttypescript-3-2.html
(5) - アニメーション
https://blog.makotoishida.com/2019/03/reacttypescript-5.html