canvasとJSで、目に優しいオリジナルテトリス



こんにちは、アドベントカレンダー初参加です。
canvasとJSを使って、テトリスが作れそうだったので挑戦してみました:baby_tone2:

<p class="codepen" data-height="735" data-theme-id="dark" data-default-tab="result" data-user="yuzukisakioka" data-slug-hash="GRjJzjE" style="height: 735px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="GRjJzjE">
  <span>See the Pen <a href="https://codepen.io/yuzukisakioka/pen/GRjJzjE">
  GRjJzjE</a> by Yuzuki (<a href="https://codepen.io/yuzukisakioka">@yuzukisakioka</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>


## 手順

htmlを準備します。 
bodyにcanvasを設置。
```<canvas id="tetris"></canvas>
```

JSを記述していきます（ざっくり流れだけ）

テトリスのブロック1個分をまず作り、
配列でテトリスのブロック4つを色々した形（テトロミノというらしいです）を作っていきます。

```
//ブロック1つ（四角いっこ）を描画

const BLOCK_SIZE = 30;

function drawBlock(x,y,c){
  let px = x * BLOCK_SIZE;
  let py = y * BLOCK_SIZE;
  con.fillStyle=TETRO_COLORS[c];
  con.fillRect(px, py, BLOCK_SIZE, BLOCK_SIZE);
  con.strokeStyle="#323324";
  con.strokeRect(px, py, BLOCK_SIZE, BLOCK_SIZE);
}
```

こんな風に配列でテトロミノを作ります。
↓はL型の配列（マス目に見立てて、1のところがブロック入る感じ）
これを何種類か作り、ランダムで出します。

```
//テトロミノ形
const TETRO_TYPES = [
  [//L
    [0,1,0,0],
    [0,1,0,0],
    [0,1,1,0],
    [0,0,0,0]
  ],
```

キーボードの操作を指定します。

```
//キーボードアクション
document.onkeydown = function(e){
  if(over)return;
  switch(e.keyCode){
    case 37:// 左
      if(checkMove(-1, 0))tetro_x--;
      break;
    case 38:// 上
      // if(checkMove(0, -1))tetro_y--; //上への移動はさせない
      break;
    case 39:// 右
      if(checkMove(1, 0))tetro_x++;
      break;
    case 40:// 下
      while(checkMove(0, 1))tetro_y++;
      break;
    case 32:// スペース
      let ntetro = rotate();
      if(checkMove(0, 0, ntetro)) tetro = ntetro;
      break;
  }
}
```

ゲームフィールドサイズや、スクリーンサイズを指定。
ゲームフィールドの中でだけブロックが動くようにしたりします。

```
//フィールドサイズ
const FIELD_COL = 10;
const FIELD_ROW = 20;

//スクリーンサイズ
const SCREEN_W =BLOCK_SIZE * FIELD_COL + 300;
const SCREEN_H =BLOCK_SIZE * FIELD_ROW
```

テトロミノを描画する処理を書きます。
ホンモノみたいに、着地する箇所にうっすらブロックを表示させたり、次にくるテトロミノを表示させたりします。

```
//描画する
function drawAll(){
  //フィールドの枠
  con.fillStyle="rgba(255,217,204)";
  con.fillRect(0,0,SCREEN_W,SCREEN_H);
  for(let y=0; y<FIELD_ROW ; y++ ){
    for(let x=0; x<FIELD_COL ; x++){
      if(field[y][x]){
        drawBlock(x,y,1);
      }
      else{
      }
    }
  }
  //着地点を計算
  let plus=0;
  while(checkMove(0,plus+1))plus++;

  //テトロミノ描画
  for(let y=0; y<TETRO_SIZE ; y++ ){
    for(let x=0; x<TETRO_SIZE ; x++){
      //テトロ本体
      if(tetro[y][x]){
        //着地点
        drawBlock(tetro_x+x, tetro_y+y+plus, 0);
        //本体
        drawBlock(tetro_x+x, tetro_y+y, 1);
      }
      //ネクストテトロ
      if(TETRO_TYPES[tetro_n][y][x]){
        drawBlock(13+x, 4+y, 1);
      }
    }
  }
}
```

回転する処理を入れます。
キーボードアクションで、スペースキーで回転するように指定してます。

```
//回転
function rotate(){
  let ntetro = [];
  for(let y=0; y<TETRO_SIZE ; y++ ){
    ntetro[y]=[];
    for(let x=0; x<TETRO_SIZE ; x++){
      ntetro[y][x] = tetro[TETRO_SIZE-x-1][y];
    }
  }
  return ntetro;
}
```

ライン揃ったら消す処理。

```
//ライン揃ったら消す
function checkLine(){
  let linec=0;
  for(let y=0; y<FIELD_ROW ; y++ ){
    let flag=true;
    for(let x=0; x<FIELD_COL ; x++){
      if(!field[y][x]){
        flag=false;
        break;
      }
    }
    if(flag){
      linec++;
      for(let ny = y; ny>0 ;ny-- ){
        for(let nx=0;nx<FIELD_COL ; nx++){
          field[ny][nx] = field[ny-1][nx];
        }
      }
    }
  }
  if(linec){
    lines += linec;
    score+=100*(2**(linec-1));
    if(speed<GAME_SPEED-10)speed+=10;
  }
}
```

テトロミノが落ちてくる処理をいれます。
setIntervalで呼び出し。

```
//落ちる処理
function dropTetro(){
  if(over)return;
  if(checkMove(0, 1))tetro_y++;
  else{
    fixTetro();
    checkLine();
    setTetro();
    if(!checkMove(0,0))over=true;
  }
  drawAll();
}

setInterval(dropTetro, GAME_SPEED);
```

スコアなどを出す処理です。

```
//インフォメーション表示
function drawInfo(){
  let w;
  con.fillStyle="#e65757";
  con.font = "20px Arial"

  let s="NEXT";
  con.fillText(s,410,100);

  s="SCORE";
  w=con.measureText(s).width;
  con.fillText(s,410,300);
  s=""+score;
  w=con.measureText(s).width;
  con.fillText(s,560-w,340);

  s="LINES";
  w=con.measureText(s).width;
  con.fillText(s,410,400);
  s=""+lines;
  w=con.measureText(s).width;
  con.fillText(s,560-w,440);

  if(over){
    s = "GAME OVER♡";
    con.font = "60px Arial bold"
    let x = SCREEN_W/6;
    let y = SCREEN_H/2;
    con.fillStyle = "#e65757";
    con.fillText(s,tetro_x+x,y);
  }
}
```

## おわりに

こんな流れで作成できました、詳細はソースをご覧ください！

もはやテトロミノ（4つのブロック）ではありませんが...
自分でもライン1つ消せません:santa_tone2:


参考：あきちょんさんのプログラミング講座動画を参考に作成いたしました！
https://www.youtube.com/watch?v=LJlKaTwtSdI

[:christmas_tree: FORK Advent Calendar 2020](/advent-calendar/2020/fork)
:arrow_left: 6日目 [面倒なカレンダー登録はGASにやらせる](/agoKnife/items/338de5127e5b2b73bd53) @agoKnife
:arrow_right: 8日目 [AWS CDKでAWS SAMのテンプレートを出力してデプロイする](/izanari/items/05f601f12ff5b0f32fd7) @izanari