Boids 2.x (JavaScript)

概要

Boids 1.3までで、基本的な個体の運動ルールを設定して群の動きを実現した。

これに対して、ルールの条件を変えて、それらが群の挙動に与える影響を見てみる。

Boids 2.0~引力と斥力の導入

考え方

Boids 1.3の基本的なコードでは、3つのルールについて、位置座標を元にした幾何的な計算で速度を変更したが、ここで結合と分離に対して引力・斥力の概念を導入する。

重力や正電気力は距離の自乗に反比例するが、この形で試したところ、かなり距離が近づくまで反応せず、近い距離になって急に力が強くなるため、挙動が不安定な傾向となった。

そこで、ここでは距離に反比例した引力・斥力が個体間に働くものとし、近傍距離以内では斥力が、それより遠く一定距離以内であれば引力が働くものとした。

挙動と考察

その挙動は、若干の振動があるものの基本コードと似ており、基本コードに対して、個体間の距離がより均等に近くなっている。心理的な行動が物理法則と類似である可能性を示唆している。

デモンストレーション

Boids 2.0を新しいタブで実行

Boids 2.1~直近個体に追随

考え方

結合と分離の実装は上記のまま、整列の条件を、直近の一個体の速度に追随するようルールを変更した。

挙動と考察

各個体が集まるのが極めて速く、群が密集状態になる。

また、群を形成してからのは振動を繰り返すのみで、、群全体としての移動は鈍い。

日常生活でも、近くの一人のみに注意して歩こうとすると却ってスムーズに移動できないことが多いが、これを示唆している。

デモンストレーション

Boids 2.1を新しいタブで実行

Boids 2.2~周辺個体に追随

考え方

整列のために追随するのが直近一個体では「近視眼的」なため、一定距離内の個体群の平均速度に追随するように変更。

挙動と考察

一個体のみに追随するよりも群として移動しやすくなるが、それほど大きな効果はなく、個体数が多くなるに従って(このケースでは個体数が15~20程度以上で)群として振動状態に入りやすくなる。

直近1個体でないとしても、周辺の動きだけに合わせると整然とした動きになりにくいことを示唆している。

デモンストレーション

Boids 2.2を新しいタブで実行

Boids 2.3~リーダーに追随

考え方

特定の一つの個体がリーダーとしてふるまい、他の個体はリーダーにのみ追随するルール。リーダーは他の個体のようなルールに従わないとしている。

挙動と考察

当然ながら、群の行動はリーダーの動きのみに追随して比較的なめらかになる。さらに、群全体が広がりを持つのも特徴。「雁行」を想起させる。

デモンストレーション

Boids 2.3を新しいタブで実行


前の記事:基本コード

 

Boids 1.x (JavaScript)

概要

Boids 1.0で準備した枠組みに、分離(Separation)、結合(Cohesion)、整列(Alignment)のルールを適用していく。

Boids 1.1~分離の導入

個体が近づいたときに、互いに近づき過ぎないように速度を修正するルールを導入。

デモンストレーション

Boids 1.1を新しいタブで実行

各個体が他の個体とぶつからないよう避けながらランダムに移動している。

グローバル定義部

個体同士が近づきすぎた場合の分離行動に入る最大距離を指定。

Creatureクラス

分離行動は個体のmove()メソッド内で実装し、相手との距離が大きいほど逆方向への力をかける。速度調整後、速さを初期設定の値にそろえる。

Boids 1.2~結合(Cohesion)の導入

分離に加えて、各個体が群として集まろうとするルール。具体には、群の中心に向かおうと速度を変更するルールを導入。

デモンストレーション

Boids 1.2を新しいタブで実行

個体がランダムに移動しながら、群を形成しようとしている。

グローバル定義部

まず、群の中心をグローバル変数として保持。

中心に向かって速度を変更する度合いを定数で指定。この値が小さいほど素早く、大きいほどゆるやかに群れようとする。

結合とのバランスで、分離の判定距離を小さめの値に設定している。

群の中心の算出

各個体が目指す群の中心を、実行関数moveAll()の中で計算する。ここでは、全個体の平均重心を群の中心としている。

