PR

6日目 テトリミノの当たり判定【関数の引数とreturnの戻り値】

テトリスを作ろう

はじめに

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

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

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

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

6日目でやること

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

今回は、テトリミノが横の壁や下の床に当たったとき、壁や床を超えて移動できないようにするところまでをやっていきます。

6日目 テトリミノの当たり判定

全体のソースコード

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

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() {
  if (inhit(mino_type,mino_angle,mino_x,mino_y + 1)) {
    // 動けるなら落下
    mino_y += 1
    display()
    drawmino()
  }
}

// テトリミノの当たり判定
function inhit(minotype,minoangle,minox,minoy) {
  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 4; j++) {
      if (minoshapes[minotype][minoangle][i][j] > 1) {
        let x = minox + j
        let y = minoy + i
        if (x < 0 || x >=  FIELD_X || y >= FIELD_Y || field[y][x] > 0) {
          return false
        }
      }
    }
  }
  return true
}



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

// スワイプ開始時の座標
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
    if (inhit(mino_type,mino_angle,mino_x - 1,mino_y)) {
      // 動けるなら左移動
      mino_x -= 1
      display()
      drawmino()
    }
  }

  if (endX - startX > 80) {
    startX += 50
    if (inhit(mino_type,mino_angle,mino_x + 1,mino_y)) {
      // 動けるなら右移動
      mino_x += 1
      display()
      drawmino()
    }
  }

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

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



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

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

JavaScriptの解説

今回新しく追加したのは、「// テトリミノの当たり判定」のところです。

// テトリミノの当たり判定
function inhit(minotype,minoangle,minox,minoy) {
  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 4; j++) {
      if (minoshapes[minotype][minoangle][i][j] > 1) {
        let x = minox + j
        let y = minoy + i
        if (x < 0 || x >=  FIELD_X || y >= FIELD_Y || field[y][x] > 0) {
          return false
        }
      }
    }
  }
  return true
}

与えられたテトリミノのデータ(ミノの形・回転・座標)が、壁や床に当たってないかを調べる関数を作ります。ほかのテトリミノがある場合も当たったかの確認ができます。

関数inhit を実行したとき、その位置にテトリミノを移動できるなら「true(真)」を返し、その位置にテトリミノを移動できないなら「false(偽)」を返します。

function inhit(minotype,minoangle,minox,minoy) { .. }

function 関数名(引数名1, 引数名2, ・・) { .. }

関数名をinhitにして、minotype,minoangle,minox,minoyの4つの引数を渡した関数を { } 内に定義します。

for (let i = 0; i < 4; i++) { |for (let j = 0; j < 4; j++) { .. } | }

繰り返し「for文」です。{ .. }内の処理を、i が 0 から 1、2、3(4になれば処理しない)と繰り返します。

i が 0 のとき、さらに「for (let j =0; j < 10; j++) { .. }」で j が 0 から 3 まで繰り返します。

if (minoshapes[minotype][minoangle][i][j] > 1) { .. }

「if文」です。if文は、条件を満たすときに { } 内の処理を行います。

つまり、テトリミノのブロックがある場合には if文の中の処理を実行します。

多次元配列の変数minoshapesから、テトリミノの配列や値を呼び出します。

変数minoshapesは、「7種類のテトリミノの形」「4つの回転角度」「縦4マス」「横4マス」の四次元配列で、minotype、minoangle、i、j がそれぞれ対応します。

