では「もっと複雑なオブジェクト(多次元配列や複合クラス)でのUndo/Redo」の例を紹介します。ここでは 多次元配列を持つ複合クラス を深いコピーしながら履歴管理し、Undo/Redoを実現する方法を解説します。
シナリオ
GameBoardクラスを作成。- フィールド:
String name(盤面名)、int[][] cells(2次元配列で盤面の状態)。
- フィールド:
GameBoard[]配列を「状態」として扱う。- 状態を変更するたびに 深いコピー を履歴に保存。
- Undo/Redoで盤面の状態を行ったり来たりできる。
コード例:複合クラス+多次元配列のUndo/Redo
import java.util.*;
class GameBoard {
String name;
int[][] cells;
GameBoard(String name, int[][] cells) {
this.name = name;
// 深いコピーして初期化
this.cells = new int[cells.length][];
for (int i = 0; i < cells.length; i++) {
this.cells[i] = Arrays.copyOf(cells[i], cells[i].length);
}
}
// 深いコピー用コンストラクタ
GameBoard(GameBoard other) {
this.name = other.name;
this.cells = new int[other.cells.length][];
for (int i = 0; i < other.cells.length; i++) {
this.cells[i] = Arrays.copyOf(other.cells[i], other.cells[i].length);
}
}
@Override
public String toString() {
return name + " " + Arrays.deepToString(cells);
}
}
public class UndoRedoComplex {
public static void main(String[] args) {
GameBoard[] state = {
new GameBoard("Board1", new int[][]{{1, 2}, {3, 4}}),
new GameBoard("Board2", new int[][]{{5, 6}, {7, 8}})
};
List<GameBoard[]> undoStack = new ArrayList<>();
List<GameBoard[]> redoStack = new ArrayList<>();
// 初期状態を保存
undoStack.add(deepCopy(state));
// ---- 状態変更 ----
state[0].cells[0][0] = 99; // Board1の左上を変更
undoStack.add(deepCopy(state));
state[1].cells[1][1] = 77; // Board2の右下を変更
undoStack.add(deepCopy(state));
System.out.println("現在の状態: " + Arrays.toString(state));
// [Board1 [[99, 2], [3, 4]], Board2 [[5, 6], [7, 77]]]
// ---- Undo ----
state = undo(state, undoStack, redoStack);
System.out.println("Undo後: " + Arrays.toString(state));
// [Board1 [[99, 2], [3, 4]], Board2 [[5, 6], [7, 8]]]
state = undo(state, undoStack, redoStack);
System.out.println("さらにUndo後: " + Arrays.toString(state));
// [Board1 [[1, 2], [3, 4]], Board2 [[5, 6], [7, 8]]]
// ---- Redo ----
state = redo(state, undoStack, redoStack);
System.out.println("Redo後: " + Arrays.toString(state));
// [Board1 [[99, 2], [3, 4]], Board2 [[5, 6], [7, 8]]]
state = redo(state, undoStack, redoStack);
System.out.println("さらにRedo後: " + Arrays.toString(state));
// [Board1 [[99, 2], [3, 4]], Board2 [[5, 6], [7, 77]]]
}
// 深いコピー(GameBoard配列)
private static GameBoard[] deepCopy(GameBoard[] src) {
GameBoard[] copy = new GameBoard[src.length];
for (int i = 0; i < src.length; i++) {
copy[i] = new GameBoard(src[i]); // 新しいGameBoardを作成
}
return copy;
}
// Undo処理
private static GameBoard[] undo(GameBoard[] state, List<GameBoard[]> undoStack, List<GameBoard[]> redoStack) {
if (undoStack.size() > 1) {
redoStack.add(deepCopy(state));
undoStack.remove(undoStack.size() - 1);
return deepCopy(undoStack.get(undoStack.size() - 1));
}
return state;
}
// Redo処理
private static GameBoard[] redo(GameBoard[] state, List<GameBoard[]> undoStack, List<GameBoard[]> redoStack) {
if (!redoStack.isEmpty()) {
GameBoard[] redoState = redoStack.remove(redoStack.size() - 1);
undoStack.add(deepCopy(redoState));
return redoState;
}
return state;
}
}
Java解説
- 深いコピー:
GameBoardのcellsは2次元配列なので、外側だけでなく内側もコピーする必要がある。- コンストラクタで
Arrays.copyOfを使って各行を複製。
- Undo:
- 最新状態を
redoStackに保存 →undoStackの1つ前を復元。
- 最新状態を
- Redo:
redoStackから取り出して復元 →undoStackに戻す。
- 結果:
- 多次元配列を持つ複合クラスでも、完全に独立した履歴が作れる。
応用ポイント
- ゲーム盤面管理: 将棋やオセロなどの盤面をUndo/Redoできる。
- シミュレーション: 複雑な状態(複数のオブジェクト+多次元配列)を履歴管理できる。
- テキストエディタ風: 複数のドキュメントやページを同時に管理してUndo/Redo可能。
✅ これで「複合クラス+多次元配列のUndo/Redo」の完全版が完成です。