Creatureクラス

結合行動も個体のmove()メソッド内で実装する。群の中心に向かって行くよう速度を変更。

ルールの適用準は、衝突回避を後にするため、結合→分離の順としている。

Boids 1.3~整列(Alignment)の導入

一通りのBoidsの実装の最終段階。

各個体が、群全体の移動方向に合わせようとするルールを導入。各個体が、自分の速度を群の全個体の平均速度に近づけようとする。

デモンストレーション

個体数

各個体が集まりながら「なんとなく」一定方向に動こうとしている。個体数が200程度と多くなると、群としての動きは鈍くなる。このとき、各個体が全く均等になるのではなく、数個程度集まって移動するさまが見られるのは興味深い。

グローバル定義部

整列の際の速度変更の度合いを指定する。この値が小さいほど素早く整列しようとする。

群の平均速度の算出

moveAll()関数を変更し、群の中心の算出に続いて平均速度を求める。

Creatureクラス

整列のルールを個体のmove()メソッドに実装する。

ルール適用の順番は、結合→整列→分離。これは一般に想定される生物の行動形態、「集まろうとする→一緒の方向に向かおうとする→衝突を避ける」と符合している。


前の記事:枠組みの準備

次の記事:条件と挙動のバリエーション


 

Boids 1.0 (JavaScript)

概要

BoidsをJavaScriptで再現するために、基本コードの枠組みとなるコード準備する。

  • 指定した下図の個体を発生させる
  • ボタン操作で各個体の動作を開始・停止する
  • 個体の動作は同じ速さでランダムな方向に動く

基本の動作から始めて順次拡充していくが、以後共通する仕様は以下の通り。

  • グローバル定義
    • HTML5のcanvasで描画することとし、canvasとそのコンテキストをグローバルオブジェクトとする
    • その他、個体や群れの挙動に係るパラメータもグローバル定義する
  • 個体のクラスCreature
    • move()メソッドは、個々のCreatureインスタンスの一定時間後の位置をその現在位置と速度から決定し、新しい位置に変更する
    • draw()メソッドは個々のCreatureインスタンスをcanvasに描く
    • 初期段階から、Boidsの3つのルールに対応するメソッドの枠のみ用意し、順次実装していく
  • Boidsの実行
    • 個体クラスのインスタンスをランダムな位置に複数発生させ、配列に格納する
    • 以下を繰返す
      • 現在位置の全てのCreatureオブジェクトをdrawAll()関数で描画
      • 一定時間間隔で全てのCreatureオブジェクトの位置をmoveAll()関数で変更する
      • moveAll()の中で3つのルールに対応する関数を呼び出すが、初期段階では枠のみ用意し、順次実装していく

仕様

以下の仕様で、個体とその群の生成、基本動作のみを実装する。

  • 操作
    • ブラウザのテキストボックスに個体数を入力
    • ボタンを押すごとに起動・停止を繰り返す
  • 初期設定
    • 初期状態で、ランダムな位置に個体を生成
    • 各個体の速さは一定とし、速度ベクトルの向きはランダムに与える
  • 挙動
    • 各個体は与えられた速度で移動
    • 上下左右の壁に当たった時は全反射
    • 個体間の干渉は考慮しない(重なりを許す)

デモンストレーション

個体数

スクリプトの内容

グローバル定義部

アプリケーション全体で保持・利用する変数・オブジェクトを定義。

Creatureクラス

一つ一つの個体を表すCreatureクラスを以下の様に構築する。これらのコンストラクタやメソッドは、以後の拡張を通して変わらない。

コンストラクタ

コンストラクタは、Creatureクラスのインスタンスを生成する。Creatureオブジェクトは、個々の座標と速度ベクトルの要素を保持する。

位置と速度のセッター

位置座標、速度ベクトルの要素を個体にセットする。

描画メソッド

自身をcanvasに描画する。ここでは位置座標を中心とする小円を描いている。canvasのコンテキストはグローバル変数contextに設定されている前提。

移動メソッド

与えられた速度で移動する。上下・左右の壁に当たった時の反射処理を行っている。

