アンドゥを無制限にする(状態を積む方式と、手を逆にたどる方式)
パズルゲームに「何手でも戻せるアンドゥ」を入れました。手を逆にたどる方式ではなく、動かすたびに状態を丸ごと積む方式にしています。重くないのかを実測(100手で4,096バイト)し、戻し忘れやすいもの、テストで見張っている点まで書きます。
公開 2026年9月19日
この記事の要点4 points
- アンドゥの作り方は2つ。 手を逆にたどるか、状態を丸ごと積むか。後者にした。
- 重さを心配したが、いちばん大きい面で100手ぶん 4,096バイト(1手あたり40バイト)だった。
- 戻すときに忘れやすいのは、数えていた値(押した回数)。 状態と一緒に戻す必要がある。
- 遊びの中身(盤・移動・アンドゥ)をUnity に依存しない場所に置いたので、エディターを開かずに36件のテストが 3.86 秒で回る。
目次6章
検証した環境最終確認 2026年9月19日
- Windows 11
- Unity 6.6(6000.6.0f1)
- Claude Code(Opus 5 / 思考レベル 高)
書いてあるのは、この環境で実際に試した結果です。ツールの仕様や料金は変わるので、重要な判断の前には公式の情報も確認してください。
箱を押すパズルに、何手でも戻せるアンドゥを入れました。詰みやすいゲームなので、戻せることは遊びの前提です。
やり方は2つありました。
手を逆にたどる(選ばなかった)
- 「上に動いた」なら「下に動かす」と、操作ごとに逆を書く
- 箱を押していたら、箱も一緒に戻す必要がある
- 持つのは操作だけなので軽い
- 逆の条件を1つ間違えると、盤が静かに壊れる
状態を丸ごと積む(選んだ)
- 動かす前の状態を、そのまま積んでおく
- 戻すときは、積んだものを取り出して差し替えるだけ
- 盤の形が変わっても、書き足すことがない
- そのぶんメモリを使う(あとで実測した)
戻したときに「元と同じ」であることを、考えずに保証したかった。
中身はこれだけ
// PuzzleSession.cs(抜粋)
readonly List<LevelState> _history = new List<LevelState>();
readonly List<bool> _pushes = new List<bool>(); // その手で箱を押したか
public bool Move(Move move)
{
if (Cleared) return false;
if (!Rules.TryMove(_board, State, move, out var next, out bool pushed)) return false;
_history.Add(State); // ← 動かす前を積む
_pushes.Add(pushed);
State = next;
if (pushed) Pushes++;
return true;
}
public bool Undo()
{
if (_history.Count == 0) return false;
int last = _history.Count - 1;
State = _history[last];
if (_pushes[last]) Pushes--; // ← 数えていた値も一緒に戻す
_history.RemoveAt(last);
_pushes.RemoveAt(last);
return true;
}
「最初からやり直す」も同じ考え方で済みます。最初の状態を持っておいて、履歴を空にするだけです。
戻し忘れやすいのは「数えていた値」
上のコードで _pushes を持っているのは、押した回数を数えながら進めているからです。この面の難しさは押した回数で測っているので、画面にも出しています。
状態だけ戻して押した回数を戻さないと、アンドゥを繰り返すほど数字だけが増えていきます。しかも盤は正しいので、見ていても気づきにくい。
戻す必要があるのは「盤から計算し直していない値」
盤を見れば分かる値(クリアしたか・箱が目印に載っているか)は、戻したあとに計算し直せば正しくなります。戻す必要があるのは、進めながら足していた値だけです。この区別をつけると、積むものが減ります。
本当に重くないのかを測った
「状態を丸ごと積む」で気になるのはメモリです。いちばん大きい面(8×8・箱3個)で100手動かして、増えたぶんを測りました。
盤=8x8 箱=3 最短押し=8 積んだ手=100 増えたメモリ=4096バイト 1手あたり=40バイト
100手で4,096バイト。1面をどれだけ長く遊んでも、まず問題になりません。
理由は、積んでいる状態が小さいからです。盤(壁と目印)は面の中で変わらないので積んでいません。積むのは「プレイヤーの位置」と「箱の位置の一覧」だけ。箱3個なら、数字が4つです。
盤ごと積むと、いきなり重くなる
- 症状
- 1手ごとに盤の大きさぶんの配列が増える
- 原因
- 壁や目印は面の中で変わらないのに、状態と一緒に持ってしまう
- 対処
- 変わらないものは1つだけ持つ。積むのは変わるものだけ
積んだ状態の中身を、あとから書き換える
この形にしてから、履歴が壊れる不具合は出ていない- 症状
- 戻したはずなのに、戻った先も同じ盤になっている
- 原因
- 箱の位置の配列を使い回して、その中身を直接書き換えていると、積んだ過去も一緒に変わる
- 対処
- 動かすときは新しい配列を作って差し替える。過去は読むだけのものとして扱う
画面はこうなった

