Redo/Undo Logik implementiert

revisionCode
Vickvick2002 2025-01-05 18:56:42 +01:00
parent dcf9c95a99
commit 75f04b51cc
8 changed files with 251 additions and 194 deletions

View File

@ -0,0 +1,63 @@
package PR2.HitoriSpiel.Domain;
public class Action {
private final int row; // Zeile, in der die Änderung erfolgt ist
private final int col; // Spalte, in der die Änderung erfolgt ist
private final String oldState; // Alter Zustand der Zelle
private final String newState; // Neuer Zustand der Zelle
public Action(int row, int col, String oldState, String newState) {
this.row = row;
this.col = col;
this.oldState = oldState;
this.newState = newState;
}
// Alternative Konstruktor für Standardwerte
public Action(int row, int col, String newState) {
this(row, col, "GRAY", newState);
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
public String getOldState() {
return oldState;
}
public String getNewState() {
return newState;
}
@Override
public String toString() {
return row + "," + col + "," + oldState + "," + newState;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Action action = (Action) o;
return row == action.row &&
col == action.col &&
oldState.equals(action.oldState) &&
newState.equals(action.newState);
}
@Override
public int hashCode() {
int result = row;
result = 31 * result + col;
result = 31 * result + oldState.hashCode();
result = 31 * result + newState.hashCode();
return result;
}
}

View File

@ -0,0 +1,83 @@
package PR2.HitoriSpiel.Domain;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Stack;
public class StateFileManager {
// Speicherort für die Undo/Redo-Datei im Benutzerverzeichnis
private static final String BASE_DIR = System.getProperty("user.home") + "/HitoriGame";
private static final String FILE_NAME = BASE_DIR + "/undoRedoLog.txt";
// Stellt sicher, dass der Ordner und die Datei existieren
private static void ensureFileExists() {
try {
// Erstellt den Ordner, falls er nicht existiert
Path directoryPath = Paths.get(BASE_DIR);
if (!Files.exists(directoryPath)) {
Files.createDirectories(directoryPath);
System.out.println("Ordner erstellt: " + BASE_DIR);
}
// Erstellt die Datei, falls sie nicht existiert
Path filePath = Paths.get(FILE_NAME);
if (!Files.exists(filePath)) {
Files.createFile(filePath);
System.out.println("Datei erstellt: " + FILE_NAME);
}
} catch (IOException e) {
System.err.println("Fehler beim Erstellen von Ordner oder Datei: " + e.getMessage());
}
}
// Speichert den Zustand in der Datei
public static void saveState(Stack<Action> undoStack, Stack<Action> redoStack) {
ensureFileExists(); // Stellt sicher, dass die Datei existiert
try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_NAME))) {
writer.write("UNDO"); // Kennzeichnung für Undo-Stack
writer.newLine();
for (Action action : undoStack) {
writer.write(action.toString());
writer.newLine();
}
writer.write("REDO"); // Kennzeichnung für Redo-Stack
writer.newLine();
for (Action action : redoStack) {
writer.write(action.toString());
writer.newLine();
}
} catch (IOException e) {
System.err.println("Fehler beim Speichern der Zustände: " + e.getMessage());
}
}
// Lädt den Zustand aus der Datei
public static void loadState(Stack<Action> undoStack, Stack<Action> redoStack) {
ensureFileExists(); // Stellt sicher, dass die Datei existiert
try (BufferedReader reader = new BufferedReader(new FileReader(FILE_NAME))) {
String line;
Stack<Action> currentStack = undoStack; // Standard: Undo-Stack
while ((line = reader.readLine()) != null) {
if (line.equals("UNDO")) {
currentStack = undoStack;
} else if (line.equals("REDO")) {
currentStack = redoStack;
} else {
String[] parts = line.split(",");
int row = Integer.parseInt(parts[0]);
int col = Integer.parseInt(parts[1]);
String oldState = parts[2];
String newState = parts[3];
currentStack.push(new Action(row, col, oldState, newState));
}
}
} catch (FileNotFoundException e) {
System.out.println("Keine Undo/Redo-Datei gefunden. Es werden leere Stacks verwendet.");
} catch (IOException e) {
System.err.println("Fehler beim Laden der Zustände: " + e.getMessage());
}
}
}

View File

