Compare commits

...

29 Commits

Author SHA1 Message Date
Simona-Ioana Purdila f336f1b15b Merge remote-tracking branch 'origin/master' 2025-01-13 23:41:02 +01:00
Simona-Ioana Purdila 03fe6c16bd Javadocs, bessere Trennung von GUI, Fassade und Domain, andere Files angepasst 2025-01-13 23:40:36 +01:00
Simona-Ioana Purdila 78aa2b9c44 Javadocs, bessere Trennung von GUI, Fassade und Domain, andere Files angepasst 2025-01-13 23:39:24 +01:00
Simona-Ioana Purdila 5544ea5e65 Javadocs, bessere Trennung von GUI, Fassade und Domain, andere Files angepasst 2025-01-13 23:38:13 +01:00
Vickvick2002 d27c7ab4cd Klassendiagramm Datei 2025-01-13 18:08:09 +01:00
Vickvick2002 9984814f7f README Datei ergänzt 2025-01-13 18:06:04 +01:00
Vickvick2002 6d0e8a154c README Datei ergänzt 2025-01-07 10:09:06 +01:00
Vickvick2002 af277a5fab Letzte Änderungen 2025-01-07 09:56:46 +01:00
Simona-Ioana Purdila 4284c262e2 GUI Testklassen erstellt, außer für BoardLoader 2025-01-06 22:40:00 +01:00
Simona-Ioana Purdila 2bb0a5bbef GUI Testklassen erstellt, außer für BoardLoader 2025-01-06 22:39:30 +01:00
Simona-Ioana Purdila ed886565fb Aktuellsten Branch nach merge von Improvements_Ioana und Improvements_Vicky 2025-01-06 22:21:06 +01:00
Simona-Ioana Purdila f7a65be2c4 Merge remote-tracking branch 'origin/Improvements_Vick' into Improvements_Together
# Conflicts:
#	Hitori/src/main/highscores/highscores.txt
2025-01-06 22:18:49 +01:00
Simona-Ioana Purdila c474b457b6 JUnit Tests für Domain und Fassade 2025-01-06 22:13:32 +01:00
Vickvick2002 da801b75b0 Logik für Zeit beim Pausieren verbessert 2025-01-06 21:53:05 +01:00
Simona-Ioana Purdila 8da0e90a5c Anpassung der Spielfeldernamen (Endung .csv entfernt) 2025-01-06 21:26:59 +01:00
Simona-Ioana Purdila ffbd2dab10 Durchschnitszeit-Funktion verbessert 2025-01-06 20:09:07 +01:00
Simona-Ioana Purdila d88e35b4e2 Durchschnitszeit-Funktion verbessert 2025-01-06 19:49:08 +01:00
Simona-Ioana Purdila a4ce98a837 README.md aktualisiert 2025-01-06 19:39:15 +01:00
Simona-Ioana Purdila 2122bfc521 README.md aktualisiert 2025-01-06 16:12:00 +01:00
Simona-Ioana Purdila 2418e2aed7 HighscoreListe verschoben 2025-01-06 16:04:27 +01:00
Simona-Ioana Purdila e77359160d HighscoreListe verschoben 2025-01-06 16:04:14 +01:00
Simona-Ioana Purdila 5f41bda301 Fassade-Klassen verschoben 2025-01-06 14:35:13 +01:00
Simona-Ioana Purdila 30f11647bb Fassade-Klassen verschoben, Test-Klassen entfernt, Umbennenung einige Spielfelder 2025-01-06 13:05:54 +01:00
Simona-Ioana Purdila e7e306642f README.md Datei aktualisiert 2025-01-06 12:46:28 +01:00
Vickvick2002 b0f1c46a8c HighscoreLogik verbessert, für jedes Spielfeld 2025-01-05 19:45:05 +01:00
Vickvick2002 75f04b51cc Redo/Undo Logik implementiert 2025-01-05 18:56:42 +01:00
Simona-Ioana Purdila dcf9c95a99 Highscore Liste wird angezeigt, Eintrag speichern noch nicht richtig 2025-01-05 17:10:02 +01:00
Vickvick2002 f7868e042f Highscore Eintrag für Name hinzugefügt 2025-01-05 16:22:30 +01:00
Vickvick2002 eb30f5cd13 Änderungen für Redo und Undo Button vorgenommen 2025-01-05 15:18:52 +01:00
49 changed files with 2323 additions and 745 deletions

View File

@ -1,62 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>HitoriTeamProjekt</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- AssertJ Swing Test-Framework -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-swing</artifactId>
<version>3.17.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>PR2.HitoriSpiel.Main.Main</mainClass> <!-- Vollqualifizierter Name deiner Main-Klasse -->
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.24.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Compiler -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!-- JAR creation -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass> PR2.HitoriSpiel.Main.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<!-- Code coverage, cf.: target/site/jacoco -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Static code analysis, cf: target/site/pmd.html -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.26.0</version>
<configuration>
<failOnViolation>false</failOnViolation>
<printFailingErrors>true</printFailingErrors>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<!-- generate Javadocs via "mvn site" and find them in the site
folder -->
</plugins>
</reporting>
</project>

View File

@ -0,0 +1,9 @@
Ioana,25,Hitori4x4_leicht.csv,0
test,22,Hitori4x4_leicht.csv,0
Test2,72,Hitori5x5_leicht.csv,0
Test3,100,Hitori8x8_medium.csv,0
Test4,200,Hitori15x15_medium.csv,10
IOANA VERSUCH 156,105,Hitori5x5_leicht.csv,0
IOANA VERSUCH 439,44,Hitori5x5_leicht.csv,0
Yovu,46,Hitori4x4_leicht.csv,0
hi,18,Hitori4x4_leicht.csv,0

View File

@ -0,0 +1,95 @@
package PR2.HitoriSpiel.Domain;
/**
* Die Klasse Action repräsentiert eine Aktion im Hitori-Spiel, die eine Änderung
* an einer bestimmten Zelle des Spielfelds beschreibt, einschließlich der Koordinaten,
* des alten Zustands und des neuen Zustands.
*/
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
/**
* Erstellt ein neues Action-Objekt mit den angegebenen Koordinaten,
* dem alten Zustand und dem neuen Zustand.
*
* @param row Die Zeile, in der die Änderung erfolgt ist.
* @param col Die Spalte, in der die Änderung erfolgt ist.
* @param oldState Der alte Zustand der Zelle.
* @param newState Der neue 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;
}
/**
* Erstellt ein neues Action-Objekt mit den angegebenen Koordinaten
* und dem neuen Zustand; der alte Zustand wird auf "GRAY" gesetzt.
*
* @param row Die Zeile, in der die Änderung erfolgt ist.
* @param col Die Spalte, in der die Änderung erfolgt ist.
* @param newState Der neue Zustand der Zelle.
*/
// 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);
}
/**
* Berechnet den Hashcode für das Action-Objekt basierend
* auf den Attributen row, col, oldState und newState.
*
* @return Der berechnete Hashcode.
*/
@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

@ -1,4 +0,0 @@
package PR2.HitoriSpiel.Domain;
public class DomainTest {
}

View File

@ -3,7 +3,8 @@ package PR2.HitoriSpiel.Domain;
import java.util.List;
/**
* Represents the Hitori game board.
* Die Klasse HitoriBoard repräsentiert das Spielfeld des Hitori-Spiels.
* Sie enthält die Zellen des Spielfelds, die Lösungskonfiguration und den Namen des Spielfelds.
*/
public class HitoriBoard {
@ -12,6 +13,15 @@ public class HitoriBoard {
private final String boardName; // Name des Spielfelds
/**
* Konstruktor, der das Spielfeld mit den Zahlen, der Lösungskonfiguration
* und dem Namen des Spielfelds initialisiert.
*
* @param numbers 2D-Array mit den Zahlen des Spielfelds.
* @param solutionCoordinates Liste der Koordinaten, die zur Lösung gehören.
* @param boardName Name des Spielfelds.
*/
public HitoriBoard(int[][] numbers, List<String> solutionCoordinates, String boardName) {
this.board = new HitoriCell[numbers.length][numbers[0].length];
this.solutionCoordinates = solutionCoordinates;
@ -81,32 +91,6 @@ public class HitoriBoard {
}
}
/**
* Gibt das gesamte Spielfeld als 2D-Array zurück.
*
* @return 2D-Array mit den Zahlen des Spielfelds.
*/
public int[][] getNumbers() {
int[][] numbers = new int[board.length][board[0].length];
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
numbers[i][j] = board[i][j].getNumber();
}
}
return numbers;
}
/**
* Setzt die Zahlen des Spielfelds basierend auf einem 2D-Array.
*
* @param numbers 2D-Array mit den neuen Zahlen.
*/
public void setNumbers(int[][] numbers) {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
board[i][j].setNumber(numbers[i][j]);
}
}
}
}

View File

