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を新しいタブで開く

 

HTML5 – Canvas

基本手順

  1. HTMLでのcanvas要素の設置、またはスクリプトでcanvas要素の動的な作成と埋め込み
  2. canvasオブジェクトの取得と描画コンテキストの取得
  3. 描画コンテキストに対する描画

canvas要素の準備

HTMLに書く場合

id、幅と高さのほか、背景色や枠線など一般的なスタイルも設定可能。idを通して、スクリプトでcanvas要素のオブジェクトを取得する。

canvasオブジェクトと描画コンテキストの取得

基本形

HTML要素でcanvasを配置し、JavaScriptで取得して操作する手順。

canvasオブジェクトに描画するために描画コンテキストを取得するが、その取得が可能かどうかの判定が必要。コンテキストは今のところ2Dのみ提供されている。

JavaScriptによる標準的な手順

※”2d”の引数を”2D”とすると読み込んでくれないので要注意。また、後述の注意点を参照。

実行結果

javascript-canvas-fillrect

CoffeeScriptとjQueryによる標準的な手順

注意点~オブジェクト取得のタイミング

先の基本手順のスクリプトファイルをheadセクションで読み込んで実行すると、canvasオブジェクトが適正に読み込まれず、コンテキストがnullになってしまう。これは、canvasオブジェクトが形成される前にheadセクションでcanvasオブジェクトの処理を行うことになるため。

解決策として、DOMがすべて読み込まれてから必要なオブジェクトを取得するようにする。

スクリプトの読み込み位置を最後にする

外部ファイルをscript要素で読み込む場所を、</body>タグの直前のように最後に持ってくる。

DOMの読み込みが終わってから取得する

たとえばjQueryのjQuery(document).ready()を実装して、DOMがすべて読み込まれてから実行することを保証する。

基本の描画方法

単一の矩形

矩形や円弧など、単一の図形を一つの命令で書く方法。矩形の外形を描くstrokeRect()などが準備されている。

パスを指定する方法

以下の手順をとる。

  • 必要に応じて、線の色や太さ、塗りつぶしの色などのスタイルを設定~strokeStyleなど
  • 前のパスをリセットし、新しいパスの描画を始める~beginPath()
  • パラメータを指定して、直線や円弧などのパスを辿っていく~lineTo()など
  • それまでのパスの輪郭描画、塗りつぶしなどを行う~stroke()など

実行結果

html-canvas-basic

スタイル設定

色と背景

strokeStyle [= value]
線・輪郭の色やスタイルを指定。色指定の場合、red, blueなどの色名、”rgb(xx, xx, xx)”、”#dddddd”の3種類の指定方法。
fillStyle [= value]
塗りつぶしの色やスタイルを指定。

線のスタイル

lineWidth [= value]
線の幅を保持する。参照・設定可能。ゼロ以上有限の値のみ設定可能。初期値は1.0。
lineCap [= value]
ラインキャップ(“butt”, “round”, “square”)を保持する。参照・設定可能。
lineJoin [= value]
線の接合方法(“bevel”, “round”, “miter”)を保持する。参照・設定可能。
miterLimit [= value]
miter接合の場合の限界値を保持する。参照・設定可能。ゼロ以上有限の値のみ設定可能。初期値は10.0。
setLineDash([segments])
破線のパターンをセットする。パターンはリストで与え、[実線(, 間隔, 実線, 間隔...)]と記述。
getLineDash()
破線のパターンを返す。
lineDashOffset [= value]
破線パターンのオフセットを返す。

描画メソッド

矩形

strokeRect( x, y, w, h )
矩形の外枠を描く。
fillRect( x, y, w, h )
矩形の内側を塗りつぶす。塗りつぶしの基準となる外枠は、線の太さに関わらず線の中心線
clearRect( x, y, w, h )
指定した矩形の内部を背景色でクリアする。

canvasの描画は、ほとんどの場合線分、曲線、図形が繋がったパスとして描かれるが、矩形に関しては、パスとは独立した図形として描くメソッドが準備されている。

引数は4つで、左上のx座標、y座標、長方形の幅、高さ。

実行結果

javascript-canvas-rect

パス

パスの構成方法

パスは線分、円弧などの要素を連続して繋いだもので、パスの軌跡を描画するだけでなく、パスに対して内部を塗りつぶしたり、ある点がパスの内部にあるか判定したりすることができる。