実行関数群

以下の関数群のうち、createCreatures()はボタンが押されるたときに与えられた数の個体を生成。

boids()はボタンが押されるたびに実行・停止を切り替える。

他の関数は、実行時に群全体を異動させ、描画するための関数。

createCreatures()~個体の生成

与えられた数の個体をCreatureインスタンスとして生成し、グローバルな配列に格納する。

boids()~メイン関数

ボタンが押されるたびに呼び出され、全体の実行/停止を制御する。

動画処理

タイマーにより個体を動かし、描画する。呼び出されるたびに、全ての個体を移動させ、描画する関数を呼び出す。

個体の移動

配列に格納された個体に移動を指示する。

個体の描画

配列に格納された個体に描画を指示する。


次の記事:基本コード

 

Boids – 群れの再現

概要

ボイド(Boids)は、アメリカのアニメーション・プログラマCraig Raynoldsが考案した人工生命シミュレーションプログラム。名称は鳥もどき(bird-oid)に由来。

多数の個体からなる群において、各個体の行動ルールを定めておき、一定のランダム性のもと自由に行動させる。個体が単純なルールに基づいて動きながら、群全体としてまとまった動きが再現される。

ルール

Boidsのルールとして、一般に以下の3項目が適用される。

分離(Separation)
他の個体とぶつからないように距離をとる。
結合(Cohesion)
群れの中心方向へ向かうように方向を変える。
整列(Alignment)
他の個体群と概ね同じ方向に飛ぶように速度と方向を合わせる。

群を構成する個体は、それぞれ独自の位置・速度を持って動きながら、上記3つのルールに従うことによって、群としての行動をとるようになる。

デモンストレーション

CoffeeScript

JavaScriptよりすっきりと書けるCoffeeScriptで、より遊べるものを作っていった。この過程で、CoffeeScript/JavaScriptの変数のスコープだとかパッケージの考え方だとかが勉強になった。

JavaScript

JavaScriptに慣れるために作ったプログラム群。

JavaScript Tips – 文字列の一括置換

String.prototype.replace()

以下の例は、文字列str中の”cd”を”CD”に一括置換し、結果は”abCDefabCDefabCDef”となる。

ただしこの方法では、//の間が直接文字列と評価されてしまうので、変数の内容を引き渡すことができない。

split/join

String.prototypeで定義されたsplit()メソッドとjoin()メソッドと用いて、文字列変数で引数を与えて一括置換ができる。

split

文字列操作のsplit()メソッドは、指定した文字列を検索して、それらで区切られた文字列を配列で返す。

上記の結果、配列arrayは以下の要素が格納される。

join

文字列操作のjoin()メソッドは、配列の要素を引数の文字列で繋いだ文字列を返す。

一つ上でsplit()の結果得られた配列に上記を適用すると、結果の文字列は以下のようになる。

一括置換

上記のsplit()join()の機能・戻値を考慮して、以下の用法で文字列の一括置換が可能。

 

JavaScript Tips – スクリプト単位のスコープ

関数はスクリプト・ローカル

関数はスクリプト間で影響を与えない。同じ関数名を異なるスクリプトで用いても干渉しない。

以下のコードのうち外部スクリプトの内容は、それより上の二つのスクリプトの内容と同じ。スクリプトごとにmain()関数を実行しているが、それぞれのスクリプトで定義されたmain()関数が実行され、エラーや干渉は起きない。

グローバル変数は干渉

以下のコードは、スクリプト間のグローバル変数の挙動を示している。

上記コードの実行結果は以下のとおり。

  • あるスクリプトでグローバル定義された変数は、それ以後のスクリプトに残される
  • それ以後のスクリプトで、重ねてvar定義は可能で、その後はそれ以降の定義による

外部ファイルの注意点

