PR

5日目 スマホでの操作と画面固定【タッチイベントの座標を取得】

テトリスを作ろう

はじめに

スマホだけを使って、一週間でテトリスを作っていきます。

1日目 キャンバスの設定と描写
2日目 フィールドの設定と描写
3日目 テトリミノの設定と描写
4日目 テトリミノの落下と回転
▶︎5日目 スマホでの操作と画面固定
6日目 テトリミノの当たり判定
7日目 ラインを消す処理

パソコンがなくても、スマホだけでwebアプリケーションの開発ができます。

プログラミングに興味がある人やプログラミングの勉強を始めたばかりの人に向けて、一からわかりやすく解説していきます。

5日目でやること

前回は、テトリミノ(テトリスのブロック)の落下処理と、画面をタップしたときにテトリミノが回転するところまでやりました。

今回は、スワイプしたときのテトリミノの左右移動と、スワイプしても画面が動かない(画面を固定する)ところまでをやっていきます。

5日目 スマホでの操作と画面固定

全体のソースコード

まずは完成したソースコードをご覧ください。

HTMLのコード

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="style.css">
  <title>テトリス</title>
</head>

<body>
  <div class="main">
    <canvas id="canvas01"></canvas>
  </div>
  <script src="script.js"></script>
</body>

</html>

CSSのコード

html {
  height: 100%;
}

body {
  height: 100%;
  margin: 0;
}

.main {
  height: 100%;
  text-align: center;
}

#canvas01 {
  height: calc(100% - 8px);
}

JavaScriptのコード

// ============ 変数 ============

// フィールドサイズ
const FIELD_X = 10   // 横のマスの数
const FIELD_Y = 20   // 縦のマスの数
const MINO_SIZE = 30 // マスの大きさ

// テトリミノが落ちる速度
const DROP_SPEED = 500

// キャンバスの設定
let canvas = document.getElementById("canvas01")
let conText = canvas.getContext("2d")
canvas.width = MINO_SIZE * FIELD_X
canvas.height = MINO_SIZE * FIELD_Y
canvas.style.border = "4px solid #050505"

// 色の設定
const COLOR = [
  "#F2F2F2",   // フィールド背景
  "#C8C8C8",   // 枠の線
  "#00F2F2",   // Iミノ 水色
  "#F2F200",   // Oミノ 黄色
  "#00F200",   // Sミノ 黄緑
  "#F20000",   // Zミノ 赤
  "#0000F2",   // Jミノ 青
  "#F2A200",   // Lミノ オレンジ
  "#F200F2"    // Tミノ 紫
]

// フィールドの初期設定
let field = []
for (let i = 0; i < FIELD_Y; i++) {
  field[i] = []
  for (let j = 0; j < FIELD_X; j++) {
    field[i][j] = 0
  }
}

// テトリミノの初期設定
let mino_x = Math.ceil((FIELD_X / 2) - 2)
let mino_y = 0

let mino_type = Math.trunc(Math.random() * 7)
let mino_angle = 0

let minoshapes = [
// mino_type_I
  // mino_angle_0
  [[
    [0,2,0,0],
    [0,2,0,0],
    [0,2,0,0],
    [0,2,0,0]
  ],
  // mino_angle_90
  [
    [0,0,0,0],
    [0,0,0,0],
    [2,2,2,2],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,2,0,0],
    [0,2,0,0],
    [0,2,0,0],
    [0,2,0,0]
  ],
  // mino_angle_270
  [
    [0,0,0,0],
    [0,0,0,0],
    [2,2,2,2],
    [0,0,0,0]
  ]],
