11. 切り札はHOLD!ミノを一時保管&ハードドロップ!

さあ、みんな!テトリス教室もいよいよ大詰めよ!今回は、テトリスプレイヤーなら誰もが使う便利な機能、「HOLD(ホールド)」と、一瞬でミノを落とす爽快な「ハードドロップ」を実装していくわ! これができれば、もう君だけのオリジナルテトリスは完成間近!先生、わくわくが止まらないわ!

「今はこれじゃない…」を解決!HOLD機能

「あー、このIミノ、今来られても困るんだけどな…」なんて時、あるわよね?そんな時に便利なのがHOLD機能! 今落ちてきているミノを一時的に「キープ」しておいて、代わりにキープしていたミノ(もしあれば)を出すことができるの。ピンチをチャンスに変える切り札よ!

HOLD機能を作るには、こんなものが必要ね。


let holdMinoData = null; //最初は何もHOLDしていない
let canUseHoldThisTurn = true; //最初はHOLDできる

function tryHoldMino() {
  if (!currentMino || !canUseHoldThisTurn) {
    if (!canUseHoldThisTurn) playRotationFailSound(); // HOLDできない時の音
    return; // HOLDできない場合は何もしない
  }

  if (holdMinoData === null) { // 初めてHOLDする場合
    // currentMino の情報を holdMinoData にコピー (x,y,rotationStateは不要)
    holdMinoData = {
      shapeDef: JSON.parse(JSON.stringify(currentMino.shape)), // 形はコピー
      colorDef: currentMino.color,
      type: currentMino.type,
      colorIndex: currentMino.colorIndex
    };
    spawnNewMino(); // 新しいミノを盤面に出す
  } else { // HOLD中のミノと交換する場合
    // currentMino と holdMinoData の中身を交換!
    let tempMinoDataForBoard = { // 盤面に出すためのミノ情報
        shapeDef: JSON.parse(JSON.stringify(holdMinoData.shapeDef)),
        colorDef: holdMinoData.colorDef,
        type: holdMinoData.type,
        colorIndex: holdMinoData.colorIndex
    };
    // currentMino の情報を holdMinoData に
    holdMinoData = {
        shapeDef: JSON.parse(JSON.stringify(currentMino.shape)),
        colorDef: currentMino.color,
        type: currentMino.type,
        colorIndex: currentMino.colorIndex
    };

    // 元々HOLDしていたミノをcurrentMinoとして盤面に出す
    const shape = tempMinoDataForBoard.shapeDef;
    const color = tempMinoDataForBoard.colorDef;
    let initialX = Math.floor((GRID_COLS - shape[0].length) / 2);
    // ... (spawnNewMino と同じような初期位置計算) ...
    currentMino = {
        shape: shape, color: color, x: initialX, y: 0, // yは0からスタート
        type: tempMinoDataForBoard.type, rotationState: 0,
        colorIndex: tempMinoDataForBoard.colorIndex
    };
    // ★重要★ 交換して出てきたミノが置けるかチェック!
    if (!canMinoMoveTo(currentMino.shape, currentMino.x, currentMino.y)) {
        // 置けないならHOLDはキャンセル! (元のcurrentMinoに戻し、holdMinoDataも元に戻す)
        // ちょっと複雑になるので、今回は「置けなかったらゲームオーバー」にするか、
        // もっと単純に「HOLD失敗」として何も変えないのが簡単かも。
        // ここでは、もし置けなかったら、HOLDしなかったことにするわ (元のcurrentMinoに戻す)。
        holdMinoData = { // currentMinoの情報を再度HOLDに(つまり元に戻す)
             shapeDef: JSON.parse(JSON.stringify(tempMinoDataForBoard.shapeDef)),
             colorDef: tempMinoDataForBoard.colorDef,
             type: tempMinoDataForBoard.type,
             colorIndex: tempMinoDataForBoard.colorIndex
        };
         // currentMinoも元に戻す (spawnNewMinoせずに前の状態を復元するのは大変なので、ここでは何もしないでおくわ)
         // → より簡単なのは、canMinoMoveToでチェックしてダメならholdMinoDataを元に戻し、currentMinoはそのままにする。
         // 先生、ここはちょっと難しいわね…。一番簡単なのは、交換後に置けなかったらHOLD自体を失敗させることね!
         // 今回は、交換後に置ける前提で進めるわ!(実際のゲームではしっかりチェックが必要よ)
    }
  }
  canUseHoldThisTurn = false; // このターンはもうHOLDできない
  drawHoldMinoDisplay(); // HOLD表示を更新
  // playSound('triangle', 300, 0.1, 0.1); // HOLD成功音
}