クラスなどの資源を共有化するために、スクリプトを外部ファイルとする場合の注意点。

  • 読み込みと実行
    • head要素内でスクリプトファイルを読み込む場合、script要素を記述した順番に読み込まれる
    • スクリプトファイルが読み込まれた時点で実行される
  • グローバル変数
    • グローバル変数を定義していた場合、スクリプトファイル読み込み時に定義される
    • 同じグローバル変数を異なるファイルで重複定義した場合、最後に読み込まれたスクリプトの定義結果が反映される(変数の重複宣言を参照)
  • クラス
    • ローカルなインスタンス名は同じ名前でも干渉しない
    • グローバルなインスタンスで重複定義されたオブジェクトは、最後に定義されたものが残る

以下に、クラス定義ファイルとインスタンス定義ファイルを別ファイルに分けた例を示す。

【HTMLファイル】

【クラス定義ファイル】

【インスタンス定義ファイル】

【別のインスタンス定義ファイル】

上記の実行結果と挙動は以下の通り。

  • head要素で3つのjsファイルが読み込まれる
  • 読み込まれた時点で、グローバルな変数とオブジェクトが定義される
  • 以降、ボタンを押すごとに、それぞれの関数でローカルな変数とオブジェクトが定義・表示される
  • グローバルな変数・オブジェクトについては、最後に定義されたinstance2.jsの定義内容

変数、オブジェクトとも、グローバル・スコープのものについては、head要素内で最後に読み込まれたinstance2.jsの内容が保持される。読み込み順を入れ替えて最後にinstance1.jsを置くと、結果も変化する。

 

JavaScript – その他

処理時間の計測

処理時間の計測には、console.time()とconsole.timeEnd()を用いることができる。

コールスタック・オーバーフロー

再帰のネストが深すぎると、コールスタック・オーバーフローが発生する。

これを回避する方法の一つとして、setTimeout()関数による方法がある。

 

Lifegame (JavaScript)

概要

Javascript学習のスタートアップのために試作したもの。

内部に配列を持ち、世代交代の結果を文字ベースで表示し、setInterval()で更新している。

htmlのdiv要素内に文字ベースで表示している。

デモンストレーション

このアプリケーションは文字ベースなため、ブラウザやスタイルによって大変見にくい。別ページで用意したこのアプリケーションで、ブラウザによっては見やすくなる場合がある

パターン選択








実行

表示エリア

コード内容

HTML

カテゴリーごとの初期パターン選択ボタン、起動/停止ボタン、表示エリアを表示させている。

JavaScript

グローバルな二次元配列にセルの情報を保持し、配列上で世代交代の処理をしたうえで、HTMLに書き出している。初期パターンの選択は、パターンごとの設定用関数を準備し、ボタンで呼び出している。主な処理ブロックは以下の通り。

グローバル処理
グローバル変数の宣言と初期値定義を行っている。
初期パターン選択
各パターンのボタンのクリックに応じて、パターンごとに準備された関数が呼び出され、初期pターンの設定と表示を行う。
起動・停止
ボタンのクリックによって、一定時間間隔で世代交代関数を実行させ、停止させる。
世代交代
ライフゲームのルールに従い、一世代後のパターンを決定し、表示する。

グローバル処理

  • 共通して利用する変数の宣言/定義
  • グリッドの初期化
  • 初期状態のグリッドの表示

初期パターン選択

ボタンで選択された初期パターンに対応した設定関数が呼び出される。設定関数はパターンごとに準備され、配列リテラルの内容を、グリッドの”ほぼ中央”にコピーする。

起動・停止

起動・停止のボタンが押されるたびに、稼働状態/停止状態がスイッチする。稼働時には、一定時間間隔で関数life()を呼び出している。life()関数内では、世代交代を施した後、表示をさせる。

世代交代

あるセルの次世代の状態は、そのセル自身の状態と、その周囲8つのセルの数によって定められる。

存在 不在
0~1 死滅 不在
2 存在 不在
3 存在 誕生
4~ 死滅 不在

JavaScript – Timer

概要

JavaScriptのタイマ関連の実装。

  • Dateオブジェクトによる経過時間の計測
  • setInterval()による等時間間隔の起動
  • setTomeout()による等時間間隔の実行キュー登録

Dateオブジェクト

Dateオブジェクトのミリ秒単位の値を用いる方法。

