これは、jupyter notebook Advent Calendar 2016の10日目の記事です。
何の話かというと
IPython.displayモジュールのHTML関数を使うと、次のように、Jupyterノートブック上でJavaScriptを実行することができます。
from IPython.display import HTML javascript = ''' <script type="text/javascript"> alert("HOGE") </script> ''' HTML(javascript) # ポップアップウィンドウを表示
この時さらに、JavaScriptからノートブックを実行中のカーネルを呼び出して、任意のコードを実行することが可能です。また、JavaScript側でコードの実行結果を受け取ることもできます。具体的には、次のようになります。
import numpy as np from IPython.display import HTML javascript = ''' <script type="text/javascript"> var kernel = IPython.notebook.kernel; var callback = function(output) { var res = output.content.data['text/plain']; alert(res); }; var x = 2.0; var command = 'np.sin(' + x + ')'; kernel.execute(command, {'iopub': {"output": callback}}, {silent:false}); </script> ''' HTML(javascript)
この例では、Pythonカーネル側で np.sin(2.0) を計算して、その結果をJavaScriptに返しています。カーネルの呼び出しは非同期に行われるので、カーネル側の計算が終わったタイミングでコールバック関数(この例では callback)が呼び出されます。JavaScriptからAjaxでREST APIを呼ぶ感覚で、カーネル側のコードを実行すると思えばよいでしょう。
やってみた
というわけで、これを応用して、Jupyterノートブックで遊べるオセロゲームを作りました。オセロの盤面の処理(コマを置いてひっくり返すとか)とコンピュータ側の思考ルーチンをPythonで実装して、JavaScriptのUIから呼び出すようにしています。UIの実装は、enchant.jsを使っています。
実行画面はこんな感じ。囲碁のパーツで作ったので超違和感がありますがwww。あと、ゲームの終了判定や「パス」ボタンの実装は、面倒だったので省略しています。
うまくいかない点
ゲーム画面が描画された後、画面を上下にスクロールするとクリック位置の判定がおかしくなります。(クリック位置が固定されたままで、一緒にスクロールしていない。)
enchant.jsの実行ループを開始すると、カーソルキーが無効になります。
むむぅ。誰が解決策がわかったら教えてください。
おまけ
コンピュータ側の思考ルーチンはMini-Max法を使っています。具体的には、次のクラスのget_values()メソッドで盤面の評価値(人間側がどの程度優勢か)を計算して、これをなるべく小さくするように打ってきます。
class SimpleMiniMax: def get_values(self, boards): result = [] for board in boards: score = 0.0 # The value is defined from the player=1's point of view. for c in sum(board, []): if c == 1: score += 1 if c == -1: score -= 1 result.append([score]) return np.array(result)
この実装では、「人間側のコマの数-コンピュータ側のコマの数」を評価値にしているので、人間側のコマをなるべく減らす(コンピュータ側のコマをなるべく増やす)という単純なアルゴリズムになっています。最近はやりのディープなんとかで、評価値の計算をなんとかすると、なんとかなあれができるかも知れません。がんばってください。