@ -1,17 +1,28 @@
package PR2.HitoriSpiel.Domain;
/**
* Represents a single cell on the Hitori board.
* Die Klasse HitoriCell repräsentiert eine einzelne Zelle auf dem Hitori-Spielfeld.
* Sie speichert die Zahl der Zelle und deren Zustand (GRAY, WHITE, BLACK).
*/
public class HitoriCell {
/**
* Enum zur Darstellung der möglichen Zustände einer Zelle.
*/
public enum CellState {
GRAY, WHITE, BLACK
}
private int number;
private CellState state;
/**
* Konstruktor, der eine Zelle mit einer Zahl erstellt und den Zustand auf GRAY setzt.
*
* @param number Die Zahl der Zelle.
*/
public HitoriCell(int number) {
this.number = number;
this.state = CellState.GRAY;

View File

@ -3,32 +3,44 @@ package PR2.HitoriSpiel.Domain;
import java.util.List;
/**
* Validates the Hitori board against the solution.
* Die Klasse HitoriValidator überprüft, ob das aktuelle Spielfeld des Hitori-Spiels korrekt gelöst ist,
* indem es mit der angegebenen Lösung verglichen wird.
*/
public class HitoriValidator {
private final HitoriBoard board;
/**
* Konstruktor, der den Validator mit einem Hitori-Spielfeld initialisiert.
*
* @param board Das zu überprüfende Hitori-Spielfeld.
*/
public HitoriValidator(HitoriBoard board) {
this.board = board;
}
/**
* Validates the current board against the solution.
* @param solutionCoordinates The coordinates of the correct black cells in "row,column" format.
* @return true if the current board matches the solution, false otherwise.
* Überprüft, ob das aktuelle Spielfeld der Lösung entspricht.
*
* @param solutionCoordinates Die Koordinaten der korrekten schwarzen Felder im Format "row,column".
* @return true, wenn das aktuelle Spielfeld der Lösung entspricht, andernfalls false.
*/
public boolean validateBoard(List<String> solutionCoordinates) {
// Überprüfe, ob alle Koordinaten der Lösung korrekt schwarz markiert sind
for (String coordinate : solutionCoordinates) {
String[] parts = coordinate.split(",");
int row = Integer.parseInt(parts[0]) - 1;
int col = Integer.parseInt(parts[1]) - 1;
if (board.getCell(row,col).getState() != HitoriCell.CellState.BLACK){
return false;
return false; // Ein Feld, das schwarz sein sollte, ist aber nicht
}
}
// Überprüfe alle Zellen des Spielfelds
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
HitoriCell cell = board.getCell(i, j);
@ -44,7 +56,7 @@ public class HitoriValidator {
}
}
return true;
return true; // Das Spielfeld entspricht der Lösung
}
}

View File

@ -0,0 +1,101 @@
package PR2.HitoriSpiel.Domain;
import java.io.*;
import java.nio.file.*;
import java.util.Stack;
/**
* Die Klasse StateFileManager verwaltet das Speichern und Laden der Zustände des Undo- und Redo-Stacks
* in eine Datei. Sie stellt sicher, dass der Speicherort existiert und ermöglicht so die Persistenz der Spielzustände.
*/
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 der Undo- und Redo-Stacks in der Datei.
*
* @param undoStack Der aktuelle Undo-Stack.
* @param redoStack Der aktuelle Redo-Stack.
*/
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 der Undo- und Redo-Stacks aus der Datei.
*
* @param undoStack Der Stack, in den die Undo-Zustände geladen werden.
* @param redoStack Der Stack, in den die Redo-Zustände geladen werden.
*/
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

@ -1,37 +0,0 @@
package PR2.HitoriSpiel.Domain;
import java.util.Stack;
public class StateManager {
private final Stack<int[][]> undoStack = new Stack<>();
private final Stack<int[][]> redoStack = new Stack<>();
public void saveState(int[][] state) {
undoStack.push(copyArray(state));
redoStack.clear(); // Redo-Stack leeren, da ein neuer Zustand hinzugefügt wurde
}
public int[][] undo(int[][] currentState) {
if (!undoStack.isEmpty()) {
redoStack.push(copyArray(currentState)); // Aktuellen Zustand im Redo-Stack speichern
return undoStack.pop();
}
return null; // Kein Zustand zum Zurücksetzen
}
public int[][] redo(int[][] currentState) {
if (!redoStack.isEmpty()) {
undoStack.push(copyArray(currentState)); // Aktuellen Zustand im Undo-Stack speichern
return redoStack.pop();
}
return null; // Kein Zustand zum Wiederherstellen
}
private int[][] copyArray(int[][] original) {
int[][] copy = new int[original.length][];
for (int i = 0; i < original.length; i++) {
copy[i] = original[i].clone();
}
return copy;
}
}

View File

@ -0,0 +1,24 @@
package PR2.HitoriSpiel.Fassade;
import PR2.HitoriSpiel.Domain.HitoriBoard;
import java.util.List;
/**
* Die Klasse BoardManager dient als Fassade, um ein HitoriBoard-Objekt zu erstellen
* und die Spielfeld-Initialisierung zu kapseln.
*/
public class BoardManager {
/**
* Erstellt und gibt ein HitoriBoard-Objekt basierend auf den angegebenen Parametern zurück.
*
* @param boardData Die Daten des Spielfelds.
* @param solutionCoordinates Die Koordinaten der Lösung.
* @param selectedFile Die ausgewählte Datei mit den Spielfeldinformationen.
* @return Das erstellte HitoriBoard-Objekt.
*/
public HitoriBoard createBoard(int[][] boardData, List<String> solutionCoordinates, String selectedFile) {
return new HitoriBoard(boardData, solutionCoordinates, selectedFile);
}
}

View File

@ -1,4 +0,0 @@
package PR2.HitoriSpiel.Fassade;
public class FassadeTest {
}

View File

@ -0,0 +1,478 @@
package PR2.HitoriSpiel.Fassade;
import PR2.HitoriSpiel.Domain.Action;
import PR2.HitoriSpiel.Domain.HitoriBoard;
import PR2.HitoriSpiel.Domain.HitoriValidator;
import PR2.HitoriSpiel.Domain.HitoriCell;
import PR2.HitoriSpiel.GUI.PauseMenu;
import PR2.HitoriSpiel.GUI.StartMenu;
import javax.swing.*;
import java.awt.*;
/**
* Die Klasse GameBoard repräsentiert das Spielfeld des Hitori-Spiels mit zugehörigen Steuerungselementen.
* Sie bietet Funktionalitäten wie Timer, Spielfeldvalidierung, Undo/Redo und Highscore-Management.
*/
public class GameBoard extends JPanel {
private final HitoriBoard board; // Das Hitori-Spielfeld-Objekt
private Timer timer; // Timer für die Spielzeitmessung
private long startTime; // Startzeit des Timers
private JLabel timerLabel; // Anzeigeelement für die aktuelle Spielzeit
private long pausedTime = 0; // Zeit, die beim Pausieren bereits abgelaufen ist
private JPanel gamePanel; // Das Spielfeld als JPanel
private final StateManager stateManager = new StateManager(); // Manager für Undo/Redo-Operationen
private long elapsedTime = 0;
/**
* Konstruktor, der das Spielfeld und die Steuerungselemente initialisiert.
*
* @param board Das Hitori-Spielfeld.
*/
public GameBoard(HitoriBoard board) {
this.board = board;
setLayout(new BorderLayout());
// Timer-Label hinzufügen
initializeTimerLabel();
// Timer starten
startTimer();
// Spielfeld erstellen
gamePanel = createGamePanel();
add(gamePanel, BorderLayout.CENTER); // Spielfeld im Zentrum hinzufügen
// Kontroll-Buttons unten hinzufügen
JPanel controlPanel = createControlPanel();
add(controlPanel, BorderLayout.SOUTH); // Buttons am unteren Rand platzieren
}
/**
* Initialisiert das Timer-Label.
*/
private void initializeTimerLabel() {
timerLabel = new JLabel("Zeit: 0 Sekunden");
timerLabel.setHorizontalAlignment(SwingConstants.CENTER);
Setup.styleLabel(timerLabel);
add(timerLabel, BorderLayout.NORTH);
}
/**
* Erstellt das Steuerungspanel mit Kontroll-Buttons.
*
* @return Das erstellte Panel.
*/
private JPanel createControlPanel() {
JPanel controlPanel = new JPanel();
controlPanel.add(createResetButton());
controlPanel.add(createUndoButton());
controlPanel.add(createRedoButton());
controlPanel.add(createValidateButton());
controlPanel.add(createPauseButton());
return controlPanel;
}
/**
* Erstellt den "Zurücksetzen"-Button.
*
* @return Der Reset-Button.
*/
private JButton createResetButton() {
JButton resetButton = Setup.createGameBoardButton("Zurücksetzen", 150, 30);
resetButton.addActionListener(e -> {
//saveStateForUndo();
resetBoard(); // Spielfeld zurücksetzen
});
return resetButton;
}
/**
* Erstellt den "Undo"-Button.
*
* @return Der Undo-Button.
*/
private JButton createUndoButton() {
JButton undoButton = Setup.createGameBoardButton("Undo", 80, 30);
undoButton.addActionListener(e -> {
Action action = stateManager.undo(); // Letzte Aktion rückgängig machen
if (action != null) {
HitoriCell cell = board.getCell(action.getRow(), action.getCol()); // Betroffene Zelle abrufen
cell.setState(HitoriCell.CellState.valueOf(action.getOldState())); // Zustand der Zelle zurücksetzen
JButton button = (JButton) gamePanel.getComponent(action.getRow() * board.getSize() + action.getCol());
updateButtonState(button, cell); // Button-Status aktualisieren
} else {
JOptionPane.showMessageDialog(this, "Kein Zustand zum Zurücksetzen vorhanden.", "Undo", JOptionPane.INFORMATION_MESSAGE);
}
});
return undoButton;
}
/**
* Erstellt den "Redo"-Button.
*
* @return Der Redo-Button.
*/
private JButton createRedoButton() {
JButton redoButton = Setup.createGameBoardButton("Redo", 80, 30);
redoButton.addActionListener(e -> {
Action action = stateManager.redo(); // Letzte rückgängig gemachte Aktion wiederherstellen
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);
}
});
return redoButton;
}
/**
* Erstellt den "Lösen"-Button.
*
* @return Der Validate-Button.
*/
private JButton createValidateButton() {
JButton validateButton = Setup.createGameBoardButton("Lösen", 80, 30);
validateButton.addActionListener(e -> {
if (validateCurrentBoard()) { // Prüfen, ob das Spielfeld korrekt gelöst ist
stopTimer(); // Timer anhalten
handleHighscore(); // Highscore bearbeiten
} else {
JOptionPane.showMessageDialog(this, "Das Spielfeld enthält Fehler!", "Fehler", JOptionPane.ERROR_MESSAGE);
}
});
return validateButton;
}
/**
* Erstellt den "Pause"-Button.
*
* @return Der Pause-Button.
*/
private JButton createPauseButton() {
JButton pauseButton = Setup.createGameBoardButton("Pause", 80, 30);
pauseButton.addActionListener(e -> {
stopTimer();
showPauseMenu();
});
return pauseButton;
}
/**
* Validiert das aktuelle Spielfeld gegen die Lösung.
*
* @return true, wenn das Spielfeld korrekt ist, sonst false.
*/
public boolean validateCurrentBoard() {
HitoriValidator validator = new HitoriValidator(board);
return validator.validateBoard(board.getSolutionCoordinates()); // Prüfen, ob das Spielfeld korrekt ist
}
/**
* Verarbeitet die Highscore-Logik, einschließlich der Überprüfung, ob ein neuer Highscore erreicht wurde,
* und speichert den Highscore, wenn der Spieler seinen Namen eingibt.
*/
private void handleHighscore() {
String boardName = board.getBoardName();
int errors = 0; // Aktuelle Fehlerzahl (falls vorhanden, muss noch angepasst werden)
HighscoreManager manager = new HighscoreManager();
boolean isNewHighscore = manager.isNewHighscore((int)elapsedTime, boardName); // Prüfen, ob ein neuer Highscore erreicht wurde
// Zeige ein Dialogfenster zur Namenseingabe
String playerName = JOptionPane.showInputDialog(this,
isNewHighscore
? "Neuer Highscore! Bitte geben Sie Ihren Namen ein:"
: "Das Spielfeld ist korrekt gelöst! Bitte geben Sie Ihren Namen ein:",
"Highscore speichern",
JOptionPane.PLAIN_MESSAGE);
if (playerName == null || playerName.trim().isEmpty()) {
JOptionPane.showMessageDialog(this, "Name wurde nicht eingegeben. Kein Highscore gespeichert.", "Info", JOptionPane.INFORMATION_MESSAGE);
return;
}
// Speichere den Highscore
manager.addHighscore(playerName, (int)elapsedTime, boardName, errors);
JOptionPane.showMessageDialog(this,
isNewHighscore
? "Glückwunsch! Dein Highscore wurde gespeichert!"
: "Dein Eintrag wurde gespeichert!",
"Highscore",
JOptionPane.INFORMATION_MESSAGE);
// Kehre ins Hauptmenü zurück
returnToMainMenu();
}
/**
* Zeigt das Pausenmenü an.
*/
private void showPauseMenu() {
stopTimer(); // Timer pausieren
PauseMenu pauseMenu = new PauseMenu(
(JFrame) SwingUtilities.getWindowAncestor(this),
this, // Das aktuelle GameBoard-Objekt übergeben
e -> startTimer(), // Spiel fortsetzen
e -> returnToMainMenu(), // Zum Hauptmenü
e -> System.exit(0) // Spiel beenden
);
pauseMenu.setVisible(true);
}
/**
* Erstellt einen Button für eine Zelle des Spielfelds.
*
* @param cell Die Zelle.
* @param row Die Zeile der Zelle.
* @param col Die Spalte der Zelle.
* @return Der Button.
*/
private JButton createCellButton(HitoriCell cell, int row, int col) {
JButton button = new JButton(String.valueOf(cell.getNumber()));
button.setBackground(Color.LIGHT_GRAY);
button.addActionListener(e -> {
toggleCellState(cell, row, col);
updateButtonState(button, cell);
});
return button;
}
/**
* Schaltet den Zustand einer Zelle um.
*
* @param cell Die zu toggelnde Zelle.
* @param row Die Zeile der Zelle.
* @param col Die Spalte der Zelle.
*/
public 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);
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);
}
/**
* Aktualisiert die Darstellung eines Buttons basierend auf dem Zustand der Zelle.
*
* @param button Der Button.
* @param cell Die Zelle.
*/
private void updateButtonState(JButton button, HitoriCell cell) {
switch (cell.getState()) {
case GRAY -> {
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); // Hintergrund schwarz
button.setForeground(Color.WHITE); // Textfarbe weiß
button.setText(String.valueOf(cell.getNumber())); // Zeigt die Zahl an
}
case WHITE -> {
button.setBackground(Color.WHITE); // Hintergrund weiß
button.setForeground(Color.BLACK); // Textfarbe schwarz
button.setText(String.valueOf(cell.getNumber())); // Zeigt die Zahl an
}
}
}
/**
* Kehrt ins Hauptmenü zurück.
*/
private void returnToMainMenu() {
/// Eltern-Frame abrufen
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
if (parentFrame != null) {
// Hauptmenü erstellen und im Frame setzen
StartMenu startMenu = new StartMenu(parentFrame);
parentFrame.getContentPane().removeAll(); // Entferne alle aktuellen Inhalte
parentFrame.add(startMenu); // Füge das Hauptmenü hinzu
parentFrame.revalidate(); // Layout aktualisieren
parentFrame.repaint(); // Oberfläche neu zeichnen
} else {
System.err.println("Fehler: Kein übergeordnetes JFrame gefunden.");
}
}
/**
* Setzt das Spielfeld zurück.
*/
public void resetBoard() {
// Spiellogik zurücksetzen
board.resetBoard();
// Spielfeld (CENTER) entfernen und neu erstellen
if (getLayout() instanceof BorderLayout) {
BorderLayout layout = (BorderLayout) getLayout();
// Komponente im CENTER entfernen
Component centerComponent = layout.getLayoutComponent(BorderLayout.CENTER);
if (centerComponent != null) {
remove(centerComponent);
}
// Neues Spielfeld hinzufügen
add(createGamePanel(), BorderLayout.CENTER);
// Kontroll-Buttons (SOUTH) entfernen und neu hinzufügen
Component southComponent = layout.getLayoutComponent(BorderLayout.SOUTH);
if (southComponent != null) {
remove(southComponent);
}
add(createControlPanel(), BorderLayout.SOUTH);
}
// Oberfläche aktualisieren
revalidate();
repaint();
}
// Aktualisiert das Spielfeld
private void refreshBoard() {
remove(1); // Entferne das aktuelle Spielfeld im CENTER
if (getLayout() instanceof BorderLayout) {
BorderLayout layout = (BorderLayout) getLayout();
// Komponente im CENTER entfernen
Component centerComponent = layout.getLayoutComponent(BorderLayout.CENTER);
if (centerComponent != null) {
remove(centerComponent);
}
// Neues Spielfeld hinzufügen
add(createGamePanel(), BorderLayout.CENTER);
// Kontroll-Buttons (SOUTH) entfernen und neu hinzufügen
Component southComponent = layout.getLayoutComponent(BorderLayout.SOUTH);
if (southComponent != null) {
remove(southComponent);
}
add(createControlPanel(), BorderLayout.SOUTH);
}
// Oberfläche aktualisieren
revalidate();
repaint();
}
/**
* Erstellt das Spielfeld als JPanel.
*
* @return Das erstellte JPanel mit Spielfeldzellen.
*/
private JPanel createGamePanel() {
JPanel gamePanel = new JPanel(new GridLayout(board.getSize(), board.getSize()));
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
HitoriCell cell = board.getCell(i, j);
JButton button = createCellButton(cell, i, j);
gamePanel.add(button);
}
}
System.out.println("Spielpanel erstellt mit " + (board.getSize() * board.getSize()) + " Buttons.");
return gamePanel;
}
private void updateBoard() {
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); // Holt den Button
updateButtonState(button, cell); // Aktualisiert den Button basierend auf dem Zustand
}
}
}
// --------Hilfsmethoden---------
// Startet den Timer
private void startTimer() {
if (timer == null) { // Timer nur beim ersten Start erstellen
timer = new Timer(1000, e -> {
elapsedTime = (System.currentTimeMillis() - startTime + pausedTime) / 1000; // Zeit berechnen
timerLabel.setText("Zeit: " + elapsedTime + " Sekunden");
});
}
startTime = System.currentTimeMillis(); // Startzeit setzen, bevor der Timer gestartet wird
timer.start(); // Timer starten
System.out.println("Startzeit: " + startTime);
System.out.println("Pausierte Zeit: " + pausedTime);
}
// Hält den Timer an
private void stopTimer() {
if (timer != null && timer.isRunning()) {
timer.stop(); // Timer anhalten
pausedTime += System.currentTimeMillis() - startTime; // Zeit während der Pause speichern
System.out.println("Timer gestoppt. Pausierte Zeit: " + pausedTime);
}
}
// Setzt den Timer fort
public void resumeTimer() {
startTime = System.currentTimeMillis(); // Startzeit neu setzen
startTimer(); // Timer erneut starten
System.out.println("Timer fortgesetzt. Startzeit: " + startTime + ", Pausierte Zeit: " + pausedTime);
}
// Setzt den Timer zurück
private void resetTimer() {
if (timer != null) {
timer.stop();
}
startTime = 0;
pausedTime = 0;
timerLabel.setText("Zeit: 0 Sekunden");
}
}

