Java | コピーを使った Undo 機能

Java Java
スポンサーリンク

ここでは「将棋やオセロのような本格的なゲーム盤面でUndo/Redoを設計する」例を、設計の考え方+サンプルコードで紹介します。


設計のポイント

本格的なゲーム盤面では以下の要素を扱う必要があります:

  1. 盤面の状態
    • 将棋なら 9×9 の二次元配列
    • オセロなら 8×8 の二次元配列
    • 各セルに「駒の種類」「プレイヤー」を格納
  2. 手番(プレイヤー情報)
    • X/O、先手/後手などを一緒に履歴に保存する必要あり
  3. 履歴管理
    • undoStack に「盤面+手番」を保存
    • redoStack に「戻した状態」を保存
  4. 深いコピー
    • 盤面は二次元配列なので、外側だけでなく内側もコピーする必要がある
    • 駒オブジェクトを持つ場合は、駒も新しく生成してコピーする

サンプルコード(オセロ風 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を繰り返すことで「一手戻す/やり直す」が可能。
Java
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました