JavaScript でテトリスを開発する その 3

# JavaScript でテトリスを開発する その 3

第 3 回です。今回は仕様 ③ 7 種類のテトリミノの作成 と ⑨ テトリミノはランダムに生成される を実装していきます。

① 画面サイズは縦 20 ブロック分、横 10 ブロック分である →[その 1 参照](https://qiita.com/namakoRice/items/4933e77a41b2ac359229)
② 4 つの正方形ブロックで構成されるテトリミノがある →[その 2 参照](https://qiita.com/namakoRice/items/4fefc6557af480d8e9a5)
③ テトリミノは 7 種類ある  
 ④ テトリミノは画面内でかつ他ブロックに干渉しなければ自由に動かせる  
 ⑤ テトリミノは画面内でかつ他ブロックに干渉しなければ自由に回転できる  
 ⑥ テトリミノは画面最下部または他ブロックの上部に面したとき動かせなくなる  
 ⑦ テトリミノは画面上部に生成される  
 ⑧ テトリミノは一定間隔で下方向に移動する  
 ⑨ テトリミノはランダムに生成される  
 ⑩ 横 10 ブロック分がそろった場合、そろった列は消えて上の列が下りてくる  
 ⑪ テトリミノ生成場所にブロックがある場合、ゲームが終了する

## 7 種類のテトリミノを作成する

まず、前回作ったＺ型のテトリミノに加えて残りのテトリミノを作っていきます。

### コード

```javascript
・省略・
// 7種類のテトリミノ達
let TETRO_TYPES = [
[
  // Z
  [0, 0, 0, 0],
  [1, 1, 0, 0],
  [0, 1, 1, 0],
  [0, 0, 0, 0],
],
[
  // S
  [0, 0, 0, 0],
  [0, 0, 1, 1],
  [0, 1, 1, 0],
  [0, 0, 0, 0],
],
[
  // I
  [0, 1, 0, 0],
  [0, 1, 0, 0],
  [0, 1, 0, 0],
  [0, 1, 0, 0],
],
[
  // J
  [0, 1, 0, 0],
  [0, 1, 0, 0],
  [0, 1, 1, 0],
  [0, 0, 0, 0],
],
[
  // L
  [0, 0, 1, 0],
  [0, 0, 1, 0],
  [0, 1, 1, 0],
  [0, 0, 0, 0],
],
[
  // T
  [0, 0, 0, 0],
  [1, 1, 1, 0],
  [0, 1, 0, 0],
  [0, 0, 0, 0],
],
[
  // O
  [0, 0, 0, 0],
  [0, 1, 1, 0],
  [0, 1, 1, 0],
  [0, 0, 0, 0],
],
・省略・
];

// テトロミノを取得する
let tetroMino = TETRO_TYPES[0];

// テトリスプレイ画面描画処理
const drawPlayScreen = () => {
// 背景色を黒に指定
CANVAS_2D.fillStyle = '#000';

// キャンバスを塗りつぶす
CANVAS_2D.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);

// 塗りに赤を設定
CANVAS_2D.fillStyle = '#E33';
// x,y =100, 100の場所に30×30のブロックを描画
CANVAS_2D.fillRect(100, 100, BLOCK_SIZE, BLOCK_SIZE);

// テトリミノを描画する
for (let y = 0; y < TET_SIZE; y++) {
  for (let x = 0; x < TET_SIZE; x++) {
    if (tetroMino[y][x]) {
      CANVAS_2D.fillRect(
        x * BLOCK_SIZE,
        y * BLOCK_SIZE,
        BLOCK_SIZE,
        BLOCK_SIZE
      );
    }
  }
}
};
```

### 解説

7 種のテトロミノは 1 つの情報として持たせておいたほうがよい事は分かると思います。そこで、二次元配列を持った配列 TETRO_TYPES（三次元配列）を作って 1 つの定数で管理をします。あとは変数 tetroMino に配列 TETRO_TYPES のインデックス番号を指定した値を格納することで自在に 7 種類のテトロミノ情報を取得することができるようになります。  
試しに 0 以外の数値を入れてみましょう。他のテトロミノが表示されるはずです。

さて、7 種類のテトロミノを表示自体はできるようになりましたが、これでは仕様 ⑦ テトロミノをランダムに生成する が達成されません。・・・課題その 3  
インデックス番号自体がランダムに変化さえすればテトロミノ自体もランダム生成できるわけなので、ランダムに数値を取得するロジックを実装していきます。

### コード

```javascript
・省略・
// TETRO_TYPESのインデックス番号をランダム取得
let tetroTypesIndex = Math.floor(Math.random() * (TETRO_TYPES.length));

// テトロミノを取得する
let tetroMino = TETRO_TYPES[tetroTypesIndex];

・省略・
```

### 解説

ランダムに数値を取得するために Math.random 関数を使っています。random 関数は 0 以上 1 未満の間の少数をランダムに返却する関数です。0 以外の戻り値は 1 未満の少数であるため、ランダムな整数を取得したい場合は「最大値＋ 1」を倍数としてかけてかつ、小数点以下は切り捨ててあげる必要があります。例えば 0 ～ 10 までのランダムな数値を得たい場合は、

```javascript
Math.floor(Math.random() * 11);
```

という式になります。戻り値として「0」がでたら「0\*11」で「0」、「0.54」がでたら「5.94」となり「5」、「0.999」がでたら「10」となります。  
配列 TETRO_TYPES のインデックス番号の最大値が「6」であることから、オンコードで倍率を 7 としても問題はありませんが、テトロミノの種類を自作で増やした場合も考慮して TETRO_TYPES.length で配列の長さを取得する形をとっています。

これで、リロードするたびに違うミノが表示されるようになったはずです。

## まとめ

今回は仕様 ③ 7 種類のテトリミノの作成 と ⑨ テトリミノはランダムに生成される を実装しました。抑えるポイントは下記です。

1.  Math.random 関数を使うことで数値をランダムに取得することができる

今回は短いですが以上となります。次回はテトリミノを自由に動かすところを実装していきます。

最後にここまでのコードを掲載します

```javascript
// 1ブロックの大きさ
const BLOCK_SIZE = 30;

// フィールドのサイズ
const PLAY_SCREEN_WIDTH = 10;
const PLAY_SCREEN_HEIGHT = 20;

// キャンバスIDの取得
const CANVAS = document.getElementById('canvas');

// 2dコンテキストの取得
const CANVAS_2D = CANVAS.getContext('2d');

// キャンバスサイズ（＝プレイ画面のサイズ）
const CANVAS_WIDTH = BLOCK_SIZE * PLAY_SCREEN_WIDTH;
const CANVAS_HEIGHT = BLOCK_SIZE * PLAY_SCREEN_HEIGHT;
CANVAS.width = CANVAS_WIDTH;
CANVAS.height = CANVAS_HEIGHT;

// テトリミノの1辺の最長
const TET_SIZE = 4;

// 7種類のテトリミノ達
let TETRO_TYPES = [
  [
    // Z
    [0, 0, 0, 0],
    [1, 1, 0, 0],
    [0, 1, 1, 0],
    [0, 0, 0, 0],
  ],
  [
    // S
    [0, 0, 0, 0],
    [0, 0, 1, 1],
    [0, 1, 1, 0],
    [0, 0, 0, 0],
  ],
  [
    // I
    [0, 1, 0, 0],
    [0, 1, 0, 0],
    [0, 1, 0, 0],
    [0, 1, 0, 0],
  ],
  [
    // J
    [0, 1, 0, 0],
    [0, 1, 0, 0],
    [0, 1, 1, 0],
    [0, 0, 0, 0],
  ],
  [
    // L
    [0, 0, 1, 0],
    [0, 0, 1, 0],
    [0, 1, 1, 0],
    [0, 0, 0, 0],
  ],
  [
    // T
    [0, 0, 0, 0],
    [1, 1, 1, 0],
    [0, 1, 0, 0],
    [0, 0, 0, 0],
  ],
  [
    // O
    [0, 0, 0, 0],
    [0, 1, 1, 0],
    [0, 1, 1, 0],
    [0, 0, 0, 0],
  ],
];

// TETRO_TYPESのインデックス番号をランダム取得
let tetroTypesIndex = Math.floor(Math.random() * 7);

// テトロミノを取得する
let tetroMino = TETRO_TYPES[tetroTypesIndex];

// テトリスプレイ画面描画処理
const drawPlayScreen = () => {
  // 背景色を黒に指定
  CANVAS_2D.fillStyle = '#000';

  // キャンバスを塗りつぶす
  CANVAS_2D.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);

  // 塗りに赤を設定
  CANVAS_2D.fillStyle = '#E33';
  // x,y =100, 100の場所に30×30のブロックを描画
  CANVAS_2D.fillRect(100, 100, BLOCK_SIZE, BLOCK_SIZE);

  // テトリミノを描画する
  for (let y = 0; y < TET_SIZE; y++) {
    for (let x = 0; x < TET_SIZE; x++) {
      if (tetroMino[y][x]) {
        CANVAS_2D.fillRect(
          x * BLOCK_SIZE,
          y * BLOCK_SIZE,
          BLOCK_SIZE,
          BLOCK_SIZE
        );
      }
    }
  }
};

// 画面を真ん中にする
const CONTAINER = document.getElementById('container');
CONTAINER.style.width = CANVAS_WIDTH + 'px';

// 初期化処理
const init = () => {
  drawPlayScreen();
};
```