@ -4,74 +4,37 @@ import java.io.*;
import java.util.Stack;
public class StateManager implements Serializable {
private final Stack<int[][]> undoStack = new Stack<>();
private final Stack<int[][]> redoStack = new Stack<>();
private final Stack<Action> undoStack = new Stack<>();
private final Stack<Action> redoStack = new Stack<>();
public void saveState(int[][] state) {
undoStack.push(copyState(state));
public void saveAction(int row, int col, String oldState, String newState) {
undoStack.push(new Action(row, col, oldState, newState));
redoStack.clear(); // Redo-Stack leeren, da ein neuer Zustand hinzugefügt wurde
StateFileManager.saveState(undoStack, redoStack); // Speichern in Datei
}
// Macht die letzte Änderung rückgängig
public int[][] undo(int[][] currentState) {
public Action undo() {
if (!undoStack.isEmpty()) {
redoStack.push(copyState(currentState)); // Speichere aktuellen Zustand für Redo
return undoStack.pop(); // Gib den vorherigen Zustand zurück
Action action = undoStack.pop();
redoStack.push(action);
StateFileManager.saveState(undoStack, redoStack); // Aktuellen Zustand speichern
return action;
}
return null; // Kein Zustand mehr verfügbar
return null;
}
// Wiederholt die letzte rückgängig gemachte Änderung
public int[][] redo(int[][] currentState) {
public Action redo() {
if (!redoStack.isEmpty()) {
undoStack.push(copyState(currentState)); // Speichere aktuellen Zustand für Undo
return redoStack.pop(); // Gib den Redo-Zustand zurück
Action action = redoStack.pop();
undoStack.push(action);
StateFileManager.saveState(undoStack, redoStack); // Aktuellen Zustand speichern
return action;
}
return null; // Kein Zustand verfügbar
return null;
}
// Hilfsmethode, um eine Kopie des Zustands zu erstellen
private int[][] copyState(int[][] state) {
int[][] copy = new int[state.length][state[0].length];
for (int i = 0; i < state.length; i++) {
System.arraycopy(state[i], 0, copy[i], 0, state[i].length);
}
return copy;
}
// Speichert die gesamte StateManager-Instanz in einer Datei
public void saveStateToFile() {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("undoRedoState.dat"))) {
out.writeObject(undoStack); // Schreibe den Undo-Stack
out.writeObject(redoStack); // Schreibe den Redo-Stack
System.out.println("Zustände wurden erfolgreich gespeichert.");
} catch (IOException e) {
System.err.println("Fehler beim Speichern der Zustände: " + e.getMessage());
}
}
// Lädt den StateManager aus einer Datei
public static StateManager loadStateFromFile() {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("undoRedoState.dat"))) {
StateManager stateManager = new StateManager();
stateManager.undoStack.addAll((Stack<int[][]>) in.readObject());
stateManager.redoStack.addAll((Stack<int[][]>) in.readObject());
System.out.println("Zustände wurden erfolgreich geladen.");
return stateManager;
} catch (IOException | ClassNotFoundException e) {
System.err.println("Fehler beim Laden der Zustände: " + e.getMessage());
return new StateManager(); // Gibt eine neue Instanz zurück, falls ein Fehler auftritt
}
}
// Überprüft, ob Undo-Operationen verfügbar sind
public boolean canUndo() {
return !undoStack.isEmpty();
}
// Überprüft, ob Redo-Operationen verfügbar sind
public boolean canRedo() {
return !redoStack.isEmpty();
public void loadStateFromFile() {
StateFileManager.loadState(undoStack, redoStack); // Stacks aus Datei laden
}
}

View File

