WindowsでClojureScriptのREPLを整備する

結論から言えばIntelliJCursiveプラグイン入れてfigwheelプロジェクト作るのが簡単そう。

ClojureなどのLisp系列言語では昔からREPLを使った開発がよく行われていて、REPL駆動開発とか呼ばれている。REPLと言うと対象のプログラミング言語の表記を入力するとその結果が帰ってくるシェルだけを指すように思われがちけど、REPL駆動開発においてはエディタ上から直接REPLへ特定のコード片を送って評価させるとかができる環境を想定していて、コードに新たなロジックを足す際にもその追加分をREPLでちょっとずつ試しながら作る、とかいう作り方を想定しているらしい。詳しいことは以下の記事や記事中のムービーを見ると分かる。

せっかくだから自分でもREPL駆動開発を試してみたい、でもブラウザで動くものじゃないと作り気がしない、となった時に選択肢として挙がってくるのがClojureScriptだ。ClojureScriptはJVM上で動くLisp系言語ClojureJavaScriptに変換するコンパイラだ。生成されたJavaScriptはもちろんブラウザ上で動かすことができる。

ClojureScriptはブラウザと連携して動作するREPLもあって、REPLで入力したコードの結果をすぐにブラウザ上に反映させたりできる。

さっそくそのREPLを何かのエディタと連携させて動作させたい、と思って最初は慣れ親しんだVisual Studio Codeでなんとかできないかと思ったのだが、なんかうまくいかない。もちろんClojure用のプラグインとかはあるんだけど、これと連携して動作するnREPLをClojureScriptに対応させたりブラウザと連携させたりがどうもうまくいかず。結局最初に書いたようにIntelliJに頼るほかなかった。

ブラウザでなにか画面出して遊ぼうと思ったときには定番のp5.jsを使うと楽だ。ちょうどp5.jsを使うサンプルコードもあったのでこれを参考に遊んでみた。

f:id:ABA:20171014170315g:plain

REPL上でrangeとかの関数の出力を確認しながらそれをmap関数でp5.jsの関数に引数として流し込んでみた例。ClojureScriptに不慣れな状態でも部分部分の動作を確認しながら進められるので、いきなり全部のロジックをコードとして書き下すよりは楽に書ける、気がする。JavaScriptの関数もjs/rectのような形で簡単に呼び出せる。

あとはClojureの作法に沿ったコーディングがどんくらいできるかが問題だなあ。個人的にはLisp系の言語にはあまり馴染みが無いし、Cursiveのエディタ拡張もかなり癖が強くて慣れるまでに時間がかかりそうだ。最初エディタ上で括弧が消せなくてなんじゃこりゃと思ったんだけど、Structural editingっていう括弧の対応をうまいこと保ちつつエディットできる機能のせいらしい。初心者に厳しい。とりあえず何か安直なゲームでも作ってみるのが良さそうではある。

倉庫番系列パズルゲームのレベル自動生成

ゲームの自動生成を目指すならばレベル、つまり面のパターンの自動生成もやっておきたい。ゲーム&ウォッチ的な単一の画面で遊ぶゲームはともかく、大抵のゲームは複数の面があったりスクロールしたりするのでレベル自動生成が必須である。

どれだけ質の良いレベルを作れるかがゲームの出来にダイレクトに効くのは何と言ってもパズルゲームだ。特に倉庫番のようなアクション性が無いパズルゲームにおいては、いかに解くのが楽しいレベルが作れるかがとても重要。

倉庫番のレベルジェネレータはすでにいろいろある。作り方としては、

  1. 箱が配置されていない空の部屋を作る
  2. 箱を正解位置に置く
  3. ゲームを逆にシミュレートして箱を適当な位置に散らす

というのが一般的のようだ。3.は要するにプレイヤーで箱を「引いて」ずらしていけば、その結果移動される箱はちゃんと正解位置へ移動可能であるということを保証できるという意味だ。

上の作り方に沿ったジェネレータ実装の一つ。プレイヤーが動けなくなったらバックトラックして他の置き方を探す。

こちらは論文。プレイヤーが箱を直線方向に押す回数や、別の箱を押しに行く回数などのメトリクスを導入して正解位置からなるべく離れた、つまりプレイヤーがやることが多くなるレベルを作っている。

この論文はさらにチャレンジングで、PuzzleScriptで作られる任意のパズルのレベルを生成することを目標としている。PuzzleScriptルールのアナライザからゲーム内オブジェクトの特性をレベルのジェネレータとレベルのエバリュエータに渡してレベルを生成させる。これは後段のエバリュエータが生成されたレベルが解けるかどうか、解けたとして楽しいものか、などを判定するアプローチなので、上記2つのような逆方向のシミュレーションをするものとはちょっと違う。

