読者です 読者をやめる 読者になる 読者になる

ES2015のProxyを使った関数呼び出しのフック

ES2015にはProxyという仕組みがある。

Proxyを使うことでオブジェクトへの書き込みや読み込みをフックすることができ、メタプログラミング的なことがJavaScript上で実現できるようになる。

例えば、関数呼び出しをフックして呼び出し前に何か別の処理をしたい、と思ったら以下のように書ける。

const obj = {
  foo: (x) => {
    console.log(x);
  }
};

const proxy = new Proxy(obj, {
  get: function(target, name) {
    const targetObj = target[name];
    if (typeof targetObj === 'function') {
      return function(...args) {
        console.log(`call ${name} ${args}`);
        const result = targetObj.apply(this, args);
        return result;
      };
    } else {
      return targetObj;
    }
  }
});

window.onload = () => {
  proxy.foo(42);
}

Proxyのgetハンドラで値の読み込みをフックし、関数を読み込もうとしたら別の関数に差し替える。差し替えた関数の中ではapplyで元の関数を呼び出すけど、その前や後に任意の処理を挟み込むことができる。

ちなみにES5の制約でBabelはProxy未サポートなのでブラウザが対応しないかぎりProxyは使えない。

今までProxyはFirefoxかEdgeでしか使えなかったのだけど、Chromeも49から対応してくれるらしい。

だとするとそろそろProxyを使ったメタプログラミングに手を出してもいいかなあとも思う。あとTypeScriptでもES6ターゲットにすればProxy書いても文句言わないので使えそうだ。

クリックだけでプログラムが作れる夢のプログラミング環境作った

ウソです。いやウソではないか……誇張です。

screenshot

上のデモ開いて、左クリックでコード生成、右クリックでコード削除。運が良いと何かのグラフィックスを描くプログラムができる。あまりに何も描かないようだったら一旦右下の[Reset]を押して下さい。グラフィックスAPIp5.js利用。

左クリックで生成されるコードはRecurrentJSを使ったLSTMで作られている。LSTMやRNNをつかった文書生成はいろんなところでやられていて、有名どころだとThe Unreasonable Effectiveness of Recurrent Neural Networksがある。この記事ではLinuxソースコードを食わせてCのプログラムを作る例もある。ただ、自動生成でできる文やプログラムはいわゆるワードサラダで、文には意味が無いし、プログラムはコンパイルできない。

ならワードサラダなプログラムでも実行できる処理系を作ればいいんではないか、と思って作ったのがsarad。スタック指向ポーランド記法な言語。文を右から見ていって数値や変数をスタックに積み、演算子や関数を処理する時にスタックから取り出して引数にする。引数が足りない場合は強制的に0を割り当てることでエラーを吐かないようにしている。'if'や'while'などの基本的なフロー制御はあるが、突然'else'とかが出てきてもエラーは出さずに無視する。そうすることで、ワードサラダなプログラムでもエラーにはならずに無理やり実行される。

LSTMの学習に使う元データはprocessingのサンプルコードを使った。ただそのまま持ってくると自動生成の元にするには無駄にバリエーションがあるので、

  • 数は一旦すべて'D'に置換して学習し、コードを生成してからランダムに'0'~'9'を割り当てる
  • 変数名は'V0'から'V4'に強制変換する

として複数のコード片を混ぜあわせても破綻しにくいようにした。

現時点で課題は山積みで、

  • 意味のあるプログラムが生成される確率があまりに低い
  • なにも処理をしていない大量のデッドコードが生成される
  • プログラムの可読性が低く生成されたコードのどこを削ればいいか分からない
  • 'if'や'while'などブロックを使った長い文脈で意味のあるものを生成するのは大変

などなど。可読性の点からいうと、独自言語よりは既存のプログラミング言語が生成できて、デッドコード削除もされてた方が良いと思われる。もうちょっと考えます。

無限ランダムひどいアクションゲーム生成器への道

ゲームそれ自体を自動生成してくれる機械が欲しい。開発者はその機械が生成するゲームを遊んで良ゲーなら採用、クソゲーなら捨てる、その作業だけでゲームが作れる。夢の機械だ。

