非エンジニアがGASでテトリス作ってみた

この記事は、ラクスパートナーズ AdventCalendar 2024の3日目の記事です。

---
### はじめに
みなさんこんにちは！
エンジニアコーディネート部（EC部）の岩崎です。
初めましての方も多いと思うので簡単に自己紹介させていただきます。

 #### プロフィール
 - 2022年2月:RP入社
   - FRエンジニアの新規営業を1年間
   - 新規営業部MatchingSuccessチームで案件精査〜選定を6ヶ月間
   - 【現在】EC部でメンバーと営業のスケジュール調整やスペックシート添削、その他諸々の課題改善など取り組み中
 - 趣味
   - YouTube鑑賞(東海オンエア,キヨ,平フラあたり)
   - バレーボール(見たりやったり)

---

お察しの通り、私の経歴にエンジニアはないので
今回アドベントカレンダーには<font color= orange >**ノリと勢い**</font>で参加してみました。

### 想定読者
 - ChatGPTを使いこなしたい人
 - GASを触ってみたい人
 - テトリスが好きな人

誰かに有益になるような情報発信したいな〜とか考えていたのですが、せっかくなら私も何か作ってみようと思います。ただ、自分で調べながらというわけではなく**ChatGPTに教えてもらうだけ**なので、ChatGPTやGASを活用するハードルが下がりみなさんの業務効率化に繋がったら幸いです。

### GASでテトリス作ってみる

先日、育成課の方々に少しずつ業務でGASを使えるようになってきたんですと話したら、 **GAS習得バトル(?)** を挑まれました。(私はChatGPTに教えてもらってるだけなんで大変恐縮ですが)
N上原さんがテトリスを作ってみたとおっしゃってたので、私もやってみることにしました。
> GAS とは<font color = green >Google Apps Script </font> のことです。使えるようになったと言いつつも、私がGASでできることはGoogleカレンダーやGmailの情報を自動で取得し、好きな形に一覧化するくらいまでです。
   


:::note info
今回記載するコードは全てChatGPTに作ってもらってます。正確性や善し悪しは目を瞑っていただけると幸いです。
:::

#### START
![IMG_0097.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929501/a46a957b-d317-dc76-92e2-dd5a5e7487fb.jpeg)

一発目の会話なのにテンション合わせて話してくれるの好きです。

##### Step 1: 環境を準備する
1. Google アカウントにログイン
2.	新しいスクリプトプロジェクトを作成
     - Drive の「+ 新規」 → 「その他」 → 「Google Apps Script」をクリック
	 - プロジェクト名を「Tetris」に変更

##### Step 2: 基本の画面を作る
Tetris のブロックを表示するための HTML を作ります。

- HTML ファイルを追加する 
 Apps Script エディタの左上メニューで「+」をクリックし、「HTML ファイル」を選択、名前を「tetris.html」に設定

- 以下のコードを貼り付ける
```html:html
<!DOCTYPE html>
<html>
  <head>
    <title>Tetris</title>
    <style>
      canvas {
        border: 1px solid black;
        display: block;
        margin: auto;
      }
    </style>
  </head>
  <body>
    <canvas id="tetris" width="200" height="400"></canvas>
    <script>
      const canvas = document.getElementById('tetris');
      const context = canvas.getContext('2d');

      // ブロックの1マスサイズを定義
      const blockSize = 20;

      // 仮のブロックを描画
      context.fillStyle = 'blue';
      context.fillRect(0, 0, blockSize, blockSize);
    </script>
  </body>
</html>
  ````
- メインスクリプトを編集する
「Code.gs」に以下のコードを追加

 ```javascript:code.gs
