Kindle Unlimited に『ゲームで学ぶJavaScript入門』というものがあったので、なんとなしに読んでみたところ、なかなか良い内容でした。
私は仕事でWEBサイト制作をやっているので「まぁJSは大体わかってるけどね……」と思いつつ読み進めたら「ゲームだとWEB制作とは違った使い方をして面白いなぁ!」と感服しました。
井の中の蛙大海を知らず、ってやつですね。お恥ずかしい。
2015年出版の本ですが、言語の基礎の部分の説明と、ゲームプログラミングの考え方が記載されているため、JavaScriptのバージョンアップによる陳腐化はしていません。
おそらく、数年後も問題なく通用する内容でしょう。
Chapter1 – Chapter6 で構成されており 、 Chapter1 – Chapter4 までは、 HTML, CSS, JavaScript, Canvas についての基本の説明です。
私は仕事でWEBサイト制作をやっているので、 Chapter4 までは基本中の基本という内容で、特筆することはありませんでしたが、初心者向けと思えば順当な内容でしょう。
しかし、Chapter5 からは私にとっても新鮮な内容でした。
例えば、ビット演算子の使い方は、WEBサイト制作の時には全然使っていなかったので「ひょえ~なるほど~。こういう風に使うものなのか~」と感心しっぱなしでした。
Chapter5 5-3 CarryIt
ビット演算子は Chapter5 5-3 CarryIt というセクションで使われています。
CarryIt の説明を引用します。
昔からある定番ゲームです。
田中賢一郎.ゲームで学ぶJavaScript入門HTML5&CSSも身につく!(Kindleの位置No.2949-2952).株式会社インプレス.Kindle版.
人が荷物を所定の位置に移動させるだけのシンプルなゲームです。
ただし、人は荷物を引っ張ることはできず、押すことしかできません。
また、ふたつを同時に押すことはできません。
すべての荷物を所定の場所に移動してください。
実際にやってみると思いのほか難しいかもしれません。
『パックマン』のような平面のマップを動かして、荷物を押してゴールに持っていくゲームのことですね。
『ゼルダの伝説』のダンジョンの推理ギミックみたいなものが近いかもしれません。
CarryIt を作るには、二次元配列に特定の数字を以下のように割り当てて仮想のマップを作ります。
0: 通路, 1: 目的地, 2: 荷物, 6: 壁
var data = [
[6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6],
[6,6,6,6,6,0,0,0,6,6,6,6,6,6,6,6,6,6,6,6],
[6,6,6,6,6,2,0,0,6,6,6,6,6,6,6,6,6,6,6,6],
[6,6,6,6,6,0,0,2,6,6,6,6,6,6,6,6,6,6,6,6],
[6,6,6,0,0,2,0,0,2,0,6,6,6,6,6,6,6,6,6,6],
[6,6,6,0,6,0,6,6,6,0,6,6,6,6,6,6,6,6,6,6],
[6,0,0,0,6,0,6,6,6,0,6,6,6,6,0,0,1,1,6,6],
[6,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,1,1,6,6],
[6,6,6,6,6,0,6,6,6,6,0,6,0,6,0,0,1,1,6,6],
[6,6,6,6,6,0,0,0,0,0,0,6,6,6,6,6,6,6,6,6],
[6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6]
];
この仮想マップにキャラクターの座標を当てはめて、ユーザーはキーボード操作で移動できるようにするわけです。
その際に、通路は移動できて、壁は移動できない、という処理が必要になります。
ただ、それだけならビット演算子を使わなくても作成は可能です。
しかしこの本では「ビット演算子に慣れること」「論理演算を学ぶこと」「コード量を少なくできること」を理由として、ビット演算子を使う方法を紹介しています。
ビット演算子を使ってやることは、
- 二次元配列の各数値の2ビット目の 0, 1 で「移動できる・できない」を判定すること
- 移動後の値の更新をする
の2つです。
移動できる・できないの判定
コードを書籍から一部だけ引用します。
function mykeydown(e) {
var dx0 = px, dx1 = px, dy0 = py, dy1 = py;
switch (e.keyCode) {
case 37: dx0--; dx1 -= 2;
break;
case 38: dy0--; dy1 -= 2;
break;
case 39: dx0++; dx1 += 2;
break;
case 40: dy0++; dy1 += 2;
break;
}
if ((data[dy0][dx0] & 0x2) == 0) { //荷物なし&壁なし→進む
px = dx0;
py = dy0;
} else if ((data[dy0][dx0] & 0x6) == 2) { //進行方向に荷物あり
if ((data[dy1][dx1] & 0x2) == 0) { //荷物なし&壁なし→進む
data[dy0][dx0] ^= 2; //隣の荷物をクリア
data[dy1][dx1] |= 2; //更に先に荷物をセット
px = dx0;
py = dy0;
}
}
repaint();
}
px, py はプレイヤー(キャラクタ)の座標で、dx0, dy0 移動先、dx1, dy1 移動先のはさらに一つ先の座標です。
そして進める、進めないの判定をしている箇所は以下の部分です。
if ((data[dy0][dx0] & 0x2) == 0) { //荷物なし&壁なし→進む
// 省略
} else if ((data[dy0][dx0] & 0x6) == 2) { //進行方向に荷物あり
if ((data[dy1][dx1] & 0x2) == 0) { //荷物なし&壁なし→進む
// 省略
}
}
進めるか・進めないかの判定
(data[dy0][dx0] & 0x2) == 0
で使われている&はビット毎AND演算子というものです。
MDNの説明を引用します。
ビット毎 AND 演算子 (&) は、両方のオペランドの対応するビットのどちらか一方が 1 である位置のビットで 1 を返します。
ビット毎 AND (&) – JavaScript | MDN
オペランドは32ビットの整数値に変換され、ビット (ゼロまたは1) の並びによって表現されます。
ビット毎 AND (&) – JavaScript | MDN
つまり、10進数にビット毎AND演算子を使っても、一旦、2進数(32ビット)に変換されてから演算されるわけですね。
おそらく、以下のような比較を行っている、ということでしょう。(私の理解は合ってるかな・・・)
console.log(5 & 3);
// 結果: 1
// 5(10進数) → 101(2進数) → 00000000000000000000000000000101(32ビット)
// 3(10進数) → 11(2進数) → 00000000000000000000000000000011(32ビット)
// 5と3の各ビットにて、どちらも1なのは右端(1ビット目)なので、
// 00000000000000000000000000000001(32ビット)となり、
// 10進数に変換すると 1 となる。
console.log(5 & 2);
// 結果: 0
// 5(10進数) → 101(2進数) → 00000000000000000000000000000101(32ビット)
// 2(10進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// 5と2の各ビットにて、どちらも1になるものがないため、
// 00000000000000000000000000000000(32ビット)となり、
// 10進数に変換すると 0 となる。
(data[dy0][dx0] & 0x2) == 0
で使われている 0x2 は16進数の表記で、10進数に変換すると2になります。
0〜6の数字を、10進数、16進数、2進数の表にすると以下のようになります。
10進数 | 16進数 | 2進数 |
---|---|---|
0 | 0x0 | 0 |
1 | 0x1 | 1 |
2 | 0x2 | 10 |
3 | 0x3 | 11 |
4 | 0x4 | 100 |
5 | 0x5 | 101 |
6 | 0x6 | 110 |
これで、進める進めないの判定を行っている箇所を読み解く準備ができました。
if ((data[dy0][dx0] & 0x2) == 0) { //荷物なし&壁なし→進む
// 省略
} else if ((data[dy0][dx0] & 0x6) == 2) { //進行方向に荷物あり
if ((data[dy1][dx1] & 0x2) == 0) { //荷物なし&壁なし→進む
// 省略
}
}
仮想マップに使っている数字を10進数と2進数の表にすると、以下のようになります。
10進数 | 2進数 | |
---|---|---|
通路 | 0 | 0 |
目的地 | 1 | 1 |
荷物 | 2 | 10 |
壁 | 6 | 110 |
これを、if 文の式に当てはめてみましょう。
// if ((data[dy0][dx0] & 0x2) == 0) {} に当てはめた場合
// 通路: 0
console.log(0 & 0x2);
// 結果: 0
// 0(10進数) → 0(2進数) → 00000000000000000000000000000000(32ビット)
// 0x2(16進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// 0と0x2の各ビットにて、どちらも1のものがないので、
// 00000000000000000000000000000000(32ビット)となり、
// 10進数に変換すると 0 となる。
// 目的地: 1
console.log(1 & 0x2);
// 結果: 0
// 1(10進数) → 0(2進数) → 00000000000000000000000000000001(32ビット)
// 0x2(16進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// 1と0x2の各ビットにて、どちらも1のものがないので、
// 00000000000000000000000000000000(32ビット)となり、
// 10進数に変換すると 0 となる。
// 荷物: 2
console.log(2 & 0x2);
// 結果: 2
// 2(10進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// 0x2(16進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// 2と0x2の各ビットにて、どちらも1のものは2ビット目(右から2番目)なので、
// 00000000000000000000000000000010(32ビット)となり、
// 10進数に変換すると 2 となる。
// 壁: 6
console.log(6 & 0x2);
// 結果: 2
// 6(10進数) → 110(2進数) → 00000000000000000000000000000110(32ビット)
// 0x2(16進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// 6と0x2の各ビットにて、どちらも1のものは2ビット目(右から2番目)なので、
// 00000000000000000000000000000010(32ビット)となり、
// 10進数に変換すると 2 となる。
表に結果をまとめると以下のようになり、if文の条件にあった通り「通路」と「目的地」は進める(0である)ことがわかります。
if ((data[dy0][dx0] & 0x2) == 0) { //荷物なし&壁なし→進む
10進数 | & 0x2 の結果 | |
---|---|---|
通路 | 0 | 0 |
目的地 | 1 | 0 |
荷物 | 2 | 2 |
壁 | 6 | 2 |
荷物があるか・荷物がないかの判定
今度は、進行方向に荷物があるかの判定をしているif文の式に当てはめてみます。
} else if ((data[dy0][dx0] & 0x6) == 2) { //進行方向に荷物あり
// } else if ((data[dy0][dx0] & 0x6) == 2) {} に当てはめた場合
// 通路: 0
console.log(0 & 0x6);
// 結果: 0
// 0(10進数) → 0(2進数) → 00000000000000000000000000000000(32ビット)
// 0x6(16進数) → 110(2進数) → 00000000000000000000000000000110(32ビット)
// 0と0x6の各ビットにて、1が重複するのがないので、
// 00000000000000000000000000000000(32ビット)となり、
// 10進数に変換すると 0 となる。
// 目的地: 1
console.log(1 & 0x6);
// 結果: 0
// 1(10進数) → 0(2進数) → 00000000000000000000000000000001(32ビット)
// 0x6(16進数) → 110(2進数) → 00000000000000000000000000000110(32ビット)
// 1と0x6の各ビットにて、1が重複するのがないので、
// 00000000000000000000000000000000(32ビット)となり、
// 10進数に変換すると 0 となる。
// 荷物: 2
console.log(2 & 0x6);
// 結果: 2
// 2(10進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// 0x6(16進数) → 110(2進数) → 00000000000000000000000000000110(32ビット)
// 2と0x6の各ビットにて、1が重複するのは2ビット目(右から2番目)なので、
// 00000000000000000000000000000010(32ビット)となり、
// 10進数に変換すると 2 となる。
// 壁: 6
console.log(6 & 0x6);
// 結果: 6
// 6(10進数) → 110(2進数) → 00000000000000000000000000000110(32ビット)
// 0x6(16進数) → 110(2進数) → 00000000000000000000000000000110(32ビット)
// 6と0x6の各ビットにて、1が重複するのは2ビット目(右から2番目)と3ビット目(右から3番目)なので、
// 00000000000000000000000000000110(32ビット)となり、
// 10進数に変換すると 6 となる。
表に結果をまとめると以下のようになります。
10進数 | & 0x6 の結果 | |
---|---|---|
通路 | 0 | 0 |
目的地 | 1 | 0 |
荷物 | 2 | 2 |
壁 | 6 | 6 |
if文の条件にあった通り、
} else if ((data[dy0][dx0] & 0x6) == 2) { //進行方向に荷物あり
「荷物」だけを判別できている(2である)ことがわかります。
2ビット目が0か1かで判定している
ここまで読み解いて私は「なぜこんなにややこしいことをしているんだろう」と思いましたが、これは、各数字の2ビット目が0か1かで、進める進めないを判定することができているのだそうです。
通路0、目的地1、荷物2、壁6と変な値を割り当てていましたが、実は荷物の有無と移動できるか否かを2ビット目が0か1かで判断していたのです。
田中 賢一郎. ゲームで学ぶJavaScript入門 HTML5&CSSも身につく! (Japanese Edition) (Kindle の位置No.3036-3040). Kindle 版.
まず、それぞれの値と2とのANDを計算してみましょう。
0x2は2進数で010なので、この数値とANDを計算するということは、2ビット目を取り出すことと同じになります。
その結果、通路と目的地は0、荷物と壁は1となります。
通路と目的地は自由に移動できます。つまり、データと0x2のAND計算結果が0であれば自由に動けるのです。
目から鱗です。
たしかに、表にすると2進数の2ビット目が0か1かで進める進めないが判定できることがわかります。
10進数 | 2進数 (3ビット表記) | & 0x2(010)とのAND結果 (2ビット目) | |
---|---|---|---|
通路 | 0 | 000 | 0 |
目的地 | 1 | 001 | 0 |
荷物 | 2 | 010 | 1 |
壁 | 6 | 110 | 1 |
移動後の値の更新
移動したあとには、仮想マップの数値を更新します。
その処理を行っているのは以下のコードです。
data[dy0][dx0] ^= 2; //隣の荷物をクリア
data[dy1][dx1] |= 2; //更に先に荷物をセット
このコードを読み解いていきます。
^演算子はビット排他論理和といって、0を1に、1を0に反転するものです。
「^=2」とすることで、2ビット目を反転した結果を自分自身に代入します。田中賢一郎.ゲームで学ぶJavaScript入門HTML5&CSSも身につく!(Kindleの位置No.3060-3062).株式会社インプレス.Kindle版.
|演算子はOR演算子です。
「|=2」とすることで、2ビット目のビットを1にした結果を自分自身に代入します。
まずはビット排他論理和からですね。
ビット排他的論理和代入演算子(^=)での値更新
ビット排他論理和のコードは以下の部分です。
data[dy0][dx0] ^= 2; //隣の荷物をクリア
キャラクタが移動する先の座標の荷物をクリア(0: 通路)にする処理です。
まずはビット排他的論理和演算子 (^)の説明をMDNから引用します。
ビット毎 XOR 演算子 (^) は、両方のオペランドの対応するビットの一方だけが 1 である位置のビットで 1 を返します。
ビット毎 XOR (^) – JavaScript | MDN
オペランドは32ビットの整数値に変換され、ビット (ゼロまたは1) の並びによって表現されます。
ビット毎 XOR (^) – JavaScript | MDN
つまり、片方のビットが1の時だけ1を返すわけなので、以下のような挙動になるはずです。
console.log(5 ^ 3);
// 結果: 6
// 5(10進数) → 101(2進数) → 00000000000000000000000000000101(32ビット)
// 3(10進数) → 11(2進数) → 00000000000000000000000000000011(32ビット)
// 5と3の各ビットにて、片方だけが1なのは3ビット目(右から3番目)と2ビット目(右から2番目)なので、
// 00000000000000000000000000000110(32ビット)となり、
// 10進数に変換すると 6 となる。
そして、それを代入するためのビット排他的論理和代入演算子(^=)は、やることはほぼ同じです。
MDNからの引用によると、XOR演算を実行した結果を代入する、というだけですね。
The bitwise XOR assignment operator (^=) uses the binary representation of both operands, does a bitwise XOR operation on them and assigns the result to the variable.
Google翻訳:ビットごとのXOR代入演算子(^=)は、両方のオペランドのバイナリ表現を使用し、それらに対してビットごとのXOR演算を実行して、結果を変数に割り当てます。
Bitwise XOR assignment (^=) – JavaScript | MDN
というわけで、代入する場合は以下のようになります。
var a = 5;
a ^= 3; // a = a ^ 3; と同じ意味
console.log(a);
// 結果: 6
// 5(10進数) → 101(2進数) → 00000000000000000000000000000101(32ビット)
// 3(10進数) → 11(2進数) → 00000000000000000000000000000011(32ビット)
// 5と3の各ビットにて、片方だけが1なのは3ビット目(右から3番目)と2ビット目(右から2番目)なので、
// 00000000000000000000000000000110(32ビット)となり、
// 10進数に変換すると 6 となる。
さて、これを元々のコードに当てはめてみましょう。
if ((data[dy0][dx0] & 0x2) == 0) { //荷物なし&壁なし→進む
// 省略
} else if ((data[dy0][dx0] & 0x6) == 2) { //進行方向に荷物あり
if ((data[dy1][dx1] & 0x2) == 0) { //荷物なし&壁なし→進む
data[dy0][dx0] ^= 2; //隣の荷物をクリア
data[dy1][dx1] |= 2; //更に先に荷物をセット
// 省略
}
}
data[dy0][dx0] ^= 2; //隣の荷物をクリア
の部分ですね。
data[dy0][dx0]
は前の行のif文にて 2: 荷物 であることが確定しています。
そのため、以下のように 0: 通路 が代入されることになります。
// data[dy0][dx0] には 2 が格納されているため、
// data[dy0][dx0] ^= 2; は ↓ と同じ意味になる
// data[dy0][dx0] = 2 ^ 2;
console.log(2 ^ 2);
// 結果: 0
// 2(10進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// 2(10進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// 2と2の各ビットにて、片方だけが1のものがないので、
// 00000000000000000000000000000000(32ビット)となり、
// 10進数に変換すると 0 となる。
次はOR演算子の説明ですね。
ビット論理和代入演算子(|=)での値更新
ビット論理和代入演算子のコードは以下の部分です。
data[dy1][dx1] |= 2; //更に先に荷物をセット
キャラクターの移動先の座標の、さらに一つ先に荷物を移動する処理です。
ビット論理和演算子についての説明はMDNから引用します。
ビット毎 OR 演算子 (|) は、両方のオペランドの対応するビットのどちらか一方が 1 である位置のビットで 1 を返します
ビット毎 OR (|) – JavaScript | MDN
オペランドは32ビットの整数値に変換され、ビット (ゼロまたは1) の並びによって表現されます。
ビット毎 OR (|) – JavaScript | MDN
つまり、どちらか一方でも1であれば1を返すわけなので、以下のような挙動になるはずです。
console.log(5 | 3);
// 結果: 7
// 5(10進数) → 101(2進数) → 00000000000000000000000000000101(32ビット)
// 3(10進数) → 11(2進数) → 00000000000000000000000000000011(32ビット)
// どちらか一方でも1なのは、3ビット目〜1ビット目(右から3番目〜1番目)なので、
// 00000000000000000000000000000111(32ビット)となり、
// 10進数に変換すると 7 となる。
この挙動をビット論理和代入演算子(|=)で代入しているわけですね。
ビット論理和代入演算子(|=)の説明をMDNから引用します。
The bitwise OR assignment operator (|=) uses the binary representation of both operands, does a bitwise OR operation on them and assigns the result to the variable.
Google翻訳:ビットごとのOR代入演算子(|=)は、両方のオペランドのバイナリ表現を使用し、それらに対してビットごとのOR演算を実行して、結果を変数に割り当てます。
Bitwise OR assignment (|=) – JavaScript | MDN
代入する場合は以下のような挙動になります。
var a = 5;
a |= 3; // a = a | 3; と同じ意味
console.log(a);
// 結果: 7
// 5(10進数) → 101(2進数) → 00000000000000000000000000000101(32ビット)
// 3(10進数) → 11(2進数) → 00000000000000000000000000000011(32ビット)
// どちらか一方でも1なのは、3ビット目〜1ビット目(右から3番目〜1番目)なので、
// 00000000000000000000000000000111(32ビット)となり、
// 10進数に変換すると 7 となる。
さて、これを元々のコードに当てはめてみましょう。
if ((data[dy0][dx0] & 0x2) == 0) { //荷物なし&壁なし→進む
// 省略
} else if ((data[dy0][dx0] & 0x6) == 2) { //進行方向に荷物あり
if ((data[dy1][dx1] & 0x2) == 0) { //荷物なし&壁なし→進む
data[dy0][dx0] ^= 2; //隣の荷物をクリア
data[dy1][dx1] |= 2; //更に先に荷物をセット
// 省略
}
}
data[dy1][dx1] |= 2; //更に先に荷物をセット
の部分ですね。
data[dy1][dx1]
は前の行のif文にて 0: 通路 または 1: 目的地 であることが確定しています。
そのため、0: 通路 の場合は以下のように 2: 荷物 が代入されます。
// data[dy1][dx1] には 0 or 1 が格納されている。
//
// data[dy1][dx1] が 0 の場合
// data[dy1][dx1] |= 2; は ↓ と同じ意味になる
// data[dy1][dx1] = 0 | 2;
console.log(0 | 2);
// 結果: 2
// 0(10進数) → 0(2進数) → 00000000000000000000000000000000(32ビット)
// 2(10進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// どちらか一方でも1なのは、2ビット目(右から2番目)なので、
// 00000000000000000000000000000010(32ビット)となり、
// 10進数に変換すると 2 となる。
また、1: 目的地 の場合は以下のように 3: 目的地と荷物が重なる状態 が代入されます。
// data[dy1][dx1] には 0 or 1 が格納されている。
//
// data[dy1][dx1] が 1 の場合
// data[dy1][dx1] |= 2; は ↓ と同じ意味になる
// data[dy1][dx1] = 1 | 2;
console.log(1 | 2);
// 結果: 3
// 1(10進数) → 1(2進数) → 00000000000000000000000000000001(32ビット)
// 2(10進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// どちらか一方でも1なのは、2ビット目(右から2番目)と1ビット目(右から1番目)なので、
// 00000000000000000000000000000011(32ビット)となり、
// 10進数に変換すると 3 となる。
急に、今までまったく出てこなかった 3 という数字が出てきました。
これは、目的地と荷物が重なった状態の座標にキャラクターを移動した時に、移動前の座標を 2:荷物 に、移動先の座標を 1: 目的地 にするために、あえて 3 という数字にしているのです。
たとえば、以下のような状態の場合があるとします。
0 | 1 | 1 |
0 (キャラクターの位置) | 3 (目的地・荷物) | 1 |
0 | 1 | 1 |
キャラクターを一つ右に移動した場合は、以下のように、荷物と目的地を更新したいわけです。
0 | 1 | 1 |
0 | 1 (目的地・キャラクターの位置) | 3 (目的地・荷物) |
0 | 1 | 1 |
ですが、もし目的地と荷物が重なった状態を 3 と表現せずに、 2: 荷物 として値を更新してしまうと、キャラクターを一つ右に移動した時に目的地なのか通路なのか判断がつかなくなります。
変更前の情報を変数に保持しておくなりすれば対応はできますが、このチャプターではビット演算子だけで解決できるようにしてあるため、3 として値を更新しています。
キャラクターを一つ右の座標に移動する時に、どういう処理になっているかを、コメントで説明します。
if ((data[dy0][dx0] & 0x2) == 0) { //荷物なし&壁なし→進む
px = dx0;
py = dy0;
} else if ((data[dy0][dx0] & 0x6) == 2) { //進行方向に荷物あり
// data[dy0][dx0] が 3 の想定なので、(3 & 0x6) は 2 で上の条件式は通る
console.log(3 & 0x6);
// 結果: 2
// 3(10進数) → 11(2進数) → 00000000000000000000000000000011(32ビット)
// 0x6(16進数) → 110(2進数) → 00000000000000000000000000000110(32ビット)
// 0と0x6の各ビットにて、どちらも1なのは2ビット目(右から2番目)なので、
// 00000000000000000000000000000010(32ビット)となり、
// 10進数に変換すると 2 となる。
// data[dy1][dx1] は移動先のさらに1つ先の座標
// data[dy1][dx1] が 1 であるという想定なので、下の条件式は通る
if ((data[dy1][dx1] & 0x2) == 0) { //荷物なし&壁なし→進む
// data[dy0][dx0] は 3 の想定なので (3 ^ 2) が代入される
console.log(3 ^ 2);
// 結果: 1
// 3(10進数) → 11(2進数) → 00000000000000000000000000000011(32ビット)
// 2(10進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// 3と2の各ビットにて、片方だけが1のものは1ビット目(右端)なので、
// 00000000000000000000000000000001(32ビット)となり、
// 10進数に変換すると 1 となる。
// data[dy0][dx0] ^= 2; には 1 が代入される。
data[dy0][dx0] ^= 2; //隣の荷物をクリア
// data[dy1][dx1] が 1 の想定なので (1 | 2) が代入される
console.log(1 | 2);
// 結果: 3
// 1(10進数) → 1(2進数) → 00000000000000000000000000000001(32ビット)
// 2(10進数) → 10(2進数) → 00000000000000000000000000000010(32ビット)
// どちらか一方でも1なのは、2ビット目(右から2番目)と1ビット目(右から1番目)なので、
// 00000000000000000000000000000011(32ビット)となり、
// 10進数に変換すると 3 となる。
// data[dy1][dx1] |= 2; には 3 が代入される
data[dy1][dx1] |= 2; //更に先に荷物をセット
px = dx0;
py = dy0;
}
}
上記のように、 3 の場合は 1: 目的地 として値が更新されます。
さいごに
私はJavaScript初心者ではないと思ってましたが、仕事で使ってないことは案外知らないものですね・・・。
「サイト制作でJavaScriptを使うこととゲームのプログラミングをするのは頭の使い方が違うんだなぁ」と、感嘆するばかりです。