とまあ既存アプローチはいろいろあるが、せっかくだから何か簡単なパズルゲームを作って、自分でもレベル生成を試してみようと思った。

slickslack_screenshot

それがこれ。倉庫番ペンゴをつまみ食いしたようなパズルゲーム。青い四角で表示される箱をフリックでスリップさせて黄色の正解位置へ置いてください。

レベル自動生成方法は他の倉庫番ジェネレータと同様の正解から逆方向にゲームをシミュレートする方式。ただ空の部屋を最初に作るのではなくて、全て壁の部屋を作ってから箱を逆方向にスリップさせつつ壁を掘る方式にした。

slickslack_screenshot_generating

  1. 適当な大きさの全て壁(正確には壁か空白どちらでもいい状態)からなる部屋を作る
  2. 正解位置に箱を置く
  3. 箱を適当な方向に適当な距離スリップさせる
  4. スリップ開始時にスリップ方向と逆方向に壁を置いてそこで止まるようにする
  5. スリップした場所は空白で確定する(壁を掘る)

上記スリップ動作を適当な回数行ってレベルを作る。ただこれで闇雲に作ってもあまり楽しいレベルが出来るとは限らないのでちょっとだけ細工をしている。

このパズルはプレイヤーキャラがいないからかなり自由に箱をスリップさせることが出来てしまう。なのでパズルっぽさを出すには箱をスリップさせる順番や、別の箱にぶつけて箱を適切な場所で止めるにはどうするかを考えるような、箱と箱の間のインタラクションが多いものにしないといけないはず。あと壁が多すぎるとより制約が減ってしまってパズルっぽさが減りそう。それらを踏まえて

  • スリップさせる時はなるべく壁を作らないで、すでにある壁や箱で止められる方向を優先する
  • スリップさせた後になるべく他の箱に隣接させるようにする

という小細工を加えた。これら小細工がうまく効いているかはよく分からん。でも一応それなりに遊べるレベルは出来ているし、

クリアできないレベルができるということもとりあえず無さそうだ。

あとはPuzzleScript論文にあるようなエバリュエータを加えてレベルの楽しさを判定して生成したレベルを選別できると本当は良いのだろう。この論文では正解位置と箱の距離や繰り返しでないプレイヤーの操作数、適用されたルール数などを使って評価を行っている。これらのメトリクスが楽しさをちゃんと反映しているかどうかはともかく、明らかに楽しくないレベルを弾くには使えそうである。似たような仕組みをちょっとずつ試していきたいところ。

Matter.jsをプラグインで拡張する

lark-matter screenshot

ちょっと前にlark-matterっていう物理エンジンMatter.jsのレンダラをドット絵っぽくするプラグインを作ったんだけど、その時に覚えたはずのMatter.jsのプラグインによる拡張方法を忘れないうちにメモっておく。

基本的には以下のドキュメントを読めば良い。

プラグインの基本情報を書く所登録する所はだいたいサンプルの通り書けばいいとして、肝心なのはエンジンへのパッチの当て方だ。

基本的にはMatter.beforeMatter.afterという関数を使えばMatter.js内のあらゆる関数の実行前と実行後にプラグインとして実行したい処理を挟み込むことができるので、そこでゴニョゴニョすれば良い。

lark-matterの例で言えば、まずRenderを差し替えたいのでRender.createの後で独自レンダラの初期化などを行って、Render.runの後で既存レンダラを止めて新しいレンダラに差し替えている。

  matter.after('Render.create', initRender);
  matter.after('Render.run', runRender);
function runRender(render: Matter.Render) {
  matter.Render.stop(render);
  renderLm(render);
}

Render.runの引数とかはそのままフックする関数の引数にくるので、それを使ってrunしたレンダラを止めるとかいう処理もできる。

後はEngine.createの後でMatter.js内のイベントのリスナーを登録しておけば、特定のイベントが発生した時の処理を差し込むこともできる。例えば衝突発生時(collisionStart)にパーティクルを飛ばすとか。

  matter.after('Engine.create', initEngine);
function initEngine() {
  const engine: Matter.Engine = this;
  matter.Events.on(engine, 'collisionStart', e => {
    e.pairs.forEach(p => {
      p.activeContacts.forEach(ac => {
        const b = ac.vertex.body;
        const v = b.velocity;
        let ratio = (<any>p).collision.depth * matter.Vector.magnitude(v) * 0.1;
        if (ratio > 2) {
          ratio = 2;
        }
        if (ratio > 0.3) {
          ppe.emit(b.ppeTypeId,
            ac.vertex.x / LarkMatter.options.dotSize,
            ac.vertex.y / LarkMatter.options.dotSize,
            Math.atan2(-v.y, -v.x),
            { countScale: ratio, speed: 0.7 * ratio });
          if (LarkMatter.options.enableSes) {
            sss.play(b.sssTypeId, 2, null, ratio > 1 ? 1 : ratio);
          }
        }
      });
    });
  });
}

