荷物運びゲームの制作

### 荷物運びゲームの制作
キーボードや画面のキーを操作してプレイヤーを移動させ、荷物を運ぶゲームを制作します。

```html
<body onload="init()">
  <canvas id="canvas" width="800" height="440"></canvas>
  <table>
    <tr><td></td><td><button onclick="u()">U</button></td><td></td></tr>
    <tr><td><button onclick="l()">L</button></td><td></td><td><button onclick="r()">R</button></td></tr>
    <tr><td></td><td><button onclick="d()">D</button></td><td></td></tr>
    <img id="imgWall" src="images/wall3.jpg" style="display: none">
    <img id="imgGoal" src="images/circle1.jpg" style="display: none">
    <img id="imgWorker" src="images/human3.png" style="display: none">
    <img id="imgLuggage" src="images/luggage2.png" style="display: none">
  </table>
</body>
```

まず、body要素内にゲームを表示するためにcanvas要素を記述します。
その下にtable要素を記述し、十字キーを作ります。Uをクリックするとプレイヤーが上に、Lをクリックすると左に、Rをクリックすると右に、Dをクリックすると下に移動するように、後で実装します。
さらにその下にimg要素を4つ作ります。後で画面に表示するための準備なので、呼び出すまで表示されないように「style＝”display: none”」を属性に追加します。

```javascript
  <script>
    const 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,6,0,0,0,6,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,6,0,0,2,6,6,6,6,6,6,6,6,6,0,0,6,6],
      [6,6,6,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,6,6],
      [6,6,6,0,6,0,6,6,6,6,6,6,6,6,6,6,0,0,0,6,6],
      [6,0,0,0,6,0,6,6,6,6,6,6,6,6,6,6,6,1,1,6,6],
      [6,0,2,0,0,2,0,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,6,6,6,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,6,6],
    ];
    let gc, px = 12, py = 8;

    function init() {
      gc = document.getElementById("canvas").getContext("2d");
      onkeydown = mykeydown;
      repaint();
    }
```

次にscript要素内を記述していきます。
最初に定数として配列dataを定義します。dataは二次元配列にします。数字の0を通路、1をゴール、2を荷物、６を壁として後で扱います。
変数はgc,px,pyを定義し、gcはcanvasで表示する画面、pxはプレイヤーのx座標、pyはプレイヤーのy座標となります。

init関数で初期化します。変数gcにcanvas要素を取得させて、キーボードを押したときにプレイヤーを操作できるようにmykeydown関数をonkeydownに代入します。repaint関数は後で定義しますが、プレイヤーを操作するごとに表示画面を更新する処理をします。


```javascript
    function repaint() {
      gc.fillStyle = "black";
      gc.fillRect(0, 0, 800, 440);

      for(let y = 0; y < data.length; y++) {
        for(let x = 0; x < data[y].length; x++) {
          if(data[y][x] & 0x1) {
            gc.drawImage(imgGoal, x * 40, y * 40, 40, 40);
          }
          if(data[y][x] & 0x2) {
            gc.drawImage(imgLuggage, x * 40, y * 40, 40, 40);
          }
          if(data[y][x] == 6) {
            gc.drawImage(imgWall, x * 40, y * 40, 40, 40);
          }
        }
      }
      gc.drawImage(imgWorker, px * 40, py * 40, 40, 40);
    }
  </script>
```

repaint関数を定義します。この関数で操作するごとに画面が更新されるようにします。
最初に「gc.fillStyle = "black";」と「gc.fillRect(0,0,800,440);」でcanvasで作成した画面全体を黒くします。配列dataで値が0の箇所が黒くなります。
次のfor文で配列dataが1のときは画像「imgGoal」、配列dataが2のときは画像「imgLuggage」、配列dataが6のときは画像「imgWall」が表示されるようにしています。
「要素.drawImage(表示したい画像, 表示するx座標, 表示するy座標, 画像の幅, 画像の高さ)」となります。
「data[y][x] & 0x1」は0x1が二進数の1を表しているので、data[y][x]と0x1が共に1のときにif文がtrueになります。
0x2は二進数の「10」を表しているので値が2のときにif文がtrueになります。
最後にプレイヤーの画像を表示します。（imgWorker）


```javascript
    function u() {
      mykeydown({keyCode: 38});
    }
    function l() {
      mykeydown({keyCode: 37});
    }
    function r() {
      mykeydown({keyCode: 39});
    }
    function d() {
      mykeydown({keyCode: 40});
    }
```