View File

@ -0,0 +1,16 @@
package PR2.HitoriSpiel.Fassade;
// TODO: Spiel fortsetzen
public class GameManager {
private static GameBoard gameBoard;
public static void setGameBoard(GameBoard gb) {
gameBoard = gb;
}
public static void resumeTimer() {
if (gameBoard != null) {
gameBoard.resumeTimer();
}
}
}

View File

@ -0,0 +1,236 @@
package PR2.HitoriSpiel.Fassade;
import java.io.*;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
/**
* Die Klasse HighscoreManager verwaltet die Highscores für das Hitori-Spiel.
* Sie unterstützt das Hinzufügen, Speichern, Laden und Analysieren von Highscore-Daten
* sowie die Berechnung von Durchschnittszeiten pro Spielfeld.
*/
public class HighscoreManager {
private static final String HIGHSCORE_FILE = "Hitori/src/main/highscores/highscores.txt";
private static final ReentrantLock fileLock = new ReentrantLock();
// Highscore-Datenstruktur
private final List<Highscore> highscoreList = new ArrayList<>();
/**
* Konstruktor, der Highscores aus der Datei lädt.
*/
public HighscoreManager() {
loadHighscores();
}
/**
* Überprüft, ob die gegebene Zeit ein neuer Highscore für das angegebene Spielfeld ist.
*
* @param elapsedTime Die benötigte Zeit.
* @param boardName Der Name des Spielfelds.
* @return true, wenn es ein neuer Highscore ist, sonst false.
*/
public boolean isNewHighscore(int elapsedTime, String boardName) {
// Filtere die Highscores für das gegebene Spielfeld
List<Highscore> highscoresForBoard = getHighscoresForBoard(boardName);
// Falls es keinen Highscore für das Spielfeld gibt, ist es automatisch ein neuer Highscore
if (highscoresForBoard.isEmpty()) {
return true;
}
// Finde die kürzeste Zeit im Highscore
int bestTime = highscoresForBoard.stream()
.mapToInt(Highscore::getTime)
.min()
.orElse(Integer.MAX_VALUE);
// Überprüfe, ob die aktuelle Zeit besser ist
return elapsedTime < bestTime;
}
/**
* Fügt einen neuen Highscore hinzu und speichert die aktualisierten Highscores.
*
* @param playerName Der Name des Spielers.
* @param time Die benötigte Zeit.
* @param boardName Der Name des Spielfelds.
* @param errors Die Anzahl der Fehler.
*/
public synchronized void addHighscore(String playerName, int time, String boardName, int errors) {
highscoreList.add(new Highscore(playerName, time, boardName, errors));
saveHighscores();
}
/**
* Gibt die Highscores für ein bestimmtes Spielfeld zurück, sortiert nach kürzester Zeit.
*
* @param boardName Der Name des Spielfelds.
* @return Eine Liste der Top 10 Highscores für das Spielfeld.
*/
public List<Highscore> getHighscoresForBoard(String boardName) {
return highscoreList.stream()
.filter(highscore -> highscore.getBoardName().equals(boardName))
.sorted(Comparator.comparingInt(Highscore::getTime)) // Sortiere nach kürzester Zeit
.limit(10) // Zeige nur die Top 10
.toList();
}
/**
* Speichert die Highscores in einer Datei.
*/
private void saveHighscores() {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(HIGHSCORE_FILE))) {
for (Highscore highscore : highscoreList) {
writer.write(highscore.toString());
writer.newLine();
}
} catch (IOException e) {
System.err.println("Fehler beim Speichern der Highscores: " + e.getMessage());
}
}
/**
* Lädt die Highscores aus einer Datei.
*/
private void loadHighscores() {
try (BufferedReader reader = new BufferedReader(new FileReader(HIGHSCORE_FILE))) {
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(",");
// Validierung der Datenstruktur
if (parts.length < 4) {
System.err.println("Fehlerhafter Eintrag in Highscore-Datei: " + line);
String[] fixedParts = new String[4];
System.arraycopy(parts, 0, fixedParts, 0, parts.length);
fixedParts[3] = "0"; // Standardwert für fehlende Fehleranzahl
parts = fixedParts;
}
try {
String playerName = parts[0];
int time = Integer.parseInt(parts[1]);
String boardName = parts[2];
int errors = Integer.parseInt(parts[3]);
// Füge den Eintrag der Liste hinzu
highscoreList.add(new Highscore(playerName, time, boardName, errors));
} catch (NumberFormatException e) {
System.err.println("Ungültiges Zahlenformat in Zeile: " + line);
}
}
} catch (IOException e) {
System.out.println("Highscore-Datei nicht gefunden. Eine neue wird erstellt.");
}
}
/**
* Berechnet die Durchschnittszeiten für jedes Spielfeld.
*
* @return Eine Map mit Spielfeldnamen und deren Durchschnittszeiten.
*/
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();
}
}
/**
* Gibt alle Highscores zurück.
*
* @return Eine Liste aller Highscores.
*/
public List<Highscore> getHighscores() {
return new ArrayList<>(highscoreList); // Modifizierbare Kopie zurückgeben
}
/**
* Löscht alle Highscores und aktualisiert die Datei.
*/
public void clearHighscores() {
fileLock.lock();
try {
highscoreList.clear(); // Liste leeren
saveHighscores(); // Datei aktualisieren
} finally {
fileLock.unlock();
}
}
/**
* Innere Klasse, die einen Highscore repräsentiert.
*/
public static class Highscore {
private final String playerName;
private final int time;
private final String boardName;
private final int errors;
/**
* Konstruktor für einen Highscore-Eintrag.
*
* @param playerName Der Name des Spielers.
* @param score Die benötigte Zeit.
* @param boardName Der Name des Spielfelds.
* @param errors Die Anzahl der Fehler.
*/
public Highscore(String playerName, int score, String boardName, int errors) {
this.playerName = playerName;
this.time = score;
this.boardName = boardName;
this.errors = errors;
}
public String getPlayerName() {
return playerName;
}
public int getTime() {
return time;
}
public String getBoardName() {
return boardName;
}
public int getErrors() {
return errors;
}
public int getScore() {return time;}
@Override
public String toString() {
return playerName + "," + time + "," + boardName + "," + errors;
}
}
}

View File

