ここでは「将棋やオセロのような本格的なゲーム盤面でUndo/Redoを設計する」例を、設計の考え方+サンプルコードで紹介します。
設計のポイント
本格的なゲーム盤面では以下の要素を扱う必要があります:
- 盤面の状態
- 将棋なら 9×9 の二次元配列
- オセロなら 8×8 の二次元配列
- 各セルに「駒の種類」「プレイヤー」を格納
- 手番(プレイヤー情報)
- X/O、先手/後手などを一緒に履歴に保存する必要あり
- 履歴管理
undoStackに「盤面+手番」を保存redoStackに「戻した状態」を保存
- 深いコピー
- 盤面は二次元配列なので、外側だけでなく内側もコピーする必要がある
- 駒オブジェクトを持つ場合は、駒も新しく生成してコピーする
サンプルコード(オセロ風 8×8 盤面)
import java.util.*;
class GameState {
int[][] board; // 0=空, 1=黒, 2=白
int currentPlayer; // 1=黒, 2=白
GameState(int[][] board, int currentPlayer) {
this.board = new int[board.length][];
for (int i = 0; i < board.length; i++) {
this.board[i] = Arrays.copyOf(board[i], board[i].length);
}
this.currentPlayer = currentPlayer;
}
// 深いコピー用コンストラクタ
GameState(GameState other) {
this(other.board, other.currentPlayer);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Player: ").append(currentPlayer == 1 ? "黒" : "白").append("\n");
for (int[] row : board) {
for (int cell : row) {
char mark = (cell == 0) ? '.' : (cell == 1 ? '●' : '○');
sb.append(mark).append(" ");
}
sb.append("\n");
}
return sb.toString();
}
}
public class OthelloUndoRedo {
public static void main(String[] args) {
// 初期盤面(中央に黒白を配置)
int[][] initialBoard = new int[8][8];
initialBoard[3][3] = 2; // 白
initialBoard[3][4] = 1; // 黒
initialBoard[4][3] = 1; // 黒
initialBoard[4][4] = 2; // 白
GameState state = new GameState(initialBoard, 1); // 黒から開始
List<GameState> undoStack = new ArrayList<>();
List<GameState> redoStack = new ArrayList<>();
// 初期状態を保存
undoStack.add(new GameState(state));
// ---- 手を進める(例として駒を置くだけ) ----
state.board[2][3] = state.currentPlayer; // 黒が置く
state.currentPlayer = 2; // 手番交代
undoStack.add(new GameState(state));
state.board[2][4] = state.currentPlayer; // 白が置く
state.currentPlayer = 1;
undoStack.add(new GameState(state));
System.out.println("現在の盤面:\n" + state);
// ---- Undo ----
state = undo(state, undoStack, redoStack);
System.out.println("Undo後:\n" + state);
state = undo(state, undoStack, redoStack);
System.out.println("さらにUndo後:\n" + state);
// ---- Redo ----
state = redo(state, undoStack, redoStack);
System.out.println("Redo後:\n" + state);
state = redo(state, undoStack, redoStack);
System.out.println("さらにRedo後:\n" + state);
}
// Undo処理
private static GameState undo(GameState state, List<GameState> undoStack, List<GameState> redoStack) {
if (undoStack.size() > 1) {
redoStack.add(new GameState(state));
undoStack.remove(undoStack.size() - 1);
return new GameState(undoStack.get(undoStack.size() - 1));
}
return state;
}
// Redo処理
private static GameState redo(GameState state, List<GameState> undoStack, List<GameState> redoStack) {
if (!redoStack.isEmpty()) {
GameState redoState = redoStack.remove(redoStack.size() - 1);
undoStack.add(new GameState(redoState));
return redoState;
}
return state;
}
}
Java実行イメージ
現在の盤面:
Player: 黒
. . . . . . . .
. . . . . . . .
. . . ● ○ . . .
. . . ○ ● . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
Undo後:
Player: 白
...(一手戻った盤面)
さらにUndo後:
Player: 黒
...(初期盤面)
Redo後:
Player: 白
...(一手進めた盤面)
さらにRedo後:
Player: 黒
...(最新盤面)
✅ まとめ
- 盤面+手番をセットで履歴管理するのが本格的なゲームUndo/Redoのポイント。
- 深いコピーを使うことで、盤面が完全に独立した履歴になる。
- Undo/Redoを繰り返すことで「一手戻す/やり直す」が可能。