function doGet() {
  return HtmlService.createHtmlOutputFromFile('tetris');
}
```
 - アプリをデプロイする
 
![IMG_0098.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929501/9b708762-4f27-1678-d769-cedb63c37ccf.jpeg)

#### 第1回:デプロイしてみた
ワクワクしながらデプロイボタンを押しました。
それでできたのがコチラ💁

![IMG_0100.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929501/bac4434d-41c8-14ca-7208-0095276d9325.jpeg)

なんじゃこれと思ってChatGPTをよく見てみたら
こっからゲームロジックを追加するらしかった。


![IMG_0101.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929501/8de30942-f38d-5b99-b203-5518c6fb3c0e.jpeg)

> 全部やってくれないのかい！と危うく思ってしまいそうでした。私から丁寧に分かりやすくとか言ったのに()
先走ってごめんねGPT


どこをどう修正すれば良いか分からなくなってしまったので、まとめてオーダーしてみます。

![IMG_0102.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929501/09f2b466-1b29-91b0-81b6-e304d33df056.jpeg)

> 相変わらずの優しさに心が洗われる。私も仕事を振ってもらった時はこういう精神で業務を行いたいなと思います。
- HTMLの修正

```html:html
<!DOCTYPE html>
<html>
  <head>
    <title>TETLIS</title>
    <style>
      body {
        background-color: #f5f5dc; /* 薄いベージュ */
        font-family: Arial, sans-serif;
        text-align: center;
        margin: 0;
        padding: 0;
      }

      h1 {
        font-size: 3rem;
        color: #ff4500; /* 鮮やかなオレンジ */
        text-shadow: 2px 2px 5px #000; /* ド派手に目立たせる */
        margin: 20px 0;
      }

      #container {
        display: flex;
        flex-direction: column;
        align-items: center;
      }

      #gameArea {
        background-color: #333; /* 濃いグレー */
        position: relative;
        margin: 10px auto;
        border: 5px solid #555;
      }

      canvas {
        display: block;
      }

      #score {
        color: #222;
        font-size: 1.5rem;
        margin: 10px 0;
      }

      #instructions {
        margin-top: 20px;
        font-size: 1rem;
        color: #444;
      }

      #instructions span {
        display: block;
        margin: 5px 0;
      }
    </style>
  </head>
  <body>
    <div id="container">
      <h1>TETLIS</h1>
      <canvas id="tetris" width="200" height="400"></canvas>
      <div id="score">Score: 0</div>
      <div id="instructions">
        <span>⬅️ 左矢印：左に移動</span>
        <span>➡️ 右矢印：右に移動</span>
        <span>⬇️ 下矢印：下に加速</span>
        <span>⏺️ 上矢印：回転</span>
      </div>
    </div>
    <script>
      // ゲームロジックをここに追加します (後述)
    </script>
  </body>
</html>
```

 - ゲームロジックの追加
 ```javascript:ひたすらにコピペしたゲームロジック

//ブロックを管理
 const tetrominoes = [
  { shape: [[1, 1, 1], [0, 1, 0]], color: 'red' }, // T字
  { shape: [[1, 1], [1, 1]], color: 'yellow' }, // 四角形
  { shape: [[1, 1, 0], [0, 1, 1]], color: 'green' }, // Z字
  { shape: [[1], [1], [1], [1]], color: 'cyan' }, // 棒
  { shape: [[1, 1, 1, 1, 1]], color: 'purple' }, // オリジナル：長い棒
];

let currentBlock = {
  shape: tetrominoes[0].shape,
  color: tetrominoes[0].color,
  x: 4,
  y: 0,
};

// 背景を描画
function drawGrid() {
  context.fillStyle = '#333'; // 背景
  context.fillRect(0, 0, canvas.width, canvas.height);

  /// マス目
  context.strokeStyle = '#555';
  for (let y = 0; y < rows; y++) {
    for (let x = 0; x < cols; x++) {
      context.strokeRect(x * blockSize, y * blockSize, blockSize, blockSize);
    }
  }
}

//スコア処理
let score = 0;

function updateScore() {
  document.getElementById('score').textContent = `Score: ${score}`;
}


//キー操作を追加
document.addEventListener('keydown', (event) => {
  if (event.key === 'ArrowLeft') {
    currentBlock.x--;
  } else if (event.key === 'ArrowRight') {
    currentBlock.x++;
  } else if (event.key === 'ArrowDown') {
    currentBlock.y++;
  } else if (event.key === 'ArrowUp') {
    /// 回転ロジック（後で追加）
  }
  drawGame();
});


