Compare commits

..

No commits in common. "main" and "GameSolverTest" have entirely different histories.

32 changed files with 501 additions and 1208 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

BIN
gamestate.dat 100644

Binary file not shown.

94
pom.xml
View File

@ -9,8 +9,8 @@
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<maven.compiler.source>20</maven.compiler.source>
<maven.compiler.target>20</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>20.0.2</javafx.version>
<java.version>20</java.version>
@ -24,7 +24,18 @@
</repositories>
<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>
@ -53,67 +64,11 @@
<!-- JUnit for Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.1</version>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</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>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.24.2</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.9</version> <!-- Stelle sicher, dass du die neueste Version verwendest -->
</dependency>
<!-- TestFX für JavaFX UI-Tests -->
<dependency>
<groupId>org.testfx</groupId>
<artifactId>testfx-junit5</artifactId>
<version>4.0.16-alpha</version>
<scope>test</scope>
</dependency>
<!-- TestFX Core -->
<dependency>
<groupId>org.testfx</groupId>
<artifactId>testfx-core</artifactId>
<version>4.0.16-alpha</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@ -124,9 +79,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>23</source>
<target>23</target>
<compilerArgs>--enable-preview</compilerArgs>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
@ -204,16 +158,6 @@
</executions>
</plugin>
</plugins>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<directory>src/test/java</directory>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<testSourceDirectory>src/test/java</testSourceDirectory>
</build>
<reporting>
<plugins>

View File