@ -1,5 +1,6 @@
package PR2.HitoriSpiel.GUI;
import PR2.HitoriSpiel.Domain.Action;
import PR2.HitoriSpiel.Domain.HitoriValidator;
import PR2.HitoriSpiel.Domain.StateManager;
import PR2.HitoriSpiel.Domain.HitoriBoard;
@ -75,7 +76,7 @@ public class GameBoard extends JPanel {
private JButton createResetButton() {
JButton resetButton = Setup.createGameBoardButton("Zurücksetzen", 150, 30);
resetButton.addActionListener(e -> {
saveStateForUndo();
//saveStateForUndo();
resetBoard();
});
return resetButton;
@ -84,24 +85,29 @@ public class GameBoard extends JPanel {
private JButton createUndoButton() {
JButton undoButton = Setup.createGameBoardButton("Undo", 80, 30);
undoButton.addActionListener(e -> {
int[][] previousState = stateManager.undo(board.getNumbers());
if (previousState != null) {
board.setNumbers(previousState);
updateBoard();
Action action = stateManager.undo();
if (action != null) {
HitoriCell cell = board.getCell(action.getRow(), action.getCol());
cell.setState(HitoriCell.CellState.valueOf(action.getOldState()));
JButton button = (JButton) gamePanel.getComponent(action.getRow() * board.getSize() + action.getCol());
updateButtonState(button, cell);
} else {
JOptionPane.showMessageDialog(this, "Kein Zustand zum Zurücksetzen vorhanden.", "Undo", JOptionPane.INFORMATION_MESSAGE);
}
});
return undoButton;
}
private JButton createRedoButton() {
JButton redoButton = Setup.createGameBoardButton("Redo", 80, 30);
redoButton.addActionListener(e -> {
int[][] nextState = stateManager.redo(board.getNumbers());
if (nextState != null) {
board.setNumbers(nextState);
updateBoard();
Action action = stateManager.redo();
if (action != null) {
HitoriCell cell = board.getCell(action.getRow(), action.getCol());
cell.setState(HitoriCell.CellState.valueOf(action.getNewState()));
JButton button = (JButton) gamePanel.getComponent(action.getRow() * board.getSize() + action.getCol());
updateButtonState(button, cell);
} else {
JOptionPane.showMessageDialog(this, "Kein Zustand zum Wiederholen vorhanden.", "Redo", JOptionPane.INFORMATION_MESSAGE);
}
@ -164,45 +170,62 @@ public class GameBoard extends JPanel {
JButton button = new JButton(String.valueOf(cell.getNumber()));
button.setBackground(Color.LIGHT_GRAY);
button.addActionListener(e -> {
toggleCellState(cell);
toggleCellState(cell, row, col);
updateButtonState(button, cell);
});
return button;
}
private void toggleCellState(HitoriCell cell) {
private void toggleCellState(HitoriCell cell, int row, int col) {
if (cell == null) {
System.err.println("Ungültige Zelle! Der Zustand kann nicht geändert werden.");
return;
}
String oldState = cell.getState().toString();
String newState;
switch (cell.getState()) {
case GRAY -> cell.setState(HitoriCell.CellState.BLACK);
case BLACK -> cell.setState(HitoriCell.CellState.WHITE);
case WHITE -> cell.setState(HitoriCell.CellState.GRAY);
case GRAY -> {
cell.setState(HitoriCell.CellState.BLACK);
newState = HitoriCell.CellState.BLACK.toString();
}
case BLACK -> {
cell.setState(HitoriCell.CellState.WHITE);
newState = HitoriCell.CellState.WHITE.toString();
}
case WHITE -> {
cell.setState(HitoriCell.CellState.GRAY);
newState = HitoriCell.CellState.GRAY.toString();
}
default -> throw new IllegalStateException("Unerwarteter Zustand: " + cell.getState());
}
stateManager.saveAction(row, col, oldState, newState);
// Debug-Ausgabe
System.out.println("Aktion gespeichert: " + oldState + " -> " + newState);
}
private void updateButtonState(JButton button, HitoriCell cell) {
switch (cell.getState()) {
case GRAY -> {
button.setBackground(Color.LIGHT_GRAY);
button.setText(String.valueOf(cell.getNumber()));
button.setBackground(Color.LIGHT_GRAY); // Hintergrund grau
button.setForeground(Color.BLACK); // Textfarbe schwarz
button.setText(String.valueOf(cell.getNumber())); // Zeigt die Zahl an
}
case BLACK -> {
button.setBackground(Color.BLACK);
button.setForeground(Color.WHITE);
button.setBackground(Color.BLACK); // Hintergrund schwarz
button.setForeground(Color.WHITE); // Textfarbe weiß
button.setText(String.valueOf(cell.getNumber())); // Zeigt die Zahl an
}
case WHITE -> {
button.setBackground(Color.WHITE);
button.setForeground(Color.BLACK);
button.setBackground(Color.WHITE); // Hintergrund weiß
button.setForeground(Color.BLACK); // Textfarbe schwarz
button.setText(String.valueOf(cell.getNumber())); // Zeigt die Zahl an
}
}
}
private void saveStateForUndo() {
stateManager.saveState(board.getNumbers());
}
private void addHighscore(int elapsedTime, String boardName) {
String playerName = JOptionPane.showInputDialog(this,
"Neuer Highscore! Bitte Namen eingeben:",
@ -306,10 +329,10 @@ public class GameBoard extends JPanel {
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
HitoriCell cell = board.getCell(i, j);
JButton button = (JButton) gamePanel.getComponent(i * board.getSize() + j); // Hole den Button
updateButtonState(button, cell); // Aktualisiere den Button basierend auf dem Zustand
JButton button = (JButton) gamePanel.getComponent(i * board.getSize() + j); // Holt den Button
updateButtonState(button, cell); // Aktualisiert den Button basierend auf dem Zustand
}
}
}
}
}

View File

@ -3,13 +3,11 @@ package PR2.HitoriSpiel.GUI;
import PR2.HitoriSpiel.Utils.HighscoreManager;
import PR2.HitoriSpiel.Utils.Setup;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.*;
import java.awt.*;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
// aktueller Stand
@ -20,11 +18,11 @@ public class HighscoreDialog extends JDialog {
public HighscoreDialog(JFrame parentFrame) {
super(parentFrame, "Highscoreliste", true);
this.highscoreManager = new HighscoreManager();
setLayout(new BorderLayout());
setSize(700, 400);
setSize(600, 400);
setLocationRelativeTo(parentFrame);
this.highscoreManager = new HighscoreManager();
setLayout(new BorderLayout());
Setup.stylePanel((JPanel) getContentPane()); // Hintergrundfarbe setzen
JLabel titleLabel = new JLabel("Highscores", SwingConstants.CENTER);
@ -32,84 +30,46 @@ public class HighscoreDialog extends JDialog {
Setup.styleLabel(titleLabel); // Schriftstil setzen
add(titleLabel, BorderLayout.NORTH);
String[] columnNames = {"Platz", "Name", "Zeit (Sek.)", "Spielfeld", "Durchschnittszeit des Spielfelds"};
String[] columnNames = {"Platz", "Name", "Zeit (Sek.)", "Spielfeld"};
tableModel = new DefaultTableModel(columnNames, 0);
JTable highscoreTable = new JTable(tableModel);
highscoreTable.setFillsViewportHeight(true);
highscoreTable.setEnabled(false); // Tabelle nur zur Anzeige
// Renderer, um Text in der Mitte der Zellen zu platzieren
DefaultTableCellRenderer centerRenderer = new DefaultTableCellRenderer();
centerRenderer.setHorizontalAlignment(SwingConstants.CENTER);
for (int i = 0; i < highscoreTable.getColumnCount(); i++) {
highscoreTable.getColumnModel().getColumn(i).setCellRenderer(centerRenderer);
}
// Automatische Spaltenbreitenanpassung und individuelle Breiten setzen
highscoreTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
highscoreTable.getColumnModel().getColumn(0).setPreferredWidth(50); // Platz
highscoreTable.getColumnModel().getColumn(1).setPreferredWidth(150); // Name
highscoreTable.getColumnModel().getColumn(2).setPreferredWidth(100); // Punkte
highscoreTable.getColumnModel().getColumn(3).setPreferredWidth(150); // Spielfeld
highscoreTable.getColumnModel().getColumn(4).setPreferredWidth(230); // Durchschnittszeit
JScrollPane scrollPane = new JScrollPane(highscoreTable);
add(scrollPane, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
// Schließen-Button
JButton closeButton = Setup.createButton("Schließen", 200, 40);
closeButton.setFont(new Font(closeButton.getFont().getName(), closeButton.getFont().getStyle(), 18)); // Schriftgröße ändern
closeButton.addActionListener(e -> dispose());
buttonPanel.add(closeButton);
// Button zum Löschen aller Einträge
JButton clearButton = Setup.createButton("Einträge löschen", 200, 40);
clearButton.setFont(new Font(closeButton.getFont().getName(), closeButton.getFont().getStyle(), 18));
clearButton.addActionListener(e -> {
int confirmation = JOptionPane.showConfirmDialog(
this,
"Möchten Sie wirklich alle Highscores löschen?",
"Highscores löschen",
JOptionPane.YES_NO_OPTION
);
if (confirmation == JOptionPane.YES_OPTION) {
highscoreManager.clearHighscores();
loadHighscoresWithAverages(); // Tabelle aktualisieren
JOptionPane.showMessageDialog(this, "Alle Highscores wurden gelöscht.");
}
});
buttonPanel.add(clearButton);
Setup.stylePanel(buttonPanel); // Hintergrundstil setzen
add(buttonPanel, BorderLayout.SOUTH);
// Highscores laden
loadHighscoresWithAverages();
loadHighscores();
}
private void loadHighscoresWithAverages() {
private void loadHighscores() {
tableModel.setRowCount(0); // Tabelle zurücksetzen
List<HighscoreManager.Highscore> highscores = highscoreManager.getSortedHighscores();
Map<String, Double> averageTimes = highscoreManager.getAverageTimesByBoard();
List<HighscoreManager.Highscore> highscores = highscoreManager.getHighscores();
int rank = 1;
highscores.sort(Comparator.comparingInt(HighscoreManager.Highscore::getScore)); // Sortierung: Kürzeste Zeit zuerst
int rank = 1; // Platznummer
for (HighscoreManager.Highscore highscore : highscores) {
double averageTime = averageTimes.getOrDefault(highscore.getBoardName(), 0.0);
tableModel.addRow(new Object[]{
rank++, // Platzierung
highscore.getPlayerName(), // Name
highscore.getScore(), // Punkte
highscore.getBoardName(), // Spielfeld
String.format("%.2f", averageTime) // Durchschnittszeit
rank++, // Platznummer hochzählen
highscore.getPlayerName(), // Name des Spielers
highscore.getScore(), // Zeit in Sekunden
highscore.getBoardName() // Name des Spielfelds
});
}
}
public void refreshHighscores() {
loadHighscoresWithAverages();
loadHighscores();
}
}

View File

@ -19,7 +19,7 @@ public class Main {
// Füge einen Shutdown-Hook hinzu, um Zustände bei Beendigung zu speichern
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
stateManager.saveStateToFile(); // Speichert die aktuellen Zustände in einer Datei
//stateManager.saveStateToFile(); // Speichert die aktuellen Zustände in einer Datei
System.out.println("Spielzustände wurden gespeichert.");
}));

View File

@ -1,12 +1,15 @@
package PR2.HitoriSpiel.Utils;
import java.io.*;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
public class HighscoreManager {
private static final String HIGHSCORE_FILE = "Files/highscores.txt";
private static final String HIGHSCORE_FILE = "highscores.txt";
private static final ReentrantLock fileLock = new ReentrantLock();
// Highscore-Datenstruktur
private final List<Highscore> highscoreList = new ArrayList<>();
@ -44,7 +47,7 @@ public class HighscoreManager {
// Neue Methode: Überprüfung, ob ein neuer Highscore erreicht wurde
public boolean isNewHighscore(int elapsedTime, String boardName) {
List<Highscore> highscores = getSortedHighscores();
List<Highscore> highscores = getHighscores();
// Prüfen, ob das Board bereits einen Highscore-Eintrag hat
for (Highscore highscore : highscores) {
@ -60,25 +63,16 @@ public class HighscoreManager {
// Highscores laden
private void loadHighscores() {
fileLock.lock();
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(HIGHSCORE_FILE)) {
if (inputStream == null) {
System.out.println("Datei highscores.txt nicht im Classpath gefunden.");
return;
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
highscoreList.clear(); // Liste zurücksetzen
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(",");
if (parts.length == 3) { // Name, Punkte, Spielfeld
String playerName = parts[0].trim();
int score = Integer.parseInt(parts[1].trim());
String boardName = parts[2].trim();
highscoreList.add(new Highscore(playerName, score, boardName));
}
try (BufferedReader reader = new BufferedReader(new FileReader(HIGHSCORE_FILE))) {
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(",");
if (parts.length == 3) { // Name, Punkte, Spielfeld
highscoreList.add(new Highscore(parts[0], Integer.parseInt(parts[1]), parts[2]));
}
}
} catch (FileNotFoundException e) {
System.out.println("Highscore-Datei nicht gefunden. Sie wird nach dem ersten Speichern erstellt.");
} catch (IOException | NumberFormatException e) {
System.err.println("Fehler beim Laden der Highscores: " + e.getMessage());
} finally {
@ -102,40 +96,11 @@ public class HighscoreManager {
}
}
// Highscores abrufen (sortiert nach kürzester Zeit)
public List<Highscore> getSortedHighscores() {
fileLock.lock();
try {
highscoreList.sort(Comparator.comparingInt(Highscore::getScore)); // Kürzeste Zeit zuerst
return new ArrayList<>(highscoreList); // Kopie der Liste zurückgeben
} finally {
fileLock.unlock();
}
// Highscores abrufen
public List<Highscore> getHighscores() {
return new ArrayList<>(highscoreList); // Modifizierbare Kopie zurückgeben
}
// Durchschnittszeit für jedes Spielfeld berechnen
public Map<String, Double> getAverageTimesByBoard() {
fileLock.lock();
try {
Map<String, List<Integer>> boardScores = new HashMap<>();
for (Highscore highscore : highscoreList) {
boardScores.computeIfAbsent(highscore.getBoardName(), k -> new ArrayList<>()).add(highscore.getScore());
}
Map<String, Double> averageTimes = new HashMap<>();
for (Map.Entry<String, List<Integer>> entry : boardScores.entrySet()) {
List<Integer> scores = entry.getValue();
double average = scores.stream().mapToInt(Integer::intValue).average().orElse(0.0);
averageTimes.put(entry.getKey(), average);
}
return averageTimes;
} finally {
fileLock.unlock();
}
}
// Löscht alle Highscores
public void clearHighscores() {
fileLock.lock();