let minoshapes = [
// mino_type_I
  // mino_angle_0
  [[
    [0,2,0,0],
    [0,2,0,0],
    [0,2,0,0],
    [0,2,0,0]
  ],
  :
  :

例えば、minoshapes[0][0][0][1] では「 2 」の値を返します。

if文の中の処理
let x = minox + j
let y = minoy + i

変数x と変数y に、テトリミノの1ブロックの x座標と y座標を指定します。

if (x < 0 || x >=  FIELD_X || y >= FIELD_Y || field[y][x] > 0) {
  return false
}

テトリミノの1ブロックの座標がフィールド内にあるかを調べます。

x < 0 のとき、フィールドの外(左側)になります。
x >= FIELD_X のとき、フィールドの外(右側)になります。
y >= FIELD_Y のとき、フィールドの外(下側)になります。

テトリミノの1ブロックの座標にほかのブロックがないかを調べます。

field[y][x] > 0 のとき、その座標にはほかのブロックが存在します。

これらのどれかの条件を満たしている場合、if文の中の処理を行います。

if文の条件にある「 || 」は、「or」「または」という意味を持ちます。

if (条件1 || 条件2 || 条件3 || 条件4) { .. } の場合、どれかの条件を満たすときに { } 内の処理を行います。

return false

return 戻り値

関数内で「return」を使うと、関数内での処理を終了し「戻り値」を返します。(戻り値を省略すると関数内の処理のみ終了します。)

false とは、条件が成り立たないことで「偽」の意味を持つ値です。また、条件が成り立つ「真」の意味を持つ値は true です。

return true
function inhit(minotype,minoangle,minox,minoy) {
  :
  :
  return true
}

return 戻り値

関数内で「return」を使うと、関数内での処理を終了し「戻り値」を返します。

true とは、条件が成り立つ「真」の意味を持つ値です。

テトリミノの1ブロックの座標がフィールド内にあり、ほかのブロックが存在しない場合に True を戻り値として返します。

関数にデータを渡す引数(ひきすう)

引数とは、関数に入れる値のことです。

function 関数名(引数名1, 引数名2, ・・) { .. }

の関数に対して

関数名(引数1, 引数2, ・・)

のように関数を呼び出すときに使用します。

関数からデータを返される戻り値(もどりち)

り値とは、関数から出てくる値のことです。

function 関数名(引数) { .. return 戻り値 }

の関数を実行すると「戻り値」が返ってきます。

変数名 = 関数名(引数)

if 関数名(引数) { .. }

のように関数で処理した値を参照したいときに使用します。

引数と戻り値を使う

if (inhit(mino_type,mino_angle,mino_x - 1,mino_y)) {
  // 動けるなら左移動
  mino_x -= 1
  display()
  drawmino()
}

先ほどの関数inhit を呼び出して、その位置にテトリミノを移動できるならテトリミノを移動します。

関数inhit は、与えられたテトリミノのデータ(ミノの形・回転・座標)が壁や床に当たってないか、ほかのテトリミノに当たったてないかを調べる関数です。

if (inhit(mino_type,mino_angle,mino_x – 1,mino_y)) { .. }

if 関数名(引数) { .. } の具体的な使用例です。inhit(引数) で関数inhit が実行されて「true(真)」か「false(偽)」の戻り値を返します。

戻り値が「true(真)」のときに {}内を処理する if文です。

mino_x -= 1

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

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

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

display()

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

drawmino()

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

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

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

テトリミノの右移動(mino_x + 1)、テトリミノの落下と下移動(mino_y + 1)、テトリミノの回転((mino_angle + 1) % 4)も同様に、関数inhit を呼び出して当たり判定を行います。

最後に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() {
  if (inhit(mino_type,mino_angle,mino_x,mino_y + 1)) {
    // 動けるなら落下
    mino_y += 1
    display()
    drawmino()
  }
}

// テトリミノの当たり判定
function inhit(minotype,minoangle,minox,minoy) {
  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 4; j++) {
      if (minoshapes[minotype][minoangle][i][j] > 1) {
        let x = minox + j
        let y = minoy + i
        if (x < 0 || x >=  FIELD_X || y >= FIELD_Y || field[y][x] > 0) {
          return false
        }
      }
    }
  }
  return true
}



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

// スワイプ開始時の座標
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
    if (inhit(mino_type,mino_angle,mino_x - 1,mino_y)) {
      // 動けるなら左移動
      mino_x -= 1
      display()
      drawmino()
    }
  }

  if (endX - startX > 80) {
    startX += 50
    if (inhit(mino_type,mino_angle,mino_x + 1,mino_y)) {
      // 動けるなら右移動
      mino_x += 1
      display()
      drawmino()
    }
  }

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

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



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

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

次回は「7日目 ラインを消す処理」です。