人が後で見て取捨選択する前提なら、出来上がるものの大半ではクソゲーでもいい。それよりも大切なのはゲームの持つルールというか、仕組みというか、ギミックというか、そういったものが十分なバリエーションを持って生成されること。似たようなゲームしか作られないのではつまらない。

どうすればそういったことができるか。一例として、プレイヤーがボタンを押した時に起こることを乱数で適当に作って組み合わせる、という方法が考えられる。例えばボタンを押した時の自機の加速度の変動パターンを適当に設定することで、上に加速すればジャンプ、右に加速すればスライディング、などの動きをいろいろ作れそうである。そういったことを自機だけでなく敵にも設定すれば、いろんなバリエーションのゲームができるかも。

そういったアプローチを採っている既存のものとして、Mechanic Minerがある。

この論文はゲームを作るAIとして有名なANGELINAプロジェクトのものである。Mechanic MinerではToggleable Game Mechanics (TGM)と呼ばれるボタンが押されるたびに自機の状態が切り替わる機構を開発し、リフレクションを使ってクラス内のフィールドに対して適用している。題材としてはレベル内のスタート地点からゴール地点へ到達するパズルジャンプアクションを使っており、自機の位置や重力に対して、ボタンが押されると倍になる、半分になる、+-が入れ替わるというTGMが用意されている。

Mechanic Minerでは遺伝的アルゴリズムを用いてランダムなTGMの組み合わせから適切なTGM群を選択している。選択のための適応度 (fitness)は、自機を適当に動かしてゴールに到達するまでにレベルのどの程度の範囲を移動できたかを用いている。レベルのあちこちを移動してやっとゴールに到達できる、というのが質の良いゲームと判定しているようだ。もちろんゴールに到達不能なものは排除される。

Mechanic Minerはこのジャンプアクションに比較的特化した作りになっている感じだったので、アクションゲーム全般にもうちょっと拡張可能なものが作れないかと思って、別のものを作ってみた。

デモゲームは以下で遊べます。

クソゲーを自動的に作る、という点のみクリアして、あとは失敗している気がする。自機や敵の動作はいろんなバリエーションが出ているが、ゲームとしてのルールとしてのバリエーションとまでは呼べない印象である。あとクソゲーを超えたプレイ不能ゲームがたくさん出てくる。ボタンを押しても何も起きないとか。

GameMechRandomizerではボタンを押した時に起こることとして、数値がn倍になる、nを足す、nになるという、より広範な動作を用意した。これらの動作がボタンを押した時、押している間、押すとトグルで切り替わる、押している間トグルで切り替わる、いずれかの条件で発生する。

適応度は2つのプレイヤーAIを作ることで評価した。一つは賢いもの、もう一つはバカなもの。それぞれのAIが自動生成されたゲームを遊んで、バカなものは多く死に、賢いものはそれほど死なない、そういったものが比較的遊べるゲームだという判断をして、適応度を高くする。逆にプレイヤーが賢かろうがバカだろうが死に方に大差ないのはゲームとして成り立ってないと考えることとした。賢いAIといっても、例えば自機を8の字で動かして敵弾に当たりにくくするとか、そのレベルの賢さだが。

GameMechRandomizerを使うことで、ある程度は汎用的なアクションゲームに対して新たな仕組みを導入することはできる。ある程度は。

課題は満載である。

  • 新しいゲームが生成されている、とはとても言えない。箱避けゲームとか箱撃ちゲームとか、もともとあるゲームに対してちょっとバリエーションを与えている、程度である

  • まともなゲームができない。クソゲーで良いといっても遊べすらしないゲームはいかがなものか

  • 遊べすらしないゲームを遊べるゲームへ調整する仕組みが必要だが、現状の遺伝的アルゴリズムベースのアプローチが妥当かどうかは不明である

  • 多分、ゲームの楽しさというものを測るもうちょっとまともな指標が必要

もうちょっと考えます。

SVGのPathの当たり判定を取る方法