@ -1,13 +1,23 @@
package PR2.HitoriSpiel.Domain;
package PR2.HitoriSpiel.Fassade;
import java.io.*;
import java.util.*;
/**
* Utility class to load the solution from a CSV file.
* Die Klasse HitoriSolutionLoader ist eine Hilfsklasse zum Laden der Lösung für ein Hitori-Spielfeld aus einer CSV-Datei.
* Die Lösung wird im Abschnitt "//Lösung" der Datei erwartet und enthält die Koordinaten der schwarzen Zellen.
*/
public class HitoriSolutionLoader {
/**
* Lädt die Lösung für ein Hitori-Spielfeld aus einer Ressourcendatei.
*
* @param resourcePath Der Pfad zur Ressourcendatei.
* @return Eine Liste der Koordinaten der schwarzen Zellen im Format "row,column".
* @throws IOException Wenn die Datei nicht gefunden wird oder ein Lesefehler auftritt.
*/
public static List<String> loadSolution(String resourcePath) throws IOException {
List<String> solutionCoordinates = new ArrayList<>();
boolean solutionSectionFound = false;

View File

@ -1,10 +1,14 @@
package PR2.HitoriSpiel.Utils;
package PR2.HitoriSpiel.Fassade;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
* Die Klasse Setup bietet zentrale Methoden und Konstanten zur Gestaltung und Vereinheitlichung der GUI-Komponenten für das Hitori-Spiel.
*/
public class Setup {
// Standardfarben

View File

@ -0,0 +1,76 @@
package PR2.HitoriSpiel.Fassade;
import PR2.HitoriSpiel.Domain.Action;
import PR2.HitoriSpiel.Domain.StateFileManager;
import java.io.*;
import java.util.Stack;
/**
* Die Klasse StateManager verwaltet die Undo- und Redo-Funktionalität für das Hitori-Spiel
* und speichert Zustandsänderungen in einer Datei.
*/
public class StateManager implements Serializable {
private final Stack<Action> undoStack = new Stack<>();
private final Stack<Action> redoStack = new Stack<>();
/**
* Speichert eine neue Aktion im Undo-Stack und leert den Redo-Stack.
* Speichert zudem den aktuellen Zustand in einer Datei.
*
* @param row Die Zeile der Aktion.
* @param col Die Spalte der Aktion.
* @param oldState Der alte Zustand.
* @param newState Der neue Zustand.
*/
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
}
/**
* Führt eine Undo-Aktion aus, verschiebt die Aktion in den Redo-Stack
* und speichert den aktuellen Zustand in einer Datei.
*
* @return Die rückgängig gemachte Aktion oder null, wenn der Undo-Stack leer ist.
*/
public Action undo() {
if (!undoStack.isEmpty()) {
Action action = undoStack.pop();
redoStack.push(action);
StateFileManager.saveState(undoStack, redoStack); // Aktuellen Zustand speichern
return action;
}
return null;
}
/**
* Führt eine Redo-Aktion aus, verschiebt die Aktion in den Undo-Stack
* und speichert den aktuellen Zustand in einer Datei.
*
* @return Die wiederhergestellte Aktion oder null, wenn der Redo-Stack leer ist.
*/
public Action redo() {
if (!redoStack.isEmpty()) {
Action action = redoStack.pop();
undoStack.push(action);
StateFileManager.saveState(undoStack, redoStack); // Aktuellen Zustand speichern
return action;
}
return null;
}
/**
* Lädt die Undo- und Redo-Stacks aus einer Datei, um den letzten gespeicherten Zustand wiederherzustellen.
*/
public void loadStateFromFile() {
StateFileManager.loadState(undoStack, redoStack); // Stacks aus Datei laden
}
}

View File

@ -2,70 +2,94 @@ package PR2.HitoriSpiel.GUI;
import java.io.*;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.*;
import java.util.jar.*;
/**
* Die Klasse BoardLoader lädt Spielfeld-Dateien für das Hitori-Spiel aus dem Dateisystem oder einem JAR-Archiv
* und bietet Funktionen zum Parsen der Dateien in ein 2D-Array.
*/
public class BoardLoader {
public static class BoardData {
private final int[][] board;
private final List<String> solution;
/**
* Lädt eine Liste von Spielfelddateien aus dem Verzeichnis oder dem JAR-Archiv.
* Sortiert die Dateien nach Schwierigkeitsgrad (leicht, medium) und Größe.
*
* @return Eine Liste der gefundenen Spielfelddateien.
*/
public BoardData(int[][] board, List<String> solution) {
this.board = board;
this.solution = solution;
}
public static List<String> loadBoardsAsList() {
List<String> boardFiles = new ArrayList<>();
try {
// Zugriff auf die Ressource
var resource = BoardLoader.class.getClassLoader().getResource("persistent/Hitori_Spielfelder/");
if (resource == null) {
throw new IOException("Das Verzeichnis 'persistent/Hitori_Spielfelder/' wurde nicht gefunden.");
}
public int[][] getBoard() {
return board;
}
// Unterscheide zwischen Dateisystem und JAR-Umgebung
if ("file".equals(resource.getProtocol())) {
// Entwicklungsmodus: Zugriff auf das Dateisystem
File directory = new File(resource.toURI());
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile() && file.getName().contains("_") && file.getName().endsWith(".csv")) {
boardFiles.add(file.getName());
}
}
} else {
System.err.println("Das Verzeichnis ist leer: " + directory.getAbsolutePath());
}
} else if ("jar".equals(resource.getProtocol())) {
// Produktionsmodus: Zugriff im JAR
String path = resource.getPath().substring(5, resource.getPath().indexOf("!")); // JAR-Pfad extrahieren
try (JarFile jar = new JarFile(URLDecoder.decode(path, "UTF-8"))) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
String name = entries.nextElement().getName();
if (name.startsWith("persistent/Hitori_Spielfelder/") && name.endsWith(".csv")) {
boardFiles.add(name.substring(name.lastIndexOf("/") + 1));
}
}
}
} else {
throw new IOException("Unbekanntes Protokoll: " + resource.getProtocol());
}
public List<String> getSolution() {
return solution;
}
}
// Sortiere die Dateien nach Schwierigkeitsgrad und Größe
boardFiles.sort((a, b) -> {
// 1. Schwierigkeitsgrad
boolean aIsLeicht = a.contains("leicht");
boolean bIsLeicht = b.contains("leicht");
boolean aIsMedium = a.contains("medium");
boolean bIsMedium = b.contains("medium");
public static List<String> loadBoardsAsList() {
List<String> boardFiles = new ArrayList<>();
try {
var resource = BoardLoader.class.getClassLoader().getResource("persistent/Hitori_Spielfelder/");
if (resource == null) {
throw new IOException("Das Verzeichnis 'persistent/Hitori_Spielfelder/' wurde nicht gefunden.");
}
if (aIsLeicht && bIsMedium) return -1;
if (aIsMedium && bIsLeicht) return 1;
if (resource.getProtocol().equals("file")) {
// Zugriff auf das Dateisystem (IDE-Umgebung)
File directory = new File(resource.toURI());
for (File file : directory.listFiles()) {
if (file.isFile() && file.getName().endsWith(".csv")) {
boardFiles.add(file.getName());
}
}
} else if (resource.getProtocol().equals("jar")) {
// Zugriff im JAR
String path = resource.getPath().substring(5, resource.getPath().indexOf("!")); // JAR-Pfad extrahieren
try (JarFile jar = new JarFile(URLDecoder.decode(path, "UTF-8"))) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
String name = entries.nextElement().getName();
if (name.startsWith("persistent/Hitori_Spielfelder/") && name.endsWith(".csv")) {
boardFiles.add(name.substring(name.lastIndexOf("/") + 1));
}
}
}
}
} catch (Exception e) {
System.err.println("Fehler beim Laden der Spielfelder: " + e.getMessage());
}
return boardFiles;
}
// 2. Größe (z. B. 8x8, 10x10)
int sizeA = extractSize(a);
int sizeB = extractSize(b);
return Integer.compare(sizeA, sizeB);
});
} catch (Exception e) {
System.err.println("Fehler beim Laden der Spielfelder: " + e.getMessage());
}
return boardFiles;
}
/**
* Lädt ein Spielfeld aus einer Ressourcendatei und gibt es als 2D-Array zurück.
*
* @param resourcePath Der Pfad zur Ressourcendatei.
* @return Ein 2D-Array, das das geladene Spielfeld darstellt.
* @throws IOException Wenn die Datei nicht gefunden oder fehlerhaft ist.
*/
public static int[][] loadBoard(String resourcePath) throws IOException {
public static int[][] loadBoard(String resourcePath) throws IOException {
List<int[]> rows = new ArrayList<>();
try (InputStream inputStream = BoardLoader.class.getResourceAsStream(resourcePath);
@ -116,4 +140,18 @@ public class BoardLoader {
return rows.toArray(new int[0][0]);
}
//--------------------------Hilfsmethoden----------------------------------
// Hilfsmethode zum Extrahieren der Größe aus dem Dateinamen
private static int extractSize(String fileName) {
try {
String sizePart = fileName.split("_")[0].replaceAll("\\D", ""); // Entfernt Nicht-Ziffern
return Integer.parseInt(sizePart);
} catch (Exception e) {
return Integer.MAX_VALUE; // Fehlerhafte Dateinamen landen am Ende
}
}
}

View File

@ -1,314 +0,0 @@
package PR2.HitoriSpiel.GUI;
import PR2.HitoriSpiel.Domain.HitoriValidator;
import PR2.HitoriSpiel.Domain.StateManager;
import PR2.HitoriSpiel.Domain.HitoriBoard;
import PR2.HitoriSpiel.Domain.HitoriCell;
import PR2.HitoriSpiel.Utils.HighscoreManager;
import PR2.HitoriSpiel.Utils.Setup;
import javax.swing.*;
import java.awt.*;
import java.io.FileWriter;
import java.io.IOException;
public class GameBoard extends JPanel {
private final HitoriBoard board;
private Timer timer;
private long startTime;
private JLabel timerLabel;
private final StateManager stateManager = new StateManager();
public GameBoard(HitoriBoard board) {
this.board = board;
setLayout(new BorderLayout());
// Timer-Label oben hinzufügen
timerLabel = new JLabel("Zeit: 0 Sekunden");
timerLabel.setHorizontalAlignment(SwingConstants.CENTER);
Setup.styleLabel(timerLabel);
add(timerLabel, BorderLayout.NORTH);
// Timer starten
startTimer();
// Spielfeld erstellen
JPanel gamePanel = new JPanel(new GridLayout(board.getSize(), board.getSize()));
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
HitoriCell cell = board.getCell(i, j);
JButton button = createCellButton(cell, i, j);
gamePanel.add(button);
}
}
add(gamePanel, BorderLayout.CENTER);
// Kontroll-Buttons unten hinzufügen
JPanel controlPanel = createControlPanel();
add(controlPanel, BorderLayout.SOUTH);
}
private void startTimer() {
startTime = System.currentTimeMillis();
timer = new Timer(1000, e -> {
long elapsedTime = (System.currentTimeMillis() - startTime) / 1000;
timerLabel.setText("Zeit: " + elapsedTime + " Sekunden");
});
timer.start();
}
private void stopTimer() {
if (timer != null) {
timer.stop();
}
}
private JPanel createControlPanel() {
JPanel controlPanel = new JPanel();
JButton resetButton = Setup.createGameBoardButton("Zurücksetzen", 150, 30);
resetButton.addActionListener(e -> {
saveStateForUndo();
resetBoard();
refreshBoard();
});
JButton undoButton = Setup.createGameBoardButton("Undo", 80, 30);
undoButton.addActionListener(e -> {
int[][] previousState = stateManager.undo(board.getNumbers());
if (previousState != null) {
board.setNumbers(previousState);
refreshBoard();
} else {
JOptionPane.showMessageDialog(this, "Kein Zustand zum Zurücksetzen vorhanden.", "Undo", JOptionPane.INFORMATION_MESSAGE);
}
});
JButton redoButton = Setup.createGameBoardButton("Redo", 80, 30);
redoButton.addActionListener(e -> {
int[][] nextState = stateManager.redo(board.getNumbers());
if (nextState != null) {
board.setNumbers(nextState);
refreshBoard();
} else {
JOptionPane.showMessageDialog(this, "Kein Zustand zum Wiederholen vorhanden.", "Redo", JOptionPane.INFORMATION_MESSAGE);
}
});
JButton validateButton = Setup.createGameBoardButton("Lösen", 80, 30);
validateButton.addActionListener(e -> {
if (validateCurrentBoard()) {
stopTimer();
JOptionPane.showMessageDialog(this, "Das Spielfeld ist korrekt gelöst!", "Erfolg", JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(this, "Das Spielfeld enthält Fehler!", "Fehler", JOptionPane.ERROR_MESSAGE);
}
});
JButton pauseButton = Setup.createGameBoardButton("Pause", 80, 30);
pauseButton.addActionListener(e -> {
stopTimer();
showPauseMenu();
});
controlPanel.add(resetButton);
controlPanel.add(undoButton);
controlPanel.add(redoButton);
controlPanel.add(validateButton);
controlPanel.add(pauseButton);
return controlPanel;
}
public void resetBoard() {
// Spiellogik zurücksetzen
board.resetBoard();
// Spielfeld (CENTER) entfernen und neu erstellen
if (getLayout() instanceof BorderLayout) {
BorderLayout layout = (BorderLayout) getLayout();
// Komponente im CENTER entfernen
Component centerComponent = layout.getLayoutComponent(BorderLayout.CENTER);
if (centerComponent != null) {
remove(centerComponent);
}
// Neues Spielfeld hinzufügen
add(createGamePanel(), BorderLayout.CENTER);
// Kontroll-Buttons (SOUTH) entfernen und neu hinzufügen
Component southComponent = layout.getLayoutComponent(BorderLayout.SOUTH);
if (southComponent != null) {
remove(southComponent);
}
add(createControlPanel(), BorderLayout.SOUTH);
}
// Oberfläche aktualisieren
revalidate();
repaint();
}
public boolean validateCurrentBoard() {
HitoriValidator validator = new HitoriValidator(board);
return validator.validateBoard(board.getSolutionCoordinates());
}
private void showPauseMenu() {
PauseMenu pauseMenu = new PauseMenu(
(JFrame) SwingUtilities.getWindowAncestor(this),
e -> startTimer(), // Spiel fortsetzen
e -> returnToMainMenu(), // Zum Hauptmenü
e -> System.exit(0) // Spiel beenden
);
pauseMenu.setVisible(true);
}
private JButton createCellButton(HitoriCell cell, int row, int col) {
JButton button = new JButton(String.valueOf(cell.getNumber()));
button.setBackground(Color.LIGHT_GRAY);
button.addActionListener(e -> {
toggleCellState(cell);
updateButtonState(button, cell);
});
return button;
}
private void toggleCellState(HitoriCell cell) {
if (cell == null) {
System.err.println("Ungültige Zelle! Der Zustand kann nicht geändert werden.");
return;
}
if (cell.getState() == HitoriCell.CellState.GRAY) {
cell.setState(HitoriCell.CellState.BLACK);
} else if (cell.getState() == HitoriCell.CellState.BLACK) {
cell.setState(HitoriCell.CellState.WHITE);
} else {
cell.setState(HitoriCell.CellState.GRAY);
}
}
private void updateButtonState(JButton button, HitoriCell cell) {
switch (cell.getState()) {
case GRAY:
button.setBackground(Color.LIGHT_GRAY);
button.setText(String.valueOf(cell.getNumber()));
break;
case BLACK:
button.setBackground(Color.BLACK);
button.setForeground(Color.WHITE);
break;
case WHITE:
button.setBackground(Color.WHITE);
button.setText(String.valueOf(cell.getNumber()));
button.setForeground(Color.BLACK);
break;
}
}
private void saveGame() {
try {
FileWriter writer = new FileWriter("hitori_savegame.txt");
writer.write("Spielzeit: " + (System.currentTimeMillis() - startTime) / 1000 + " Sekunden\n");
writer.write("Spielfeldzustand:\n");
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
writer.write(board.getCell(i, j).getState().name() + " ");
}
writer.write("\n");
}
writer.close();
JOptionPane.showMessageDialog(this, "Spiel erfolgreich gespeichert.");
} catch (IOException ex) {
JOptionPane.showMessageDialog(this, "Fehler beim Speichern des Spiels: " + ex.getMessage(), "Fehler", JOptionPane.ERROR_MESSAGE);
}
}
private void addHighscore() {
String playerName = JOptionPane.showInputDialog(this,
"Bitte geben Sie Ihren Namen ein:",
"Highscore speichern",
JOptionPane.PLAIN_MESSAGE);
if (playerName == null || playerName.trim().isEmpty()) {
JOptionPane.showMessageDialog(this,
"Highscore wurde nicht gespeichert. Kein Name eingegeben.",
"Info",
JOptionPane.INFORMATION_MESSAGE);
return;
}
int elapsedTime = (int) ((System.currentTimeMillis() - startTime) / 1000);
String boardName = board.getBoardName();
try {
HighscoreManager manager = new HighscoreManager();
manager.addHighscore(playerName, elapsedTime, boardName);
JOptionPane.showMessageDialog(this,
"Highscore erfolgreich gespeichert!",
"Erfolg",
JOptionPane.INFORMATION_MESSAGE);
} catch (Exception e) {
JOptionPane.showMessageDialog(this,
"Fehler beim Speichern des Highscores: " + e.getMessage(),
"Fehler",
JOptionPane.ERROR_MESSAGE);
}
}
private void returnToMainMenu() {
/// Eltern-Frame abrufen
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
if (parentFrame != null) {
// Hauptmenü erstellen und im Frame setzen
StartMenu startMenu = new StartMenu(parentFrame);
parentFrame.getContentPane().removeAll(); // Entferne alle aktuellen Inhalte
parentFrame.add(startMenu); // Füge das Hauptmenü hinzu
parentFrame.revalidate(); // Layout aktualisieren
parentFrame.repaint(); // Oberfläche neu zeichnen
} else {
System.err.println("Fehler: Kein übergeordnetes JFrame gefunden.");
}
}
private JButton createButton(String text, int width, int height) {
JButton button = new JButton(text);
button.setPreferredSize(new Dimension(width, height));
return button;
}
private void refreshBoard() {
remove(1); // Entferne das aktuelle Spielfeld im CENTER
add(createGamePanel(), BorderLayout.CENTER); // Füge das neue Spielfeld hinzu
revalidate();
repaint();
}
private void saveStateForUndo() {
stateManager.saveState(board.getNumbers());
}
private JPanel createGamePanel() {
JPanel gamePanel = new JPanel(new GridLayout(board.getSize(), board.getSize()));
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
HitoriCell cell = board.getCell(i, j);
JButton button = createCellButton(cell, i, j);
gamePanel.add(button);
}
}
System.out.println("Spielpanel erstellt mit " + (board.getSize() * board.getSize()) + " Buttons.");
return gamePanel;
}
}