どのようなイベントが存在するかはMatter.jsのリファレンスを見れば分かる。例えばEngineで発生するイベントの一覧とか。

あとはプラグインのリストもあるので必要に応じて他の人の使ったプラグインを参考にすれば良い。

ブラウザ上でサーバサイドコードをエディット、実行できるGlitch

jsbinみたいなクライアントサイドJavaScriptをブラウザ上でエディット、共有するサイトはいろいろあるけど、このGlitchはサーバサイドでのコードをエディットして実行できるのが特徴。

とりあえずアクセスした人が適当にセルを埋められる多人数ライフゲームを作ってみた。Glitchのエディタ上でコードを書くだけで適当なURLが割り当てられてnode.jsでサーバサイド実行してくれる。

ドキュメントがあまり無いので結局何をしてくれてコードが実行されるのかがよく分からんのだが、たぶんこのエディタ上のルートディレクトリでnpm installnpm startした結果が動作しているのだと思う。だからpackage.json上のscriptsで実行させたいサーバサイドコードのエントリポイントを示しておいて、そのエントリポイントからexpressなどのサーバを起動、express.staticでクライアントサイドのJavaScriptやHTMLの置き場を示せば良い、のだと思われる。

ただブラウザ上エディタで昨今の近代的なJavaScript開発を行うのはなかなか限界があるので、その時はGitHub上にプロジェクトを置いてそれをインポートするのが現実的かもしれず。

5分間くらいアクセスが無いと自動的にサーバは止まるので、データを永続化したい時は外部DBに頼るか、sqliteなどを使ってファイルに記録しておく必要がある。そのほかにもいろいろ制約はあるけど、今のところサービスは無料で使えるようになっているので、簡単なnode.jsアプリを作って公開するにはお手軽かも。

Twitter上でそのまま遊べるアクションゲーム

はタイムライン上でJavaScriptが動かないので原理的にムリだ。なので、

こうなる。プレイヤーの良心に期待だ。

この方式の欠点はくさるほどあるが、一番の問題は避けゲーしか作れないこと。プレイヤーが能動的に何かを取ったり撃ったりすることはできない。Gifだから。

あとはこういったタントアール的な方向に走る方向もあるけど、できればアクションゲームが作りたいなあ。なにかいい方法はないかね。

ソースコードは以下にある。

not-interactive

無限ミニゲーム生成器を今度は遺伝的プログラミングで作ろうかと

思っていたのだけどやはりうまくいかん。

game-combinator

前回の無限ランダムひどいアクションゲーム生成器への道ではボタンを押した時にゲームに与える影響をランダムに変化させてゲームを生成しようとしていたけど、いまいちゲームにバリエーションが出ないのが欠点だった。

もうちょっとドラスティックにゲームの構成を変えないとバリエーションが得られないかなと思って、今度は遺伝的プログラミングっぽくゲームのコードを組み合わせて新しいゲームを作るアプローチを試してみた。

例えば、

game-combinator-helmet

のような上から降ってくる物を避けるゲームを

(game helmet
  (actor stage
    (if initial (spawn player))
    (if (random frequently) (spawn enemy))
  )
  (actor player
    (if initial (place bottom_left))
    (if (key left) (move left))
    (if (key right) (move right))
    (if (touch out_of_screen_right) (
      score
      (place bottom_left)
    ))
    (if (touch out_of_screen) (move step_back))
  )
  (actor enemy
    (if initial (place top))
    (accelerate down normal)
    (if (touch player) miss)
    (if (touch out_of_screen) remove)
  )
)

のようなDSLで書く。同様にこの手のゲームを10個ほど作り、それらの構文木を適当に混ぜ合わせる、と以下のようにゲーム、か?みたいなものがいくつができる。

game-combinator-generated

上記の謎のDSLは、ゲームを混ぜ合わせたときでもそれほど破綻のないよう、

  • 構文が簡単
  • 数値を使わない
  • ゲーム中のアクターは自機とショットと敵と敵弾、あとステージに限定

という方針で作ったもの。お陰で一応混ぜたものはだいたい実行できる。実行はできるのだが……

問題は、できたものがかろうじてゲームと呼べるものになることが極めてまれということだ。ゲームと呼べるとは、

  • キー入力すると何かが起こる
  • プレイヤーが意図してスコアを得る手段がある
  • プレイヤーが何かを失敗してミスすることができる

という極めて意識の低い設定によるものだが、このハードルすら越えられないひどいものがほとんどである。

ごくまれにハードルを越えてゲームと呼んでやってもいいものもできる。

rainy day

game-combinator-rainy-day

