ActionScript 3.0で始めるオブジェクト指向スクリプティング

第60回 【特別編】1/0で考えよう − ビット演算

この記事を読むのに必要な時間:およそ 4 分

複数フラグを配列で扱う

ゲームの内容はつぎのとおりだ。まず,4つのインスタンスを同じ向きで並べる図1上段⁠⁠。つぎに,ボタンをクリックすると,その中のランダムなひとつの向きが逆になる図1中段⁠⁠。そして,ボタンクリックを繰返し,すべてのインスタンスの向きが初めと逆になれば上がりとする図1下段⁠⁠。

図1 ボタンでランダムにひとつのインスタンスを反転する
初めはみんな右向き

初めはみんな右向き

ボタンでランダムなひとつの向きが逆転

ボタンでランダムなひとつの向きが逆転

すべてが左を向くと上がり

すべてが左を向くと上がり

先に,2進数を使わないやり方を考えよう。初めのインスタンスを裏向きとして,クリックするたびに逆転する。ゲームを進めるうえで,そのときどきの向きをインスタンス4つ分つねにもっておかなければならない。このようなとき,インスタンスの向きはブール(論理)値として,4つの値を配列に納めるのが定石といえる。

オン/オフあるいはYes/Noのような2値の状態を示す変数は「フラグ」と呼ばれる。つまり,今回の考え方はインスタンス4つの向きをフラグにして,配列に入れて扱うということだ※2⁠。初めの向きをfalseとしておこう。

var flag:Array = [false, false, false, false];

配列のエレメント4つからランダムなひとつのブール値を反転するには,つぎのようなスクリプトを書けばよいだろう。論理否定演算子!は,ブール値のtruefalseを反転する。

var nLength:uint = 4;
var nRandom:uint = uint(Math.random() * nLength);
flag[nRandom] = !flag[nRandom];

上がり,つまりすべてのフラグがtrueになったかどうかは,関数(xAllOn())を定義して確かめることにする。フラグの配列(flag)からforループで取出したエレメントの値を順に調べる。そして,falseの値が見つかったら,returnステートメントで直ちに関数から抜けて戻り値falseを返した。これで無駄な繰返しが避けられる。途中で抜けることなくループ処理が済んだとき,エレメントすべてがtrueだったことを意味するので,戻り値としてtrueを返している。

function xAllOn():Boolean {
  for (var i:uint = 0; i < nLength; i++) {
    if (flag[i] == false) {
      return false;
    }
  }
  return true;
}

このようにフラグを配列に入れる考え方は,実際によく使われる。このゲームくらいの内容であれば,最適化の具体的な問題も生じないだろう。しかし,2進数とビット単位の論理演算を使うと,慣れないと見た目わかりづらいものの,スクリプトそのものはかなりすっきりする。

なお,ゲームにおけるインスタンスのアニメーションやボタンのスクリプトなどは,今回の本題ではないので省く。ダウンロードサンプルには加えておくので,興味がある読者はそちらをご覧いただきたい。

※2
もちろん,第54回【特別編】配列の処理をVectorオブジェクトで最適化するに述べたとおり,Vectorオブジェクト(Booleanベース型)を使う方が最適化で勝る。しかし,ここでは考え方の説明に重きを置きたいので,配列で進める。

2進数の各桁をフラグとして扱う

2進数の各桁の数字は1か0だ。したがって,ひとつひとつの桁をフラグとして捉えることができる。4つのインスタンスの初めの向きを2進数4桁の0000と考えれば,10進数の(もちろん2進数でも)0になる。すると,上がりは2進数1111となり,10進数では15だ。

var flag:uint = 0;   // 2進数0000
var end:uint = 15;   // 2進数1111

2進数のある桁の1と0を入替えるには,ビット単位の排他的論理和演算子^を使う。演算結果は次表3のとおりだ。論理和演算子|と同じく,上から3つの組合わせは加算演算子+と変わらない。1同士の演算をしたとき,排他的論理和は0になる。

表3 ビット単位の排他的論理和演算子^の演算結果

ビット単位の論理和演算結果の2進数
0^0
0
0^1
1^0
1
1^1
0