View File

@ -1,25 +1,43 @@
package PR2.HitoriSpiel.GUI;
import PR2.HitoriSpiel.Utils.HighscoreManager;
import PR2.HitoriSpiel.Utils.Setup;
import PR2.HitoriSpiel.Fassade.HighscoreManager;
import PR2.HitoriSpiel.Fassade.Setup;
import javax.swing.table.DefaultTableModel;
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.List;
// aktueller Stand
/**
* Die Klasse HighscoreDialog erstellt ein Dialogfenster, das die Highscores für verschiedene Spielfelder des Hitori-Spiels
* anzeigt und verwaltet. Sie ermöglicht die Auswahl eines Spielfelds, das Anzeigen der zugehörigen Highscores und die
* Berechnung der Durchschnittszeiten.
*/
public class HighscoreDialog extends JDialog {
private final HighscoreManager highscoreManager;
private final DefaultTableModel tableModel;
private final JComboBox<String> boardSelector;
public HighscoreDialog(JFrame parentFrame) {
/**
* Konstruktor, der das Highscore-Dialogfenster erstellt und initialisiert.
*
* @param parentFrame Das übergeordnete Fenster, in dem der Dialog angezeigt wird.
* @param boardNames Eine Liste von Spielfelddateinamen.
*/
public HighscoreDialog(JFrame parentFrame, List<String> boardNames) {
super(parentFrame, "Highscoreliste", true);
this.highscoreManager = new HighscoreManager();
this.tableModel = new DefaultTableModel(new String[]{"Platz", "Name", "Zeit (Sek.)", "Fehler", "Spielfeld", "Durchschnittszeit"}, 0);
setLayout(new BorderLayout());
setSize(500, 400);
setSize(600, 400);
setLocationRelativeTo(parentFrame);
Setup.stylePanel((JPanel) getContentPane()); // Hintergrundfarbe setzen
@ -28,42 +46,78 @@ public class HighscoreDialog extends JDialog {
Setup.styleLabel(titleLabel); // Schriftstil setzen
add(titleLabel, BorderLayout.NORTH);
String[] columnNames = {"Name", "Punkte", "Spielfeld"};
tableModel = new DefaultTableModel(columnNames, 0);
JTable highscoreTable = new JTable(tableModel);
highscoreTable.setFillsViewportHeight(true);
highscoreTable.setEnabled(false); // Tabelle nur zur Anzeige
JScrollPane scrollPane = new JScrollPane(highscoreTable);
// Map für angezeigte Namen und tatsächliche Dateinamen
Map<String, String> displayToFileNameMap = new LinkedHashMap<>();
for (String fileName : boardNames) {
String displayName = fileName.endsWith(".csv") ? fileName.substring(0, fileName.lastIndexOf(".csv")) : fileName;
displayToFileNameMap.put(displayName, fileName);
}
// Spielfeld-Auswahl (ComboBox) mit angezeigten Namen
List<String> displayBoardNames = new ArrayList<>(displayToFileNameMap.keySet());
boardSelector = new JComboBox<>(displayBoardNames.toArray(new String[0]));
boardSelector.addActionListener(e -> {
String selectedDisplayName = (String) boardSelector.getSelectedItem();
if (selectedDisplayName != null) {
String actualFileName = displayToFileNameMap.get(selectedDisplayName);
loadHighscoresForBoard(actualFileName);
}
});
add(boardSelector, BorderLayout.NORTH);
// Tabelle für Highscores
JTable table = new JTable(tableModel);
JScrollPane scrollPane = new JScrollPane(table);
add(scrollPane, BorderLayout.CENTER);
// Schließen-Button
JButton closeButton = Setup.createButton("Schließen", 120, 40);
closeButton.setFont(new Font(closeButton.getFont().getName(), closeButton.getFont().getStyle(), 14)); // Schriftgröße ändern
closeButton.addActionListener(e -> dispose());
JPanel buttonPanel = new JPanel();
Setup.stylePanel(buttonPanel); // Hintergrundstil setzen
// 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);
Setup.stylePanel(buttonPanel); // Hintergrundstil setzen
add(buttonPanel, BorderLayout.SOUTH);
// Highscores laden
loadHighscores();
// Lade Highscores für das erste Spielfeld
if (!boardNames.isEmpty()) {
String firstFileName = displayToFileNameMap.get(displayBoardNames.get(0));
loadHighscoresForBoard(firstFileName);
}
}
private void loadHighscores() {
/**
* Lädt die Highscores für ein ausgewähltes Spielfeld und aktualisiert die Tabelle.
*
* @param boardName Der Name des ausgewählten Spielfelds.
*/
private void loadHighscoresForBoard(String boardName) {
tableModel.setRowCount(0); // Tabelle zurücksetzen
List<HighscoreManager.Highscore> highscores = highscoreManager.getHighscores();
List<HighscoreManager.Highscore> highscores = highscoreManager.getHighscoresForBoard(boardName);
Map<String, Double> averageTimes = highscoreManager.getAverageTimesByBoard();
highscores.stream()
.sorted((a, b) -> Integer.compare(b.getScore(), a.getScore())) // Sortierung: Höchste Punkte zuerst
.forEach(highscore -> tableModel.addRow(new Object[]{
highscore.getPlayerName(),
highscore.getScore(),
highscore.getBoardName()
}));
if (highscores.isEmpty()) {
JOptionPane.showMessageDialog(this,
"Keine Highscores für das ausgewählte Spielfeld vorhanden.",
"Info",
JOptionPane.INFORMATION_MESSAGE);
return;
}
int rank = 1;
for (HighscoreManager.Highscore highscore : highscores) {
String bName = highscore.getBoardName().replaceAll("\\.csv$", ""); // .csv entfernen
double averageTime = averageTimes.getOrDefault(highscore.getBoardName(), 0.0);
tableModel.addRow(new Object[]{
rank++, // Platzierung
highscore.getPlayerName(), // Name
highscore.getScore(), // Punkte
highscore.getErrors(), //Errors
bName, // Spielfeld
String.format("%.2f", averageTime) // Durchschnittszeit
});
}
}
public void refreshHighscores() {
loadHighscores();
}
}

View File

@ -1,28 +1,50 @@
package PR2.HitoriSpiel.GUI;
import PR2.HitoriSpiel.Fassade.Setup;
import PR2.HitoriSpiel.Fassade.GameBoard;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
public class PauseMenu extends JDialog {
/**
* Die Klasse PauseMenu erstellt ein Dialogfenster, das während einer Pause im Hitori-Spiel angezeigt wird.
* Sie bietet Optionen, das Spiel fortzusetzen, zum Hauptmenü zurückzukehren oder das Spiel zu beenden.
*/
public PauseMenu(JFrame parent, ActionListener resumeAction, ActionListener mainMenuAction, ActionListener exitAction) {
public class PauseMenu extends JDialog {
private final GameBoard gameBoard;
/**
* Konstruktor, der das Pause-Menü erstellt und die entsprechenden Buttons konfiguriert.
*
* @param parent Das übergeordnete Fenster, in dem der Dialog angezeigt wird.
* @param gameBoard Das aktuelle GameBoard-Objekt, um den Timer fortzusetzen.
* @param resumeAction Aktion, die ausgeführt wird, wenn der "Spiel fortsetzen"-Button geklickt wird.
* @param mainMenuAction Aktion, die ausgeführt wird, wenn der "Zum Hauptmenü"-Button geklickt wird.
* @param exitAction Aktion, die ausgeführt wird, wenn der "Spiel beenden"-Button geklickt wird.
*/
public PauseMenu(JFrame parent, GameBoard gameBoard, ActionListener resumeAction, ActionListener mainMenuAction, ActionListener exitAction) {
super(parent, "Pause", true);
this.gameBoard = gameBoard; // Instanz speichern
setLayout(new GridLayout(3, 1));
setSize(300, 200);
setLocationRelativeTo(parent);
// "Spiel fortsetzen"-Button
JButton resumeButton = new JButton("Spiel fortsetzen");
JButton resumeButton = Setup.createButton("Spiel fortsetzen", 200, 40);
resumeButton.addActionListener(e -> {
dispose();
if (resumeAction != null) {
resumeAction.actionPerformed(e);
}
// Timer fortsetzen
gameBoard.resumeTimer();
});
// "Zum Hauptmenü"-Button
JButton mainMenuButton = new JButton("Zum Hauptmenü");
JButton mainMenuButton = Setup.createButton("Zum Hauptmenü", 200, 40);
mainMenuButton.addActionListener(e -> {
dispose();
if (mainMenuAction != null) {
@ -31,7 +53,7 @@ public class PauseMenu extends JDialog {
});
// "Spiel beenden"-Button
JButton exitButton = new JButton("Spiel beenden");
JButton exitButton = Setup.createButton("Spiel beenden", 200, 40);
exitButton.addActionListener(e -> {
dispose();
if (exitAction != null) {

View File

@ -1,20 +1,31 @@
package PR2.HitoriSpiel.GUI;
import PR2.HitoriSpiel.Domain.HitoriBoard;
import PR2.HitoriSpiel.Domain.HitoriSolutionLoader;
import PR2.HitoriSpiel.Utils.Setup;
import PR2.HitoriSpiel.Fassade.BoardManager;
import PR2.HitoriSpiel.Fassade.GameBoard;
import PR2.HitoriSpiel.Fassade.HitoriSolutionLoader;
import PR2.HitoriSpiel.Fassade.Setup;
import javax.swing.*;
import java.awt.*;
import javax.swing.JPanel;
import java.util.*;
import java.util.List;
import java.util.Random;
import static PR2.HitoriSpiel.GUI.BoardLoader.loadBoard;
/**
* Die Klasse StartMenu erstellt das Hauptmenü des Hitori-Spiels und bietet Optionen wie Spielfeldauswahl,
* Fortsetzen eines Spiels, Start eines zufälligen Spielfelds, Highscore-Anzeige und Spielbeendigung.
*/
public class StartMenu extends JPanel {
private final JFrame parentFrame;
/**
* Konstruktor, der das Hauptmenü erstellt und alle relevanten Buttons und Aktionen initialisiert.
*
* @param parentFrame Das übergeordnete JFrame-Fenster, in dem das Menü angezeigt wird.
*/
public StartMenu(JFrame parentFrame) {
this.parentFrame = parentFrame;
setLayout(new GridBagLayout());
@ -54,13 +65,24 @@ public class StartMenu extends JPanel {
}
private void continueGame() {
/**
* Aktion für den "Spiel fortsetzen"-Button.
* Zeigt einen Hinweis an, da die Funktion noch nicht implementiert ist.
*/
public void continueGame() {
// TODO: Logik zum Fortsetzen des Spiels implementieren
JOptionPane.showMessageDialog(this, "Spiel fortsetzen wurde angeklickt");
}
protected void selectBoard() {
/**
* Aktion für den "Spielfeld aussuchen"-Button.
* Öffnet ein Dialogfenster zur Auswahl eines Spielfelds aus der verfügbaren Liste.
*/
public void selectBoard() {
// Lade die Liste der Dateinamen
List<String> boardFileNames = BoardLoader.loadBoardsAsList();
System.out.println("Verfügbare Spielfelder: " + boardFileNames);
@ -69,7 +91,15 @@ public class StartMenu extends JPanel {
return;
}
JComboBox<String> boardSelector = new JComboBox<>(boardFileNames.toArray(new String[0]));
// Map für angezeigte Namen und tatsächliche Dateinamen
Map<String, String> displayToFileNameMap = new LinkedHashMap<>();
for (String fileName : boardFileNames) {
String displayName = fileName.endsWith(".csv") ? fileName.substring(0, fileName.lastIndexOf(".csv")) : fileName;
displayToFileNameMap.put(displayName, fileName);
}
// JComboBox mit den angezeigten Namen füllen
JComboBox<String> boardSelector = new JComboBox<>(displayToFileNameMap.keySet().toArray(new String[0]));
boardSelector.setPreferredSize(new Dimension(250, 30));
JPanel selectionPanel = new JPanel(new BorderLayout());
@ -85,17 +115,25 @@ public class StartMenu extends JPanel {
);
if (option == JOptionPane.OK_OPTION) {
String selectedFile = (String) boardSelector.getSelectedItem();
System.out.println("Ausgewählte Datei: " + selectedFile);
if (selectedFile != null) {
String selectedDisplayName = (String) boardSelector.getSelectedItem();
System.out.println("Ausgewähltes Spielfeld (Angezeigter Name): " + selectedDisplayName);
if (selectedDisplayName != null) {
// Hole den tatsächlichen Dateinamen
String selectedFileName = displayToFileNameMap.get(selectedDisplayName);
System.out.println("Ausgewählte Datei (mit .csv): " + selectedFileName);
// Lade das ausgewählte Spielfeld
System.out.println("Ich bin drin.");
loadAndDisplayBoard(selectedFile);
loadAndDisplayBoard(selectedFileName);
}
}
}
protected void randomBoard() {
/**
* Aktion für den "Zufälliges Spielfeld"-Button.
* Wählt ein zufälliges Spielfeld aus der Liste und lädt es.
*/
public void randomBoard() {
List<String> boardFileNames = BoardLoader.loadBoardsAsList();
if (boardFileNames == null || boardFileNames.isEmpty()) {
JOptionPane.showMessageDialog(this, "Keine Spielfelder gefunden.", "Fehler", JOptionPane.ERROR_MESSAGE);
@ -106,21 +144,26 @@ public class StartMenu extends JPanel {
loadAndDisplayBoard(randomFile);
}
/**
* Aktion für den "Highscoreliste anschauen"-Button.
* Öffnet das Highscore-Dialogfenster.
*/
private void highscorelist() {
new HighscoreDialog((JFrame) SwingUtilities.getWindowAncestor(this)).setVisible(true);
public void highscorelist() {
List<String> boardNames = BoardLoader.loadBoardsAsList(); // Lade die Spielfeldnamen
if (boardNames == null || boardNames.isEmpty()) {
JOptionPane.showMessageDialog(this, "Keine Highscores verfügbar, da keine Spielfelder gefunden wurden.", "Info", JOptionPane.INFORMATION_MESSAGE);
return;
}
new HighscoreDialog((JFrame) SwingUtilities.getWindowAncestor(this), boardNames).setVisible(true);
}
// Hilfsmethoden
private void loadGameBoard(GameBoard gameBoard, List<String> solutionCoordinates) {
removeAll();
setLayout(new BorderLayout());
add(gameBoard, BorderLayout.CENTER);
revalidate();
repaint();
}
/**
* Lädt ein Spielfeld aus einer Datei und zeigt es im Hauptfenster an.
*
* @param selectedFile Der Name der ausgewählten Datei.
*/
private void loadAndDisplayBoard(String selectedFile) {
try {
@ -128,10 +171,12 @@ public class StartMenu extends JPanel {
System.out.println("Lade Datei von Pfad: " + resourcePath);
int[][] boardData = loadBoard(resourcePath);
List<String> solutionCoordinates = HitoriSolutionLoader.loadSolution(resourcePath);
java.util.List<String> solutionCoordinates = HitoriSolutionLoader.loadSolution(resourcePath);
HitoriBoard hitoriBoard = new HitoriBoard(boardData, solutionCoordinates, selectedFile); // Stelle sicher, dass die Lösung korrekt geladen wird
GameBoard gameBoard = new GameBoard(hitoriBoard);
//HitoriBoard hitoriBoard = new HitoriBoard(boardData, solutionCoordinates, selectedFile); // Stelle sicher, dass die Lösung korrekt geladen wird
// BoardManager-Instanz erstellen
BoardManager boardManager = new BoardManager();
GameBoard gameBoard = new GameBoard(boardManager.createBoard(boardData, solutionCoordinates, selectedFile));
loadGameBoard(gameBoard, solutionCoordinates);
System.out.println("Ausgewählte Datei: " + selectedFile);
@ -141,4 +186,19 @@ public class StartMenu extends JPanel {
JOptionPane.showMessageDialog(this, "Fehler beim Laden des Spielfelds: " + e.getMessage(), "Fehler", JOptionPane.ERROR_MESSAGE);
}
}
// --------------------Hilfsmethoden--------------------------------------------
//Aktualisiert die Benutzeroberfläche, um das geladene Spielfeld anzuzeigen
private void loadGameBoard(GameBoard gameBoard, java.util.List<String> solutionCoordinates) {
removeAll();
setLayout(new BorderLayout());
add(gameBoard, BorderLayout.CENTER);
revalidate();
repaint();
}
}

View File

@ -1,26 +1,37 @@
package PR2.HitoriSpiel.Main;
import PR2.HitoriSpiel.GUI.StartMenu;
import javax.swing.*;
/**
* Einstiegspunkt des Hitori-Spiels. Initialisiert die GUI und startet das Programm.
*/
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Hitori - Hauptmenü");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
StartMenu startMenu = new StartMenu(frame);
frame.add(startMenu);
frame.setVisible(true);
// 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
System.out.println("Spielzustände wurden gespeichert.");
}));
// Starte das Hauptmenü der Anwendung
SwingUtilities.invokeLater(() -> {
try {
JFrame frame = new JFrame("Hitori - Hauptmenü");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
// Lade das Startmenü
frame.add(new StartMenu(frame));
frame.setVisible(true);
frame.setLocationRelativeTo(null);
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "Ein schwerer Fehler ist aufgetreten: " + e.getMessage(), "Fehler", JOptionPane.ERROR_MESSAGE);
}
});
}
}

View File

@ -1,113 +0,0 @@
package PR2.HitoriSpiel.Utils;
import java.io.*;
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 = "highscores.txt";
private static final ReentrantLock fileLock = new ReentrantLock();
// Highscore-Datenstruktur
private final List<Highscore> highscoreList = new ArrayList<>();
public HighscoreManager() {
loadHighscores();
}
// Highscore hinzufügen
public synchronized void addHighscore(String playerName, int score, String boardName) {
fileLock.lock();
try {
highscoreList.add(new Highscore(playerName, score, boardName));
highscoreList.sort(Comparator.comparingInt(Highscore::getScore).reversed()); // Sortierung absteigend
saveHighscores();
} finally {
fileLock.unlock();
}
}
// Highscores laden
private void loadHighscores() {
fileLock.lock();
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 {
fileLock.unlock();
}
}
// Highscores speichern
private void saveHighscores() {
fileLock.lock();
try (BufferedWriter writer = new BufferedWriter(new FileWriter(HIGHSCORE_FILE))) {
for (Highscore highscore : highscoreList) {
writer.write(highscore.getPlayerName() + "," + highscore.getScore() + "," + highscore.getBoardName());
writer.newLine();
}
} catch (IOException e) {
System.err.println("Fehler beim Speichern der Highscores: " + e.getMessage());
} finally {
fileLock.unlock();
}
}
// Highscores abrufen
public List<Highscore> getHighscores() {
return Collections.unmodifiableList(highscoreList);
}
// Alte Highscores bereinigen
public void cleanOldHighscores(int maxEntries) {
fileLock.lock();
try {
if (highscoreList.size() > maxEntries) {
highscoreList.subList(maxEntries, highscoreList.size()).clear();
saveHighscores();
}
} finally {
fileLock.unlock();
}
}
// Innere Highscore-Klasse
public static class Highscore {
private final String playerName;
private final int score;
private final String boardName;
public Highscore(String playerName, int score, String boardName) {
this.playerName = playerName;
this.score = score;
this.boardName = boardName;
}
public String getPlayerName() {
return playerName;
}
public int getScore() {
return score;
}
public String getBoardName() {
return boardName;
}
}
}

View File

@ -0,0 +1,118 @@
//Klassendiagramm
@startuml
package PR2.HitoriSpiel.Domain {
class Action {
- int row
- int col
- String oldState
- String newState
+ getRow(): int
+ getCol(): int
+ getOldState(): String
+ getNewState(): String
}
class HitoriBoard {
- int[][] numbers
- List<String> solutionCoordinates
- String boardName
+ getNumbers(): int[][]
+ setNumbers(int[][] numbers): void
+ getCell(int row, int col): HitoriCell
+ resetBoard(): void
}
class HitoriCell {
- int number
- CellState state
+ getNumber(): int
+ getState(): CellState
+ setState(CellState state): void
}
class HitoriValidator {
- HitoriBoard board
+ validateBoard(List<String> solution): boolean
}
class StateFileManager {
+ saveState(Stack<Action> undoStack, Stack<Action> redoStack): void
+ loadState(Stack<Action> undoStack, Stack<Action> redoStack): void
}
}
package PR2.HitoriSpiel.Fassade {
class GameBoard {
- HitoriBoard board
- Timer timer
- long startTime
- long pausedTime
+ startTimer(): void
+ stopTimer(): void
+ resumeTimer(): void
+ resetBoard(): void
}
class StateManager {
- Stack<Action> undoStack
- Stack<Action> redoStack
+ saveAction(int row, int col, String oldState, String newState): void
+ undo(): Action
+ redo(): Action
}
class Setup {
+ stylePanel(JPanel panel): void
+ styleLabel(JLabel label): void
+ createButton(String text, int width, int height): JButton
}
class HighscoreManager {
- List<Highscore> highscoreList
+ addHighscore(String playerName, int score, String boardName, int errors): void
+ getHighscoresForBoard(String boardName): List<Highscore>
}
class BoardManager {
+ createBoard(int[][] boardData, List<String> solutionCoordinates, String selectedFile): HitoriBoard
}
}
package PR2.HitoriSpiel.GUI {
class BoardLoader {
+ loadBoardsAsList(): List<String>
+ loadBoard(String resourcePath): int[][]
}
class HighscoreDialog {
- HighscoreManager highscoreManager
- DefaultTableModel tableModel
+ HighscoreDialog(JFrame parentFrame, List<String> boardNames)
}
class PauseMenu {
- GameBoard gameBoard
+ PauseMenu(JFrame parent, GameBoard gameBoard, ActionListener resumeAction, ActionListener mainMenuAction, ActionListener exitAction)
}
class StartMenu {
+ selectBoard(): void
+ randomBoard(): void
+ highscorelist(): void
}
}
package PR2.HitoriSpiel.Main {
class Main {
+ main(String[] args): void
}
}
PR2.HitoriSpiel.Domain.HitoriBoard "1" *-- "many" PR2.HitoriSpiel.Domain.HitoriCell
PR2.HitoriSpiel.Domain.StateManager "1" *-- "many" PR2.HitoriSpiel.Domain.Action
PR2.HitoriSpiel.Fassade.GameBoard "1" *-- "1" PR2.HitoriSpiel.Domain.HitoriBoard
PR2.HitoriSpiel.Fassade.BoardManager ..> PR2.HitoriSpiel.GUI.StartMenu
PR2.HitoriSpiel.GUI.HighscoreDialog ..> PR2.HitoriSpiel.Fassade.HighscoreManager
PR2.HitoriSpiel.GUI.PauseMenu --> PR2.HitoriSpiel.Fassade.GameBoard
PR2.HitoriSpiel.GUI.StartMenu --> PR2.HitoriSpiel.GUI.BoardLoader
@enduml

View File

@ -1,4 +0,0 @@
package PR2.HitoriSpiel.test.GUI;
public class PauseMenuTest {
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

View File

@ -1,3 +0,0 @@
Name, Zeit (Sekunden), Spielfeld
NameTest, 123 Sekunden, Hitori4x4_leicht.csv

View File

@ -9,7 +9,7 @@
9,6,10,8,5,3,10,2,5,8
9,10,2,2,4,7,9,8,5,7
// Lösung (schwarze Felder)
//Lösung (schwarze Felder)
1,1
1,3
1,6
Can't render this file because it has a wrong number of fields in line 12.

View File

@ -3,7 +3,7 @@
1,3,4,2
3,4,3,2
//Lösung (schwarze Felder)
//Lösung
1,2
2,4
3,2

1 3,3,1,4
3 1,3,4,2
4 3,4,3,2
5 //Lösung (schwarze Felder) //Lösung
6 1,2
7 2,4
8 3,2
9 4,1

View File

@ -7,7 +7,7 @@
8,1,3,3,6,4,2,6
5,3,6,4,3,4,8,2
// Lösung (schwarze Felder)
//Lösung (schwarze Felder)
1,1
1,8
2,2
1 2,7,2,1,5,8,3,5
7 8,1,3,3,6,4,2,6
8 5,3,6,4,3,4,8,2
9 // Lösung (schwarze Felder) //Lösung (schwarze Felder)
10 1,1
11 1,8
12 2,2
13 2,4

View File

@ -0,0 +1,31 @@
package Domain;
import PR2.HitoriSpiel.Domain.Action;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class ActionTest {
@Test
public void testConstructorAndGetters() {
Action action = new Action(2, 3, "WHITE", "BLACK");
assertEquals(2, action.getRow());
assertEquals(3, action.getCol());
assertEquals("WHITE", action.getOldState());
assertEquals("BLACK", action.getNewState());
}
@Test
public void testEqualsAndHashCode() {
Action action1 = new Action(1, 2, "GRAY", "WHITE");
Action action2 = new Action(1, 2, "GRAY", "WHITE");
assertEquals(action1, action2);
assertEquals(action1.hashCode(), action2.hashCode());
}
@Test
public void testToString() {
Action action = new Action(4, 5, "GRAY", "BLACK");
assertEquals("4,5,GRAY,BLACK", action.toString());
}
}

View File

@ -0,0 +1,28 @@
package Domain;
import PR2.HitoriSpiel.Domain.HitoriBoard;
import PR2.HitoriSpiel.Domain.HitoriCell;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
public class HitoriBoardTest {
@Test
public void testBoardInitialization() {
int[][] numbers = {{1, 2}, {3, 4}};
HitoriBoard board = new HitoriBoard(numbers, Arrays.asList("1,1", "2,2"), "TestBoard");
assertEquals(2, board.getSize());
assertEquals("TestBoard", board.getBoardName());
assertEquals(1, board.getCell(0, 0).getNumber());
}
@Test
public void testResetBoard() {
int[][] numbers = {{5, 6}, {7, 8}};
HitoriBoard board = new HitoriBoard(numbers, Arrays.asList("1,1", "2,2"), "ResetTest");
board.resetBoard();
assertEquals(HitoriCell.CellState.GRAY, board.getCell(0, 0).getState());
}
}

View File

@ -0,0 +1,23 @@
package Domain;
import PR2.HitoriSpiel.Domain.HitoriCell;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class HitoriCellTest {
@Test
public void testCellInitialization() {
HitoriCell cell = new HitoriCell(7);
assertEquals(7, cell.getNumber());
assertEquals(HitoriCell.CellState.GRAY, cell.getState());
}
@Test
public void testSetState() {
HitoriCell cell = new HitoriCell(8);
cell.setState(HitoriCell.CellState.BLACK);
assertEquals(HitoriCell.CellState.BLACK, cell.getState());
}
}

View File

@ -0,0 +1,89 @@
package Domain;
import PR2.HitoriSpiel.Domain.HitoriBoard;
import PR2.HitoriSpiel.Domain.HitoriValidator;
import PR2.HitoriSpiel.Domain.HitoriCell;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
public class HitoriValidatorTest {
@Test
public void testValidateBoardValidSolution() {
// Spielfeld-Setup
int[][] numbers = {{1, 2, 3}, {4, 1, 6}, {7, 8, 1}};
HitoriBoard board = new HitoriBoard(numbers, Arrays.asList("1,1", "2,2", "3,3"), "ValidSolutionTest");
// Lösung manuell setzen (alle Zellen aus der Lösungsliste auf BLACK setzen)
board.getCell(0, 0).setState(HitoriCell.CellState.BLACK); // Koordinaten 1,1
board.getCell(1, 1).setState(HitoriCell.CellState.BLACK); // Koordinaten 2,2
board.getCell(2, 2).setState(HitoriCell.CellState.BLACK); // Koordinaten 3,3
// Alle anderen Zellen auf WHITE setzen
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
if (!Arrays.asList("1,1", "2,2", "3,3").contains((i + 1) + "," + (j + 1))) {
board.getCell(i, j).setState(HitoriCell.CellState.WHITE);
}
}
}
// Validator initialisieren und prüfen
HitoriValidator validator = new HitoriValidator(board);
assertTrue(validator.validateBoard(Arrays.asList("1,1", "2,2", "3,3")));
}
@Test
public void testValidateBoardInvalidSolution() {
// Spielfeld-Setup
int[][] numbers = {{1, 2, 3}, {4, 1, 6}, {7, 8, 1}};
HitoriBoard board = new HitoriBoard(numbers, Arrays.asList("1,1", "2,2", "3,3"), "InvalidSolutionTest");
// Ungültige Lösung setzen (eine Zelle aus der Lösungsliste bleibt nicht BLACK)
board.getCell(0, 0).setState(HitoriCell.CellState.BLACK); // Koordinaten 1,1
board.getCell(1, 1).setState(HitoriCell.CellState.WHITE); // Koordinaten 2,2 (falsch)
board.getCell(2, 2).setState(HitoriCell.CellState.BLACK); // Koordinaten 3,3
// Alle anderen Zellen auf WHITE setzen
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
if (!Arrays.asList("1,1", "2,2", "3,3").contains((i + 1) + "," + (j + 1))) {
board.getCell(i, j).setState(HitoriCell.CellState.WHITE);
}
}
}
// Validator initialisieren und prüfen
HitoriValidator validator = new HitoriValidator(board);
assertFalse(validator.validateBoard(Arrays.asList("1,1", "2,2", "3,3")));
}
@Test
public void testValidateBoardExtraBlackCells() {
// Spielfeld-Setup
int[][] numbers = {{1, 2, 3}, {4, 1, 6}, {7, 8, 1}};
HitoriBoard board = new HitoriBoard(numbers, Arrays.asList("1,1", "2,2", "3,3"), "ExtraBlackCellsTest");
// Lösung setzen
board.getCell(0, 0).setState(HitoriCell.CellState.BLACK); // Koordinaten 1,1
board.getCell(1, 1).setState(HitoriCell.CellState.BLACK); // Koordinaten 2,2
board.getCell(2, 2).setState(HitoriCell.CellState.BLACK); // Koordinaten 3,3
// Zusätzliche schwarze Zelle setzen (ungültig)
board.getCell(0, 1).setState(HitoriCell.CellState.BLACK); // Koordinaten 1,2 (nicht Teil der Lösung)
// Alle anderen Zellen auf WHITE setzen
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
if (!Arrays.asList("1,1", "2,2", "3,3", "1,2").contains((i + 1) + "," + (j + 1))) {
board.getCell(i, j).setState(HitoriCell.CellState.WHITE);
}
}
}
// Validator initialisieren und prüfen
HitoriValidator validator = new HitoriValidator(board);
assertFalse(validator.validateBoard(Arrays.asList("1,1", "2,2", "3,3")));
}
}

View File

@ -0,0 +1,29 @@
package Domain;
import PR2.HitoriSpiel.Domain.StateFileManager;
import PR2.HitoriSpiel.Domain.Action;
import org.junit.jupiter.api.Test;
import java.util.Stack;
import static org.junit.jupiter.api.Assertions.*;
public class StateFileManagerTest {
@Test
public void testSaveAndLoadState() {
Stack<Action> undoStack = new Stack<>();
Stack<Action> redoStack = new Stack<>();
undoStack.push(new Action(1, 1, "GRAY", "BLACK"));
redoStack.push(new Action(2, 2, "WHITE", "GRAY"));
StateFileManager.saveState(undoStack, redoStack);
Stack<Action> loadedUndo = new Stack<>();
Stack<Action> loadedRedo = new Stack<>();
StateFileManager.loadState(loadedUndo, loadedRedo);
assertEquals(undoStack, loadedUndo);
assertEquals(redoStack, loadedRedo);
}
}

View File

@ -0,0 +1,95 @@
package Fassade;
import PR2.HitoriSpiel.Domain.HitoriBoard;
import PR2.HitoriSpiel.Domain.HitoriCell;
import PR2.HitoriSpiel.Fassade.GameBoard;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
public class GameBoardTest {
@Test
public void testGameBoardInitialization() {
int[][] numbers = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
HitoriBoard board = new HitoriBoard(numbers, Arrays.asList("1,1", "2,2", "3,3"), "TestBoard");
GameBoard gameBoard = new GameBoard(board);
assertNotNull(gameBoard, "GameBoard sollte erfolgreich initialisiert werden.");
assertNotNull(gameBoard.validateCurrentBoard(), "Das Spielfeld sollte validierbar sein.");
}
@Test
public void testResetBoard() {
int[][] numbers = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
HitoriBoard board = new HitoriBoard(numbers, Arrays.asList("1,1", "2,2", "3,3"), "TestBoard");
GameBoard gameBoard = new GameBoard(board);
// Setze einige Zellen auf unterschiedliche Zustände
board.getCell(0, 0).setState(HitoriCell.CellState.BLACK);
board.getCell(1, 1).setState(HitoriCell.CellState.WHITE);
// Führe das Zurücksetzen durch
gameBoard.resetBoard();
// Verifiziere, dass alle Zellen zurückgesetzt wurden
for (int i = 0; i < numbers.length; i++) {
for (int j = 0; j < numbers[i].length; j++) {
assertEquals(HitoriCell.CellState.GRAY, board.getCell(i, j).getState(),
"Alle Zellen sollten nach dem Zurücksetzen grau sein.");
}
}
}
@Test
public void testValidateCurrentBoard() {
int[][] numbers = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
HitoriBoard board = new HitoriBoard(numbers, Arrays.asList("1,1", "2,2", "3,3"), "TestBoard");
GameBoard gameBoard = new GameBoard(board);
// Setze Zellen auf die Lösung
board.getCell(0, 0).setState(HitoriCell.CellState.BLACK);
board.getCell(1, 1).setState(HitoriCell.CellState.BLACK);
board.getCell(2, 2).setState(HitoriCell.CellState.BLACK);
// Alle anderen Zellen auf weiß setzen
for (int i = 0; i < numbers.length; i++) {
for (int j = 0; j < numbers[i].length; j++) {
if (!Arrays.asList("1,1", "2,2", "3,3").contains((i + 1) + "," + (j + 1))) {
board.getCell(i, j).setState(HitoriCell.CellState.WHITE);
}
}
}
assertTrue(gameBoard.validateCurrentBoard(), "Das Board sollte als gültig erkannt werden.");
}
@Test
public void testToggleCellState() {
int[][] numbers = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
HitoriBoard board = new HitoriBoard(numbers, Arrays.asList("1,1", "2,2", "3,3"), "TestBoard");
GameBoard gameBoard = new GameBoard(board);
HitoriCell cell = board.getCell(0, 0);
// Initialer Zustand: GRAY
assertEquals(HitoriCell.CellState.GRAY, cell.getState(), "Die Zelle sollte initial grau sein.");
// Zustand wechseln: BLACK
gameBoard.toggleCellState(cell, 0, 0);
assertEquals(HitoriCell.CellState.BLACK, cell.getState(), "Die Zelle sollte schwarz sein.");
// Zustand wechseln: WHITE
gameBoard.toggleCellState(cell, 0, 0);
assertEquals(HitoriCell.CellState.WHITE, cell.getState(), "Die Zelle sollte weiß sein.");
// Zustand wechseln: GRAY
gameBoard.toggleCellState(cell, 0, 0);
assertEquals(HitoriCell.CellState.GRAY, cell.getState(), "Die Zelle sollte wieder grau sein.");
}
}

View File

@ -0,0 +1,44 @@
package Fassade;
import PR2.HitoriSpiel.Fassade.HighscoreManager;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class HighscoreManagerTest {
@Test
public void testAddHighscore() {
HighscoreManager manager = new HighscoreManager();
manager.addHighscore("Alice", 120, "TestBoard", 0);
List<HighscoreManager.Highscore> highscores = manager.getHighscoresForBoard("TestBoard");
assertEquals(1, highscores.size());
assertEquals("Alice", highscores.get(0).getPlayerName());
assertEquals(120, highscores.get(0).getTime());
assertEquals("TestBoard", highscores.get(0).getBoardName());
}
@Test
public void testIsNewHighscore() {
HighscoreManager manager = new HighscoreManager();
manager.addHighscore("Bob", 150, "TestBoard", 0);
assertTrue(manager.isNewHighscore(140, "TestBoard"));
assertFalse(manager.isNewHighscore(160, "TestBoard"));
}
@Test
public void testClearHighscores() {
HighscoreManager manager = new HighscoreManager();
manager.addHighscore("Alice", 100, "TestBoard", 0);
manager.clearHighscores();
List<HighscoreManager.Highscore> highscores = manager.getHighscores();
assertTrue(highscores.isEmpty(), "Alle Highscores sollten gelöscht sein.");
}
}

View File

@ -0,0 +1,102 @@
package Fassade;
import PR2.HitoriSpiel.Fassade.HitoriSolutionLoader;
import org.junit.jupiter.api.Test;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class HitoriSolutionLoaderTest {
@Test
public void testLoadSolutionValidFile() throws IOException {
// Simuliere eine valide Lösung im InputStream
String solutionContent = """
//Lösung
1,1
2,2
3,3
""";
InputStream inputStream = new ByteArrayInputStream(solutionContent.getBytes());
// Mock den Aufruf von getResourceAsStream
List<String> solution = loadSolutionFromStream(inputStream);
assertNotNull(solution, "Die Lösungsliste sollte nicht null sein.");
assertFalse(solution.isEmpty(), "Die Lösungsliste sollte nicht leer sein.");
assertEquals("1,1", solution.get(0), "Die erste Koordinate sollte korrekt geladen werden.");
assertEquals("3,3", solution.get(2), "Die letzte Koordinate sollte korrekt geladen werden.");
}
@Test
public void testLoadSolutionNoSolutionSection() throws IOException {
// Simuliere eine Datei ohne "//Lösung"
String solutionContent = """
1,1
2,2
3,3
""";
InputStream inputStream = new ByteArrayInputStream(solutionContent.getBytes());
// Mock den Aufruf von getResourceAsStream
List<String> solution = loadSolutionFromStream(inputStream);
assertNotNull(solution, "Die Lösungsliste sollte nicht null sein.");
assertTrue(solution.isEmpty(), "Die Lösungsliste sollte leer sein, wenn kein Lösungsteil gefunden wird.");
}
@Test
public void testLoadSolutionWithEmptyLines() throws IOException {
// Simuliere eine Datei mit leeren Zeilen
String solutionContent = """
//Lösung
1,1
2,2
3,3
""";
InputStream inputStream = new ByteArrayInputStream(solutionContent.getBytes());
// Mock den Aufruf von getResourceAsStream
List<String> solution = loadSolutionFromStream(inputStream);
assertNotNull(solution, "Die Lösungsliste sollte nicht null sein.");
assertFalse(solution.isEmpty(), "Die Lösungsliste sollte nicht leer sein.");
assertEquals(3, solution.size(), "Die Lösungsliste sollte nur 3 gültige Einträge enthalten.");
assertEquals("1,1", solution.get(0), "Die erste Koordinate sollte korrekt geladen werden.");
}
// Hilfsmethode: Simuliert die Verarbeitung des InputStreams
private List<String> loadSolutionFromStream(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
List<String> solutionCoordinates = new ArrayList<>();
boolean solutionSectionFound = false;
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("//Lösung")) {
solutionSectionFound = true;
continue;
}
if (solutionSectionFound) {
if (!line.isEmpty()) {
solutionCoordinates.add(line);
}
}
}
if (!solutionSectionFound) {
System.err.println("Warnung: Lösungsteil wurde in der Datei nicht gefunden.");
}
return solutionCoordinates;
}
}

View File

@ -0,0 +1,35 @@
package Fassade;
import PR2.HitoriSpiel.Fassade.Setup;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import java.awt.*;
import static org.junit.jupiter.api.Assertions.*;
public class SetupTest {
@Test
public void testCreateButton() {
JButton button = Setup.createButton("TestButton", 100, 50);
assertNotNull(button);
assertEquals("TestButton", button.getText());
assertEquals(new Dimension(100, 50), button.getPreferredSize());
assertEquals(Setup.BUTTON_COLOR, button.getBackground());
assertEquals(Setup.BUTTON_TEXT_COLOR, button.getForeground());
}
@Test
public void testCreateGameBoardButton() {
JButton button = Setup.createGameBoardButton("GameButton", 150, 60);
assertNotNull(button);
assertEquals("GameButton", button.getText());
assertEquals(new Dimension(150, 60), button.getPreferredSize());
assertEquals(Setup.GAME_BUTTON_COLOR, button.getBackground());
assertEquals(Setup.GAME_BUTTON_TEXT_COLOR, button.getForeground());
}
}

View File

@ -0,0 +1,50 @@
package Fassade;
import PR2.HitoriSpiel.Fassade.StateManager;
import PR2.HitoriSpiel.Domain.Action;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class StateManagerTest {
@Test
public void testSaveActionAndUndo() {
StateManager stateManager = new StateManager();
stateManager.saveAction(0, 0, "GRAY", "BLACK");
Action undoAction = stateManager.undo();
assertNotNull(undoAction);
assertEquals(0, undoAction.getRow());
assertEquals(0, undoAction.getCol());
assertEquals("GRAY", undoAction.getOldState());
assertEquals("BLACK", undoAction.getNewState());
}
@Test
public void testRedo() {
StateManager stateManager = new StateManager();
stateManager.saveAction(1, 1, "WHITE", "BLACK");
stateManager.undo();
Action redoAction = stateManager.redo();
assertNotNull(redoAction);
assertEquals(1, redoAction.getRow());
assertEquals(1, redoAction.getCol());
assertEquals("WHITE", redoAction.getOldState());
assertEquals("BLACK", redoAction.getNewState());
}
@Test
public void testLoadStateFromFile() {
StateManager stateManager = new StateManager();
stateManager.saveAction(0, 0, "GRAY", "BLACK");
stateManager.saveAction(1, 1, "WHITE", "BLACK");
stateManager.loadStateFromFile();
assertNotNull(stateManager.undo(), "Der Undo-Stack sollte geladen werden.");
assertNotNull(stateManager.redo(), "Der Redo-Stack sollte geladen werden.");
}
}

View File

@ -0,0 +1,5 @@
package GUI;
public class BoardLoaderTest {
}

View File

@ -0,0 +1,42 @@
package GUI;
import PR2.HitoriSpiel.GUI.HighscoreDialog;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class HighscoreDialogTest {
@Test
public void testHighscoreDialogInitialization() {
SwingUtilities.invokeLater(() -> {
JFrame parentFrame = new JFrame();
List<String> boardNames = List.of("leicht_8x8.csv", "medium_10x10.csv");
HighscoreDialog dialog = new HighscoreDialog(parentFrame, boardNames);
assertNotNull(dialog, "Der HighscoreDialog sollte erfolgreich erstellt werden.");
assertEquals("Highscoreliste", dialog.getTitle(), "Der Titel des Dialogs sollte korrekt sein.");
assertEquals(600, dialog.getWidth(), "Die Breite des Dialogs sollte 600 Pixel betragen.");
assertEquals(400, dialog.getHeight(), "Die Höhe des Dialogs sollte 400 Pixel betragen.");
});
}
@Test
public void testLoadHighscoresForBoard() {
SwingUtilities.invokeLater(() -> {
JFrame parentFrame = new JFrame();
List<String> boardNames = List.of("leicht_8x8.csv", "medium_10x10.csv");
HighscoreDialog dialog = new HighscoreDialog(parentFrame, boardNames);
JComboBox<?> comboBox = (JComboBox<?>) dialog.getContentPane().getComponent(1); // ComboBox ist das 2. Element
assertNotNull(comboBox, "Die Spielfeld-Auswahl sollte existieren.");
assertEquals(2, comboBox.getItemCount(), "Die ComboBox sollte zwei Einträge enthalten.");
JTable table = (JTable) ((JScrollPane) dialog.getContentPane().getComponent(2)).getViewport().getView();
assertNotNull(table, "Die Tabelle für Highscores sollte existieren.");
});
}
}

View File

@ -0,0 +1,61 @@
package GUI;
import PR2.HitoriSpiel.GUI.PauseMenu;
import PR2.HitoriSpiel.Fassade.GameBoard;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import static org.junit.jupiter.api.Assertions.*;
public class PauseMenuTest {
@Test
public void testPauseMenuInitialization() {
SwingUtilities.invokeLater(() -> {
JFrame parentFrame = new JFrame();
GameBoard gameBoard = new GameBoard(null); // GameBoard-Mock
ActionListener resumeAction = e -> System.out.println("Resume Action Triggered");
ActionListener mainMenuAction = e -> System.out.println("Main Menu Action Triggered");
ActionListener exitAction = e -> System.out.println("Exit Action Triggered");
PauseMenu pauseMenu = new PauseMenu(parentFrame, gameBoard, resumeAction, mainMenuAction, exitAction);
assertNotNull(pauseMenu, "Das PauseMenu sollte erfolgreich erstellt werden.");
assertEquals("Pause", pauseMenu.getTitle(), "Der Titel des Dialogs sollte 'Pause' sein.");
assertEquals(300, pauseMenu.getWidth(), "Die Breite des Dialogs sollte 300 Pixel betragen.");
assertEquals(200, pauseMenu.getHeight(), "Die Höhe des Dialogs sollte 200 Pixel betragen.");
});
}
@Test
public void testPauseMenuButtonActions() {
SwingUtilities.invokeLater(() -> {
JFrame parentFrame = new JFrame();
GameBoard gameBoard = new GameBoard(null); // GameBoard-Mock
boolean[] actionTriggered = {false, false, false};
ActionListener resumeAction = e -> actionTriggered[0] = true;
ActionListener mainMenuAction = e -> actionTriggered[1] = true;
ActionListener exitAction = e -> actionTriggered[2] = true;
PauseMenu pauseMenu = new PauseMenu(parentFrame, gameBoard, resumeAction, mainMenuAction, exitAction);
// Teste Buttons
JButton resumeButton = (JButton) pauseMenu.getContentPane().getComponent(0);
JButton mainMenuButton = (JButton) pauseMenu.getContentPane().getComponent(1);
JButton exitButton = (JButton) pauseMenu.getContentPane().getComponent(2);
resumeButton.doClick();
assertTrue(actionTriggered[0], "Resume-Action sollte ausgelöst werden.");
mainMenuButton.doClick();
assertTrue(actionTriggered[1], "MainMenu-Action sollte ausgelöst werden.");
exitButton.doClick();
assertTrue(actionTriggered[2], "Exit-Action sollte ausgelöst werden.");
});
}
}

View File

@ -1,53 +1,46 @@
package GUI;
import PR2.HitoriSpiel.GUI.StartMenu;
import org.assertj.swing.edt.GuiActionRunner;
import org.assertj.swing.fixture.FrameFixture;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class StartMenuTest {
private FrameFixture window;
@BeforeEach
public void setUp() {
JFrame frame = GuiActionRunner.execute(() -> {
JFrame testFrame = new JFrame();
testFrame.add(new StartMenu(testFrame));
testFrame.pack();
testFrame.setVisible(true);
return testFrame;
});
window = new FrameFixture(frame);
}
@AfterEach
public void tearDown() {
window.cleanUp();
}
@Test
public void testButtonsExist() {
// Buttons über ihre Namen finden und testen
assertThat(window.button("continueButton")).isNotNull();
assertThat(window.button("selectBoardButton")).isNotNull();
assertThat(window.button("randomBoardButton")).isNotNull();
assertThat(window.button("highscorelistButton")).isNotNull();
assertThat(window.button("exitButton")).isNotNull();
public void testStartMenuInitialization() {
SwingUtilities.invokeLater(() -> {
JFrame parentFrame = new JFrame();
StartMenu startMenu = new StartMenu(parentFrame);
assertNotNull(startMenu, "Das StartMenu sollte erfolgreich erstellt werden.");
assertEquals(5, startMenu.getComponentCount(), "Das StartMenu sollte fünf Buttons enthalten.");
// Überprüfe Buttons
JButton continueButton = (JButton) startMenu.getComponent(1);
assertEquals("Spiel fortsetzen", continueButton.getText(), "Der Text des ersten Buttons sollte korrekt sein.");
JButton selectBoardButton = (JButton) startMenu.getComponent(2);
assertEquals("Spielfeld aussuchen", selectBoardButton.getText(), "Der Text des zweiten Buttons sollte korrekt sein.");
});
}
@Test
public void testButtonActions() {
window.button("continueButton").click();
window.button("selectBoardButton").click();
window.button("randomBoardButton").click();
window.button("highscorelistButton").click();
window.button("exitButton").click();
SwingUtilities.invokeLater(() -> {
JFrame parentFrame = new JFrame();
StartMenu startMenu = new StartMenu(parentFrame);
// Simuliere Button-Klicks
JButton continueButton = (JButton) startMenu.getComponent(1);
continueButton.doClick();
JButton exitButton = (JButton) startMenu.getComponent(4);
exitButton.doClick();
// Die tatsächliche Logik muss separat getestet werden (z. B. via Mocking).
});
}
}

View File

@ -4,7 +4,27 @@
* Simona-Ioana Purdila - 3015825
* Victoria Petropoulos - 3014866
## Plan für die Bearbeitung der Aufgabe:
- [ ] Aufgabestellung durchgehen und Issues erstellen
- [ ] Klassendiagramme erstellen
- [ ]
## Plan für die Bearbeitung der Aufgabenstellung:
- [x] Aufteilung des Produktiv-Codes in Domain, Fassade und GUI
- [x] Spielfelder von den Profs hochladen
- [x] Verwendung von Maven
- [x] Verwendung von Git
- [x] Verwendung von Feature-Branches
- [x] Erstellung Unit-Tests mit JUnit
## Die Aufgabenstellung:
- [x] GUI: GUI, in der beliebige Spielfelder angezeigt und bespielt werden können.
- [x] GUI: Die Möglichkeit ein bestimmtes Spielfeld auszuwählen oder zufällig eines ausgesucht zu bekommen.
- [x] Domain + GUI: Die Felder des Spielfelds sollen zu Beginn grau hinterlegt sein und auf schwarz (gestrichen) oder weiß gesetzt werden können, um einen besseren Überblick zu behalten (vgl. janko.at)
- [x] Domain + GUI: Alle gesetzten Farb-Markierungen sollen gespeichert werden und beliebig vor und zurück geklickt werden können (Undo/Redo-Funktion).
- [x] GUI: Ferner soll es einen Reset-Button geben, der das laufende Spiel wieder komplett auf den Anfangsstand zurücksetzt; die Zeit soll aber weiterlaufen.
- [x] GUI: Eine Zeitmessung, die während des Spiels mitläuft.
- [x] Domain: Ein Highscore für jedes Spielfeld, in dem die Zeiten zusammen mit einem vom Spieler einzugebenden Namen (auf der Platte) gespeichert werden.
- [x] GUI + Domain: Sobald das Spielfeld komplett korrekt gelöst ist (die geschwärzten Zahlen alle korrekt gesetzt wurden), stoppt das Spiel die Zeit und gibt die Möglichkeit einen Namen für den Highscore einzutragen.
- [ ] GUI + Domain: Das Spiel soll eine Markierung der fälschlich schwarz markierten Felder als Hilfestellung anzeigen können, es soll beim Highscore mitgespeichert werden, wie viele Fehler dabei insgesamt über das Spiel angezeigt wurden.
- [x] GUI + Domain: Beim Highscore soll für jedes Spielfeld auch die Durchschnittszeit aller bisherigen Einträge angezeigt werden.
## Was nicht funktioniert:
- [x] Nach Reset funktionieren Undo/Redo Button nicht mehr
- [x] Nach Spiel fortsetzen -> Timer speichert nicht richtige Zeit
- [x] Jar-Datei -> Einträge aus highscores.txt werden nicht angezeigt und neue Einträge werden nicht gespeichert.