パス描画の手順は、抽象的な構造を保存するパスに対して要素を追加していく段階と、それらに対して描画や塗りつぶしなどの操作を行う段階からなる。

パスの実装方法として、以下のいくつかがありうる。

beginPath()による直接描画
beginPath()メソッドで開始し、一連のパス追加などの操作の後、stroke()メソッドで描画する。パスの再利用はできない。
【例】画面中央に白抜きの三角形が描画される。

begtinPath()を書かないと、前の描画がリフレッシュされず全て残ってしまう。
stroke()を書かないとパスが描画されない。
パス構築を関数とする方法
パスの構築までを関数化し、その関数呼び出し後に描画する。関数を呼び出すことで、直前のパスとして再利用可能
【例】上記と同じ三角形が一旦白抜きで描かれた後、すぐに同じ三角形が黒で塗りつぶされて描かれる。
Path2Dオブジェクトを用いる方法
Path2Dオブジェクトのインスタンスにパスを追加し、これに対して描画する。パスオブジェクトが残るので、再利用可能。
【例】上記と同じく、白抜きの三角形の描画後に黒塗りの三角形が描画される。

 

クリッピング

<略>

パス内部の判定

  1. context.isPointInPath( x, y )
    • 現在の設定されているサブパスに対して、点(x, y)が内部にあればtrue、外ならばfalseを返す
  2. context.isPointInPath( path, x, y )
    • pathで指定されたPath2Dオブジェクトに対して、天(x, y)が内部にあるかどうかを判定する
直接描画に対する判定の問題

1番目の表現の場合、直前に構成されたパスが判定対象となる。以下の例では二つの矩形を描いているが、内部判定は最後に書かれた矩形に対してのみなされる。

javascript-canvas-inner

パス構成の関数化

複数のパスに対して判定を繰り返す場合、stroke()/fill()の描画メソッドを実行する前でもパスが構成されることを利用して、パス構成のための関数を切り分ける方法が考えられる。

Path2Dオブジェクトに対する判定

isPointInPath()メソッドの2番目の表現は、第1引数にpathを渡すことができる。各々のパスをPath2Dオブジェクトに保持することで、よりシンプルに内部判定を行うことができる。

パスの要素

線分
  • moveTo() 開始点に移動
  • lineTo() 次の点まで線を描く~以下、繰り返し
  • closePath() パスの終了時、開始点まで繋いでパスを閉じる場合に指定

実行結果

javascript-canvas-line

曲線
  • quadraticCurveTo(…)
  • bezierCurveTo(…)

<略>

円弧
  • arc( x, y, r, startAngle, endAngle, anticlockwise )
  • arcTo( x1, y1, x2, y2, r )

arcメソッドはパスの一部として円弧を描く。直前の座標軸からarcの開始点までと、arcの終了点から次の開始点なまでは線分が挿入される。

座標軸の正の方向が、x軸は右、y軸は下となっていることに注意。このため、一般的な数学表現と上限反転した体系になっていて、一般的な正の回転方向も時計回りとなる。

