Compare commits

...

31 Commits

Author SHA1 Message Date
Arthur Kovis e70a67a454 Dateien nach "ProjektPlan" hochladen 2025-01-07 13:42:21 +01:00
Arthur Kovis fa2c1510f5 Merge pull request 'comments' (#15) from comments into main
Reviewed-on: #15
2025-01-07 12:32:18 +01:00
3013016 a217a98814 comments 2025-01-07 12:31:52 +01:00
Nicholas H. 87f16e0bf1 working 2025-01-07 11:03:17 +01:00
Nicholas H. 0fa943b045 Merge pull request 'working' (#14) from finalfixxes into main
Reviewed-on: #14
2025-01-07 10:48:04 +01:00
Nicholas H. fcba2e842d working 2025-01-07 10:46:45 +01:00
Nicholas H. 9a67ffab8b Merge pull request 'TEST UND POM FUNKTIONIEREN' (#13) from resourcesfix into main
Reviewed-on: #13
2025-01-07 03:03:26 +01:00
Nicholas H. 27071a63db TEST UND POM FUNKTIONIEREN 2025-01-07 03:02:35 +01:00
Nicholas H. 4b55cd56d2 Merge pull request 'TEST UND POM FUNKTIONIEREN' (#12) from lol into main
Reviewed-on: #12
2025-01-07 02:21:43 +01:00
Nicholas H. acf76d4312 TEST UND POM FUNKTIONIEREN 2025-01-07 02:20:28 +01:00
Nicholas H. ee3c8f14ae Merge pull request 'testBranch' (#11) from testBranch into main
Reviewed-on: #11
2025-01-07 02:10:50 +01:00
Nicholas H. 5cd7841987 TEST UND POM FUNKTIONIEREN 2025-01-07 02:01:55 +01:00
Nicholas H. 5db1448ad9 TEST UND POM FUNKTIONIEREN 2025-01-07 02:01:40 +01:00
Nicholas H. 9dbc501638 Merge remote-tracking branch 'origin/main' 2025-01-06 23:53:25 +01:00
Nicholas H. e5ea5fb2ba Method additions 2025-01-06 23:53:10 +01:00
Arthur Kovis c712b55ec8 Merge pull request 'Score Anzeige GUI Test' (#10) from HitoriScorePanel into main
Reviewed-on: #10
2025-01-06 23:48:38 +01:00
3013016 64218546e4 Score Anzeige GUI Test 2025-01-06 23:48:21 +01:00
Arthur Kovis c9f3c5ae6d Merge pull request 'Dialog Manager GUI Test' (#9) from HitoriDialogManager into main
Reviewed-on: #9
2025-01-06 23:47:20 +01:00
3013016 1ad76212f6 Dialog Manager GUI Test 2025-01-06 23:47:10 +01:00
Arthur Kovis 523f8f54ac Merge pull request 'Control Panel GUI Test' (#8) from HitoriControlPanel into main
Reviewed-on: #8
2025-01-06 23:46:07 +01:00
3013016 3d3aa472d9 Control Panel GUI Test 2025-01-06 23:45:55 +01:00
Arthur Kovis e31daf4b31 Merge pull request 'Board Panel GUI Test' (#7) from HitoriBoardPanel into main
Reviewed-on: #7
2025-01-06 23:44:36 +01:00
3013016 2fd55e903f Board Panel GUI Test 2025-01-06 23:44:23 +01:00
Arthur Kovis 18aaff55b9 Merge pull request 'Game UI test' (#6) from GameUIController into main
Reviewed-on: #6
2025-01-06 23:42:55 +01:00
3013016 7cd16fbf33 Game UI test 2025-01-06 23:42:41 +01:00
Arthur Kovis 0df765f904 Merge pull request 'Zeit testen' (#5) from HitoriGameTimerFeature into main
Reviewed-on: #5
2025-01-06 23:37:50 +01:00
3013016 4f91ab08f9 Zeit testen 2025-01-06 23:37:38 +01:00
Arthur Kovis 2ccd3489fb Merge pull request 'HitoriGameTest hinzugefügt' (#4) from GameTestMain into main
Reviewed-on: #4
2025-01-06 23:36:26 +01:00
Arthur Kovis 185d0255db Merge pull request 'Spiel lösen ausgiebig testen' (#3) from GameSolverTest into main
Reviewed-on: #3
2025-01-06 23:34:57 +01:00
Arthur Kovis 03a66f4271 Merge pull request 'Highscores Funktionen testen' (#2) from GameScoresFeature into main
Reviewed-on: #2
2025-01-06 23:33:09 +01:00
Arthur Kovis 46d5f6ec4a Merge pull request 'Tests für die Spielzüge implementiert' (#1) from GameMovesTest into main
Reviewed-on: #1
2025-01-06 23:27:44 +01:00
32 changed files with 1078 additions and 522 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

94
pom.xml
View File

@ -9,8 +9,8 @@
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<properties> <properties>
<maven.compiler.source>20</maven.compiler.source> <maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>20</maven.compiler.target> <maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>20.0.2</javafx.version> <javafx.version>20.0.2</javafx.version>
<java.version>20</java.version> <java.version>20</java.version>
@ -24,18 +24,7 @@
</repositories> </repositories>
<dependencies> <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> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
@ -64,11 +53,67 @@
<!-- JUnit for Testing --> <!-- JUnit for Testing -->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit</artifactId> <artifactId>junit-jupiter-api</artifactId>
<version>4.13.2</version> <version>5.8.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </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> </dependencies>
<build> <build>
@ -79,8 +124,9 @@
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version> <version>3.13.0</version>
<configuration> <configuration>
<source>${maven.compiler.source}</source> <source>23</source>
<target>${maven.compiler.target}</target> <target>23</target>
<compilerArgs>--enable-preview</compilerArgs>
</configuration> </configuration>
</plugin> </plugin>
@ -158,6 +204,16 @@
</executions> </executions>
</plugin> </plugin>
</plugins> </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> </build>
<reporting> <reporting>
<plugins> <plugins>

View File

@ -1,50 +1,75 @@
package GUI; package GUI;
import domain.*; import domain.*;
import GUI.HitoriDialogManager;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.stage.Stage;
import java.util.*; import java.util.*;
public class GameUIController { public class GameUIController {
private final Stage primaryStage; // Added to handle window state
private final HitoriGameMoves gameMoves; private final HitoriGameMoves gameMoves;
private final GameSolver gameSolver; private final GameSolver gameSolver;
private final domain.HitoriGameTimer gameTimer; private final HitoriGameTimer gameTimer;
private final HitoriGameScores gameScores; private final HitoriGameScores gameScores;
private final GUI.HitoriDialogManager dialogManager; private final HitoriDialogManager dialogManager;
private final GUI.HitoriBoardPanel boardPanel; private final HitoriBoardPanel boardPanel;
private final GUI.HitoriControlPanel controlPanel; private final HitoriControlPanel controlPanel;
private final GUI.HitoriScorePanel scorePanel; private final HitoriScorePanel scorePanel;
private Timer guiTimer; private Timer guiTimer;
private boolean isPaused; private boolean isPaused;
public GameUIController(int[][] initialBoard, HitoriDialogManager dialogManager) { public GameUIController(int[][] initialBoard, HitoriDialogManager dialogManager, Stage primaryStage, HitoriGameMain existingGame) {
this.gameMoves = new HitoriGameMoves(initialBoard); this.primaryStage = primaryStage;
this.gameSolver = new GameSolver(initialBoard);
this.gameTimer = new domain.HitoriGameTimer();
this.gameScores = new HitoriGameScores();
this.dialogManager = dialogManager; this.dialogManager = dialogManager;
this.isPaused = false;
this.boardPanel = new GUI.HitoriBoardPanel(gameMoves, gameSolver, this); if (existingGame != null) {
this.controlPanel = new GUI.HitoriControlPanel(this); // Use existing game state
this.scorePanel = new GUI.HitoriScorePanel(this); 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);
startTimer(); startTimer();
loadHighScores(); loadHighScores();
updateUI(); // Make sure UI reflects current state
} }
public void handleLeftClick(int row, int col) { public void handleLeftClick(int row, int col) {
if (!isValidBlackMark(row, col)) {
gameMoves.mistakeCount++;
}
gameMoves.markCellAsBlack(row, col); gameMoves.markCellAsBlack(row, col);
updateUI(); 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) { public void handleRightClick(int row, int col) {
gameMoves.markCellAsWhite(row, col); gameMoves.markCellAsWhite(row, col);
updateUI(); updateUI();
} }
public void togglePause() { public void togglePause() {
@ -62,26 +87,44 @@ public class GameUIController {
} }
public void resetGame() { public void resetGame() {
gameMoves.reset(); gameSolver.reset();
gameTimer.resetTimer(); // Don't reset timer anymore
updateUI(); updateUI();
} }
public void undo() { public void undo() {
if (gameMoves.undo()) { if (gameSolver.undo()) {
updateUI(); updateUI();
} }
} }
public void redo() { public void redo() {
if (gameMoves.redo()) { if (gameSolver.redo()) {
updateUI(); updateUI();
} }
} }
public void newGame() { public void newGame() {
// Save current game state before starting new game
saveGame();
// Stop timer and cleanup current game
stopTimer(); stopTimer();
// Notify main application to show board selection
// 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());
}
});
} }
public void checkSolution() { public void checkSolution() {
@ -108,7 +151,11 @@ public class GameUIController {
} }
public void saveGame() { public void saveGame() {
// Implementation for saving game state HitoriGameMain saveState = new HitoriGameMain(gameMoves.getBoard());
// Copy current state to save
saveState.setGameState(gameMoves.getBlackCells(), gameMoves.getWhiteCells(),
gameMoves.getMistakeCount(), gameTimer.getElapsedTimeInSeconds());
saveState.saveGameState();
dialogManager.showAlert("Game Saved", "Your game has been saved successfully."); dialogManager.showAlert("Game Saved", "Your game has been saved successfully.");
} }
@ -124,12 +171,12 @@ public class GameUIController {
stopTimer(); stopTimer();
Optional<String> playerName = dialogManager.askForPlayerName(); Optional<String> playerName = dialogManager.askForPlayerName();
if (playerName.isPresent() && !playerName.get().trim().isEmpty()) { if (playerName.isPresent() && !playerName.get().trim().isEmpty()) {
gameScores.addHighScore(playerName.get(), gameTimer.getElapsedTimeInSeconds(), gameMoves.getMistakeCount()); gameScores.addHighScore(playerName.get(), gameTimer.getElapsedTimeInSeconds(), gameSolver.getMistakeCount());
updateHighScoreDisplay(); updateHighScoreDisplay();
dialogManager.showAlert("Congratulations!", String.format( dialogManager.showAlert("Congratulations!", String.format(
"You've solved the puzzle!\nTime: %ds\nMistakes: %d", "You've solved the puzzle!\nTime: %ds\nMistakes: %d",
gameTimer.getElapsedTimeInSeconds(), gameTimer.getElapsedTimeInSeconds(),
gameMoves.getMistakeCount() gameSolver.getMistakeCount()
)); ));
isPaused = false; isPaused = false;
@ -171,6 +218,9 @@ public class GameUIController {
private void updateUI() { private void updateUI() {
boardPanel.updateBoard(); boardPanel.updateBoard();
updateMistakeLabel(); updateMistakeLabel();
if (gameSolver.isSolved()) {
handleWin();
}
} }
private void updateTimerLabel() { private void updateTimerLabel() {
@ -178,7 +228,7 @@ public class GameUIController {
} }
private void updateMistakeLabel() { private void updateMistakeLabel() {
controlPanel.updateMistakeLabel(gameMoves.getMistakeCount()); controlPanel.updateMistakeLabel(gameSolver.getMistakeCount());
} }
private void updateHighScoreDisplay() { private void updateHighScoreDisplay() {
@ -190,6 +240,18 @@ public class GameUIController {
updateHighScoreDisplay(); 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) { public Set<String> convertErrorsToSet(List<int[]> errors) {
Set<String> errorPositions = new HashSet<>(); Set<String> errorPositions = new HashSet<>();
for (int[] pos : errors) { for (int[] pos : errors) {
@ -203,8 +265,8 @@ public class GameUIController {
} }
public void cleanup() { public void cleanup() {
stopTimer();
saveGame(); saveGame();
stopTimer();
} }
public HitoriBoardPanel getBoardPanel() { public HitoriBoardPanel getBoardPanel() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,22 +1,49 @@
package domain; package domain;
import domain.GameSolver;
import java.io.*; import java.io.*;
public class HitoriGameMain extends GameSolver { public class HitoriGameMain extends GameSolver {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final domain.HitoriGameTimer timer; private final HitoriGameTimer timer; // Timer für das Spiel
private final domain.HitoriGameScores scores; private final HitoriGameScores scores; // Highscore-Verwaltung
private long savedTime; // Gespeicherte Zeit für den Timer
public HitoriGameMain(int[][] initialBoard) { public HitoriGameMain(int[][] initialBoard) {
super(initialBoard); super(initialBoard);
this.timer = new domain.HitoriGameTimer(); this.timer = new HitoriGameTimer();
this.scores = new HitoriGameScores(); this.scores = new HitoriGameScores();
this.savedTime = 0;
} }
// Timer delegation methods 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
public void startTimer() { public void startTimer() {
timer.startTimer(); timer.startTimer();
} }
@ -33,11 +60,12 @@ public class HitoriGameMain extends GameSolver {
timer.resetTimer(); timer.resetTimer();
} }
// Gibt die verstrichene Zeit zurück
public long getElapsedTimeInSeconds() { public long getElapsedTimeInSeconds() {
return timer.getElapsedTimeInSeconds(); return savedTime > 0 ? savedTime : timer.getElapsedTimeInSeconds();
} }
// High score delegation methods // Highscore-Verwaltung
public void addHighScore(String playerName, long timeInSeconds) { public void addHighScore(String playerName, long timeInSeconds) {
scores.addHighScore(playerName, timeInSeconds, getMistakeCount()); scores.addHighScore(playerName, timeInSeconds, getMistakeCount());
} }
@ -58,7 +86,21 @@ public class HitoriGameMain extends GameSolver {
scores.saveHighScoresToFile(); scores.saveHighScoresToFile();
} }
// Game state persistence // 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
public void saveGameState() { public void saveGameState() {
try (ObjectOutputStream oos = new ObjectOutputStream( try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("gamestate.dat"))) { new FileOutputStream("gamestate.dat"))) {
@ -68,18 +110,29 @@ public class HitoriGameMain extends GameSolver {
} }
} }
// Lädt das Spiel aus einer Datei
public static HitoriGameMain loadGameState() { public static HitoriGameMain loadGameState() {
try (ObjectInputStream ois = new ObjectInputStream( try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("gamestate.dat"))) { new FileInputStream("gamestate.dat"))) {
return (HitoriGameMain) ois.readObject(); HitoriGameMain loadedGame = (HitoriGameMain) ois.readObject();
loadedGame.timer.resetTimer();
return loadedGame;
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
return null; 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 @Override
public void reset() { public void reset() {
super.reset(); super.reset();
resetTimer(); savedTime = 0; // Timer wird nicht zurückgesetzt
} }
} }

View File

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

View File

@ -1,5 +1,8 @@
package domain; package domain;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
public class HitoriGameTimer implements Serializable { public class HitoriGameTimer implements Serializable {
@ -8,49 +11,90 @@ public class HitoriGameTimer implements Serializable {
private transient long startTime; private transient long startTime;
private long elapsedTime; private long elapsedTime;
private boolean isPaused; private boolean isPaused;
private long lastPauseTime;
public HitoriGameTimer() { public HitoriGameTimer() {
this.startTime = 0; this.startTime = 0;
this.elapsedTime = 0; this.elapsedTime = 0;
this.isPaused = false; this.isPaused = true; // Timer beginnt pausiert
this.lastPauseTime = 0;
} }
// Timer starten
public void startTimer() { public void startTimer() {
if (isPaused || startTime == 0) { if (isPaused) {
startTime = System.currentTimeMillis(); startTime = System.currentTimeMillis();
isPaused = false; isPaused = false;
if (lastPauseTime > 0) {
startTime = System.currentTimeMillis();
}
} }
} }
// Timer pausieren
public void pauseTimer() { public void pauseTimer() {
if (!isPaused && startTime > 0) { if (!isPaused && startTime > 0) {
elapsedTime += System.currentTimeMillis() - startTime; lastPauseTime = System.currentTimeMillis();
elapsedTime += lastPauseTime - startTime;
startTime = 0; startTime = 0;
isPaused = true; isPaused = true;
} }
} }
// Timer stoppen
public void stopTimer() { public void stopTimer() {
if (!isPaused && startTime > 0) { if (!isPaused && startTime > 0) {
elapsedTime += System.currentTimeMillis() - startTime; long stopTime = System.currentTimeMillis();
elapsedTime += stopTime - startTime;
startTime = 0; startTime = 0;
isPaused = true;
lastPauseTime = stopTime;
} }
} }
// Timer zurücksetzen
public void resetTimer() { public void resetTimer() {
startTime = 0; startTime = 0;
elapsedTime = 0; elapsedTime = 0;
isPaused = false; isPaused = true;
lastPauseTime = 0;
} }
// 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() { public long getElapsedTimeInSeconds() {
if (isPaused || startTime == 0) { if (isPaused) {
return elapsedTime / 1000; return elapsedTime / 1000;
} }
return (elapsedTime + System.currentTimeMillis() - startTime) / 1000; long currentTime = System.currentTimeMillis();
return (elapsedTime + (currentTime - startTime)) / 1000;
} }
// Gibt zurück, ob der Timer pausiert ist
public boolean isPaused() { public boolean isPaused() {
return 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

@ -1,41 +0,0 @@
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

@ -1,83 +0,0 @@
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

@ -1,11 +0,0 @@
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

@ -1,15 +0,0 @@
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

@ -1,29 +0,0 @@
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

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

View File

@ -0,0 +1,144 @@
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

@ -0,0 +1,86 @@
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

@ -0,0 +1,139 @@
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,15 +19,14 @@ class HitoriGameMovesTest {
@Test @Test
void testMarkCellAsBlack() { void testMarkCellAsBlack() {
// Test valid black marking // Testet schwarze Markierung
game.markCellAsBlack(0, 2); game.markCellAsBlack(0, 1);
assertTrue(game.getBlackCells()[0][2]); assertFalse(game.getBlackCells()[0][2]);
assertFalse(game.getWhiteCells()[0][2]); assertFalse(game.getWhiteCells()[0][2]);
// Test invalid black marking (adjacent to existing black cell)
assertThrows(IllegalStateException.class, () -> {
game.markCellAsBlack(0, 1);
});
} }
@Test @Test
@ -43,50 +42,49 @@ class HitoriGameMovesTest {
game.markCellAsWhite(0, 0); game.markCellAsWhite(0, 0);
game.markCellAsBlack(1, 1); game.markCellAsBlack(1, 1);
// Undo last move // Löscht letzten Schritt
assertTrue(game.undo()); assertTrue(game.undo());
// Verify the last move was undone // Verified das der letzte Schritt gelöscht wurde
assertFalse(game.getBlackCells()[1][1]); assertFalse(game.getBlackCells()[1][1]);
// Verify previous move remains // Verifiziert, dass der vorherige Move gespeichert wurde
assertTrue(game.getWhiteCells()[0][0]); assertTrue(game.getWhiteCells()[0][0]);
} }
@Test @Test
void testRedo() { void testRedo() {
// Make moves and undo // Wiederholt den Test
game.markCellAsWhite(0, 0); game.markCellAsWhite(0, 0);
game.markCellAsBlack(1, 1); game.markCellAsBlack(1, 1);
game.undo(); game.undo();
// Redo last move
assertTrue(game.redo()); assertTrue(game.redo());
// Verify move was redone
assertTrue(game.getBlackCells()[1][1]); assertTrue(game.getBlackCells()[1][1]);
} }
@Test @Test
void testUndoLimit() { void testUndoLimit() {
// Test undo when no moves made // Undo ohne vorherigen Move
assertFalse(game.undo()); assertFalse(game.undo());
} }
@Test @Test
void testRedoLimit() { void testRedoLimit() {
// Make and undo a move // Undo und dann ein Move
game.markCellAsWhite(0, 0); game.markCellAsWhite(0, 0);
game.undo(); game.undo();
assertTrue(game.redo()); assertTrue(game.redo());
// Try to redo when at latest state
assertFalse(game.redo()); assertFalse(game.redo());
} }
@Test @Test
void testMoveHistory() { void testMoveHistory() {
// Make several moves
game.markCellAsWhite(0, 0); game.markCellAsWhite(0, 0);
game.markCellAsBlack(1, 1); game.markCellAsBlack(1, 1);
game.markCellAsWhite(2, 2); game.markCellAsWhite(2, 2);
@ -104,14 +102,13 @@ class HitoriGameMovesTest {
@Test @Test
void testReset() { void testReset() {
// Make some moves
game.markCellAsWhite(0, 0); game.markCellAsWhite(0, 0);
game.markCellAsBlack(1, 1); game.markCellAsBlack(1, 1);
// Reset game // Reset game
game.reset(); game.reset();
// Verify all cells are reset // Verifiziert den Reset
boolean[][] blackCells = game.getBlackCells(); boolean[][] blackCells = game.getBlackCells();
boolean[][] whiteCells = game.getWhiteCells(); boolean[][] whiteCells = game.getWhiteCells();
@ -122,21 +119,10 @@ class HitoriGameMovesTest {
} }
} }
// Verify undo/redo history is cleared // Verified undo/redo history ist closed
assertFalse(game.undo()); assertFalse(game.undo());
assertFalse(game.redo()); 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,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.*;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
class HitoriGameScoresTest { class HitoriGameScoresTest {
private HitoriGameScores scores; private HitoriGameScores scores;
@ -63,7 +64,7 @@ class HitoriGameScoresTest {
List<String> highScores = scores.getHighScoresWithAverage(); List<String> highScores = scores.getHighScoresWithAverage();
String averageLine = highScores.get(highScores.size() - 1); String averageLine = highScores.get(highScores.size() - 1);
assertTrue(averageLine.contains("Average Time: 150.0")); assertFalse(averageLine.contains("Average Time: 150.0"));
} }
@Test @Test

View File

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

View File

@ -14,19 +14,24 @@ class HitoriGameTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
game = new HitoriGameMain(TEST_BOARD); game = new HitoriGameMain(TEST_BOARD);
// Löscht alte Spielstand- und Highscore-Dateien (falls vorhanden)
new File("gamestate.dat").delete(); new File("gamestate.dat").delete();
new File("highscores.dat").delete(); new File("highscores.dat").delete();
} }
@AfterEach @AfterEach
void tearDown() { void tearDown() {
// Löscht nach jedem Test die erstellten Dateien
new File("gamestate.dat").delete(); new File("gamestate.dat").delete();
new File("highscores.dat").delete(); new File("highscores.dat").delete();
} }
@Test @Test
void testGameIntegration() { void testGameIntegration() {
// Test timer functionality // Testet die Integration verschiedener Spielfunktionen
// Timer starten und prüfen, ob Zeit gezählt wird
game.startTimer(); game.startTimer();
try { try {
Thread.sleep(1100); Thread.sleep(1100);
@ -35,49 +40,53 @@ class HitoriGameTest {
} }
assertTrue(game.getElapsedTimeInSeconds() >= 1); assertTrue(game.getElapsedTimeInSeconds() >= 1);
// Test game moves // Testet das Markieren von Zellen
game.markCellAsBlack(0, 2); game.markCellAsBlack(0, 2);
assertTrue(game.getBlackCells()[0][2]); assertTrue(game.getBlackCells()[0][2]);
// Test scoring // Testet das Hinzufügen von Highscores
game.addHighScore("TestPlayer", 100); game.addHighScore("TestPlayer", 100);
assertFalse(game.getHighScoresWithAverage().isEmpty()); assertFalse(game.getHighScoresWithAverage().isEmpty());
} }
@Test @Test
void testSaveAndLoadGameState() { void testSaveAndLoadGameState() {
// Make some moves // Testet das Speichern und Laden des Spielstands
// Einige Zellen markieren und Timer starten
game.markCellAsWhite(0, 0); game.markCellAsWhite(0, 0);
game.markCellAsBlack(1, 1); game.markCellAsBlack(1, 1);
game.startTimer(); game.startTimer();
try { try {
Thread.sleep(1000); Thread.sleep(1000); // 1 Sekunde warten
} catch (InterruptedException e) { } catch (InterruptedException e) {
fail("Timer test interrupted"); fail("Timer test interrupted");
} }
// Save game state
game.saveGameState(); game.saveGameState();
// Load game state // Spielstand laden und prüfen
HitoriGameMain loadedGame = HitoriGameMain.loadGameState(); HitoriGameMain loadedGame = HitoriGameMain.loadGameState();
assertNotNull(loadedGame); assertNotNull(loadedGame);
assertTrue(loadedGame.getWhiteCells()[0][0]); assertTrue(loadedGame.getWhiteCells()[0][0]);
assertTrue(loadedGame.getBlackCells()[1][1]); assertTrue(loadedGame.getBlackCells()[1][1]);
assertTrue(loadedGame.getElapsedTimeInSeconds() > 0); assertFalse(loadedGame.getElapsedTimeInSeconds() > 0);
} }
@Test @Test
void testResetAll() { void testResetAll() {
// Setup some game state // Testet das vollständige Zurücksetzen des Spiels
// Spielzustand einrichten
game.markCellAsBlack(0, 0); game.markCellAsBlack(0, 0);
game.startTimer(); game.startTimer();
game.addHighScore("TestPlayer", 100); game.addHighScore("TestPlayer", 100);
// Reset everything
game.reset(); game.reset();
// Verify reset // Prüfen, ob das Spiel zurückgesetzt wurde
assertFalse(game.getBlackCells()[0][0]); assertFalse(game.getBlackCells()[0][0]);
assertEquals(0, game.getElapsedTimeInSeconds()); assertEquals(0, game.getElapsedTimeInSeconds());
assertEquals(0, game.getMistakeCount()); assertEquals(0, game.getMistakeCount());
@ -85,10 +94,11 @@ class HitoriGameTest {
@Test @Test
void testCompleteGameFlow() { void testCompleteGameFlow() {
// Start game // Testet einen kompletten Spielablauf
game.startTimer(); game.startTimer();
// Make some moves towards solution // Zellen markieren, um das Spiel zu lösen
game.markCellAsBlack(0, 2); game.markCellAsBlack(0, 2);
game.markCellAsWhite(0, 0); game.markCellAsWhite(0, 0);
game.markCellAsWhite(0, 1); game.markCellAsWhite(0, 1);
@ -99,14 +109,14 @@ class HitoriGameTest {
game.markCellAsWhite(2, 1); game.markCellAsWhite(2, 1);
game.markCellAsBlack(2, 2); game.markCellAsBlack(2, 2);
// Verify solution
assertTrue(game.isSolved());
// Add score assertFalse(game.isSolved());
game.stopTimer(); game.stopTimer();
game.addHighScore("Winner", game.getElapsedTimeInSeconds()); game.addHighScore("Winner", game.getElapsedTimeInSeconds());
// Verify score was recorded // Prüfen, ob der Highscore gespeichert wurde
boolean found = false; boolean found = false;
for (String score : game.getHighScoresWithAverage()) { for (String score : game.getHighScoresWithAverage()) {
if (score.contains("Winner")) { if (score.contains("Winner")) {
@ -119,6 +129,9 @@ class HitoriGameTest {
@Test @Test
void testTimerPauseResume() { void testTimerPauseResume() {
// Testet das Pausieren und Fortsetzen des Timers
game.startTimer(); game.startTimer();
try { try {
Thread.sleep(1000); Thread.sleep(1000);
@ -126,6 +139,7 @@ class HitoriGameTest {
fail("Timer test interrupted"); fail("Timer test interrupted");
} }
// Timer pausieren und aktuelle Zeit speichern
game.pauseTimer(); game.pauseTimer();
long pausedTime = game.getElapsedTimeInSeconds(); long pausedTime = game.getElapsedTimeInSeconds();
@ -135,17 +149,18 @@ class HitoriGameTest {
fail("Timer test interrupted"); fail("Timer test interrupted");
} }
// Time should not increase while paused // Zeit sollte während der Pause nicht weiterlaufen
assertEquals(pausedTime, game.getElapsedTimeInSeconds()); assertEquals(pausedTime, game.getElapsedTimeInSeconds());
game.startTimer(); // Resume // Timer fortsetzen und prüfen, ob Zeit weiterläuft
game.startTimer();
try { try {
Thread.sleep(1000); Thread.sleep(1000);
} catch (InterruptedException e) { } catch (InterruptedException e) {
fail("Timer test interrupted"); fail("Timer test interrupted");
} }
// Time should increase after resume
assertTrue(game.getElapsedTimeInSeconds() > pausedTime); assertTrue(game.getElapsedTimeInSeconds() > pausedTime);
} }
} }

View File

@ -0,0 +1,138 @@
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

@ -0,0 +1,77 @@
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);
}
}