DateオブジェクトのvalueOf()メソッドでミリ秒単位の現在時刻を取得し、これを用いて時間を判定する。これに続く処理を純粋に遅延させることになる。

なお、valueOf()メソッドの代わりに、+newオペレータを用いることができる。

setInterval()

起動

指定した時間間隔で関数を呼び出す。他の処理とは無関係に呼び出されるため、関数の処理内容が重い場合には、処理が終わる前に呼び出されてしまうことがある。

setInterval()関数の第1引数に繰り返し実行したい関数を、第2引数に繰り返し間隔をミリ秒単位の数値で指定。関数の指定方法としては、以下の3通り。

文字列で記述 関数名を文字列として記述。その際、関数の後の()も付けて記述
直接記述 関数名をクォート/ダブルクォートで囲まず直接記述。関数の後ろの()はつけない
無名関数を使って記述 function(){}の中にターゲットとなる関数を記述

 

引数がある場合

繰り返し実行する関数が引数を持つ場合、文字列で引数を記述するか、無名関数を使って記述する。

停止

タイマーの起動と停止を制御するには、起動時にsetInterval()を変数で参照し、そのオブジェクトに対してclearInterval()で停止をかける。

setTimeout()

基本形

setTimeout()の第1引数に実行したい関数を文字列で、第2引数に遅延時間をミリ秒で指定する。

上記の例では、以下のように推移する。

  1. “start”を表示
  2. setTimeout()を実行→3秒後にmain()関数が呼び出されるよう指定
  3. “end”を表示
  4. 約3秒後にmain()が実行され、”in main()”を表示

起動

関数を文字列とする方法

文字列で引数を分離する方法

無名関数を使う方法

タイマとしての利用

等時間間隔で実行したい関数で、setTimeout()において再帰的に自己を呼び出すことによってタイマーとして利用できる。

遅延時間後に実行キューに登録されることから、それぞれの関数の実行は重複することなく担保される。このため、setInterval()に比べて関数間の衝突は生じないが、実際の実行間隔は遅延時間より長くなる

停止

setTimeout()setInterval()と同じくタイマーIDを返し、これに対してclearTimeout()を実行することでタイマーを停止できる。

上記の例では、タイマーを止めるstop()関数が5秒後に実行されるようにし、counter()を開始している。counter()自身がが終了する前にstop()が実行され、counter()は停止する。

補足

実行キュー

setTimeout()は、指定した関数を指定した遅延時間後に実行キューに登録する。このため、setTimeout()実行後に時間がかかる処理に移った場合、その処理中に遅延指定した関数がキューに登録され、処理実行後に関数が実行される。

上記の例の場合、

  1. setTimeout()が実行され、3秒の遅延時間のカウント開始
  2. whileループに入る
  3. 3秒後、hoo()が実行キューに登録される
  4. 5秒後、whileループから抜ける
  5. 実行キューに登録されたhoo()が即時実行される

スタック・オーバーフローの回避

setTimeout()を用いるシーンの一つに、再帰定義のコールスタック・オーバーフローの回避がある。この場合、再帰呼び出しの度にsetTimeout()でラップし、待機時間をゼロとする。

これにより、関数はスタックではなく実行キューに並べられ、オーバーフローは発生しない。ただし、通常の再帰呼び出しに比べて大幅に実行時間が増加する。

たとえば以下の例では単純なループにかかる時間を測っており、

次の例では、再帰的にsetTimeout()で自己を呼び出すようにしているが、処理時間の乖離が大きい。

なお、この方法で関数をsetTimeout()に引き渡す際、引数まで含めて文字列で引き渡そうとするとUncaught ReferenceError: 引数 is not definedとエラーになる。

 

HTML Entity Converter (JavaScript)

概要

HTMLの文字のうち、'<‘、’>’、’&’とそれぞれの実体参照を相互変換。

  • 変換の時は’&’→”&amp;”の変換を最初にし、逆変換の時は”&amp;”→’&’の変換を最後にする。
  • 文字列の検索と置換には、[string].split[beforeString].join[afterString]で行っている。

実行

Entity Converterを新しいタブで開く