// mino_type_O
  // mino_angle_0
  [[
    [0,3,3,0],
    [0,3,3,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,3,3,0],
    [0,3,3,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,3,3,0],
    [0,3,3,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [0,3,3,0],
    [0,3,3,0],
    [0,0,0,0],
    [0,0,0,0]
  ]],
// mino_type_S
  // mino_angle_0
  [[
    [0,4,4,0],
    [4,4,0,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,4,0,0],
    [0,4,4,0],
    [0,0,4,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,4,4,0],
    [4,4,0,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [0,4,0,0],
    [0,4,4,0],
    [0,0,4,0],
    [0,0,0,0]
  ]],
// mino_type_Z
  // mino_angle_0
  [[
    [5,5,0,0],
    [0,5,5,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,0,5,0],
    [0,5,5,0],
    [0,5,0,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [5,5,0,0],
    [0,5,5,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [0,0,5,0],
    [0,5,5,0],
    [0,5,0,0],
    [0,0,0,0]
  ]],
// mino_type_J
  // mino_angle_0
  [[
    [0,0,6,0],
    [0,0,6,0],
    [0,6,6,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,6,0,0],
    [0,6,6,6],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,6,6,0],
    [0,6,0,0],
    [0,6,0,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [6,6,6,0],
    [0,0,6,0],
    [0,0,0,0],
    [0,0,0,0]
  ]],
// mino_type_L
  // mino_angle_0
  [[
    [0,7,0,0],
    [0,7,0,0],
    [0,7,7,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,7,7,7],
    [0,7,0,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,7,7,0],
    [0,0,7,0],
    [0,0,7,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [0,0,7,0],
    [7,7,7,0],
    [0,0,0,0],
    [0,0,0,0]
  ]],
// mino_type_T
  // mino_angle_0
  [[
    [8,8,8,0],
    [0,8,0,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,8,0,0],
    [8,8,0,0],
    [0,8,0,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,8,0,0],
    [8,8,8,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [0,8,0,0],
    [0,8,8,0],
    [0,8,0,0],
    [0,0,0,0]
  ]]
]



// ============ 関数 ============

// フィールドの表示
function display() {
  for (let i = 0; i < FIELD_Y; i++) {
    for (let j =0; j < FIELD_X; j++) {
      // 塗りつぶしの四角を描画
      conText.fillStyle = COLOR[field[i][j]]
      conText.fillRect(MINO_SIZE * j, MINO_SIZE * i, MINO_SIZE, MINO_SIZE)

      // 輪郭の四角を描画
      conText.strokeStyle = COLOR[1]
      conText.lineWidth = 1
      conText.strokeRect(MINO_SIZE * j, MINO_SIZE * i, MINO_SIZE, MINO_SIZE)
    }
  }
}

// テトリミノの表示
function drawmino() {
  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 4; j++) {
      if (minoshapes[mino_type][mino_angle][i][j] > 1) {
        // 新しい座標を定義
        let px = MINO_SIZE * (mino_x + j)
        let py = MINO_SIZE * (mino_y + i)

        // 塗りつぶしの四角を描画
        conText.fillStyle = COLOR[minoshapes[mino_type][mino_angle][i][j]]
        conText.fillRect(px, py, MINO_SIZE, MINO_SIZE)

        // 輪郭の四角を描画
        conText.strokeStyle = COLOR[1]
        conText.lineWidth = 1
        conText.strokeRect(px, py, MINO_SIZE, MINO_SIZE)
      }
    }
  }
}

// テトリミノの落下
function dropmino() {
  // 動けるなら落下
  mino_y += 1
  display()
  drawmino()
}



// ========= スマホ操作 ==========

// スワイプ開始時の座標
let startX
let startY

// スワイプ終了時の座標
let endX
let endY

canvas.addEventListener("touchstart", SwipeStart)
canvas.addEventListener("touchmove", SwipeMove)
canvas.addEventListener("touchend", SwipeEnd)

function SwipeStart(event) {
  // スワイプ開始時の座標
  startX = event.touches[0].pageX
  startY = event.touches[0].pageY

  // スワイプ終了時の座標
  endX = 0
  endY = 0
}

function SwipeMove(event) {
  // 画面をスクロールしないようにする(画面固定)
  event.preventDefault()

  endX = event.changedTouches[0].pageX
  endY = event.changedTouches[0].pageY

  if (endX - startX < -80) {
    startX -= 50

    // 動けるなら左移動
    mino_x -= 1
    display()
    drawmino()
  }

  if (endX - startX > 80) {
    startX += 50

    // 動けるなら右移動
    mino_x += 1
    display()
    drawmino()
  }

  if (endY - startY > 160) {
    // 動けるなら下移動
    mino_y += 1
    display()
    drawmino()
  }
}

function SwipeEnd(event) {
  if (endX == 0) {
    // 動けるなら回転
    mino_angle = (mino_angle + 1) % 4
    display()
    drawmino()
  }
}



// ============ 実行 ============

// 初期画面の表示
display()
drawmino()
interval = setInterval(dropmino, DROP_SPEED)

JavaScriptの解説

今回変わったのは、// ========= スマホ操作 ========== のところです。

// ========= スマホ操作 ==========

// スワイプ開始時の座標
let startX
let startY

// スワイプ終了時の座標
let endX
let endY

canvas.addEventListener("touchstart", SwipeStart)
canvas.addEventListener("touchmove", SwipeMove)
canvas.addEventListener("touchend", SwipeEnd)

function SwipeStart(event) {
  // スワイプ開始時の座標
  startX = event.touches[0].pageX
  startY = event.touches[0].pageY

  // スワイプ終了時の座標
  endX = 0
  endY = 0
}

function SwipeMove(event) {
  // 画面をスクロールしないようにする(画面固定)
  event.preventDefault()

  endX = event.changedTouches[0].pageX
  endY = event.changedTouches[0].pageY

  if (endX - startX < -80) {
    startX -= 50

    // 動けるなら左移動
    mino_x -= 1
    display()
    drawmino()
  }

  if (endX - startX > 80) {
    startX += 50

    // 動けるなら右移動
    mino_x += 1
    display()
    drawmino()
  }

  if (endY - startY > 160) {
    // 動けるなら下移動
    mino_y += 1
    display()
    drawmino()
  }
}

function SwipeEnd(event) {
  if (endX == 0) {
    // 動けるなら回転
    mino_angle = (mino_angle + 1) % 4
    display()
    drawmino()
  }
}

変数の宣言

// スワイプ開始時の座標
let startX
let startY

// スワイプ終了時の座標
let endX
let endY

let を使って、変数の値を指定せずに、変数の名前だけを宣言することができます。

短いプログラムなら問題ないですが、長く複雑なプログラムになると意図せず変数名がかぶりエラーに繋がる可能性があります。(変数に同じ名前を付けて別の目的で利用しようとすると、元の変数の値も変わってしまい不具合を起こす。)

値が変わる変数の場合、エラーを出さないためにも同じ名前の変数を作ること(再宣言)ができない「let」で変数を宣言するのがいいです。

特定の操作で関数を呼び出す「addEventListener」

canvas.addEventListener("touchstart", SwipeStart)
canvas.addEventListener("touchmove", SwipeMove)
canvas.addEventListener("touchend", SwipeEnd)

対象の要素.addEventListener(特定の操作, 呼び出される関数名)

対象の要素に特定の操作が行われた時に、呼び出される関数を設定します。

よく使う特定の操作

タッチイベント
“touchstart”:タップしたとき(画面に指が触れたとき)
“touchmove”:スワイプしたとき(触れている指が動いたとき)
“touchend”:タップが終わったとき(触れている指が離れたとき)

マウスイベント
“click”:マウスボタンをクリックしたとき
“mousedown”:マウスボタンを押したとき
“mousemove”:マウスカーソルが移動したとき
“mouseup”:マウスボタンを離したとき

canvas.addEventListener(“touchstart”, SwipeStart)

キャンバスの要素上でタップした時に、関数SwipeStartを実行します。

canvas.addEventListener(“touchmove”, SwipeMove)

キャンバスの要素上でスワイプした時に、関数SwipeMoveを実行します。

canvas.addEventListener(“touchend”, SwipeEnd)

キャンバスの要素上でタップが終わった時に、関数SwipeEndを実行します。

タップしたとき(画面に指が触れたとき)の処理

function SwipeStart(event) {
  // スワイプ開始時の座標
  startX = event.touches[0].pageX
  startY = event.touches[0].pageY

  // スワイプ終了時の座標
  endX = 0
  endY = 0
}

変数startX と変数startY に、タップした点の横座標と縦座標を指定します。

変数endX と変数endY には、0 を指定します。

function SwipeStart(event) { .. }

function 関数名(変数名) { .. }

関数名をSwipeStartに変数名をeventにして、{ } 内に実行する処理を定義します。

event引数には、イベントに関する情報が含まれています。この情報を使用して、タップした座標を取得したりできるようになります。

event.touches[0].pageX|event.touches[0].pageY

event.touches には、現在画面に接触しているタッチ点すべての情報が含まれます。例えば、2本の指が画面に触れているときは2つの点の情報を持っています。

event.touches[0] とすることで、画面にタップした1つの点の情報を取得します。

event.touches[0].pageX は、画面にタップした点の画面左上からの x座標(左からの距離)を取得します。

event.touches[0].pageY は、画面にタップした点の画面左上からの y座標(上からの距離)を取得します。

スワイプしたとき(触れている指が動いたとき)の処理

function SwipeMove(event) {
  :
  :

  endX = event.changedTouches[0].pageX
  endY = event.changedTouches[0].pageY

  if (endX - startX < -80) {
    startX -= 50

    // 動けるなら左移動
    mino_x -= 1
    display()
    drawmino()
  }

  :
  :
}

変数endX と変数endY に、移動した接触点の横座標と縦座標を指定します。

タップした時の座標と移動した時の座標を比べて、テトリミノを移動する処理を行います。

function SwipeMove(event) { .. }

function 関数名(変数名) { .. }

関数名をSwipeMoveに変数名をeventにして、{ } 内に実行する処理を定義します。

event引数には、イベントに関する情報が含まれています。この情報を使用して、タップした座標を取得したりできるようになります。

event.changedTouches[0].pageX|event.changedTouches[0].pageY

event.changedTouches には、現在画面に接触しているタッチ点すべての情報が含まれます。例えば、2本の指が画面に触れているときは2つの点の情報を持っています。

event.changedTouches[0] とすることで、1つの点が移動した情報を取得します。

event.changedTouches[0].pageX は、移動した点の画面左上からの x座標(左からの距離)を取得します。

event.changedTouches[0].pageY は、移動した点の画面左上からの y座標(上からの距離)を取得します。

テトリミノを移動する

if (endX – startX < -80) { .. }

テトリミノを左へ移動する

  if (endX - startX < -80) {
    startX -= 50

    // 動けるなら左移動
    mino_x -= 1
    display()
    drawmino()
  }

最初にタップした点の横座標 startX より、移動した点の横座標 endX-80 より小さければ、テトリミノの横座標 mino_x を -1 します。

-80 の数値を変更することで移動する感度が変更できます。例えば、-80 を -160 に変更すると大きくスワイプしないと移動しなくなります。

startX -= 50

「startX -= 50」は、「startX = startX – 50」と同じです。

最初にタップした点の横座標 startX の値がそのままだとずっと左移動してしまうので、横座標を -50 することで移動し続けなくします。

また、80 より小さくすることで、大きくスワイプした時にはテトリミノは複数マス移動します。

-50 の数値を変更することで複数マス移動する感度が変更できます。例えば、-50 を -80 に変更すると大きくスワイプしないと複数マスは移動しなくなります。

mino_x -= 1

テトリミノを描画する x座標「mino_x」から1を引きます。

つまり、テトリミノが1つ左のマスに移動することになります。

「mino_x -= 1」は、「mino_x = mino_x – 1」と同じです。

また、「mino_x–」や「–mino_x」も同じく変数の値から1を減らす演算となります。

mino_x = mino_x - 1
mino_x -= 1
mino_x--
--mino_x

変数の値から 1を減算するだけなら、結果はすべて同じです。

display()

フィールドを表示する関数を呼び出します。

drawmino()

テトリミノを表示する関数を呼び出します。

新しいフィールドを描画することで元のテトリミノが消えて、新しい位置にテトリミノを描画します。

そうすることで、テトリミノが左に移動しているように見えます。

if (endX – startX > 80) { .. }

テトリミノを右へ移動する

  if (endX - startX > 80) {
    startX += 50

    // 動けるなら右移動
    mino_x += 1
    display()
    drawmino()
  }

左へ移動する場合と同様です。

最初にタップした点の横座標 startX より、移動した点の横座標 endX が 80 より大きければ、テトリミノの横座標 mino_x を +1 します。

if (endY – startY > 160) { .. }

テトリミノを落下させる

  if (endY - startY > 160) {
    // 動けるなら下移動
    mino_y += 1
    display()
    drawmino()
  }

左右へ移動する場合と同様です。ただし、startY の値をそのままにしているので、触れている指が離れない限りずっと下移動を続けます。

最初にタップした点の縦座標 startY より、移動した点の縦座標 endY が 160 より大きければ、テトリミノの縦座標 mino_y を +1 します。

タップが終わったとき(触れている指が離れたとき)の処理

function SwipeEnd(event) {
  if (endX == 0) {
    // 動けるなら回転
    mino_angle = (mino_angle + 1) % 4
    display()
    drawmino()
  }
}

スワイプしていなければ(タップしたら)テトリミノを回転させます。

function SwipeEnd() { .. }

関数名を SwipeEnd にして、{ } 内に実行する処理を定義します。SwipeEnd() で関数を呼び出すことができます。

if (endX == 0) { .. }

触れている指を離した時に、移動した点の横座標 endX が 0 のままならスワイプしていない(触れていた指が動いていない)ということなので、テトリミノを回転させます。

mino_angle = (mino_angle + 1) % 4

テトリミノの回転した形の変数mino_angleの値を 0 → 1 → 2 → 3 → 0 →・・と変更します。

mino_angleの値に 1を足して、4になったら 0に戻します。

「 % 」は、割り算の余りを求める計算式です。

0 % 4 = 0
1 % 4 = 1
2 % 4 = 2
3 % 4 = 3
4 % 4 = 0
5 % 4 = 1
  :
  :
display()

フィールドを表示する関数を呼び出します。

drawmino()

テトリミノを表示する関数を呼び出します。

新しいフィールドを描画することで元のテトリミノが消えて、回転したテトリミノを新たに描画します。

そうすることで、テトリミノが回転しているように見えます。

画面をスクロールしないようにする(画面固定)

スワイプしたとき(触れている指が動いたとき)に、画面が一緒に動かないようにする

function SwipeMove(event) {
  // 画面をスクロールしないようにする(画面固定)
  event.preventDefault()
  :
  :
}

event.preventDefault()

.preventDefault は、イベント発生時のデフォルトの動作を抑止します。

今回の場合は、
・イベント発生時 = スワイプした時
・デフォルトの動作 = 画面をスクロールする
となります。

つまり、スワイプしたとき(触れている指が動いたとき)に、画面がスクロールしないようにします。

最後にJavaScript全体のソースコードを載せておきます。

// ============ 変数 ============

// フィールドサイズ
const FIELD_X = 10   // 横のマスの数
const FIELD_Y = 20   // 縦のマスの数
const MINO_SIZE = 30 // マスの大きさ

// テトリミノが落ちる速度
const DROP_SPEED = 500

// キャンバスの設定
let canvas = document.getElementById("canvas01")
let conText = canvas.getContext("2d")
canvas.width = MINO_SIZE * FIELD_X
canvas.height = MINO_SIZE * FIELD_Y
canvas.style.border = "4px solid #050505"

// 色の設定
const COLOR = [
  "#F2F2F2",   // フィールド背景
  "#C8C8C8",   // 枠の線
  "#00F2F2",   // Iミノ 水色
  "#F2F200",   // Oミノ 黄色
  "#00F200",   // Sミノ 黄緑
  "#F20000",   // Zミノ 赤
  "#0000F2",   // Jミノ 青
  "#F2A200",   // Lミノ オレンジ
  "#F200F2"    // Tミノ 紫
]

// フィールドの初期設定
let field = []
for (let i = 0; i < FIELD_Y; i++) {
  field[i] = []
  for (let j = 0; j < FIELD_X; j++) {
    field[i][j] = 0
  }
}

// テトリミノの初期設定
let mino_x = Math.ceil((FIELD_X / 2) - 2)
let mino_y = 0

let mino_type = Math.trunc(Math.random() * 7)
let mino_angle = 0

let minoshapes = [
// mino_type_I
  // mino_angle_0
  [[
    [0,2,0,0],
    [0,2,0,0],
    [0,2,0,0],
    [0,2,0,0]
  ],
  // mino_angle_90
  [
    [0,0,0,0],
    [0,0,0,0],
    [2,2,2,2],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,2,0,0],
    [0,2,0,0],
    [0,2,0,0],
    [0,2,0,0]
  ],
  // mino_angle_270
  [
    [0,0,0,0],
    [0,0,0,0],
    [2,2,2,2],
    [0,0,0,0]
  ]],
// mino_type_O
  // mino_angle_0
  [[
    [0,3,3,0],
    [0,3,3,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,3,3,0],
    [0,3,3,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,3,3,0],
    [0,3,3,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [0,3,3,0],
    [0,3,3,0],
    [0,0,0,0],
    [0,0,0,0]
  ]],
// mino_type_S
  // mino_angle_0
  [[
    [0,4,4,0],
    [4,4,0,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,4,0,0],
    [0,4,4,0],
    [0,0,4,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,4,4,0],
    [4,4,0,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [0,4,0,0],
    [0,4,4,0],
    [0,0,4,0],
    [0,0,0,0]
  ]],
// mino_type_Z
  // mino_angle_0
  [[
    [5,5,0,0],
    [0,5,5,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,0,5,0],
    [0,5,5,0],
    [0,5,0,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [5,5,0,0],
    [0,5,5,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [0,0,5,0],
    [0,5,5,0],
    [0,5,0,0],
    [0,0,0,0]
  ]],
// mino_type_J
  // mino_angle_0
  [[
    [0,0,6,0],
    [0,0,6,0],
    [0,6,6,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,6,0,0],
    [0,6,6,6],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,6,6,0],
    [0,6,0,0],
    [0,6,0,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [6,6,6,0],
    [0,0,6,0],
    [0,0,0,0],
    [0,0,0,0]
  ]],
// mino_type_L
  // mino_angle_0
  [[
    [0,7,0,0],
    [0,7,0,0],
    [0,7,7,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,7,7,7],
    [0,7,0,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,7,7,0],
    [0,0,7,0],
    [0,0,7,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [0,0,7,0],
    [7,7,7,0],
    [0,0,0,0],
    [0,0,0,0]
  ]],
// mino_type_T
  // mino_angle_0
  [[
    [8,8,8,0],
    [0,8,0,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_90
  [
    [0,8,0,0],
    [8,8,0,0],
    [0,8,0,0],
    [0,0,0,0]
  ],
  // mino_angle_180
  [
    [0,8,0,0],
    [8,8,8,0],
    [0,0,0,0],
    [0,0,0,0]
  ],
  // mino_angle_270
  [
    [0,8,0,0],
    [0,8,8,0],
    [0,8,0,0],
    [0,0,0,0]
  ]]
]



// ============ 関数 ============

// フィールドの表示
function display() {
  for (let i = 0; i < FIELD_Y; i++) {
    for (let j =0; j < FIELD_X; j++) {
      // 塗りつぶしの四角を描画
      conText.fillStyle = COLOR[field[i][j]]
      conText.fillRect(MINO_SIZE * j, MINO_SIZE * i, MINO_SIZE, MINO_SIZE)

      // 輪郭の四角を描画
      conText.strokeStyle = COLOR[1]
      conText.lineWidth = 1
      conText.strokeRect(MINO_SIZE * j, MINO_SIZE * i, MINO_SIZE, MINO_SIZE)
    }
  }
}

// テトリミノの表示
function drawmino() {
  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 4; j++) {
      if (minoshapes[mino_type][mino_angle][i][j] > 1) {
        // 新しい座標を定義
        let px = MINO_SIZE * (mino_x + j)
        let py = MINO_SIZE * (mino_y + i)

        // 塗りつぶしの四角を描画
        conText.fillStyle = COLOR[minoshapes[mino_type][mino_angle][i][j]]
        conText.fillRect(px, py, MINO_SIZE, MINO_SIZE)

        // 輪郭の四角を描画
        conText.strokeStyle = COLOR[1]
        conText.lineWidth = 1
        conText.strokeRect(px, py, MINO_SIZE, MINO_SIZE)
      }
    }
  }
}

// テトリミノの落下
function dropmino() {
  // 動けるなら落下
  mino_y += 1
  display()
  drawmino()
}



// ========= スマホ操作 ==========

// スワイプ開始時の座標
let startX
let startY

// スワイプ終了時の座標
let endX
let endY

canvas.addEventListener("touchstart", SwipeStart)
canvas.addEventListener("touchmove", SwipeMove)
canvas.addEventListener("touchend", SwipeEnd)

function SwipeStart(event) {
  // スワイプ開始時の座標
  startX = event.touches[0].pageX
  startY = event.touches[0].pageY

  // スワイプ終了時の座標
  endX = 0
  endY = 0
}

function SwipeMove(event) {
  // 画面をスクロールしないようにする(画面固定)
  event.preventDefault()

  endX = event.changedTouches[0].pageX
  endY = event.changedTouches[0].pageY

  if (endX - startX < -80) {
    startX -= 50

    // 動けるなら左移動
    mino_x -= 1
    display()
    drawmino()
  }

  if (endX - startX > 80) {
    startX += 50

    // 動けるなら右移動
    mino_x += 1
    display()
    drawmino()
  }

  if (endY - startY > 160) {
    // 動けるなら下移動
    mino_y += 1
    display()
    drawmino()
  }
}

function SwipeEnd(event) {
  if (endX == 0) {
    // 動けるなら回転
    mino_angle = (mino_angle + 1) % 4
    display()
    drawmino()
  }
}



// ============ 実行 ============

// 初期画面の表示
display()
drawmino()
interval = setInterval(dropmino, DROP_SPEED)

次回は「6日目 テトリミノの当たり判定」です。