Pathで囲まれたSVGの当たり判定を取ることができれば、ベクター形状の間で正確に当たり判定が取れる。現代版1ドットのエクスタシーを魅せることが可能だ。以下の様なコードを書いた。

stars stars

SVG自体にはバウンディングボックスを使ったおおまかな当たり判定の仕組みしかないので、厳密な当たり判定は自前でなんとかするしかない。

この記事にあるようにまずバウンディングボックスでの衝突を判定し、衝突している場合のみパスを使った厳密な当たり判定を取るのがパフォーマンスの面から見て望ましい。

バウンディングボックスはこのスレにあるように、getBBox()で取ったバウンディングボックスを自力でtransformすることで得た。

パスを使った当たり判定は、まじめにパスとパスの交差を計算するととても大変なので、いくつかの線分で近似するのが現実的だろう。SVGPathElementにはgetPointAtLength()というパスの始点から特定の長さの位置にある点を取る関数があるのでこれを利用する。getTotalLength()で取ったパス全体の長さを適当な数で割ってその長さごとの点で囲まれた多角形を当たり判定とする。

実際に当たり判定を行うのはsat-jsに丸投げ。ただsat-jsのPolygonの当たり判定は凸面のポリゴンにしか対応してないので、凹面の多角形は適当な凸面ポリゴンに分割する必要がある。今回は安直に多角形の中心点と各辺から成る三角形に分割した。

こうすればそこそこ正確に当たり判定を取れるはずだが、処理としては結構重たいかも。ゲームで使っていいスピードが出るかどうかは微妙かもしれず。

Elmのエラーメッセージが分かりやすくなっている

だいぶ前にElm触ってゲーム作った時はElmに色々と不満があったんだけど、あれからElmもだいぶバージョンアップして0.15.1ではエラーが人フレンドリーになったらしい。前回の不満がどれだけ解決されているか調べてみよう。

関数の引数の数間違いが謎の型エラーになったりする

module Test where

import Html exposing (text)

message2: String -> String -> String
message2 world world2 = "Hello, " ++ world ++ " " ++ world2

main = text (message2 "World?" "World!")

というメッセージを表示するだけのコードを書いて、

main = text (message2 "World?")

と引数の数を間違うと、

The 1st argument to function `text` has an unexpected type.

8|        text (message2 "World?")
                ^^^^^^^^^^^^^^^^^
As I infer the type of values flowing through your program, I see a conflict
between these two types:

    String

    String -> String

8行目のここの引数がStringで無くString -> Stringだぞと出る。素晴らしい。抜群に分かりやすくなっとる。

レコードのアップデートと追加構文を間違った時のパーサーのエラーが謎だった

main関数を

main =
  let
     world = {str = "World??"}
     world2 = {world | str2 = "World!!"}
  in text (message2 world2.str world2.str2)

とし、

     world2 = {world | str2 = "World!!", str3 = "World?!"}

と許可されていない複数のフィールド追加を行うと、

11|      world2 = {world | str2 = "World!!", str3 = "World?!"}
                                           ^
I am looking for one of the following things:

    a closing bracket '}'
    an expression
    an infix operator like (+)
    whitespace

とちゃんとここで'}'を閉じろと言う。素晴らしい。

型の宣言でのエラーや変数名の重複でその行番号を教えてくれない

message2: String -> String -> String2

とすると

Cannot find type `String2`

5| message2: String -> String -> String2
                                 ^^^^^^^
Maybe you want one of the following?

    String

こう。

     world = {str = "World??"}
     world = {world | str2 = "World!!"}

とすると

Naming multiple values `world` in a single let-expression makes
things ambiguous. When you say `world` which one do you want?

11|      world = {world | str2 = "World!!"}
         ^^^^^
Find all the values named `world` in this let-expression and
do some renaming. Make sure the names are distinct!

こう。完璧ではないか。

乱数シードに現在時刻が欲しい

これはエラーメッセージとは関係ないのだが、乱数の初期化のために現在時刻が欲しかったのだが、その方法が良く分からなかった。

Elmの中だけでなんとかしようと思わないでportを使ってJavaScriptから供給すればいいらいいぞ。