画面上の十字キーに関数を実装します。
それぞれ、キーをクリックしたときにmykeydown関数が呼び出されるようにします。
keyCodeは37が矢印キー左、38が上、39が右、40が下です。
(ブラウザで「javascript keyCode」)で検索すれば、割り振られているキーコードが記載されているサイトは調べられます。

```javascript
    function mykeydown(e) {
      let dx0 = px, dx1 = px, dy0 = py, dy1 = py;
      switch(e.keyCode) {
        case 38:
          dy0--;
          dy1 -= 2;
          break;
        case 37:
          dx0--;
          dx1 -= 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();
    }
```

mykeydown関数は引数に「e」を指定してクリックしたボタンのデータを取得します。
変数はdx0がプレイヤーの現在位置の一マス左右の座標(右がプラス側)、dy0が一マス上下の座標(下がプラス側)を表します。dx1がプレイヤーの現在位置の二マス左右の座標(右がプラス側)、dy1が二マス上下の座標(下がプラス側)です。

switch文で取得したキーコードごとに変数に値を増減させます。

次の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」で移動先のさらに一つ先のマスに荷物を置きます。
^=は排他的論理和演算することができ、左辺と右辺の値が同じときに左辺の値をfalseにでき、値が違うときにtrueにできます。
|=は論理和演算することができ、左辺と右辺のどちらかが値を持つときに左辺の値をtrueにできます。
荷物のあるなしに関わらず、プレイヤーの座標にはdx0とdy0を代入します。

下記のコードをコピーしてファイルに貼り付ければ試すことができます。
img要素内のsrc属性に指定している画像データはご自分で用意してください。

```javascript
<!DOCTYPE html>

<html>

<head>
  <meta charset="UTF-8">
  <title>荷物運びゲーム</title>

  <script>
    const 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,6,0,0,0,6,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,6,0,0,2,6,6,6,6,6,6,6,6,6,0,0,6,6],
      [6,6,6,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,6,6],
      [6,6,6,0,6,0,6,6,6,6,6,6,6,6,6,6,0,0,0,6,6],
      [6,0,0,0,6,0,6,6,6,6,6,6,6,6,6,6,6,1,1,6,6],
      [6,0,2,0,0,2,0,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,6,6,6,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,6,6],
    ];
    let gc, px = 12, py = 8;

    function init() {
      gc = document.getElementById("canvas").getContext("2d");
      onkeydown = mykeydown;
      repaint();
    }

    function u() {
      mykeydown({keyCode: 38});
    }
    function l() {
      mykeydown({keyCode: 37});
    }
    function r() {
      mykeydown({keyCode: 39});
    }
    function d() {
      mykeydown({keyCode: 40});
    }
    function mykeydown(e) {
      let dx0 = px, dx1 = px, dy0 = py, dy1 = py;
      switch(e.keyCode) {
        case 38:
          dy0--;
          dy1 -= 2;
          break;
        case 37:
          dx0--;
          dx1 -= 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();
    }

    function repaint() {
      gc.fillStyle = "black";
      gc.fillRect(0, 0, 800, 440);

      for(let y = 0; y < data.length; y++) {
        for(let x = 0; x < data[y].length; x++) {
          if(data[y][x] & 0x1) {
            gc.drawImage(imgGoal, x * 40, y * 40, 40, 40);
          }
          if(data[y][x] & 0x2) {
            gc.drawImage(imgLuggage, x * 40, y * 40, 40, 40);
          }
          if(data[y][x] == 6) {
            gc.drawImage(imgWall, x * 40, y * 40, 40, 40);
          }
        }
      }
      gc.drawImage(imgWorker, px * 40, py * 40, 40, 40);
    }
  </script>
</head>

<body onload="init()">
  <canvas id="canvas" width="800" height="440"></canvas>
  <table>
    <tr><td></td><td><button onclick="u()">U</button></td><td></td></tr>
    <tr><td><button onclick="l()">L</button></td><td></td><td><button onclick="r()">R</button></td></tr>
    <tr><td></td><td><button onclick="d()">D</button></td><td></td></tr>
  </table>
  <img id="imgWall" src="images/wall3.jpg" style="display: none">
  <img id="imgGoal" src="images/circle1.jpg" style="display: none">
  <img id="imgWorker" src="images/human3.png" style="display: none">
  <img id="imgLuggage" src="images/luggage2.png" style="display: none">
</body>
</html>
```