作ったもの
Same Game — https://sen.ltd/portfolio/same-game/
- 3 難易度(10×10/3 色、15×12/4 色、20×15/5 色)
- ホバーで候補グループをハイライト、クリックで削除(モバイルはダブルタップ)
- Undo 機能
- 難易度別ベストスコア(localStorage)
- 全消しボーナス(+1000)
- 日英 UI、ダーク / ライト
vanilla JS、ゼロ依存、ビルド不要。node --test で 53 ケース。
昔作った自作の Same Game を、portfolio 用にリファクタして移植しました。
スコア = (個数 - 2)²
指数スコアが戦略を生む。2 個だと 0 点、3 個で 1、5 個で 9、10 個で 64、20 個で 324。1 つの大きい塊を作る方が、小さく削るより圧倒的に得。上手いプレイヤーは色を溜めて一気に消す。
3 段階のアルゴリズム
1. フラッドフィル(グループ検出)
const stack = [[row, col]];
while (stack.length > 0) {
const [r, c] = stack.pop();
for (const [dr, dc] of [[-1,0],[1,0],[0,-1],[0,1]]) {
if (board[r+dr]?.[c+dc] === target) {
points.push([r+dr, c+dc]);
stack.push([r+dr, c+dc]);
}
}
}
DFS + Set で訪問済み管理。1 マスしか見つからなければ null を返す(単独クリックで消えるのはルール違反)。
2. 重力(各列を底に詰める)
各列について、下から順に非 null 値を積み直す:
for (let col = 0; col < cols; col++) {
const stack = [];
for (let row = rows - 1; row >= 0; row--) {
if (board[row][col] !== null) stack.push(board[row][col]);
}
for (let i = 0; i < stack.length; i++) {
result[rows - 1 - i][col] = stack[i];
}
}
3. 列圧縮(空の列を左に寄せる)
列全体が空になったら、右側の列を左にシフト。これで盤面が散らばらず、連鎖が続きやすくなる。
次の手の有無チェック
// 右と下だけ見れば十分(重複チェック回避)
if (board[row][col + 1] === v) return true;
if (board[row + 1][col] === v) return true;
(r, c) と (r, c+1) がマッチしている場合、(r, c) から見つかる。左や上を見る必要はない。
Undo は参照コピーで無料
play() が新しい盤面オブジェクトを返すので、履歴には参照を積むだけ。deep-copy 不要。50 手分の履歴でもメモリはほぼゼロ。
シリーズ
100+ 公開ポートフォリオ シリーズの #101 です(目標通過後のオーバーアチーブ)。
- 📦 リポジトリ: https://github.com/sen-ltd/same-game
- 🌐 デモ: https://sen.ltd/portfolio/same-game/
- 🏢 会社: https://sen.ltd/