portはElmとJavaScriptの間で値をやりとりする仕組みで、JavaScriptからsendされた値をElmのSignalとして受け取ることができる。

Elm.fullscreen(Elm.Test, {beginTime: Date.now()});

JavaScriptからElmを起動する時に引数で与えた値を、

port beginTime: Float

main =
  let
     world = {str = toString beginTime}

とportを介してもらえば良い。portってSignalじゃなくてもいいのね。たぶん不変な値限定だけど。

requestAnimationFrame対応

ElmのPongの例とかだと画面の更新はSignal.map inSeconds (fps 35)とか使っていてタイマーベースだけど、できればこれはrequestAnimationFrameにしたい。

それにはelm-animation-frameを使えば良い。elm package install jwmerrill/elm-animation-frameでパッケージをインストールし、

main =
  let
     frameNumSignal = Signal.map toString 
       (Signal.foldp (+) beginTime AnimationFrame.frame)
     messageSignal = Signal.map (message2 "World?!") frameNumSignal
  in
    Signal.map text messageSignal

のようにAnimationFrame.frameをトリガに画面を更新すれば良いらしいぞ。

Elmイケているではないか

バージョンアップで着実に欠点が埋められている感じ。portを使ったJavaScriptとの連携がうまく使えれば、ゲーム作りにもいいかも。あとは良いIDEが欲しいよな。

物理エンジンMatter.jsをテキストでレンダリング

CRTっぽいテキスト画面をWebGLで作るLocatePrintを使って物理エンジンをレンダリングするというデモも作った。

consolephysics

物理エンジンMatter.jsを使っている。Matter.jsはもちろん自前のレンダラを持っているんだけどそれを他のレンダラで差し替えられる

差し替える独自レンダラはMatter.jsのRender.jsを参考に作れば良い。特にRender.bodyWireframes関数にボディの頂点を線でつなぐ一番単純な描画方法が実装されているので、これを真似るのが簡単だ。

デモのコードでは、頂点を線でつなぐ部分でテキストを書くようにした。画面をテキストの幅と高さ(40キャラ x 20キャラ)のグリッドに分割し、線とグリッドが交わるポイントをリストアップする。グリッド内の各キャラについて、それらポイントがどこにあるかに応じて書くテキストを変える。例えば上端右と下端左にポイントがあれば/、右端上と下端左にあればFという具合に。それらのパターンを全部配列で記述した。

後はボディごとに設定された色に従ってcolor, locate, printするだけ。昔ながらの画面で近代的な物理エンジンが動く妙な絵が作れる。楽しい。

昔のCRTっぽい画面をWebGLで作る

マイコンをつないだテレビみたいな画面を作りたいと思ってLocatePrintっていうコードを書いた。

skigame

こんな画面が作れる、し遊べる。locate, print, colorなどの命令でテキストを書けます。というかテキストしか書けない。

こういうブラウン管っぽい画面を作るにはWebGLをポストプロセスとして使うのが便利。

この記事に必要なことはほとんど書いてある。glfx.jsっていうWebGLのイメージエフェクトライブラリを使って、ブラウン管の丸みっぽく画面を歪ませ (bulgePinch)、ふちをちょっと暗くすれば (vignette)、昔懐かしのテレビっぽい絵になる。

ただこの記事だとスキャンラインはPNGイメージを重ねることで実現しているけど、これもできればWebGLのシェーダーでやりたい。あとパピコンAppleIIみたいな色のにじみもつけたい。

そういう時はglfx.jsにカスタムのエフェクトが追加できればいいんだけど、残念ながらglfx.js自体はそのような仕組みを用意してない、が、やればできないことはない。

glfx.jsのいくつかの関数を無理やりexportsして外部から使えるようにする。そうすればスキャンライン色のにじみを再現するシェーダーをglfx.js内で使うことができる。

WebGL使えばブラウザ上でも簡単にポストエフェクトで遊べて良いね。フラットなLCDを丸いCRTにするという後ろ向きな使い方でも楽しければ良いのだ。