Unity に依存しない場所に置いた
盤・移動・押し・アンドゥは、Unity の型を1つも使わずに書いています(Core という場所にまとめている)。おかげでアンドゥのテストは、シーンもゲームオブジェクトも作らずに動きます。
run_tests true 36/36 件成功 (EditMode, 3.86s)
テストで見張っているのは、たとえばこういうことです。
アンドゥで見張っていること
まとめ
- アンドゥは状態を丸ごと積む方式にした。戻したときに元と一致することを、考えずに保証できる
- 積むのは変わるものだけ(プレイヤーと箱)。盤は積まない
- 100手で 4,096バイト。心配するほど重くなかった
- 戻し忘れやすいのは、進めながら数えていた値
- 遊びの中身をエンジンから切り離すと、アンドゥのテストが軽く回る
FAQ
よくある質問
アンドゥは「手を逆にたどる」のと「状態を積む」の、どちらがいいですか?
戻したときに元と一致することを確実にしたいなら、状態を積むほうが安全です。手を逆にたどる方式は、逆の操作を1つずつ正しく書く必要があり、押した・押していないの条件を間違えると盤が壊れます。状態を積む方式は「積んで、戻すだけ」で済みます。
状態を全部積んだら、メモリを使いすぎませんか?
このゲームでは使いすぎませんでした。8×8・箱3個の面で100手動かして、増えたのは 4,096 バイト(1手あたり40バイト)です。盤が大きい・履歴が何千手も続く種類のゲームなら、手を逆にたどる方式や、間引いて積む方式を検討することになります。
アンドゥで戻し忘れやすいものは?
盤の状態から計算し直していない値です。このゲームでは「箱を押した回数」を数えながら進めていたので、1手ごとに押したかどうかも一緒に積んで、戻すときに減らしています。
自動生成の「巻き戻し」と同じ仕組みですか?
考え方は似ていますが、別物です。面の自動生成はクリア状態から箱を引いて作るので、作り手が逆の操作を選んでいます。アンドゥは遊ぶ人が何を選ぶか分からないので、状態を積むほうにしました。
この記事は役に立ちましたか?
NEXT
あわせて読む
自動生成したパズルが必ず解ける作り方(逆再生+ソルバーで難しさも揃える)
箱押しパズルの面を自動生成しました。クリア状態から巻き戻して作れば必ず解けますが、それだけでは難しさが揃いません。1回引くたびに解かせて狙いの手数に合わせる方法と、狙いが外れ続けた原因、探索が重すぎて A* に変えた経緯を、実測つきで残します。
AIに作らせたゲームはなぜ単調になるのか(遊んで直した6つ)
仕様どおりに動き、自動テスト43件も全部通ったのに、遊ぶと退屈でした。実際に遊んで出てきた不満6つと、それぞれ何を直したかを、数字と作業時間つきで残します。AI に「面白くして」と言っても面白くならない理由の記録です。
Webゲームの進行を保存する(何を保存しないか・壊れても落ちない形)
ブラウザで遊ぶパズルに「どこまでクリアしたか」と自己ベストを保存しました。盤面は保存しません。保存先がブラウザの領域であること、壊れた保存データで落ちない書き方、記事を書いていて気づいた「版が無い」という穴と、その直し方を残します。