// ミノが着地した時 (handleMinoLanded の中で) に canUseHoldThisTurn を true に戻すのを忘れないでね!
// handleMinoLanded() { ... canUseHoldThisTurn = true; ... }
        

HOLDしたミノも、NEXTミノみたいに専用の小さなCanvasに表示してあげましょう。HTMLの gameInfo の部分に <canvas id="holdMinoCanvas"></canvas> を追加するのよ。 drawHoldMinoDisplay() 関数は、drawNextMinoDisplay() とほとんど同じように作れるわ。

一瞬でズドン!ハードドロップ!

「もうここで決まり!」って時に、ミノを一瞬で一番下まで落とすのがハードドロップ!これができるとゲームのテンポがすごく良くなるの。スペースキーで発動できるようにしましょう。

ハードドロップの仕組みはこうよ。

  1. 現在のミノが、今の列のままで一番下まで落ちたら、どの行に着地するかを計算する。
    • currentMino.y を1ずつ増やしながら、canMinoMoveTo で下に動けるかチェックし続けるの。
    • 動けなくなる一つ手前の y座標が、ハードドロップの着地点ね。
  2. currentMino.y を、計算した着地点のy座標に一気に変える!
  3. すぐに handleMinoLanded() を呼び出して、ミノを固定し、ラインチェックなどを行う。

function doHardDrop() {
  if (!currentMino || !gameRunning) return;

  let dropY = currentMino.y;
  // 1マスずつ下にずらしてみて、どこまで行けるか調べる
  while (canMinoMoveTo(currentMino.shape, currentMino.x, dropY + 1)) {
    dropY++;
  }

  if (dropY > currentMino.y) { // 少しでも下に落ちるなら
    // (ここでハードドロップした行数に応じてスコアを加算してもいいわね!)
    // currentScore += (dropY - currentMino.y) * 1; // 例えば1行につき1点とか
    // updateScoreDisplay(); // スコア表示を更新
    currentMino.y = dropY; // 一気に着地点へ!
  }

  handleMinoLanded(); // 即座に着地処理!
  // redrawGame(); // handleMinoLanded の中で spawnNewMino -> redrawGame が呼ばれるはず
  playSound('square', 220, 0.15, 0.15); // ハードドロップ音!
}
        

おまけの挑戦:ゴーストピース
もっと本格的にするなら、「ゴーストピース」といって、ハードドロップでミノがどこに着地するかを半透明で表示する機能もあるわ。 これは、ハードドロップの着地点を計算して、そこに現在のミノの形を薄い色で描画するの。redrawGame の中で、固定ブロックを描いた後、ゴーストピースを描き、その上に現在のミノを描く、という順番ね。 ちょっと難しいけど、できたらすごくカッコいいわよ!今回はまずハードドロップの機能だけでも完璧にしましょうね。

最終調整…の前に、もう一つステップアップ!

さあ、これでHOLDもハードドロップも使えるようになって、テトリスゲームの操作性はバッチリね! NEXTミノを見ながら、いらないミノはHOLDに回し、ここぞという時はハードドロップで素早く決める!これで君もテトリスマスターに一歩近づいたわ!

スコア: 0

ライン: 0

レベル: 0

HOLD:

NEXT:

(ゲームの状態や操作方法が表示されるわ)


Summary of this page (Click to read in English)

This page implements two advanced Tetris features: the "HOLD" function and "Hard Drop."

The HOLD function allows the player to temporarily store the current Tetromino and swap it with a previously held one (or spawn a new piece if HOLD is empty). This is typically allowed once per turn (resetting when a piece locks). A holdMinoData variable stores the held piece, and a separate small canvas displays it. A key (e.g., 'Shift' or 'c') triggers the hold attempt.

Hard Drop instantly drops the current Tetromino to its lowest possible position on the board. This is usually triggered by the 'Space' key. The game calculates how far the piece can fall in its current column, updates its y-coordinate directly, and then immediately processes it as a landed piece (locking, line clearing, etc.). An optional "ghost piece" could show where the hard drop will land, but is not implemented in detail here.