表3の演算の組合わせから^演算子の右側が1の場合(2番目と4番目)を取出すと,演算結果はつぎのように左側の1と0が反転している。つまり,2進数のある桁の1と0をひっくり返すには,1との排他的論理和をとればよい。

画像

後は,1をもつ2進数の桁の変え方だ。これには,ビット単位の左シフト<<という,まさに桁をずらすための演算子がある※3⁠。たとえば,1<<2で1の後に0がふたつついた2進数100(10進数4)になる。

2進数として扱う数<<左にずらす桁数

これで4桁の2進数のランダムなひと桁を反転させることができる。スクリプトはつぎのようになる。

var nLength:uint = 4;
var nRandom:uint = uint(Math.random() * nLength);
flag ^= (1 << nRandom);

そして,このお題の目玉は4つのフラグすべてが反転したかどうかを確かめる関数(xAllOn())だ。配列に入れたフラグすべてを調べる前項の場合と比べて,あっけないほど簡単だ。4桁の2進数が1111,つまり10進数の15と等しいかどうか比べれば済む。

function xAllOn():Boolean {
  return (flag == end);
}

以上の2進数のビット演算を組合わせて,簡単なテスト用スクリプトを書いてみよう。タイムラインにインスタンスは置かない。ステージをクリックするたびに,4桁の2進数のうちのひと桁がランダムに反転して,[出力]パネルに表示される図2⁠。1111が表示されれば上がりだ。

図2 4桁の2進数のうちのひと桁がランダムに反転する

図2 4桁の2進数のうちのひと桁がランダムに反転する

StageオブジェクトのInteractiveObject.clickイベント(定数MouseEvent.CLICKにリスナー関数(xRandomReverse())を加え,2進数のランダムなひと桁を反転させるようにしたのが以下のスクリプト001だ。フラグを確かめる関数(xAllOn())で上がりとされたら,"congratulations!!"の文字を[出力]してリスナー関数を除いている。

スクリプト1 ステージをクリックするたびに4桁の2進数のうちのひと桁がランダムに反転する

// フレームアクション: メインタイムライン
var flag:uint = 0;   // 2進数0000
var end:uint = 15;   // 2進数1111
var nLength:uint = 4;
stage.addEventListener(MouseEvent.CLICK, xRandomReverse);
function xRandomReverse(eventObject:MouseEvent):void {
  var nRandom:uint = uint(Math.random() * nLength);
  flag ^= (1 << nRandom);
  trace(("000" + flag.toString(2)).substr(-nLength));
  if (xAllOn()) {
     trace("congratulations!!");
     stage.removeEventListener(MouseEvent.CLICK, xRandomReverse);
  }
}
function xAllOn():Boolean {
  return (flag == end);
}

uint.toString()メソッドは,整数(uint)を文字列に換える。そのとき,引数に基数として2を渡すと,数字は2進数で表される。String.substr()メソッドは,String.substring()と同じく※4⁠,文字列から一部の文字を取出す。ただ,ふたつのメソッドは引数の定めが異なる。

文字列.substr(開始インデックス, 文字数)

第2引数は,終了インデックスではなく,文字数となる。これを省けば最後の文字までとみなされる。もうひとつ,String.substring()メソッドと違うのは,第1引数に負数が渡せ,文字列の最後からインデックスが数えられる。つまり,前掲スクリプト001のように-4(nLength)を渡すと,下4桁が取出せるのだ。

インスタンスのアニメーションやButtonコンポーネントを加えたサンプルは,参考までにダウンロードファイルに加えておくので,関心のある読者はそれをご覧いただきたい(前掲図1参照⁠⁠。

さて,これで拙著『ActionScript 3.0パフォーマンスチューニング』の紹介を兼ねた特別編を終える。興味をもたれた方は書店で手にとってご覧いただくか,筆者の書籍紹介サイトにChapter01をPDFプレビューとして公開しているのでお読みいただきたい。

※3
もちろん,桁を右にずらすこともできる。ビット単位の右シフト>>演算子を使えばよい。一般に,ビット単位の演算は整数を対象とする。そのため,右シフトして小数点以下になった桁は切捨てられる。
※4
String.substring()メソッドについては,第8回Stringクラスによる文字列の操作と値を返す関数で説明した。

今回解説した次のサンプルファイルがダウンロードできます。

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書