これは上から降ってくる雨をよけるゲームだと思われる。この青いキャラは本来はプレイヤーのショットなのだがそれが降ってくる障害物っぽい挙動に変わった上でプレイヤーに当たるとミス扱いになるようにもコードがたまたま変わったのでゲームとして成り立っているっぽい。上端にあるキャラは謎。

racket fire

racket_fire

最初の10個のゲームの中にゲーム&ウォッチのファイアっぽいゲームがあったのだが、それはプレイヤーの幅が1キャラ分しかなかったので同時に複数の人が落ちてくるとさばききれないという問題があった。この生成されたバージョンはプレイヤーを横に長くしてちゃんと受け取れるようにしてくれている。ありがとうございます。でもなんでプレイヤーを画面中央に置き直しているんですかね。元のゲームの通り画面下でよかったんですけど。

とまあ、ぎりぎりゲームと呼べるものがたまにできるが、それもベースの10個のゲームがそれほど混ぜられてなくて遊べる状態で残っている、みたいなものが多く、全く新しいものができてしかもそれがゲームになっている、みたいな感じはあまりしない。

今回のデモでは100個のゲームを生成した上でそれから有望そうなのを10個表示して、それが同時に遊べるようにした。

有望そうってのを判定するためにいわゆるゲームとしての適応度みたいのを一応算出しようとしている。対象のゲームを複数同時に動かして、それぞれに操作をランダムに与えた時の

の差分を算出して、それが最初の10個のゲームと似たような傾向にあればゲームっぽいという判定をしてそれを適応度とした。ゲームなんだから違う操作をした時にはゲーム展開になんらかの差分が出るべきだろうという基準。

また[GENERATE FROM LIKED]ボタンを押すと表示した10個のうちLikeチェックボックスがオンのものから次のゲームを生成するという、Interactive Genetic Algorithmっぽいこともできるようにした。

現時点の問題点としては、

  • ゲームのバリエーションがまだまだ確保できてない
  • できたものからゲームっぽいものが正しく選別できてない

あたり。

バリエーションについては種になる10個のゲームをもっと増やしたり、DSLの命令セットを増やすことで少しはましになるかと思われる。

ゲームの選別基準は、難しいね。今回使った適応度は操作しても何もおこらないみたいな明らかにダメなものを弾くには使えるけど、ちゃんと遊べるゲームを選ぶという点ではあまり期待できない。DQNみたいなゲームAIに遊んでもらってAIがちゃんと上達するかを見る、みたいなアプローチもありそうだけど、ちょっと計算コストが高すぎて気軽に試すのは難しそう。

とりあえずもう少し種ゲーム増やしてみて様子見してみることにします。

難度曲線をいじっていい具合のプレイ感覚を探る

ということを昔tweetして、今でも基本こんな感じで問題ないとは思っている。ただ全てのミニゲームでこの難度曲線でうまくいくかというとそうとも限らない。場合によってはもっととっとと難度を上げたほうが緊張感が出て良かったり。

この辺の感覚は実際にゲームとして遊べるものにしないと分かりにくい。なので難度曲線を可視化&調整した上でゲームに反映する物を作ってみた。

diffi-tween

screenshot

右で曲線を調整して左をクリックしてプレイ。上から落ちてくる岩を避けて下さい。難度の上昇具合が分かりやすいようtweetの式よりは難度の上がり方はだいぶ速い。デフォルトだと30秒で2倍になる。

曲線はその計算式 (sqrt, linear, pow)と上昇具合 (climb)、あとGame & Watch式に一定時間ごとにがくんと難度が下がるノコギリ波 (saw)の調整ができるようになっている。また複数のパラメタに対して難度が設定できる。今回の例だと岩の落下速度 (speed)と大きさ (size)。

難度に影響を受けるパラメタが複数あると調整はより難しい。また、パラメタには難しくなってもプレイヤーの技量でなんとかなりそうと思えるものと、難しくなるとどうしようもない感が出るものがあって、今回の例だと前者が落下速度、後者が大きさ。速く落ちてくる分にはマウスさばきでなんとかなりそうだけど、デカイのが降ってくるのはどうしようもなく理不尽な印象を受ける。

緊張感を持たせるためには前者はlinearでゲーム序盤からどんどん難しくしてしまってたまにノコギリ波でゆるめる、後者はsqrtで頭打ちするようにして不条理感を減らす。例えば以下のグラフのように。

speed: linear, size: sqrt

ただこのへんは好みの問題なのでその方針が一意にいいとはいえず。落下速度も十分に上がってしまえば理不尽な難度感が出てしまうのは結局同じだし。

speed: pow

これは極端にpowで上げたけど20秒くらいから無理ゲー。そもそもpowで難度曲線を書く必要がある場面ってあるのかしらんという根本的な疑問も。

このような調整UIが必要かはともかく、ポチポチ変更して体感を試してみるくらいしか適切な難度曲線を書く手法はないのかねえ。