//ブロックを描画
function drawBlock() {
  context.fillStyle = currentBlock.color;
  currentBlock.shape.forEach((row, dy) => {
    row.forEach((value, dx) => {
      if (value !== 0) {
        context.fillRect(
          (currentBlock.x + dx) * blockSize,
          (currentBlock.y + dy) * blockSize,
          blockSize,
          blockSize
        );
      }
    });
  });
}
```

> なぜかここからTetris→TETLISになってました。（笑)ニセモノということでお願いしますw

#### 第2回:デプロイしてみた


![IMG_0103.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929501/634b6ea3-fae8-368d-b327-6bba905f64cb.jpeg)

**・・・ゲーム画面がない:frowning2:**
（あと私の好きなスヌーピーを背景に入れて欲しいっていうオーダーはスルーされた）

![IMG_0105.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929501/54550545-c836-de88-35ac-529714e7fe48.jpeg)

> せっかちが出てしまい完成を急ぐ私に対して、ChatGPTはいつでもポジティブで本当尊敬してます。

原因として考えられるポイントは以下らしいです。

:::::note info
1.	canvas の描画ロジックエラー
	•	drawGame() や drawGrid() が正しく呼ばれていない可能性。
	•	canvas がHTMLに正しく定義されていない可能性。
2.	JavaScriptのエラー
	•	ブロック位置やサイズに誤りがある場合、描画されないことがあります。
	•	グリッドやブロックの配列の範囲外を参照している可能性。
::::

1.canvas 要素の設定
最初の段階では'<canvas>タグのサイズや背景色の設定が不足していました。<canvas>を正しく表示するために、widthとheightを設定することが重要です。また、canvasが正しく描画されるようにJavaScript側で描画処理を行う必要があります。

    #gameArea {
    background-color: #333; /* ゲーム背景 */
    position: relative;
    margin: 10px auto;
    border: 5px solid #555;
    }


 2.JavaScript: 描画処理の修正
描画処理が正しく行われるように、JavaScriptでcanvasに描画する際の処理を見直しました。最初は、描画が行われるタイミングや、`<canvas>`が更新されるロジックが欠けていました。
draw関数を使って、`<canvas>`にブロックを描画し、ゲームが動作しているようにしました。draw関数がブロックを正しい位置に描画するように、currentPieceの位置を更新して描画する処理を追加しました。

``` Javascript:描画処理の修正
// ブロックを描画
function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height); // 画面をクリア
  currentPiece.shape.forEach((row, y) => {
    row.forEach((cell, x) => {
      if (cell) {
        ctx.fillRect((currentPiece.x + x) * grid, (currentPiece.y + y) * grid, grid, grid); // マス目に描画
      }
    });
  });

  // スコアの表示
  document.getElementById("score").textContent = `Score: ${score}`;
}
```
3.ついでにゲームループの追加をしてほぼ完成にする

```javascript:ゲームループを追加する

// ゲームループ
function gameLoop() {
  currentPiece.y++; // ピースを下に移動
  if (currentPiece.y + currentPiece.shape.length > 20) {
    // 画面上端に達したらゲームオーバー
    alert("Game Over");
    location.reload(); // ゲームリセット
  }
  draw(); // 描画を更新
  setTimeout(gameLoop, 500); // 速度調整
}

// ゲーム開始
gameLoop();
```

#### 第3回:デプロイしてみた
![IMG_0107.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929501/74d707a3-fc21-3ec3-e1d6-5f287332926b.jpeg)

とりあえず形にはなった！のですが、
矢印キーを押すと、ページも動いちゃったので再度修正をします。

:::note info
 現在のコードでは、矢印キーを押すことでブロックを移動させていますが、ページがスクロールしてしまう問題を解決するには、keydownイベントリスナー内でevent.preventDefault()を追加するだけです。
:::

`window.addEventListener("keydown", (event) => {
  event.preventDefault(); // ページのスクロールを防ぐ` 

矢印キーでスクロールしちゃう現象がなおったので、ロジック側はもういっかなと。ここからは自分なりに画面側を改造してみようと思います。

**色々いじってみた結果。**
![IMG_0112.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929501/faff5f43-effd-b424-636e-25622114965e.jpeg)

体裁崩れまくりました。涙
なんとなくのHTMLでは無理だった。

そして<font color = blue>ブロックが一向に落ちてこない・・・笑</font>
完全にゲームのロジックがおかしくなったよう、、、


ChatGPTと<font color =red>n回</font>ほどやりとりをして<font color =red>n回</font>やり直したけどなおらず・・・

#### 最終形態

結局、ほとんどの修正を諦めて
フォントサイズとカラー、
あとは特殊なブロックを入れてみるだけになっちゃいました^^;


![IMG_0111.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3929501/31206e9f-a380-145a-e692-ee5e23bb1166.jpeg)

今回はここまでにします。
約5時間の対戦でした。ありがとうございました。

### 感想

多分、有益な情報はほぼなかったと思いますが
ここまで読んでいただきありがとうございました。
今回取り組んでみて<font color = orange>**エンジニアってやっぱりすごいな**</font>と改めて思いました。
私がChatGPTに書いてもらったコード自力で書けちゃうんですよね。コンピューターと会話してるんですもんね。


少しずつ分解していって、ChatGPTが教えてくれた内容をちゃんと理解できるようになりたいです。

あと、久々にこんな短時間で 「**何でこうなるんだろう**」とか「**分からない！**」「**難しい！**」みたいな<font color = green>**感情**</font>と出会いました。

RPに入って約3年、多分他の人よりも色々な仕事をしてきましたが、慣れも出てきて壁にぶつかることが少なくなってきているなと気付かされました。

まだまだ成長したいのでこれからも色々なことにチャレンジしていくぞ〜！


### 最後に

本当は実務にも使えることを勉強してアウトプットしようかなと思っていました:sweat_smile:今回は勉強が間にあわずテトリスを作っちゃいましたが、実は**LookerStudio**を使えるようになるのが次の目標です！絶賛躓きまくり中です。有識者助けてください。勉強の過程をいつかきっと<font color = bladorange >**Dash**</font>に書きます！