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 が二つ、無効な移動ではタイルを出さない、種を固定すれば出現順が一致する。画面上ではこうした不具合は「数字が少し変」に見えるだけなので、断言でしか固定できません。

コメント
…