arcTo()メソッドの挙動は以下の通り。

  1. 直前の描画点(x0, y0)と(x1, y1)を結ぶ線分l1を想定する
  2. (x1, y1)と(x2, y2)を結ぶ線分l2を想定する
  3. 半径rでこの二つの線分に接する円弧を想定する(二つの直線の角をとるイメージ
  4. l1、l2と円弧の接点を算出する
  5. (x0, y0)~l1と円弧の接点を直線で結ぶ
  6. l1とl2を円弧で結ぶ
  7. l2の接点と次の描画開始点を直線で結ぶ

すなわち、(x2, y2)を次の描画開始点と一致させておけば、折れ線(x0, y0)~(x1, y1)~(x2, y2)の角が半径rの円弧で丸くなる。

実行結果

javascript-canvas-arc

矩形
  • rect( x, y, w, h )

rect()はサブパスを持つ矩形を描く。

  • 先行するパスとは接続しない
  • 後続のパスがある場合、(x, y)から次の描画開始点まで線分で結ばれる

実行結果

javascript-canvas-rectpath

JavaScript – クラス

クラスの構築

コンストラクタ

JavaScriptではクラス定義はなく、コンストラクタを関数で定義する。

  • thisはこの関数が含まれるオブジェクト(のインスタンス)を指す
  • newによって新しいオブジェクトの無名インスタンスが生成される
  • 代入演算子(=)によって、変数が上記のインスタンスを参照する
  • その結果、thisは定義されたインスタンスを指す

コンストラクタで定義されていないプロパティでも、インスタンスごとに後から追加可能だが、これは可読性やクラスとしての一貫性を損なう。

インスタンスの識別

同じクラスのインスタンスへの参照が同一か異なるかを比較することができる。

この比較はthisを用いても可能。

メソッド

非効率的な方法

コンストラクタの中で、プロパティと同じようにthis.[関数名]と無名関数定義によってプロパティを定義可能。

ただし、thisで指定された対象はインスタンスごとに生成・保持されるため、上記コードではインスタンスの数分のメソッドが生成されてしまう。

prototypeによる方法

JavaScriptにおける全てのオブジェクトはObjectに由来する。すべてのオブジェクトはObject.prototypeからメソッドとプロパティを継承しているが、それらは上書きすることが可能。

インスタンスでメソッドを実行しようとした時にそのメソッドが存在しない場合、そのクラスのprototypeにメソッドを探しに行く。 新たに定義したクラスのprototypeプロパティに関数を定義しておくことで、全てのインスタンスが共通のprototype下のメソッドを利用することになる。

prototypeにメソッドを定義する方法の一つは、メソッド単位でクラスのprototypeに登録する方法と、オブジェクトリテラルでまとめて定義する方法がある。

メソッド単位で定義する方法

メソッドごとにクラスのprotottypeに一つずつ記述していく方法。

オブジェクトリテラルでまとめて定義する方法

クラスの初期設定時に連想配列で定義する。

この方法は、新規にメソッドを定義する場合に一回のみ使用するもので、後から同じ方法でメソッドを追加しようとしした場合、最初の定義内容がすべてオーバーライドされる。

ただ、メソッド定義を敢えてダイナミックに変更するのでなければ、この方法はメソッドの定義のあり方として明快ともいえる。

メソッド定義の注意点

クラスの生成はコンクトラクタの前後を問わないが、メソッドを実行する段階では、prototypeへのメソッドの登録が実行済みでなければならない(記述位置の前後関係ではなく、関数呼び出しも含めた実体上の実行順序)。

C++やJavaなどコンパイラが全体の宣言・定義をパースする場合は問題ないが、インタプリタ系のJavaScriptは、定義=宣言と実行の順序については厳しい。

継承

子クラスのprototypeに親クラスを登録することで継承でき、親クラスのプロパティ、メソッドも利用可能となる。

これは厳密な意味でのクラスの継承というよりは、そのような挙動をするためのprototypeチェーンに組み込んだというところ。

注意点

子クラスのメソッドの定義は必ず親クラスの継承の後で行うこと。継承の際にprototypeの内容が上書きされてしまうため、継承の前にメソッド定義すると子クラスのメソッドが参照されなくなってしまう(下記の例では、”instance.setAge is not a function”とエラーになる)。

子クラスのメソッド定義でオブジェクトリテラルを使ってはいけない。継承の後にこれを行うと、親クラスのメソッドがすべて上書きされてしまう。

 

JavaScript – 変数・スコープ

変数のスコープ

グローバルスコープ/ローカルスコープ

  • var宣言で定義された変数は、その関数内のローカルスコープ
  • var宣言なしで定義された変数は、どこで定義されてもグローバルスコープ

ブロックスコープ

JavaScriptにはブロックスコープの概念はない。

関数の中のif{}やfor{}など複数のブロックで同じ変数名を用いると、関数内で共通の変数になる。

特に、for文のループカウンタなどの一時的な変数は、不用意にvar宣言なしで用いると、予期しないところでグローバルに影響を与えてしまう可能性がある。

関数の引数のスコープ

関数の引数はvarなしで宣言するが、スコープは関数内ローカルとなる。

  • グローバル領域でn = 1を定義
  • f( n )で引数を引き取った後、関数内ではn == 1
  • 関数内でnをインクリメントし、n = 2
  • 関数の結果はf( n ) = 2
  • その後、グローバル領域でn == 1で変化していない

変数操作のタイミング

グローバル変数への操作は、スクリプトがの実行の際に行われる。htmlファイル内であれば、そのファイルが読み込まれた時。別のjsファイルの場合はそれが読み込まれた時。

ローカル変数への操作は、その関数が実行された時。

以下の例では、htmlファイル読み込み時にグローバル変数が定義・参照され、その後ボタンを押すたびにmain()関数が実行され、ローカル変数が定義・参照される。