JavaScriptでテトリスを作成して学んだこと

# 目次
[はじめに](#はじめに)
[学んだこと](#学んだこと)
　[1.型var、let、constの違い](#1-型varletconstの違い)
　[2.キャンバス用意の方法](#2-キャンバス用意の方法)
　[3.JavaScript二次元配列初期化の方法](#3-javascript二次元配列初期化の方法)
　[4.Mathオブジェクトのメソッド](#4-mathオブジェクトのメソッド)
　[5.Contextオブジェクトのメソッド](#5-contextオブジェクトのメソッド)
　[6.Contextオブジェクトのプロパティ](#6-contextオブジェクトのプロパティ)
　[7.undefined判定について](#7-undefined判定について)
　[8.setInterval、clearIntervalの使い方](#8-setintervalclearintervalの使い方)
　[9.innerHTMLの使い方](#9-innerhtmlの使い方)
　[10.ボタンの実装方法](#10-ボタンの実装方法)
[作成したテトリスのソースコード](#作成したテトリスのソースコード)
[さいごに](#さいごに)

# はじめに
前回の現場でJavaScriptを勉強することの重要さを痛感したため、
研修以来触っていなかったJavaScriptでテトリスを作成しました。

説明が分かり易く、講座という形で説明してくださるあきちょんさんの
下記動画をもとに作成を行いました。講座は第13回〜第20回までとなっており、
それぞれの動画に解説ページもあるので後から復習もし易く、おすすめです。

https://youtube.com/playlist?list=PLa3BDwShqOrThKoaWauNo8EKZda4pat7Z

具体的な作成方法についてはこちらの動画で説明されているため、
この記事では今回のテトリス作成で「学んだこと」を記録と復習のためにまとめてみようと思います。
（JavaScriptはほぼ初心者のため初歩的な内容を多く含みます）

# 学んだこと
# 1. 型var、let、constの違い
◆``var``
再代入と再宣言◯。ほぼ使わない。
スコープ範囲は関数内。

◆``let``
再代入◯。再宣言×。
スコープ範囲はブロック{}内。

◆``const``
再代入と再宣言×。定数化するときに使用。
スコープ範囲はブロック{}内。

※再代入を少なくすると変数の中身が明確になるので、
なるべくconstを使用しconstが使えない時は基本letを使用する。

# 2. キャンバス用意の方法
```html:html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
</head>
<body>
    <canvas id="canvasId"></canvas>
    <script>
        // キャンバスのIDを取得
        let canvasId = document.getElementById("canvasId");
        // キャンバスのコンテキストを取得
        let conText = canvasId.getContext("2d");
    </script>
</body>
</html>
```
【手順】
①　```HTMLファイルを作成し必要なタグを追記。```
②　```canvasタグを作成しidを決める。```
③　```document.getElementById("canvasId")でcanvasタグのidを取得```し、
　　新たに用意した```canvasIdという変数に詰める。```
④　その変数```canvasIdからgetContextで2dでキャンバスのコンテキストを取得```し、
　　新たに用意した```conTextという変数に詰める。```

◆```getContext()```
テトリスのテトロミノ描画のようにcanvasに描画をおこなうために必要なメソッドで、
引数にコンテキストの種類を指定。主に2d（2Dグラフィック描画時）、
webgl（3Dグラフィック描画時）が引数になる。

↓下記のようにキャンバスのサイズを決め、枠を描くこともできる。

```html:html
    <script>
        let canvasId = document.getElementById("canvasId");
        let conText = canvasId.getContext("2d");

        // キャンバスのサイズを決める
        canvasId.width = 300;
        canvasId.height = 600;

        // キャンバスの枠を描く
        canvasId.style.border = "4px solid #555";
    </script>
```
※scriptタグ内にJavaScriptを書く。

# 3. JavaScript二次元配列初期化の方法
```html:html
    <script>
        // 一次元配列を明示
        let field = [];
        for (let y = 0; y < 20; y++) {
            field[y] = [];
            for (let x = 0; x < 10; x++) {
                // 二次元配列として初期化
                field[y][x] = 0;
            }
        }
    </script>
```
JavaScriptは二次元配列の初期化がないため、最初に一次元配列を明示し、
上記方法で二次元配列として初期化をおこなう。
今回のテトリス作成ではフィールドを初期化する際に使用。

※ひとつひとつ初期化するのは面倒なのでforループを使う。

# 4. Mathオブジェクトのメソッド
◆```Math.random()メソッド```
1未満の乱数を発生させる。
なのでそこに7を掛けたら７未満の乱数を発生させることになる。

◆```Math.floor()メソッド```
数値の小数点以下の切り捨て処理を行う。

```html:html
    <script>
        let tetrominoType;
        // 乱数を発生させる
        tetrominoType = Math.floor(Math.random() * 7);
    </script>
```
今回のテトリス作成では、テトロミノをランダムで落下させるよう実装する際に使用。

[ページ上部へ戻る](#目次)

# 5. Contextオブジェクトのメソッド

◆```fillRect(x,y,w,h)メソッド```
塗りつぶしの四角形を描画する際に使用。

◆```strokeRect(x,y,w,h)メソッド```
輪郭の四角形を描画する際に使用。

引数x：　四角形の左上のX座標
引数y：　四角形の左上のY座標
引数w：　四角形の幅
引数h：　四角形の高さ

```html:html
    <script>
        conText.fillRect(x, y, 30, 30);
        conText.strokeRect(x, y, 30, 30);
    </script>
```
今回のテトリス作成では、テトロミノを構成する一つのブロックの描画部分で使用。


◆```fillText(text,x,y)メソッド```
塗りつぶしのテキストを指定座標に描画する際に使用。

◆```strokeText(text,x,y)メソッド```
輪郭のテキストを指定座標に描画する際に使用。

引数text：　描画する文字列
引数x：　テキストを描画するX座標
引数y：　テキストを描画するY座標

```html:html
    <script>
        conText.fillText("GAME OVER", x, y);
        conText.strokeText("GAME OVER", x, y);
    </script>
```
今回のテトリス作成では「GAME OVER」の描画で使用。

# 6. Contextオブジェクトのプロパティ
◆```fillStyle```
塗りつぶしの色やスタイルを設定できる。
今回のテトリス作成では一つのブロックと「GAME OVER」の塗りつぶしの色を指定する際に使用。

```html:html
    <script>
        conText.fillStyle = "white";
    </script>
```

◆```strokeStyle```
輪郭の色やスタイルを指定できる。
今回のテトリス作成では一つのブロックの輪郭の色を指定する際に使用。

```html:html
    <script>
        conText.strokeStyle = "black";
    </script>
```

◆```lineWidth```
輪郭・線の幅を指定できる。
今回のテトリス作成では一つのブロックと「GAME OVER」の輪郭の幅を指定する際に使用。

```html:html
    <script>
        conText.lineWidth = 4;
    </script>
```

◆```clearRect```
四角形の形で透明化（クリア）される。
引数に始点のX座標、Y座標、長方形の幅、長方形の高さを渡す。

```html:html
    <script>
        conText.clearRect(0, 0, 300, 600);
    </script>
```

# 7. undefined判定について
◆```undefined```
JavaScriptでは変宣言後一度も値を格納していないときundefinedが格納される。
undefinedを判定することで、簡潔にコードを書くことができる。

```html:html
<script>
    // キーボード押下時の処理
    document.onkeydown = function (e) {
        switch (e.keyCode) {
            case 37: // 左
                if (checkMove(-1, 0)) tetromino_x--;
                break;
            case 39: // 右
                if (checkMove(1, 0)) tetromino_x++;
                break;
            case 40: // 下
                if (checkMove(0, 1)) tetromino_y++;
                break;
            case 32: // スペース（回転）
                let newTetromino = 回転後のテトロミノ;
                if (checkMove(0, 0, newTetromino)) 現在のテトロミノ = newTetromino;
                break;
        }
    }
</script>
```
上記のうち宣言後newTetrominoに値を格納しているのはスペースキーが押されたときのみ。なので、そのほかのキーでcheckMoveを呼び出したとき、下記checkMove関数のnewTetrominoにはundefinedが格納されている。


```html:html
<script>
    //移動できるかチェックを行う関数
    function checkMove(mx, my, newTetromino) {
        if (newTetromino == undefined) {
            newTetromino = 現在のテトロミノ;
        }
        // 具体的な処理は省略
    }
</script>
```
undefinedを判定せずキーボード押下時の処理部分で「newTetromino = 現在のテトロミノ;」を記載する方法もあるが、同じ処理を何度も書くことになるので望ましくない。

[ページ上部へ戻る](#目次)

# 8. setInterval、clearIntervalの使い方
◆```setInterval```
一定間隔で同じ処理を実行したい時に使用。
今回のテトリス作成では、テトロミノを落下させる関数を連続で呼び出す際に使用。

◆```clearInterval```
setIntervalで実行中の処理を止めたい時に使用。
今回のテトリス作成では、STARTボタンとRESTARTボタンが押された時にsetIntervalを実行している。
clearIntervalを行わないととボタン押下2回目以降setIntervalが何度も実行され、
テトロミノ落下の速度がだんだん速くなってしまう。

※それを防ぐためにclearIntervalの実行を先に行うようにしている。


```html:html
<script>
    // 定義部
    var interval;

    // 実行部
    onClearInterval();
    onSetInterval();

    // 関数部
    function onClearInterval() {
        clearInterval(interval);
    }

    function onSetInterval() {
        interval = setInterval(呼び出したい関数名, ミリ秒);
    }
</script>
```

# 9. innerHTMLの使い方

```html:html
<div class="drawInfo">
    <div>スコア：</div>
    <div id="score-count">0</div>
    <div>消したライン数：</div>
    <div id="line-count">0</div>
</div>

<script>
    let scoreCount = 100;
    let lineCount = 1;
    document.getElementById('score-count').innerHTML = scoreCount;
    document.getElementById('line-count').innerHTML = lineCount;
</script>

```
scriptタグ内でdivタグのidを取得し、それぞれのinnerHTMLプロパティに値を設定することで、
JavaScriptでHTML要素の中身を変えることができる。

# 10. ボタンの実装方法
今回のテトリス作成ではSTARTボタンとSTOPボタン、RESTARTボタンを実装。
STOPボタンを押下するとテトロミノの落下を停止し、RESTARTボタンになる。
RESTARTボタンを押下するとテトロミノの落下が再開し、STARTボタンになる。

```html:html
<div class="button">
    <span class="start-button" id="start-button">
        <button type="button" id="text">START</button>
    </span>
    <span class="stop-button" id="stop-button">
        <button type="button" id="action">STOP</button>
    </span>
</div>

<script>
    document.getElementById("start-button").onclick = function () {
        // スタートボタンが押された時に行う処理（省略）
    }

    document.getElementById("stop-button").onclick = function () {
        onStopButton();
    }

    // ストップボタン押下後の処理を行う関数
    function onStopButton() {
        var repeatFlg = true;

        if (repeatFlg) {
            onClearInterval();
            document.getElementById('action').innerHTML = 'RESTART';
            repeatFlg = false;

        } else {
            onClearInterval();
            onSetInterval();
            document.getElementById('action').innerHTML = 'STOP';
            repeatFlg = true;
        }
    }
</script>

```
repeatFlgと、ここまでに紹介したclearInterval、setInterval、innerHTMLを使用。
RESTARTボタンが押された時にsetIntervalの前にclearIntervalを行なっているのは、
ボタン押下2回目以降setIntervalが何度も実行され、テトロミノ落下の速度がだんだん速くなってしまうのを防ぐため。

[ページ上部へ戻る](#目次)

# 作成したテトリスのソースコード
今回HTMLにJavaScriptを記載するようにしています。
ソースコード自体の個別の解説はコメントアウト部分で行っています。
そのためコメントアウトが多くなってしまっていますがご容赦ください。

<details><summary>HTML</summary><div>

```html:tetris.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="tetris.css" type="text/css" />
    <title>簡易テトリス</title>
</head>

<body>
    <div class="main">
        <h1>
            <div>TETRIS OF OSAKANA</div>
        </h1>
        <div class="drawInfo">
            <div>スコア：</div>
            <div id="score-count">0</div>
            <!-- どうしてもここに空白が入らないので力技 -->
            <div>　</div>
            <div>消したライン数：</div>
            <div id="line-count">0</div>
        </div>
        <div class="button">
            <span class="start-button" id="start-button">
                <button type="button" id="text">START</button>
            </span>
            <span class="stop-button" id="stop-button">
                <button type="button" id="action"> STOP </button>
            </span>
        </div></br>
        <canvas id="canvasId"></canvas>
        <script>
            //---------------------- 定数部 --------------------------
            // フィールドサイズ
            const FIELD_YOKO = 10;
            const FIELD_TATE = 20;

            // フィールドの配列を1次元配列として定義（フィールドを初期化部分で使用）
            let field = [];

            // ブロック一つのサイズ（ピクセル）
            const BLOCK_SIZE = 30;

            // テトロミノのサイズ
            const TETROMINO_SIZE = 4;

            // テトロミノの定義
            const TETROMINO_TYPES = [
                // 空
                [],
                // I
                [
                    [1, 1, 1, 1],
                    [0, 0, 0, 0],
                    [0, 0, 0, 0],
                    [0, 0, 0, 0]
                ],
                // L
                [
                    [0, 1, 0, 0],
                    [0, 1, 0, 0],
                    [0, 1, 1, 0],
                    [0, 0, 0, 0]
                ],
                // J
                [
                    [0, 0, 1, 0],
                    [0, 0, 1, 0],
                    [0, 1, 1, 0],
                    [0, 0, 0, 0]
                ],
                // T
                [
                    [0, 1, 0, 0],
                    [0, 1, 1, 0],
                    [0, 1, 0, 0],
                    [0, 0, 0, 0]
                ],
                // O
                [
                    [0, 1, 1, 0],
                    [0, 1, 1, 0],
                    [0, 0, 0, 0],
                    [0, 0, 0, 0]
                ],
                // Z
                [
                    [1, 1, 0, 0],
                    [0, 1, 1, 0],
                    [0, 0, 0, 0],
                    [0, 0, 0, 0]
                ],
                // S
                [
                    [0, 1, 1, 0],
                    [1, 1, 0, 0],
                    [0, 0, 0, 0],
                    [0, 0, 0, 0]
                ]
            ]

            // テトロミノ本体
            let tetromino;
            // テトロミノの形
            let tetrominoType;
            // テトロミノの形をランダムで決める
            tetrominoType = Math.floor(Math.random() * (TETROMINO_TYPES.length - 1)) + 1;
            tetromino = TETROMINO_TYPES[tetrominoType];

            // テトロミノが落ち始める座標
            const START_X = FIELD_YOKO / 2 - TETROMINO_SIZE / 2;
            const START_Y = 0;

            // テトロミノに座標を持たせる(キーボード押下処理で座標が必要になるため)
            let tetromino_x = START_X;
            let tetromino_y = START_Y;

            // テトロミノの色
            const TETROMINO_COLORS = [
                "",
                "#FF1493",
                "#FF69B4",
                "#FF00FF",
                "#C71585",
                "#FF367F",
                "#EE82EE",
                "#CC0099"
            ]

            // キャンバス用意
            // キャンバスのサイズ = ブロック一つのサイズ × フィールドサイズ
            const CANVAS_SIZE_YOKO = BLOCK_SIZE * FIELD_YOKO;
            const CANVAS_SIZE_TATE = BLOCK_SIZE * FIELD_TATE;
            let canvasId = document.getElementById("canvasId");
            let conText = canvasId.getContext("2d");
            canvasId.width = CANVAS_SIZE_YOKO;
            canvasId.height = CANVAS_SIZE_TATE;
            canvasId.style.border = "4px solid #555";

            // テトロミノが落ちる速度
            const DROP_SPEED = 500;
            let gameOverFlg = false;
            // 消したライン数
            let lineCount = 0;
            // スコア計算結果
            let result = 0;

            // setInterval、clearIntervalで使用
            var interval;
            // ストップボタン実装で使用
            var repeatFlg = true;

            //---------------------- 実行部 --------------------------

            document.getElementById("start-button").onclick = function () {
                // スタートボタン押された時にも念の為初期化を行う
                field = [];
                tetromino_x = START_X;
                tetromino_y = START_Y;

                /*
                2回目以降スタート押した時にスコアが前のスコアのままになってしまうので、
                スタートボタンが押された時点で、初期化したものをhtmlに渡す
                */
                lineCount = 0;
                result = 0;
                document.getElementById('score-count').innerHTML = result;
                document.getElementById('line-count').innerHTML = lineCount;

                if (gameOverFlg) {
                    gameOverFlg = false;
                }

                init();
                onClearInterval();
                onSetInterval();
                drawField();
                drawTetromino();
            }

            // ストップボタン押されたときの処理を行う関数の呼び出し
            document.getElementById("stop-button").onclick = function () {
                onStopButton();
            }

            /*
            setInterval内で呼び出されるdropTetromino内でフィールドを使用するのだが、ページを更新した時には
            まだフィールドに値が詰められていない状態のため、init呼び出す必要ある（initでフィールドにはじめて値入るので）
            */
            init();

            //---------------------- 関数部 --------------------------

            // フィールドを初期化
            function init() {
                for (let y = 0; y < FIELD_TATE; y++) {
                    field[y] = [];
                    for (let x = 0; x < FIELD_YOKO; x++) {
                        field[y][x] = 0;
                    }
                }

                // テスト(試しにただのブロックを置いてみる)
                // field[5][8] = 1;
            }

            // setIntervalを動かす関数
            function onSetInterval() {
                interval = setInterval(dropTetromino, DROP_SPEED);
            }

            // clearIntervalを動かす関数
            function onClearInterval() {
                clearInterval(interval);
            }

            // 一時停止ボタン押下時の処理を行う関数
            function onStopButton() {
                if (repeatFlg) {
                    onClearInterval();
                    document.getElementById('action').innerHTML = 'RESTART';
                    repeatFlg = false;

                } else {
                    // ここにクリア入れないとボタン押下2回目以降速くなってしまう
                    onClearInterval();
                    onSetInterval();
                    document.getElementById('action').innerHTML = ' STOP ';
                    repeatFlg = true;
                }
            }

            // ブロック一つを描画する関数
            function drawBlock(x, y, ransu) {
                // 新しい座標を定義
                let px = x * BLOCK_SIZE;
                let py = y * BLOCK_SIZE;

                if (!ransu == 0) {
                    // 塗りつぶしの四角を描画
                    // 配列TETROMINO_COLORSの[乱数番目]を指定することでテトロミノの形ごとの色が毎回統一される
                    conText.fillStyle = TETROMINO_COLORS[ransu];
                    conText.fillRect(px, py, BLOCK_SIZE, BLOCK_SIZE);
                }

                // 輪郭の四角を描画
                conText.strokeStyle = "black";
                /*
                2回目以降テトロミノの枠線が太くなってしまうのは、
                フィールドの太い線が引き継がれてしまっていたことが原因だったのでlineWidthを指定
                */
                conText.lineWidth = 2;
                conText.strokeRect(px, py, BLOCK_SIZE, BLOCK_SIZE);
            }

            // フィールド（ブロック）を描画する関数
            function drawField() {
                // 描画前に移動前の描画をクリア
                conText.clearRect(0, 0, CANVAS_SIZE_YOKO, CANVAS_SIZE_TATE);
                for (let y = 0; y < FIELD_TATE; y++) {
                    for (let x = 0; x < FIELD_YOKO; x++) {
                        // field[y][x]が1のときにブロックを描画
                        if (field[y][x]) {
                            /*
                            この第3引数にfield[y][x]を入れているのは、
                            テトロミノ固定後に諸々初期化されるが色情報は引き継がれるため、
                            field[y][x]は色情報を引き継いでいるため指定している
                            */
                            drawBlock(x, y, field[y][x]);
                        }
                    }
                }
            }

            // テトロミノを描画する関数
            function drawTetromino() {

                // 着地点の計算
                let plus = 0;
                /*
                Y座標が移動できなくなるところまでplusをカウントアップ（shiftキー実装時と同じ要領）
                plus + 1してるのは一つ下の段を調べたいから
                */
                while (checkMove(0, plus + 1)) plus++;

                // テトロミノの中身をチェック
                // 配列をチェック
                for (let y = 0; y < TETROMINO_SIZE; y++) {
                    for (let x = 0; x < TETROMINO_SIZE; x++) {
                        if (tetromino[y][x]) {
                            // 本体
                            drawBlock(tetromino_x + x, tetromino_y + y, tetrominoType);

                            // 着地点
                            /*
                            （tetromino_x + x, tetromino_y + y）　はテトロミノが表示される座標
                            予測マスには色つける必要がないので、etrominoTypeランダムにせず、固定で空の「0」を渡す
                            このplus分が、現在のテトロミノと表示先のテトロミノのY座標の差になる
                            */
                            drawBlock(tetromino_x + x, tetromino_y + y + plus, 0);
                        }
                    }
                }
            }

            // 移動できるかチェックを行う関数
            /*
            フィールド範囲を超えている場合と、テトロミノがフィールドブロックと同じ位置にあったらfalseを返す
            キーボード押下時の処理で呼び出し時に引数に渡していた移動先の座標をmx、myとして受け取り
            */
            function checkMove(mx, my, newTetromino) {
                /*
                checkMove呼び出し時に渡ってくる引数の値の数が足りないものを絞る（スペースキー以外）
                スペースキー以外ならnewTetrominoに現在のテトロミノを詰める
                */
                if (newTetromino == undefined) newTetromino = tetromino;
                for (let y = 0; y < TETROMINO_SIZE; y++) {
                    for (let x = 0; x < TETROMINO_SIZE; x++) {
                        if (newTetromino[y][x]) {
                            // テトロミノ描画の座標に移動後の座標を足す
                            let nx = mx + tetromino_x + x;
                            let ny = my + tetromino_y + y;
                            if (nx < 0
                                || nx >= FIELD_YOKO
                                || ny >= FIELD_TATE
                                || field[ny][nx]) {
                                return false;
                            }
                        }
                    }
                }

                return true;
            }

            // テトロミノを回転させる関数
            function rotate() {
                let newTetromino = [];
                for (let y = 0; y < TETROMINO_SIZE; y++) {
                    newTetromino[y] = [];
                    for (let x = 0; x < TETROMINO_SIZE; x++) {
                        // 0度回転
                        newTetromino[y][x] = tetromino[TETROMINO_SIZE - x - 1][y];
                    }
                }

                return newTetromino;
            }

            // テトロミノを落下させる関数
            function dropTetromino() {
                // 動けるなら落下
                if (checkMove(0, 1)) {
                    tetromino_y++;

                } else {
                    // 動けないならテトロミノ固定
                    fixTetromino();
                    checkLine();
                    // 固定後ランダムを初期化
                    tetrominoType = Math.floor(Math.random() * (TETROMINO_TYPES.length - 1)) + 1;
                    tetromino = TETROMINO_TYPES[tetrominoType];
                    // 固定後座標も初期化
                    tetromino_x = START_X;
                    tetromino_y = START_Y;

                    // 固定 → ラインチェック → 初期化 → 再び自動で落ちる前に（固定直後に）動けるか確認
                    if (!checkMove(0, 0)) {
                        // 動けなかったらフラグを立てる
                        gameOverFlg = true;
                        drawGameOver();
                        // ゲームオーバー表示後に他処理が走るためここで終了するよう修正
                        return;
                    }
                }

                // 移動後再度描画
                drawField();
                drawTetromino();
            }

            // テトロミノをフィールドに固定する関数
            function fixTetromino() {
                for (let y = 0; y < TETROMINO_SIZE; y++) {
                    for (let x = 0; x < TETROMINO_SIZE; x++) {
                        if (tetromino[y][x]) {
                            /*
                            テスト用にブロック置いた時（field[5][8] = 1;）のように、現在のテトロミノの座標にブロックを置く
                            この関数に入ってきている時点で、現在の座標以上動けないことが確定しているので、とりあえずブロックを置いてしまう
                            テトロミノ色付けに伴いフィールドに固定するときに、1ではなくtetrominoTypeを設定するのは、
                            field[tetromino_y + y][tetromino_x + x] = 1;だとその座標にテスト時と同じくブロック一つを置くだけだが、
                            右辺をtetrominoTypeにすることで、そのテトロミノの形でブロックを固定してくれる
                            */
                            field[tetromino_y + y][tetromino_x + x] = tetrominoType;
                        }
                    }
                }
            }

            // ラインが揃ったかチェックして消す関数
            function checkLine() {
                // チェック部
                // テトロミノではなくただのブロックの座標を調べるのでフィールドのループを流用
                for (let y = 0; y < FIELD_TATE; y++) {
                    let flg = true;
                    for (let x = 0; x < FIELD_YOKO; x++) {
                        // ブロック描画されていないものがあれば（一列揃ってなければ）処理終了
                        if (!field[y][x]) {
                            flg = false;
                            break;
                        }
                    }

                    // 消す部
                    // trueのとき（field[y][x]==1のとき、ラインが揃ったとき）この処理入る
                    if (flg) {
                        lineCount++;

                        // 上のループのyを使用、yはこのif文内では必ず1になるためy>0、下を見るのでy--
                        for (let ny = y; ny > 0; ny--) {
                            for (let nx = 0; nx < FIELD_YOKO; nx++) {
                                /*
                                テスト用のブロック置いたときのコード流用
                                現在の上の行のブロックをコピーしてくることで消せる
                                */
                                field[ny][nx] = field[ny - 1][nx];
                            }
                        }
                    }
                }

                calculateScore(lineCount);
                drawInfo();
            }

            // スコアを計算する関数
            function calculateScore(lineCount) {
                result = lineCount * 100;
            }

            // スコアと消したライン数の表示を行う関数
            function drawInfo() {
                // ここでメソットごと代入するとループ2回まわるので変数で代入
                document.getElementById('score-count').innerHTML = result;
                document.getElementById('line-count').innerHTML = lineCount;
            }

            // ゲームオーバーを表示する
            function drawGameOver() {
                let text = "☺︎GAME OVER☺︎";
                let x = 7;
                let y = CANVAS_SIZE_TATE / 2;
                conText.font = "40px 'Osaka'";
                // 枠線の幅
                conText.lineWidth = 4;
                // 枠線を描画
                conText.strokeText(text, x, y);
                // ゲームオーバーに色をつける
                conText.fillStyle = "white";
                // テキストを描画
                conText.fillText(text, x, y);
            }

            // キーボード押下時の処理
            // document.onkeydownで取得したKeyboardEventを引数eで受け取る
            document.onkeydown = function (e) {
                // ゲームオーバーフラグとリピートフラグが立ってたらキーボード使えなくする
                if (gameOverFlg) return;
                if (!repeatFlg) return;
                switch (e.keyCode) {
                    case 37: // 左
                        // 移動前に移動できるかチェックを追加、引数に移動先の座標を渡す
                        if (checkMove(-1, 0)) tetromino_x--;
                        break;
                    case 39: // 右
                        if (checkMove(1, 0)) tetromino_x++;
                        break;
                    case 40: // 下
                        if (checkMove(0, 1)) tetromino_y++;
                        break;
                    case 32: // スペース
                        let newTetromino = rotate();
                        /*
                        テトロミノを回転させる関数で返される、
                        回転後のテトロミノの座標newTetrominoをtetrominoに設定
                        */
                        if (checkMove(0, 0, newTetromino)) tetromino = newTetromino;
                        break;
                    case 16: // shiftキー
                        // 移動できないと判断されるとこまで下に落ちる
                        while (checkMove(0, 1)) tetromino_y++;
                        break;
                }
                // 移動後再度描画
                drawField();
                drawTetromino();
            }

        </script>
        <p>方向キー: 移動、　shiftキー: 一気に落とす、　スペースキー: 回転</p>
    </div>
</body>

</html>
```

</div></details>


<details><summary>CSS</summary><div>

```css:tetris.css
.main {
    width: 1420px;
    height: 800px;
    text-align: center;
    justify-content: center;
}

.h1 {
    color: white;
}

/* ボタンのサイズを変える時はwidth（幅）、paddingを指定　*/
.button {
    justify-content: center;
    align-items: center;
    color: white;
}

.drawInfo {
    /* 自動で直下の要素を並列にしてくれる */
    display: flex;
    justify-content: center;
    align-items: center;
    color: black;
}

.body {
    justify-content: center;
}

```
</div></details>

完成したものはこのようになります。
![スクリーンショット 2021-08-26 16.00.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1558666/e97975a9-86a4-13b4-4688-44cc189ede38.png)


[ページ上部へ戻る](#目次)

# さいごに
今回はJavaScriptでのテトリス作成を通して学んだことをアウトプットしました。
他にも詰まったことや、理解するのが難しかったところなどもまとめようと思っています。
私の記録が誰かの役に立つと嬉しいです。。