2048:四方向を一つの操作に畳み込む

スライド・合成・得点は一度書けば足り、残り三方向は転置と反転で再利用できます。難しいのは「各タイルは 1 回の移動で最大 1 回しか合成しない」という規則です。

まず正規化し、一度だけ実装する

四方向それぞれにスライド処理を書くと、ほぼ同じで細部だけ違うコードが四つ生まれます。安く確実な方法は、「一行を左へスライドして合成する」処理だけを実装し、他の三方向は盤面を変換して同じ処理を呼び、戻すことです。

左右は行の反転で、上下は先に転置してから左右の経路を再利用します。変換はゲーム規則を含まない純粋な配列操作なので、単独で検証できます。

合成はスライドより難しい

スライドの第一歩は 0 を取り除き、残りを左へ詰めることです。間違えやすいのは合成で、各タイルは 1 回の移動で最大 1 回しか合成に関与しません。

したがって [2, 2, 4] は [4, 4] になり、[8] にはなりません。二つ目の 2 は一つ目に消費済みで、新しくできた 4 とさらに合成することはできないからです。同じく [2, 2, 2, 2] は [4, 4] で [8] ではありません。

実装では等しい組を見つけて合成し、添字を余分に一つ進めて食べられた側を飛ばします。

if (i + 1 < nums.length && nums[i] === nums[i + 1]) {
  const merged = nums[i] * 2;
  out.push(merged);
  gained += merged;
  i += 1; // 食べられた側を飛ばす
}

この一行の加算が、このゲームで最も削れない行です。削っても動きますが、特定の盤面で本来あり得ない 8 や 16 が現れ、プレイヤーはどの手が誤りだったのか説明できません。

盤面が実際に動いたときだけ出す

見落としやすいもう一つの規則は、スライドしても盤面が変わらない場合(すべてのタイルが既にその側に寄っている場合)、新しいタイルを出さず、手番にも数えないことです。この判定が無いと、方向キーを押し続けるだけでタイルを無限に増やせます。

新しいタイルは空きマスにだけ置かれ、しかも再現可能でなければなりません。乱数は種を持つ mulberry32 を使うので、自検で種を固定すれば出現順も固定されます。

終了判定

空きマスが無く、四方向のいずれでも合成が起きないときに終了します。どちらか一方では足りません。盤面が満杯でも隣接する等しいタイルがあれば続行です。

自検で捕まえられる理由

これらの規則はすべて、DOM に触れない素の配列上の純粋関数にあります。アサーションはまさにその境界を狙います。[2, 2, 4] は [4, 4]、2 が四つなら 4 が二つ、無効な移動ではタイルを出さない、種を固定すれば出現順が一致する。画面上ではこうした不具合は「数字が少し変」に見えるだけなので、断言でしか固定できません。

← 記事一覧に戻る

コメント

…