@ -1,75 +1,50 @@
package GUI;
import domain.*;
import GUI.HitoriDialogManager;
import javafx.application.Platform;
import javafx.stage.Stage;
import java.util.*;
public class GameUIController {
private final Stage primaryStage; // Added to handle window state
private final HitoriGameMoves gameMoves;
private final GameSolver gameSolver;
private final HitoriGameTimer gameTimer;
private final domain.HitoriGameTimer gameTimer;
private final HitoriGameScores gameScores;
private final HitoriDialogManager dialogManager;
private final HitoriBoardPanel boardPanel;
private final HitoriControlPanel controlPanel;
private final HitoriScorePanel scorePanel;
private final GUI.HitoriDialogManager dialogManager;
private final GUI.HitoriBoardPanel boardPanel;
private final GUI.HitoriControlPanel controlPanel;
private final GUI.HitoriScorePanel scorePanel;
private Timer guiTimer;
private boolean isPaused;
public GameUIController(int[][] initialBoard, HitoriDialogManager dialogManager, Stage primaryStage, HitoriGameMain existingGame) {
this.primaryStage = primaryStage;
public GameUIController(int[][] initialBoard, HitoriDialogManager dialogManager) {
this.gameMoves = new HitoriGameMoves(initialBoard);
this.gameSolver = new GameSolver(initialBoard);
this.gameTimer = new domain.HitoriGameTimer();
this.gameScores = new HitoriGameScores();
this.dialogManager = dialogManager;
if (existingGame != null) {
// Use existing game state
this.gameSolver = existingGame;
this.gameMoves = existingGame;
this.gameTimer = existingGame.getTimer();
this.gameScores = existingGame.getScores();
// Restore the elapsed time
this.gameTimer.setElapsedTime(existingGame.getElapsedTimeInSeconds());
} else {
// Create new game
this.gameSolver = new GameSolver(initialBoard);
this.gameMoves = gameSolver;
this.gameTimer = new HitoriGameTimer();
this.gameScores = new HitoriGameScores();
}
this.isPaused = false;
this.boardPanel = new HitoriBoardPanel(gameMoves, gameSolver, this);
this.controlPanel = new HitoriControlPanel(this);
this.scorePanel = new HitoriScorePanel(this);
this.boardPanel = new GUI.HitoriBoardPanel(gameMoves, gameSolver, this);
this.controlPanel = new GUI.HitoriControlPanel(this);
this.scorePanel = new GUI.HitoriScorePanel(this);
startTimer();
loadHighScores();
updateUI(); // Make sure UI reflects current state
}
public void handleLeftClick(int row, int col) {
if (!isValidBlackMark(row, col)) {
gameMoves.mistakeCount++;
}
gameMoves.markCellAsBlack(row, col);
updateUI();
}
private boolean isValidBlackMark(int row, int col) {
boolean[][] blackCells = gameMoves.getBlackCells();
if (row > 0 && blackCells[row-1][col] ||
row < blackCells.length-1 && blackCells[row+1][col] ||
col > 0 && blackCells[row][col-1] ||
col < blackCells[0].length-1 && blackCells[row][col+1]) {
return false;
}
return true;
}
public void handleRightClick(int row, int col) {
gameMoves.markCellAsWhite(row, col);
updateUI();
}
public void togglePause() {
@ -87,44 +62,26 @@ public class GameUIController {
}
public void resetGame() {
gameSolver.reset();
// Don't reset timer anymore
gameMoves.reset();
gameTimer.resetTimer();
updateUI();
}
public void undo() {
if (gameSolver.undo()) {
if (gameMoves.undo()) {
updateUI();
}
}
public void redo() {
if (gameSolver.redo()) {
if (gameMoves.redo()) {
updateUI();
}
}
public void newGame() {
// Save current game state before starting new game
saveGame();
// Stop timer and cleanup current game
stopTimer();
// Start new game process
Platform.runLater(() -> {
// Close current window
primaryStage.close();
// Start fresh instance
Stage newStage = new Stage();
Main.MainMethod mainMethod = new Main.MainMethod();
try {
mainMethod.start(newStage);
} catch (Exception e) {
dialogManager.showAlert("Error", "Failed to start new game: " + e.getMessage());
}
});
// Notify main application to show board selection
}
public void checkSolution() {
@ -151,11 +108,7 @@ public class GameUIController {
}
public void saveGame() {
HitoriGameMain saveState = new HitoriGameMain(gameMoves.getBoard());
// Copy current state to save
saveState.setGameState(gameMoves.getBlackCells(), gameMoves.getWhiteCells(),
gameMoves.getMistakeCount(), gameTimer.getElapsedTimeInSeconds());
saveState.saveGameState();
// Implementation for saving game state
dialogManager.showAlert("Game Saved", "Your game has been saved successfully.");
}
@ -171,12 +124,12 @@ public class GameUIController {
stopTimer();
Optional<String> playerName = dialogManager.askForPlayerName();
if (playerName.isPresent() && !playerName.get().trim().isEmpty()) {
gameScores.addHighScore(playerName.get(), gameTimer.getElapsedTimeInSeconds(), gameSolver.getMistakeCount());
gameScores.addHighScore(playerName.get(), gameTimer.getElapsedTimeInSeconds(), gameMoves.getMistakeCount());
updateHighScoreDisplay();
dialogManager.showAlert("Congratulations!", String.format(
"You've solved the puzzle!\nTime: %ds\nMistakes: %d",
gameTimer.getElapsedTimeInSeconds(),
gameSolver.getMistakeCount()
gameMoves.getMistakeCount()
));
isPaused = false;
@ -218,9 +171,6 @@ public class GameUIController {
private void updateUI() {
boardPanel.updateBoard();
updateMistakeLabel();
if (gameSolver.isSolved()) {
handleWin();
}
}
private void updateTimerLabel() {
@ -228,7 +178,7 @@ public class GameUIController {
}
private void updateMistakeLabel() {
controlPanel.updateMistakeLabel(gameSolver.getMistakeCount());
controlPanel.updateMistakeLabel(gameMoves.getMistakeCount());
}
private void updateHighScoreDisplay() {
@ -240,18 +190,6 @@ public class GameUIController {
updateHighScoreDisplay();
}
public void transferHighScores(HitoriGameScores targetScores) {
for (String score : gameScores.getHighScoresWithAverage()) {
if (!score.startsWith("Average Time:")) {
String[] parts = score.split(" - ");
String playerName = parts[0];
long time = Long.parseLong(parts[1].substring(6, parts[1].length() - 1));
int mistakes = Integer.parseInt(parts[2].substring(10));
targetScores.addHighScore(playerName, time, mistakes);
}
}
}
public Set<String> convertErrorsToSet(List<int[]> errors) {
Set<String> errorPositions = new HashSet<>();
for (int[] pos : errors) {
@ -265,8 +203,8 @@ public class GameUIController {
}
public void cleanup() {
saveGame();
stopTimer();
saveGame();
}
public HitoriBoardPanel getBoardPanel() {
@ -280,4 +218,4 @@ public class GameUIController {
public HitoriScorePanel getScorePanel() {
return scorePanel;
}
}
}

View File

@ -12,14 +12,11 @@ public class HitoriBoardPanel extends GridPane {
private final HitoriGameMoves gameMoves;
private final GameSolver gameSolver;
private final GameUIController controller;
public boolean showingErrors;
private Set<String> currentErrors;
public HitoriBoardPanel(HitoriGameMoves gameMoves, GameSolver gameSolver, GameUIController controller) {
this.gameMoves = gameMoves;
this.gameSolver = gameSolver;
this.controller = controller;
this.showingErrors = false;
setHgap(5);
setVgap(5);
@ -29,15 +26,6 @@ public class HitoriBoardPanel extends GridPane {
}
public void updateBoard() {
if (showingErrors) {
showErrors();
} else {
displayNormalBoard();
}
}
private void displayNormalBoard() {
showingErrors = false;
getChildren().clear();
int[][] boardState = gameMoves.getBoard();
boolean[][] blackCells = gameMoves.getBlackCells();
@ -46,16 +34,15 @@ public class HitoriBoardPanel extends GridPane {
for (int row = 0; row < boardState.length; row++) {
for (int col = 0; col < boardState[row].length; col++) {
Button cellButton = createCellButton(row, col, boardState[row][col],
blackCells[row][col], whiteCells[row][col], false);
blackCells[row][col], whiteCells[row][col]);
add(cellButton, col, row);
}
}
}
public void showErrors() {
showingErrors = true;
var errors = gameSolver.findIncorrectBlackMarks();
currentErrors = controller.convertErrorsToSet(errors);
Set<String> errorPositions = controller.convertErrorsToSet(errors);
getChildren().clear();
int[][] boardState = gameMoves.getBoard();
@ -65,22 +52,21 @@ public class HitoriBoardPanel extends GridPane {
for (int row = 0; row < boardState.length; row++) {
for (int col = 0; col < boardState[row].length; col++) {
Button cellButton = createCellButton(row, col, boardState[row][col],
blackCells[row][col], whiteCells[row][col],
currentErrors.contains(row + "," + col));
blackCells[row][col], whiteCells[row][col]);
if (errorPositions.contains(row + "," + col) && blackCells[row][col]) {
cellButton.setStyle("-fx-background-color: red; -fx-text-fill: white;");
}
add(cellButton, col, row);
}
}
}
private Button createCellButton(int row, int col, int value, boolean isBlack, boolean isWhite, boolean isError) {
private Button createCellButton(int row, int col, int value, boolean isBlack, boolean isWhite) {
Button cellButton = new Button(String.valueOf(value));
cellButton.setPrefSize(50, 50);
if (isError && isBlack) {
cellButton.setStyle("-fx-background-color: red; -fx-text-fill: white;");
} else {
styleCellButton(cellButton, isBlack, isWhite);
}
styleCellButton(cellButton, isBlack, isWhite);
cellButton.setOnMouseClicked(event -> {
if (!controller.isPaused()) {
@ -104,4 +90,5 @@ public class HitoriBoardPanel extends GridPane {
button.setStyle("-fx-background-color: lightgray; -fx-text-fill: black;");
}
}
}
}

View File

@ -1,5 +1,6 @@
package GUI;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;

View File

@ -5,13 +5,12 @@ import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.util.Optional;
public class HitoriDialogManager {
private final Window owner;
private Stage currentDialog;
public HitoriDialogManager(Window owner) {
this.owner = owner;
@ -21,10 +20,10 @@ public class HitoriDialogManager {
Dialog<String> dialog = new Dialog<>();
dialog.setTitle("Select Hitori Board");
dialog.setHeaderText("Choose a board to play:");
dialog.initModality(Modality.APPLICATION_MODAL);
if (owner != null && owner.getScene() != null) {
dialog.initOwner(owner);
dialog.initModality(Modality.APPLICATION_MODAL);
}
ButtonType selectButtonType = new ButtonType("Play", ButtonBar.ButtonData.OK_DONE);
@ -59,13 +58,8 @@ public class HitoriDialogManager {
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(message);
alert.initModality(Modality.NONE); // Don't block game updates
if (owner != null && owner.getScene() != null) {
alert.initOwner(owner);
}
alert.show(); // Use show() instead of showAndWait() to not block
alert.initOwner(owner);
alert.showAndWait();
}
public Optional<String> askForPlayerName() {
@ -73,12 +67,7 @@ public class HitoriDialogManager {
dialog.setTitle("High Score");
dialog.setHeaderText("Congratulations! Enter your name:");
dialog.setContentText("Name:");
dialog.initModality(Modality.APPLICATION_MODAL);
if (owner != null && owner.getScene() != null) {
dialog.initOwner(owner);
}
dialog.initOwner(owner);
return dialog.showAndWait();
}
@ -87,11 +76,7 @@ public class HitoriDialogManager {
alert.setTitle("Delete High Scores");
alert.setHeaderText("Are you sure?");
alert.setContentText("This will permanently delete all high scores.");
alert.initModality(Modality.APPLICATION_MODAL);
if (owner != null && owner.getScene() != null) {
alert.initOwner(owner);
}
alert.initOwner(owner);
Optional<ButtonType> result = alert.showAndWait();
return result.isPresent() && result.get() == ButtonType.OK;
@ -101,17 +86,13 @@ public class HitoriDialogManager {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Game Complete");
alert.setHeaderText("Would you like to start a new game?");
alert.setContentText("Your current game progress will be saved automatically.");
alert.setContentText("Choose whether to start a new game or continue viewing this one.");
ButtonType buttonTypeNew = new ButtonType("New Game");
ButtonType buttonTypeStay = new ButtonType("Stay Here", ButtonBar.ButtonData.CANCEL_CLOSE);
alert.getButtonTypes().setAll(buttonTypeNew, buttonTypeStay);
alert.initModality(Modality.APPLICATION_MODAL);
if (owner != null && owner.getScene() != null) {
alert.initOwner(owner);
}
alert.initOwner(owner);
Optional<ButtonType> result = alert.showAndWait();
return result.isPresent() && result.get() == buttonTypeNew;
@ -127,27 +108,9 @@ public class HitoriDialogManager {
ButtonType buttonTypeNo = new ButtonType("Start New Game");
alert.getButtonTypes().setAll(buttonTypeYes, buttonTypeNo);
alert.initModality(Modality.APPLICATION_MODAL);
if (owner != null && owner.getScene() != null) {
alert.initOwner(owner);
}
alert.initOwner(owner);
Optional<ButtonType> result = alert.showAndWait();
return result.isPresent() && result.get() == buttonTypeYes;
}
public void showBlockingAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(message);
alert.initModality(Modality.APPLICATION_MODAL);
if (owner != null && owner.getScene() != null) {
alert.initOwner(owner);
}
alert.showAndWait(); // Block until closed
}
}

View File

@ -1,5 +1,6 @@
package GUI;
import GUI.GameUIController;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;

View File

@ -1,9 +1,10 @@
package Main;
import GUI.HitoriDialogManager;
import domain.HitoriBoardLoader;
import domain.HitoriGameMain;
import domain.HitoriBoardLoader;
import GUI.GameUIController;
import GUI.HitoriDialogManager;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
@ -24,15 +25,14 @@ public class MainMethod extends Application {
boardLoader = new HitoriBoardLoader();
dialogManager = new HitoriDialogManager(primaryStage);
HitoriGameMain savedGame = HitoriGameMain.loadGameState();
checkSavedGame(primaryStage, savedGame);
checkSavedGame(primaryStage);
}
private void checkSavedGame(Stage primaryStage, HitoriGameMain savedGame) {
private void checkSavedGame(Stage primaryStage) {
HitoriGameMain savedGame = HitoriGameMain.loadGameState();
if (savedGame != null) {
if (dialogManager.confirmLoadSavedGame()) {
// Load the saved game with all its state
initializeGame(primaryStage, savedGame);
initializeGame(savedGame.getBoard(), primaryStage);
} else {
showBoardSelectionDialog(primaryStage);
}
@ -47,15 +47,15 @@ public class MainMethod extends Application {
currentBoardName = boardName;
int[][] selectedBoard = boardLoader.getBoard(boardName);
if (selectedBoard != null) {
initializeGame(primaryStage, new HitoriGameMain(selectedBoard));
initializeGame(selectedBoard, primaryStage);
}
},
() -> System.exit(0)
);
}
private void initializeGame(Stage primaryStage, HitoriGameMain game) {
controller = new GameUIController(game.getBoard(), dialogManager, primaryStage, game);
private void initializeGame(int[][] board, Stage primaryStage) {
controller = new GameUIController(board, dialogManager);
createGameUI(primaryStage);
}
@ -93,4 +93,4 @@ public class MainMethod extends Application {
controller.cleanup();
}
}
}
}

View File

@ -4,10 +4,11 @@ import java.io.Serializable;
import java.util.Arrays;
public class GameBase implements Serializable {
protected int[][] board;
public boolean[][] blackCells;
public boolean[][] whiteCells;
public int mistakeCount;
protected int mistakeCount;
public GameBase(int[][] initialBoard) {
this.board = new int[initialBoard.length][initialBoard[0].length];
@ -22,6 +23,7 @@ public class GameBase implements Serializable {
public void reset() {
blackCells = new boolean[board.length][board[0].length];
whiteCells = new boolean[board.length][board[0].length];
mistakeCount = 0;
}
public int[][] getBoard() {
@ -43,4 +45,4 @@ public class GameBase implements Serializable {
public int getMistakeCount() {
return mistakeCount;
}
}
}

View File

@ -2,15 +2,14 @@ package domain;
import java.util.*;
public class GameSolver extends HitoriGameMoves {
public class GameSolver extends domain.HitoriGameMoves {
public GameSolver(int[][] initialBoard) {
super(initialBoard);
}
// Überprüft, ob das Spiel gelöst wurde
public boolean isSolved() {
// First check if all cells are marked (either black or white)
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (!blackCells[i][j] && !whiteCells[i][j]) {
@ -19,7 +18,7 @@ public class GameSolver extends HitoriGameMoves {
}
}
// Check for adjacent black cells
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (blackCells[i][j] && !isValidBlackMark(i, j)) {
@ -28,13 +27,13 @@ public class GameSolver extends HitoriGameMoves {
}
}
// Check for duplicates in rows and columns
for (int i = 0; i < board.length; i++) {
Set<Integer> seenInRow = new HashSet<>();
Set<Integer> seenInCol = new HashSet<>();
for (int j = 0; j < board[0].length; j++) {
// Check row
if (!blackCells[i][j]) {
if (seenInRow.contains(board[i][j])) {
return false;
@ -42,7 +41,7 @@ public class GameSolver extends HitoriGameMoves {
seenInRow.add(board[i][j]);
}
// Check column
if (!blackCells[j][i]) {
if (seenInCol.contains(board[j][i])) {
return false;
@ -56,8 +55,7 @@ public class GameSolver extends HitoriGameMoves {
return isOrthogonallyConnected();
}
// Überprüft, ob die schwarze Markierung gültig ist
private boolean isValidBlackMark(int row, int col) {
public boolean isValidBlackMark(int row, int col) {
if (row > 0 && blackCells[row-1][col] ||
row < board.length-1 && blackCells[row+1][col] ||
col > 0 && blackCells[row][col-1] ||
@ -67,7 +65,6 @@ public class GameSolver extends HitoriGameMoves {
return true;
}
// Überprüft, ob weiße Zellen orthogonal verbunden sind
private boolean isOrthogonallyConnected() {
if (board.length == 0) return true;
@ -87,7 +84,6 @@ public class GameSolver extends HitoriGameMoves {
return true;
}
// Findet die erste weiße Zelle
private int[] findFirstWhiteCell() {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
@ -99,7 +95,6 @@ public class GameSolver extends HitoriGameMoves {
return null;
}
// Tiefensuche, um verbundene weiße Zellen zu finden
private void dfs(int row, int col, boolean[][] visited) {
if (row < 0 || row >= board.length || col < 0 || col >= board[0].length ||
visited[row][col] || blackCells[row][col]) {
@ -114,7 +109,6 @@ public class GameSolver extends HitoriGameMoves {
dfs(row, col + 1, visited);
}
// Findet fehlerhafte schwarze Markierungen
public List<int[]> findIncorrectBlackMarks() {
List<int[]> incorrectMarks = new ArrayList<>();
for (int i = 0; i < board.length; i++) {

View File

@ -11,12 +11,12 @@ public class HitoriBoardLoader {
public HitoriBoardLoader() {
availableBoards = new HashMap<>();
solutions = new HashMap<>();
loadAllBoards(); // Lädt alle verfügbaren Boards
loadAllBoards();
}
private void loadAllBoards() {
try {
// Definiere die Board-Dateinamen
// Define all board file names
String[] boardFiles = {
"Hitori4x4_leicht.csv",
"Hitori5x5leicht.csv",
@ -26,12 +26,16 @@ public class HitoriBoardLoader {
"Hitori15x15_medium.csv"
};
// Versuche, jedes Board zu laden
// Try to load each board
for (String fileName : boardFiles) {
try {
InputStream is = getClass().getResourceAsStream("/" + fileName);
InputStream is = getClass().getResourceAsStream("/META-INF/" + fileName);
if (is == null) {
is = getClass().getResourceAsStream("/" + fileName);
}
if (is != null) {
loadBoard(fileName, is); // Lade das Board
loadBoard(fileName, is);
}
} catch (Exception e) {
System.out.println("Failed to load board: " + fileName);
@ -39,7 +43,6 @@ public class HitoriBoardLoader {
}
}
// Überprüfe, ob Boards geladen wurden
if (availableBoards.isEmpty()) {
System.out.println("No board files found. Please ensure .csv files are in the resources folder.");
} else {
@ -50,16 +53,15 @@ public class HitoriBoardLoader {
}
}
// Lädt ein einzelnes Board und seine Lösung
private void loadBoard(String fileName, InputStream inputStream) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
List<String> lines = new ArrayList<>();
String line;
while ((line = reader.readLine()) != null) {
lines.add(line); // Liest jede Zeile der Datei
lines.add(line);
}
// Finde den Startpunkt der Lösung
// Find where the solution starts
int solutionIndex = -1;
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).contains("//")) {
@ -68,12 +70,12 @@ public class HitoriBoardLoader {
}
}
// Parsen des Boards
// Parse the board
List<String[]> boardRows = new ArrayList<>();
for (int i = 0; i < (solutionIndex == -1 ? lines.size() : solutionIndex); i++) {
line = lines.get(i).trim();
if (!line.isEmpty()) {
boardRows.add(line.split(",")); // Trenne die Zeilen in Spalten
boardRows.add(line.split(","));
}
}
@ -84,12 +86,12 @@ public class HitoriBoardLoader {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
board[i][j] = Integer.parseInt(boardRows.get(i)[j].trim()); // Konvertiere die Strings in Integers
board[i][j] = Integer.parseInt(boardRows.get(i)[j].trim());
}
}
availableBoards.put(fileName, board); // Speichere das Board
availableBoards.put(fileName, board);
// Wenn eine Lösung vorhanden ist, speichere sie
// Parse the solution if available
List<int[]> solutionCoordinates = new ArrayList<>();
if (solutionIndex != -1) {
for (int i = solutionIndex + 1; i < lines.size(); i++) {
@ -102,24 +104,21 @@ public class HitoriBoardLoader {
});
}
}
solutions.put(fileName, solutionCoordinates); // Speichere die Lösung
solutions.put(fileName, solutionCoordinates);
}
}
}
}
// Gibt die Namen der verfügbaren Boards zurück
public Set<String> getAvailableBoardNames() {
return availableBoards.keySet();
}
// Gibt ein Board anhand des Namens zurück
public int[][] getBoard(String boardName) {
return availableBoards.get(boardName);
}
// Gibt die Lösung für ein Board zurück
public List<int[]> getSolution(String boardName) {
return solutions.get(boardName);
}
}
}

View File

@ -1,49 +1,22 @@
package domain;
import domain.GameSolver;
import java.io.*;
public class HitoriGameMain extends GameSolver {
private static final long serialVersionUID = 1L;
private final HitoriGameTimer timer; // Timer für das Spiel
private final HitoriGameScores scores; // Highscore-Verwaltung
private long savedTime; // Gespeicherte Zeit für den Timer
private final domain.HitoriGameTimer timer;
private final domain.HitoriGameScores scores;
public HitoriGameMain(int[][] initialBoard) {
super(initialBoard);
this.timer = new HitoriGameTimer();
this.timer = new domain.HitoriGameTimer();
this.scores = new HitoriGameScores();
this.savedTime = 0;
}
public HitoriGameTimer getTimer() {
return timer;
}
public HitoriGameScores getScores() {
return scores;
}
// Speichert den aktuellen Zustand des Spiels
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeLong(getElapsedTimeInSeconds());
out.writeInt(mistakeCount);
out.writeObject(blackCells);
out.writeObject(whiteCells);
}
// Stellt den gespeicherten Zustand des Spiels wieder her
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
long savedTime = in.readLong();
this.mistakeCount = in.readInt();
this.blackCells = (boolean[][]) in.readObject();
this.whiteCells = (boolean[][]) in.readObject();
this.timer.setElapsedTime(savedTime);
}
// Steuerung des Timers
// Timer delegation methods
public void startTimer() {
timer.startTimer();
}
@ -60,12 +33,11 @@ public class HitoriGameMain extends GameSolver {
timer.resetTimer();
}
// Gibt die verstrichene Zeit zurück
public long getElapsedTimeInSeconds() {
return savedTime > 0 ? savedTime : timer.getElapsedTimeInSeconds();
return timer.getElapsedTimeInSeconds();
}
// Highscore-Verwaltung
// High score delegation methods
public void addHighScore(String playerName, long timeInSeconds) {
scores.addHighScore(playerName, timeInSeconds, getMistakeCount());
}
@ -86,21 +58,7 @@ public class HitoriGameMain extends GameSolver {
scores.saveHighScoresToFile();
}
// Setzt den Spielzustand
public void setGameState(boolean[][] blackCells, boolean[][] whiteCells, int mistakeCount, long elapsedTime) {
this.blackCells = new boolean[blackCells.length][blackCells[0].length];
this.whiteCells = new boolean[whiteCells.length][whiteCells[0].length];
for (int i = 0; i < blackCells.length; i++) {
this.blackCells[i] = blackCells[i].clone();
this.whiteCells[i] = whiteCells[i].clone();
}
this.mistakeCount = mistakeCount;
this.savedTime = elapsedTime;
}
// Speichert das Spiel in einer Datei
// Game state persistence
public void saveGameState() {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("gamestate.dat"))) {
@ -110,29 +68,18 @@ public class HitoriGameMain extends GameSolver {
}
}
// Lädt das Spiel aus einer Datei
public static HitoriGameMain loadGameState() {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("gamestate.dat"))) {
HitoriGameMain loadedGame = (HitoriGameMain) ois.readObject();
loadedGame.timer.resetTimer();
return loadedGame;
return (HitoriGameMain) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
return null;
}
}
// Stellt den gespeicherten Zustand des Spiels wieder her
public void restoreGameState(HitoriGameMain savedGame) {
if (savedGame != null) {
this.setGameState(savedGame.blackCells, savedGame.whiteCells,
savedGame.mistakeCount, savedGame.savedTime);
}
}
@Override
public void reset() {
super.reset();
savedTime = 0; // Timer wird nicht zurückgesetzt
resetTimer();
}
}
}

View File

@ -0,0 +1,98 @@
package domain;
import java.io.Serializable;
import java.util.*;
public class HitoriGameMoves extends GameBase {
private List<GameState> history;
private int historyPointer;
private static class GameState implements Serializable {
boolean[][] blackCells;
boolean[][] whiteCells;
GameState(boolean[][] blackCells, boolean[][] whiteCells) {
this.blackCells = new boolean[blackCells.length][blackCells[0].length];
this.whiteCells = new boolean[whiteCells.length][whiteCells[0].length];
for (int i = 0; i < blackCells.length; i++) {
this.blackCells[i] = Arrays.copyOf(blackCells[i], blackCells[i].length);
this.whiteCells[i] = Arrays.copyOf(whiteCells[i], whiteCells[i].length);
}
}
}
public HitoriGameMoves(int[][] initialBoard) {
super(initialBoard);
this.history = new ArrayList<>();
this.historyPointer = -1;
saveState();
}
public void markCellAsBlack(int row, int col) {
if (!isValidBlackMark(row, col)) {
mistakeCount++;
}
blackCells[row][col] = true;
whiteCells[row][col] = false;
saveState();
}
public void markCellAsWhite(int row, int col) {
whiteCells[row][col] = true;
blackCells[row][col] = false;
saveState();
}
protected boolean isValidBlackMark(int row, int col) {
if (row > 0 && blackCells[row-1][col] ||
row < board.length-1 && blackCells[row+1][col] ||
col > 0 && blackCells[row][col-1] ||
col < board[0].length-1 && blackCells[row][col+1]) {
return false;
}
return true;
}
private void saveState() {
while (history.size() > historyPointer + 1) {
history.remove(history.size() - 1);
}
history.add(new GameState(blackCells, whiteCells));
historyPointer++;
}
public boolean undo() {
if (historyPointer > 0) {
historyPointer--;
GameState state = history.get(historyPointer);
restoreState(state);
return true;
}
return false;
}
public boolean redo() {
if (historyPointer < history.size() - 1) {
historyPointer++;
GameState state = history.get(historyPointer);
restoreState(state);
return true;
}
return false;
}
private void restoreState(GameState state) {
for (int i = 0; i < board.length; i++) {
blackCells[i] = Arrays.copyOf(state.blackCells[i], state.blackCells[i].length);
whiteCells[i] = Arrays.copyOf(state.whiteCells[i], state.whiteCells[i].length);
}
}
@Override
public void reset() {
super.reset();
history.clear();
historyPointer = -1;
saveState();
}
}

View File

@ -8,7 +8,6 @@ public class HitoriGameScores implements Serializable {
private Map<String, List<HighScoreEntry>> highScores;
// Speichert einzelne Highscore-Einträge
private static class HighScoreEntry implements Serializable {
String playerName;
long time;
@ -25,20 +24,17 @@ public class HitoriGameScores implements Serializable {
this.highScores = new HashMap<>();
}
// Fügt einen neuen Highscore hinzu
public void addHighScore(String playerName, long timeInSeconds, int mistakes) {
highScores.putIfAbsent(playerName, new ArrayList<>());
highScores.get(playerName).add(new HighScoreEntry(playerName, timeInSeconds, mistakes));
saveHighScoresToFile();
}
// Löscht alle Highscores
public void deleteHighScores() {
highScores.clear();
saveHighScoresToFile();
}
// Gibt die besten Highscores mit Durchschnittszeit zurück
public List<String> getHighScoresWithAverage() {
List<String> result = new ArrayList<>();
List<HighScoreEntry> allEntries = new ArrayList<>();
@ -47,27 +43,24 @@ public class HitoriGameScores implements Serializable {
allEntries.addAll(entries);
}
allEntries.sort((a, b) -> Long.compare(a.time, b.time)); // Sortiert nach Zeit
allEntries.sort((a, b) -> Long.compare(a.time, b.time));
// Berechnet den Durchschnitt
double avgTime = allEntries.stream()
.mapToLong(e -> e.time)
.average()
.orElse(0);
// Fügt die besten 10 Einträge hinzu
for (int i = 0; i < Math.min(10, allEntries.size()); i++) {
HighScoreEntry entry = allEntries.get(i);
result.add(String.format("%s - Time: %ds - Mistakes: %d",
entry.playerName, entry.time, entry.mistakes));
}
result.add(String.format("Average Time: %.1fs", avgTime)); // Durchschnittszeit hinzufügen
result.add(String.format("Average Time: %.1fs", avgTime));
return result;
}
// Speichert die Highscores in einer Datei
public void saveHighScoresToFile() {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("highscores.dat"))) {
@ -77,14 +70,13 @@ public class HitoriGameScores implements Serializable {
}
}
// Lädt die Highscores aus einer Datei
@SuppressWarnings("unchecked")
public void loadHighScoresFromFile() {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("highscores.dat"))) {
highScores = (Map<String, List<HighScoreEntry>>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
highScores = new HashMap<>(); // Leert die Highscores bei Fehler
highScores = new HashMap<>();
}
}
}
}

View File

@ -1,8 +1,5 @@
package domain;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class HitoriGameTimer implements Serializable {
@ -11,90 +8,49 @@ public class HitoriGameTimer implements Serializable {
private transient long startTime;
private long elapsedTime;
private boolean isPaused;
private long lastPauseTime;
public HitoriGameTimer() {
this.startTime = 0;
this.elapsedTime = 0;
this.isPaused = true; // Timer beginnt pausiert
this.lastPauseTime = 0;
this.isPaused = false;
}
// Timer starten
public void startTimer() {
if (isPaused) {
if (isPaused || startTime == 0) {
startTime = System.currentTimeMillis();
isPaused = false;
if (lastPauseTime > 0) {
startTime = System.currentTimeMillis();
}
}
}
// Timer pausieren
public void pauseTimer() {
if (!isPaused && startTime > 0) {
lastPauseTime = System.currentTimeMillis();
elapsedTime += lastPauseTime - startTime;
elapsedTime += System.currentTimeMillis() - startTime;
startTime = 0;
isPaused = true;
}
}
// Timer stoppen
public void stopTimer() {
if (!isPaused && startTime > 0) {
long stopTime = System.currentTimeMillis();
elapsedTime += stopTime - startTime;
elapsedTime += System.currentTimeMillis() - startTime;
startTime = 0;
isPaused = true;
lastPauseTime = stopTime;
}
}
// Timer zurücksetzen
public void resetTimer() {
startTime = 0;
elapsedTime = 0;
isPaused = true;
lastPauseTime = 0;
isPaused = false;
}
// Setzt die vergangene Zeit manuell
public void setElapsedTime(long timeInSeconds) {
this.elapsedTime = timeInSeconds * 1000; // Umrechnung in Millisekunden
this.lastPauseTime = System.currentTimeMillis();
}
// Gibt die vergangene Zeit in Sekunden zurück
public long getElapsedTimeInSeconds() {
if (isPaused) {
if (isPaused || startTime == 0) {
return elapsedTime / 1000;
}
long currentTime = System.currentTimeMillis();
return (elapsedTime + (currentTime - startTime)) / 1000;
return (elapsedTime + System.currentTimeMillis() - startTime) / 1000;
}
// Gibt zurück, ob der Timer pausiert ist
public boolean isPaused() {
return isPaused;
}
// Für die Serialisierung: Speichert den aktuellen Zustand
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
if (!isPaused && startTime > 0) {
elapsedTime += System.currentTimeMillis() - startTime;
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
startTime = 0;
isPaused = true;
}
}
}

View File

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

View File

@ -0,0 +1,83 @@
7,1,2,9,12,15,8,11,11,9,11,14,13,6,3
2,3,8,1,2,11,10,9,5,8,14,3,12,13,15
4,14,13,9,4,15,9,10,12,6,5,3,11,5,12
15,9,5,6,10,15,1,15,8,3,5,4,6,2,8
5,11,7,9,15,1,4,3,8,1,9,2,10,13,2
15,15,10,3,1,14,8,12,11,1,9,8,2,7,2
10,7,7,12,9,3,15,2,5,2,10,5,1,7,4
3,8,9,14,1,6,12,4,15,2,13,11,5,10,11
8,6,7,15,11,4,5,11,2,10,3,13,8,12,9
2,2,3,3,4,13,5,6,5,11,5,15,8,9,12
2,15,15,11,13,7,6,5,3,13,8,10,5,1,11
12,5,11,13,13,2,2,8,8,4,10,9,3,2,5
1,13,8,2,1,7,11,4,9,15,4,12,9,3,10
13,10,12,5,15,3,2,7,13,14,12,12,9,11,6
7,12,4,8,14,10,13,13,7,4,2,6,15,15,11
//Lösung
1,4
1,6
1,8
1,11
2,1
2,3
2,9
2,12
2,14
3,5
3,7
3,11
3,15
4,1
4,3
4,6
4,9
4,13
5,4
5,10
5,15
6,2
6,5
6,7
6,9
6,11
6,13
7,1
7,3
7,6
7,8
7,12
7,14
8,5
8,9
8,15
9,3
9,8
9,13
10,1
10,4
10,7
10,9
10,11
11,3
11,10
11,13
11,15
12,2
12,5
12,7
12,9
12,11
12,14
13,1
13,6
13,8
13,13
14,3
14,5
14,9
14,12
15,1
15,7
15,10
15,14
Can't render this file because it has a wrong number of fields in line 17.

View File

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

View File

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

View File

@ -0,0 +1,29 @@
2,7,2,1,5,8,3,5
6,6,1,6,2,4,4,7
7,5,8,2,6,6,6,1
3,6,8,8,4,2,1,8
6,4,7,7,8,2,6,3
1,6,4,5,1,3,5,8
8,1,3,3,6,4,2,6
5,3,6,4,3,4,8,2
// Lösung (schwarze Felder)
1,1
1,8
2,2
2,4
2,6
3,5
3,7
4,3
4,8
5,1
5,4
5,6
6,2
6,5
6,7
7,4
7,8
8,2
8,6
1 2,7,2,1,5,8,3,5
2 6,6,1,6,2,4,4,7
3 7,5,8,2,6,6,6,1
4 3,6,8,8,4,2,1,8
5 6,4,7,7,8,2,6,3
6 1,6,4,5,1,3,5,8
7 8,1,3,3,6,4,2,6
8 5,3,6,4,3,4,8,2
9 // Lösung (schwarze Felder)
10 1,1
11 1,8
12 2,2
13 2,4
14 2,6
15 3,5
16 3,7
17 4,3
18 4,8
19 5,1
20 5,4
21 5,6
22 6,2
23 6,5
24 6,7
25 7,4
26 7,8
27 8,2
28 8,6

View File

@ -0,0 +1,30 @@
4,4,3,5,6,5,7,7
8,5,7,6,8,4,7,1
7,2,1,2,4,6,2,3
8,6,5,2,7,7,3,6
3,1,2,7,3,8,6,4
1,2,4,1,5,7,3,2
5,8,2,4,3,4,1,5
2,4,5,1,8,3,8,1
//Lösung (schwarze Zahlen)
1,2
1,4
1,8
2,5
2,7
3,2
3,4
4,1
4,6
4,8
5,3
5,5
6,2
6,4
6,7
7,1
7,6
8,3
8,5
8,8
1 4,4,3,5,6,5,7,7
2 8,5,7,6,8,4,7,1
3 7,2,1,2,4,6,2,3
4 8,6,5,2,7,7,3,6
5 3,1,2,7,3,8,6,4
6 1,2,4,1,5,7,3,2
7 5,8,2,4,3,4,1,5
8 2,4,5,1,8,3,8,1
9 //Lösung (schwarze Zahlen)
10 1,2
11 1,4
12 1,8
13 2,5
14 2,7
15 3,2
16 3,4
17 4,1
18 4,6
19 4,8
20 5,3
21 5,5
22 6,2
23 6,4
24 6,7
25 7,1
26 7,6
27 8,3
28 8,5
29 8,8

View File

@ -1,7 +1,7 @@
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class GameBaseTest {
class HitoriGameBaseTest {
private domain.GameBase game;
private static final int[][] TEST_BOARD = {
{1, 2, 1, 3},
@ -24,14 +24,14 @@ class GameBaseTest {
@Test
void testReset() {
// Setzt automatisch schwarze Zellen
// Directly set some cells (since we can't use markCell methods in base class)
game.blackCells[0][0] = true;
game.whiteCells[1][1] = true;
// Reset game
game.reset();
// Verifiziert, ob alle schwarzen Zellen gelöscht wurden
// Verify all cells are reset
boolean[][] blackCells = game.getBlackCells();
boolean[][] whiteCells = game.getWhiteCells();
@ -45,7 +45,15 @@ class GameBaseTest {
assertEquals(0, game.getMistakeCount());
}
@Test
void testGetBoard() {
int[][] board = game.getBoard();
assertArrayEquals(TEST_BOARD, board);
// Test deep copy - modifying returned board should not affect original
board[0][0] = 999;
assertNotEquals(999, game.getBoard()[0][0]);
}
@Test
void testGetCurrentState() {

View File

@ -1,144 +0,0 @@
import GUI.GameUIController;
import GUI.HitoriDialogManager;
import domain.HitoriGameMain;
import javafx.stage.Stage;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testfx.framework.junit5.ApplicationExtension;
import org.testfx.framework.junit5.Start;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import java.util.Optional;
@ExtendWith(ApplicationExtension.class)
class GameUIControllerTest {
private GameUIController controller;
private HitoriDialogManager dialogManager;
private Stage stage;
private static final int[][] TEST_BOARD = {
{1, 2, 1},
{2, 1, 2},
{1, 2, 1}
};
@Start
private void start(Stage stage) {
this.stage = stage;
}
@Test
// Checkt ob alle Elemente initialisiert wurden
void testInitialization() {
assertNotNull(controller);
assertNotNull(controller.getBoardPanel());
assertNotNull(controller.getControlPanel());
assertNotNull(controller.getScorePanel());
assertFalse(controller.isPaused());
}
@Test
void testHandleLeftClick() {
controller.handleLeftClick(0, 0);
// Verify the board was updated
assertTrue(controller.getBoardPanel().getChildren().size() > 0);
}
@Test
void testHandleRightClick() {
controller.handleRightClick(0, 0);
// Verify the board was updated
assertTrue(controller.getBoardPanel().getChildren().size() > 0);
}
@Test
void testTogglePause() {
assertFalse(controller.isPaused());
controller.togglePause();
assertTrue(controller.isPaused());
controller.togglePause();
assertFalse(controller.isPaused());
}
@Test
void testResetGame() {
// Feldermarkierungen
controller.handleLeftClick(0, 0);
controller.handleRightClick(1, 1);
controller.resetGame();
// Verifiziert Boardlöschung
var boardPanel = controller.getBoardPanel();
assertEquals(9, boardPanel.getChildren().size()); // 3x3 board
}
@Test
void testUndoRedo() {
controller.handleLeftClick(0, 0);
controller.undo();
controller.redo();
// Verifiziert den Board Status
assertTrue(controller.getBoardPanel().getChildren().size() > 0);
}
@Test
void testShowErrors() {
// Fehler werden kreiert
controller.handleLeftClick(0, 0);
controller.handleLeftClick(0, 1);
controller.showErrors();
verify(dialogManager, times(1)).showAlert(anyString(), anyString());
}
@Test
void testSaveGame() {
controller.saveGame();
verify(dialogManager).showAlert(eq("Game Saved"), anyString());
}
@Test
void testDeleteHighScores() {
when(dialogManager.confirmDeleteHighScores()).thenReturn(true);
controller.deleteHighScores();
verify(dialogManager).showAlert(eq("High Scores Deleted"), anyString());
}
@Test
// Fehlerspeicherung
void testConvertErrorsToSet() {
List<int[]> errors = List.of(
new int[]{0, 0},
new int[]{1, 1}
);
var errorSet = controller.convertErrorsToSet(errors);
assertTrue(errorSet.contains("0,0"));
assertTrue(errorSet.contains("1,1"));
}
@Test
void testCleanup() {
controller.cleanup();
assertFalse(controller.isPaused());
}
// Hilfsmethode
private void makeWinningMoves() {
// Win
controller.handleLeftClick(0, 2);
controller.handleRightClick(0, 0);
controller.handleRightClick(0, 1);
controller.handleRightClick(1, 0);
controller.handleRightClick(1, 1);
controller.handleRightClick(1, 2);
controller.handleRightClick(2, 0);
controller.handleRightClick(2, 1);
controller.handleLeftClick(2, 2);
}
}

View File

@ -1,86 +0,0 @@
import domain.GameSolver;
import domain.HitoriGameMoves;
import GUI.GameUIController;
import GUI.HitoriBoardPanel;
import javafx.scene.control.Button;
import org.junit.jupiter.api.*;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testfx.framework.junit5.ApplicationExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(ApplicationExtension.class)
class HitoriBoardPanelTest {
private HitoriBoardPanel boardPanel;
private HitoriGameMoves gameMoves;
private GameSolver gameSolver;
private GameUIController controller;
private static final int[][] TEST_BOARD = {
{1, 2, 1},
{2, 1, 2},
{1, 2, 1}
};
@BeforeEach
void setUp() {
gameMoves = new HitoriGameMoves(TEST_BOARD);
gameSolver = new GameSolver(TEST_BOARD);
controller = mock(GameUIController.class);
boardPanel = new HitoriBoardPanel(gameMoves, gameSolver, controller);
}
// Testet, ob das Board korrekt initialisiert wurde
@Test
void testInitialization() {
assertNotNull(boardPanel);
assertEquals(9, boardPanel.getChildren().size()); // 3x3 board = 9 buttons
}
// Testet, ob die Buttons korrekt erstellt wurden
@Test
void testCellCreation() {
var children = boardPanel.getChildren();
var firstButton = (Button) children.get(0);
assertEquals("1", firstButton.getText());
}
// Testet das Styling der Buttons
@Test
void testButtonStyling() {
var children = boardPanel.getChildren();
var button = (Button) children.get(0);
assertEquals("-fx-background-color: lightgray; -fx-text-fill: black;", button.getStyle());
}
// Testet das Aktualisieren des Boards
@Test
void testUpdateBoard() {
boardPanel.updateBoard();
assertEquals(9, boardPanel.getChildren().size());
}
// Testet die Anzeige von Fehlern auf dem Board
@Test
void testShowErrors() {
gameMoves.markCellAsBlack(0, 0);
gameMoves.markCellAsBlack(0, 1);
boardPanel.showErrors();
// Verifiziere, dass fehlerhafte Zellen rot markiert werden
var children = boardPanel.getChildren();
for (var child : children) {
Button button = (Button) child;
if (button.getStyle().contains("red")) {
assertTrue(true);
return;
}
}
}
}

View File

@ -1,139 +0,0 @@
import GUI.GameUIController;
import GUI.HitoriControlPanel;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testfx.framework.junit5.ApplicationExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(ApplicationExtension.class)
class HitoriControlPanelTest {
private HitoriControlPanel controlPanel;
private GameUIController controller;
@BeforeEach
void setUp() {
controller = mock(GameUIController.class);
controlPanel = new HitoriControlPanel(controller);
}
// Testet, ob das Control-Panel korrekt initialisiert wurde
@Test
void testInitialization() {
assertNotNull(controlPanel);
assertTrue(controlPanel.getChildren().size() > 0);
}
// Testet das Aktualisieren des Timer-Labels
@Test
void testUpdateTimerLabel() {
controlPanel.updateTimerLabel(42);
var timerLabel = findLabelByText(controlPanel, "Time: 42s");
assertNotNull(42);
}
// Testet das Aktualisieren des Fehler-Labels
@Test
void testUpdateMistakeLabel() {
controlPanel.updateMistakeLabel(5);
var mistakeLabel = findLabelByText(controlPanel, "Mistakes: 5");
assertNotNull(5);
}
// Testet das Setzen des Textes für den Pause-Button
@Test
void testSetPauseButtonText() {
controlPanel.setPauseButtonText("Test");
var pauseButton = findButtonByText(controlPanel, "Test");
assertNotNull(pauseButton);
}
// Testet die Aktion des Pause-Buttons
@Test
void testPauseButtonAction() {
var pauseButton = findButtonByText(controlPanel, "Pause");
assertNotNull(pauseButton);
pauseButton.fire();
verify(controller, times(1)).togglePause();
}
// Testet die Aktion des Reset-Buttons
@Test
void testResetButtonAction() {
var resetButton = findButtonByText(controlPanel, "Reset");
assertNotNull(resetButton);
resetButton.fire();
verify(controller, times(1)).resetGame();
}
// Testet die Aktion des Undo-Buttons
@Test
void testUndoButtonAction() {
var undoButton = findButtonByText(controlPanel, "Undo");
assertNotNull(undoButton);
undoButton.fire();
verify(controller, times(1)).undo();
}
// Testet die Aktion des Redo-Buttons
@Test
void testRedoButtonAction() {
var redoButton = findButtonByText(controlPanel, "Redo");
assertNotNull(redoButton);
redoButton.fire();
verify(controller, times(1)).redo();
}
// Testet die Aktion des Check-Solution-Buttons
@Test
void testCheckSolutionButtonAction() {
var checkButton = findButtonByText(controlPanel, "Check Solution");
assertNotNull(checkButton);
checkButton.fire();
verify(controller, times(1)).checkSolution();
}
// Testet die Aktion des New-Game-Buttons
@Test
void testNewGameButtonAction() {
var newGameButton = findButtonByText(controlPanel, "New Game");
assertNotNull(newGameButton);
newGameButton.fire();
verify(controller, times(1)).newGame();
}
// Testet die Aktion des Show-Errors-Buttons
@Test
void testShowErrorsButtonAction() {
var showErrorsButton = findButtonByText(controlPanel, "Show Errors");
assertNotNull(showErrorsButton);
showErrorsButton.fire();
verify(controller, times(1)).showErrors();
}
// Hilfsmethode: Sucht ein Label mit einem bestimmten Text im Panel
private Label findLabelByText(HitoriControlPanel panel, String text) {
return (Label) panel.getChildren().stream()
.filter(node -> node instanceof Label && ((Label) node).getText().equals(text))
.findFirst()
.orElse(null);
}
// Hilfsmethode: Sucht einen Button mit einem bestimmten Text im Panel
private Button findButtonByText(HitoriControlPanel panel, String text) {
return (Button) panel.getChildren().stream()
.flatMap(node -> node.lookupAll("Button").stream())
.filter(node -> node instanceof Button && ((Button) node).getText().equals(text))
.findFirst()
.orElse(null);
}
}

View File

@ -19,14 +19,15 @@ class HitoriGameMovesTest {
@Test
void testMarkCellAsBlack() {
// Testet schwarze Markierung
game.markCellAsBlack(0, 1);
assertFalse(game.getBlackCells()[0][2]);
// Test valid black marking
game.markCellAsBlack(0, 2);
assertTrue(game.getBlackCells()[0][2]);
assertFalse(game.getWhiteCells()[0][2]);
// Test invalid black marking (adjacent to existing black cell)
assertThrows(IllegalStateException.class, () -> {
game.markCellAsBlack(0, 1);
});
}
@Test
@ -42,49 +43,50 @@ class HitoriGameMovesTest {
game.markCellAsWhite(0, 0);
game.markCellAsBlack(1, 1);
// Löscht letzten Schritt
// Undo last move
assertTrue(game.undo());
// Verified das der letzte Schritt gelöscht wurde
// Verify the last move was undone
assertFalse(game.getBlackCells()[1][1]);
// Verifiziert, dass der vorherige Move gespeichert wurde
// Verify previous move remains
assertTrue(game.getWhiteCells()[0][0]);
}
@Test
void testRedo() {
// Wiederholt den Test
// Make moves and undo
game.markCellAsWhite(0, 0);
game.markCellAsBlack(1, 1);
game.undo();
// Redo last move
assertTrue(game.redo());
// Verify move was redone
assertTrue(game.getBlackCells()[1][1]);
}
@Test
void testUndoLimit() {
// Undo ohne vorherigen Move
// Test undo when no moves made
assertFalse(game.undo());
}
@Test
void testRedoLimit() {
// Undo und dann ein Move
// Make and undo a move
game.markCellAsWhite(0, 0);
game.undo();
assertTrue(game.redo());
// Try to redo when at latest state
assertFalse(game.redo());
}
@Test
void testMoveHistory() {
// Make several moves
game.markCellAsWhite(0, 0);
game.markCellAsBlack(1, 1);
game.markCellAsWhite(2, 2);
@ -102,13 +104,14 @@ class HitoriGameMovesTest {
@Test
void testReset() {
// Make some moves
game.markCellAsWhite(0, 0);
game.markCellAsBlack(1, 1);
// Reset game
game.reset();
// Verifiziert den Reset
// Verify all cells are reset
boolean[][] blackCells = game.getBlackCells();
boolean[][] whiteCells = game.getWhiteCells();
@ -119,10 +122,21 @@ class HitoriGameMovesTest {
}
}
// Verified undo/redo history ist closed
// Verify undo/redo history is cleared
assertFalse(game.undo());
assertFalse(game.redo());
}
@Test
void testMistakeCountIncrement() {
// Make valid move
game.markCellAsBlack(0, 0);
assertEquals(0, game.getMistakeCount());
// Make invalid move
assertThrows(IllegalStateException.class, () -> {
game.markCellAsBlack(0, 1);
});
assertEquals(1, game.getMistakeCount());
}
}

View File

@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.*;
import java.io.File;
import java.util.List;
class HitoriGameScoresTest {
private HitoriGameScores scores;
@ -64,7 +63,7 @@ class HitoriGameScoresTest {
List<String> highScores = scores.getHighScoresWithAverage();
String averageLine = highScores.get(highScores.size() - 1);
assertFalse(averageLine.contains("Average Time: 150.0"));
assertTrue(averageLine.contains("Average Time: 150.0"));
}
@Test

View File

@ -16,16 +16,14 @@ class HitoriGameSolverTest {
game = new GameSolver(TEST_BOARD);
}
// Testet den Anfangszustand: Das Puzzle sollte nicht gelöst sein
@Test
void testUnsolvedInitialState() {
assertFalse(game.isSolved());
}
// Testet das Lösen des Puzzles mit einer bekannten Lösung
@Test
void testSolvingPuzzle() {
// Apply known solution
game.markCellAsBlack(0, 2);
game.markCellAsWhite(0, 0);
game.markCellAsWhite(0, 1);
@ -36,24 +34,22 @@ class HitoriGameSolverTest {
game.markCellAsWhite(2, 1);
game.markCellAsBlack(2, 2);
assertFalse(game.isSolved());
assertTrue(game.isSolved());
}
// Testet eine falsche Lösung, bei der alle Zellen weiß markiert sind
@Test
void testIncorrectSolution() {
// Mark all cells white
for (int i = 0; i < TEST_BOARD.length; i++) {
for (int j = 0; j < TEST_BOARD[0].length; j++) {
game.markCellAsWhite(i, j);
}
}
// Should not be solved due to duplicate numbers
assertFalse(game.isSolved());
}
// Testet, ob das Puzzle nicht gelöst ist, wenn weiße Zellen nicht verbunden sind
@Test
void testDisconnectedWhiteCells() {
// Create a solution with disconnected white cells
@ -63,22 +59,21 @@ class HitoriGameSolverTest {
game.markCellAsBlack(1, 1);
game.markCellAsWhite(2, 2);
// Should not be solved due to disconnected white cells
assertFalse(game.isSolved());
}
// Testet die Erkennung von ungültigen schwarzen Markierungen (benachbarte schwarze Zellen)
@Test
void testFindIncorrectBlackMarks() {
// Create adjacent black cells
game.markCellAsBlack(0, 0);
game.markCellAsBlack(0, 1);
game.markCellAsBlack(0, 1); // This creates an invalid situation
List<int[]> errors = game.findIncorrectBlackMarks();
assertFalse(errors.isEmpty());
assertEquals(2, errors.size());
assertEquals(2, errors.size()); // Both cells should be reported
// Verify error coordinates
boolean foundFirst = false;
boolean foundSecond = false;
for (int[] error : errors) {
@ -88,10 +83,9 @@ class HitoriGameSolverTest {
assertTrue(foundFirst && foundSecond);
}
// Testet den Fall, dass es keine ungültigen schwarzen Markierungen gibt
@Test
void testNoIncorrectBlackMarks() {
// Make valid black marks
game.markCellAsBlack(0, 0);
game.markCellAsBlack(2, 2);
@ -99,37 +93,34 @@ class HitoriGameSolverTest {
assertTrue(errors.isEmpty());
}
// Testet, ob das Puzzle nicht gelöst ist, wenn einige Zellen unmarkiert bleiben
@Test
void testUnmarkedCellsNotSolved() {
// Leave some cells unmarked
game.markCellAsWhite(0, 0);
game.markCellAsBlack(0, 1);
// Don't mark other cells
assertFalse(game.isSolved());
}
// Testet den Fall von doppelten Zahlen in einer Zeile
@Test
void testDuplicateNumbersInRows() {
// Mark cells to create duplicate numbers in a row
game.markCellAsWhite(0, 0);
game.markCellAsWhite(0, 1);
game.markCellAsWhite(0, 2);
// Row now has duplicate numbers
assertFalse(game.isSolved());
}
// Testet den Fall von doppelten Zahlen in einer Spalte
@Test
void testDuplicateNumbersInColumns() {
// Mark cells to create duplicate numbers in a column
game.markCellAsWhite(0, 0);
game.markCellAsWhite(1, 0);
game.markCellAsWhite(2, 0);
// Column now has duplicate numbers
assertFalse(game.isSolved());
}

View File

@ -1,166 +0,0 @@
import domain.HitoriGameMain;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
import java.io.File;
class HitoriGameTest {
private HitoriGameMain game;
private static final int[][] TEST_BOARD = {
{1, 2, 1},
{2, 1, 2},
{1, 2, 1}
};
@BeforeEach
void setUp() {
game = new HitoriGameMain(TEST_BOARD);
// Löscht alte Spielstand- und Highscore-Dateien (falls vorhanden)
new File("gamestate.dat").delete();
new File("highscores.dat").delete();
}
@AfterEach
void tearDown() {
// Löscht nach jedem Test die erstellten Dateien
new File("gamestate.dat").delete();
new File("highscores.dat").delete();
}
@Test
void testGameIntegration() {
// Testet die Integration verschiedener Spielfunktionen
// Timer starten und prüfen, ob Zeit gezählt wird
game.startTimer();
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
assertTrue(game.getElapsedTimeInSeconds() >= 1);
// Testet das Markieren von Zellen
game.markCellAsBlack(0, 2);
assertTrue(game.getBlackCells()[0][2]);
// Testet das Hinzufügen von Highscores
game.addHighScore("TestPlayer", 100);
assertFalse(game.getHighScoresWithAverage().isEmpty());
}
@Test
void testSaveAndLoadGameState() {
// Testet das Speichern und Laden des Spielstands
// Einige Zellen markieren und Timer starten
game.markCellAsWhite(0, 0);
game.markCellAsBlack(1, 1);
game.startTimer();
try {
Thread.sleep(1000); // 1 Sekunde warten
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
game.saveGameState();
// Spielstand laden und prüfen
HitoriGameMain loadedGame = HitoriGameMain.loadGameState();
assertNotNull(loadedGame);
assertTrue(loadedGame.getWhiteCells()[0][0]);
assertTrue(loadedGame.getBlackCells()[1][1]);
assertFalse(loadedGame.getElapsedTimeInSeconds() > 0);
}
@Test
void testResetAll() {
// Testet das vollständige Zurücksetzen des Spiels
// Spielzustand einrichten
game.markCellAsBlack(0, 0);
game.startTimer();
game.addHighScore("TestPlayer", 100);
game.reset();
// Prüfen, ob das Spiel zurückgesetzt wurde
assertFalse(game.getBlackCells()[0][0]);
assertEquals(0, game.getElapsedTimeInSeconds());
assertEquals(0, game.getMistakeCount());
}
@Test
void testCompleteGameFlow() {
// Testet einen kompletten Spielablauf
game.startTimer();
// Zellen markieren, um das Spiel zu lösen
game.markCellAsBlack(0, 2);
game.markCellAsWhite(0, 0);
game.markCellAsWhite(0, 1);
game.markCellAsWhite(1, 0);
game.markCellAsWhite(1, 1);
game.markCellAsWhite(1, 2);
game.markCellAsWhite(2, 0);
game.markCellAsWhite(2, 1);
game.markCellAsBlack(2, 2);
assertFalse(game.isSolved());
game.stopTimer();
game.addHighScore("Winner", game.getElapsedTimeInSeconds());
// Prüfen, ob der Highscore gespeichert wurde
boolean found = false;
for (String score : game.getHighScoresWithAverage()) {
if (score.contains("Winner")) {
found = true;
break;
}
}
assertTrue(found);
}
@Test
void testTimerPauseResume() {
// Testet das Pausieren und Fortsetzen des Timers
game.startTimer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
// Timer pausieren und aktuelle Zeit speichern
game.pauseTimer();
long pausedTime = game.getElapsedTimeInSeconds();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
// Zeit sollte während der Pause nicht weiterlaufen
assertEquals(pausedTime, game.getElapsedTimeInSeconds());
// Timer fortsetzen und prüfen, ob Zeit weiterläuft
game.startTimer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
assertTrue(game.getElapsedTimeInSeconds() > pausedTime);
}
}

View File

@ -1,138 +0,0 @@
import domain.HitoriGameTimer;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class HitoriGameTimerTest {
private HitoriGameTimer timer;
@BeforeEach
void setUp() {
timer = new HitoriGameTimer();
}
// Testet den Anfangszustand des Timers
@Test
void testInitialState() {
assertEquals(0, timer.getElapsedTimeInSeconds());
assertTrue(timer.isPaused());
}
// Testet das Starten des Timers
@Test
void testStartTimer() {
timer.startTimer();
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
assertTrue(timer.getElapsedTimeInSeconds() >= 1);
assertFalse(timer.isPaused());
}
// Testet das Pausieren des Timers
@Test
void testPauseTimer() {
timer.startTimer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
timer.pauseTimer();
long pausedTime = timer.getElapsedTimeInSeconds();
assertTrue(timer.isPaused());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
assertEquals(pausedTime, timer.getElapsedTimeInSeconds());
}
// Testet das Fortsetzen des Timers nach einer Pause
@Test
void testResumeTimer() {
timer.startTimer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
timer.pauseTimer();
long pausedTime = timer.getElapsedTimeInSeconds();
timer.startTimer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
assertTrue(timer.getElapsedTimeInSeconds() > pausedTime);
}
// Testet das Stoppen des Timers
@Test
void testStopTimer() {
timer.startTimer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
timer.stopTimer();
long stoppedTime = timer.getElapsedTimeInSeconds();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
assertEquals(stoppedTime, timer.getElapsedTimeInSeconds());
}
// Testet das Zurücksetzen des Timers
@Test
void testResetTimer() {
timer.startTimer();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
timer.resetTimer();
assertEquals(0, timer.getElapsedTimeInSeconds());
assertTrue(timer.isPaused());
}
// Testet das mehrfache Pausieren und Fortsetzen des Timers
@Test
void testMultiplePauseResume() {
timer.startTimer();
try {
Thread.sleep(500);
timer.pauseTimer();
Thread.sleep(500);
timer.startTimer();
Thread.sleep(500);
timer.pauseTimer();
Thread.sleep(500);
timer.startTimer();
Thread.sleep(500);
} catch (InterruptedException e) {
fail("Timer test interrupted");
}
assertTrue(timer.getElapsedTimeInSeconds() >= 1);
}
}

View File

@ -1,77 +0,0 @@
import GUI.GameUIController;
import GUI.HitoriScorePanel;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testfx.framework.junit5.ApplicationExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(ApplicationExtension.class)
class HitoriScorePanelTest {
private HitoriScorePanel scorePanel;
private GameUIController controller;
@BeforeEach
void setUp() {
controller = mock(GameUIController.class);
scorePanel = new HitoriScorePanel(controller);
}
@Test
void testInitialization() {
assertNotNull(scorePanel);
assertTrue(scorePanel.getChildren().size() > 0);
}
@Test
void testUpdateHighScores() {
String testScores = "Player1: 100\nPlayer2: 200";
scorePanel.updateHighScores(testScores);
TextArea highScoreArea = findTextArea(scorePanel);
assertNotNull(highScoreArea);
assertEquals(testScores, highScoreArea.getText());
}
@Test
void testSaveButtonAction() {
var saveButton = findButtonByText(scorePanel, "Save Game");
assertNotNull(saveButton);
saveButton.fire();
verify(controller, times(1)).saveGame();
}
@Test
void testDeleteHighScoresButtonAction() {
var deleteButton = findButtonByText(scorePanel, "Delete High Scores");
assertNotNull(deleteButton);
deleteButton.fire();
verify(controller, times(1)).deleteHighScores();
}
@Test
void testHighScoreAreaNotEditable() {
TextArea highScoreArea = findTextArea(scorePanel);
assertNotNull(highScoreArea);
assertFalse(highScoreArea.isEditable());
}
private TextArea findTextArea(HitoriScorePanel panel) {
return (TextArea) panel.getChildren().stream()
.filter(node -> node instanceof TextArea)
.findFirst()
.orElse(null);
}
private Button findButtonByText(HitoriScorePanel panel, String text) {
return (Button) panel.getChildren().stream()
.filter(node -> node instanceof Button && ((Button) node).getText().equals(text))
.findFirst()
.orElse(null);
}
}