Initial commit

GameMovesTest
Nicholas H. 2025-01-06 21:04:04 +01:00
commit 1d806f85a3
25 changed files with 1688 additions and 0 deletions

38
.gitignore vendored 100644
View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

3
.idea/.gitignore vendored 100644
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

4
.idea/vcs.xml 100644
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings" defaultProject="true" />
</project>

BIN
gamestate.dat 100644

Binary file not shown.

182
pom.xml 100644
View File

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.hs_mannheim.informatik.backend</groupId>
<artifactId>HitoriFinal</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<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>
</properties>
<repositories>
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</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>
<version>2.24.2</version>
</dependency>
<!-- JavaFX Dependencies -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId>
<version>${javafx.version}</version>
<scope>compile</scope>
</dependency>
<!-- JUnit for Testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Compiler -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.1</version>
<configuration>
<mainClass>Main.MainMethod</mainClass>
</configuration>
</plugin>
<!-- JAR creation -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>Main.MainMethod</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<!-- Code coverage, cf.: target/site/jacoco -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Static code analysis, cf: target/site/pmd.html -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.26.0</version>
<configuration>
<failOnViolation>false</failOnViolation>
<printFailingErrors>true</printFailingErrors>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<!-- generate Javadocs via "mvn site" and find them in the site
folder -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.11.2</version>
<configuration>
<show>private</show>
<nohelp>true</nohelp>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
</plugin>
</plugins>
</reporting>
</project>

View File

@ -0,0 +1,221 @@
package GUI;
import domain.*;
import GUI.HitoriDialogManager;
import javafx.application.Platform;
import java.util.*;
public class GameUIController {
private final HitoriGameMoves gameMoves;
private final GameSolver gameSolver;
private final domain.HitoriGameTimer gameTimer;
private final HitoriGameScores gameScores;
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) {
this.gameMoves = new HitoriGameMoves(initialBoard);
this.gameSolver = new GameSolver(initialBoard);
this.gameTimer = new domain.HitoriGameTimer();
this.gameScores = new HitoriGameScores();
this.dialogManager = dialogManager;
this.isPaused = false;
this.boardPanel = new GUI.HitoriBoardPanel(gameMoves, gameSolver, this);
this.controlPanel = new GUI.HitoriControlPanel(this);
this.scorePanel = new GUI.HitoriScorePanel(this);
startTimer();
loadHighScores();
}
public void handleLeftClick(int row, int col) {
gameMoves.markCellAsBlack(row, col);
updateUI();
}
public void handleRightClick(int row, int col) {
gameMoves.markCellAsWhite(row, col);
updateUI();
}
public void togglePause() {
isPaused = !isPaused;
if (isPaused) {
gameTimer.pauseTimer();
controlPanel.setPauseButtonText("Resume");
boardPanel.setDisable(true);
} else {
gameTimer.startTimer();
controlPanel.setPauseButtonText("Pause");
boardPanel.setDisable(false);
}
updateTimerLabel();
}
public void resetGame() {
gameMoves.reset();
gameTimer.resetTimer();
updateUI();
}
public void undo() {
if (gameMoves.undo()) {
updateUI();
}
}
public void redo() {
if (gameMoves.redo()) {
updateUI();
}
}
public void newGame() {
stopTimer();
// Notify main application to show board selection
}
public void checkSolution() {
if (gameSolver.isSolved()) {
handleWin();
} else {
dialogManager.showAlert("Not Solved", "The current solution is not correct. Keep trying!");
}
}
public void showErrors() {
List<int[]> errors = gameSolver.findIncorrectBlackMarks();
if (errors.isEmpty()) {
dialogManager.showAlert("No Errors", "No rule violations found in current black markings.");
return;
}
boardPanel.showErrors();
StringBuilder message = new StringBuilder("Found " + errors.size() + " error(s):\n");
for (int[] pos : errors) {
message.append(String.format("Row %d, Column %d\n", pos[0] + 1, pos[1] + 1));
}
dialogManager.showAlert("Errors Found", message.toString());
}
public void saveGame() {
// Implementation for saving game state
dialogManager.showAlert("Game Saved", "Your game has been saved successfully.");
}
public void deleteHighScores() {
if (dialogManager.confirmDeleteHighScores()) {
gameScores.deleteHighScores();
updateHighScoreDisplay();
dialogManager.showAlert("High Scores Deleted", "All high scores have been deleted successfully.");
}
}
private void handleWin() {
stopTimer();
Optional<String> playerName = dialogManager.askForPlayerName();
if (playerName.isPresent() && !playerName.get().trim().isEmpty()) {
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(),
gameMoves.getMistakeCount()
));
isPaused = false;
boardPanel.setDisable(false);
controlPanel.setPauseButtonText("Pause");
if (dialogManager.confirmNewGame()) {
newGame();
}
}
}
private void startTimer() {
if (guiTimer != null) {
guiTimer.cancel();
}
gameTimer.startTimer();
guiTimer = new Timer(true);
guiTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Platform.runLater(() -> {
if (!isPaused) {
updateTimerLabel();
}
});
}
}, 0, 1000);
}
private void stopTimer() {
if (guiTimer != null) {
guiTimer.cancel();
guiTimer = null;
}
gameTimer.stopTimer();
}
private void updateUI() {
boardPanel.updateBoard();
updateMistakeLabel();
}
private void updateTimerLabel() {
controlPanel.updateTimerLabel(gameTimer.getElapsedTimeInSeconds());
}
private void updateMistakeLabel() {
controlPanel.updateMistakeLabel(gameMoves.getMistakeCount());
}
private void updateHighScoreDisplay() {
scorePanel.updateHighScores(String.join("\n", gameScores.getHighScoresWithAverage()));
}
private void loadHighScores() {
gameScores.loadHighScoresFromFile();
updateHighScoreDisplay();
}
public Set<String> convertErrorsToSet(List<int[]> errors) {
Set<String> errorPositions = new HashSet<>();
for (int[] pos : errors) {
errorPositions.add(pos[0] + "," + pos[1]);
}
return errorPositions;
}
public boolean isPaused() {
return isPaused;
}
public void cleanup() {
stopTimer();
saveGame();
}
public HitoriBoardPanel getBoardPanel() {
return boardPanel;
}
public HitoriControlPanel getControlPanel() {
return controlPanel;
}
public HitoriScorePanel getScorePanel() {
return scorePanel;
}
}

View File

@ -0,0 +1,94 @@
package GUI;
import domain.GameSolver;
import domain.HitoriGameMoves;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.GridPane;
import java.util.Set;
public class HitoriBoardPanel extends GridPane {
private final HitoriGameMoves gameMoves;
private final GameSolver gameSolver;
private final GameUIController controller;
public HitoriBoardPanel(HitoriGameMoves gameMoves, GameSolver gameSolver, GameUIController controller) {
this.gameMoves = gameMoves;
this.gameSolver = gameSolver;
this.controller = controller;
setHgap(5);
setVgap(5);
setPadding(new Insets(10));
updateBoard();
}
public void updateBoard() {
getChildren().clear();
int[][] boardState = gameMoves.getBoard();
boolean[][] blackCells = gameMoves.getBlackCells();
boolean[][] whiteCells = gameMoves.getWhiteCells();
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]);
add(cellButton, col, row);
}
}
}
public void showErrors() {
var errors = gameSolver.findIncorrectBlackMarks();
Set<String> errorPositions = controller.convertErrorsToSet(errors);
getChildren().clear();
int[][] boardState = gameMoves.getBoard();
boolean[][] blackCells = gameMoves.getBlackCells();
boolean[][] whiteCells = gameMoves.getWhiteCells();
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]);
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) {
Button cellButton = new Button(String.valueOf(value));
cellButton.setPrefSize(50, 50);
styleCellButton(cellButton, isBlack, isWhite);
cellButton.setOnMouseClicked(event -> {
if (!controller.isPaused()) {
if (event.getButton() == MouseButton.PRIMARY) {
controller.handleLeftClick(row, col);
} else if (event.getButton() == MouseButton.SECONDARY) {
controller.handleRightClick(row, col);
}
}
});
return cellButton;
}
private void styleCellButton(Button button, boolean isBlack, boolean isWhite) {
if (isBlack) {
button.setStyle("-fx-background-color: black; -fx-text-fill: white;");
} else if (isWhite) {
button.setStyle("-fx-background-color: white; -fx-text-fill: black; -fx-border-color: gray;");
} else {
button.setStyle("-fx-background-color: lightgray; -fx-text-fill: black;");
}
}
}

View File

@ -0,0 +1,67 @@
package GUI;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
public class HitoriControlPanel extends VBox {
private final GameUIController controller;
private final Label timerLabel;
private final Label mistakeLabel;
private final Button pauseButton;
public HitoriControlPanel(GameUIController controller) {
this.controller = controller;
// Timer box setup
HBox timerBox = new HBox(10);
timerLabel = new Label("Time: 0s");
mistakeLabel = new Label("Mistakes: 0");
pauseButton = new Button("Pause");
timerBox.getChildren().addAll(timerLabel, mistakeLabel, pauseButton);
timerBox.setAlignment(Pos.CENTER);
// Button box setup
HBox buttonBox = new HBox(10);
Button resetButton = new Button("Reset");
Button undoButton = new Button("Undo");
Button redoButton = new Button("Redo");
Button checkButton = new Button("Check Solution");
Button newGameButton = new Button("New Game");
Button showErrorsButton = new Button("Show Errors");
buttonBox.getChildren().addAll(resetButton, undoButton, redoButton,
checkButton, newGameButton, showErrorsButton);
buttonBox.setAlignment(Pos.CENTER);
// Event handlers
pauseButton.setOnAction(e -> controller.togglePause());
resetButton.setOnAction(e -> controller.resetGame());
undoButton.setOnAction(e -> controller.undo());
redoButton.setOnAction(e -> controller.redo());
newGameButton.setOnAction(e -> controller.newGame());
checkButton.setOnAction(e -> controller.checkSolution());
showErrorsButton.setOnAction(e -> controller.showErrors());
// Layout setup
getChildren().addAll(timerBox, buttonBox);
setPadding(new Insets(10));
setSpacing(10);
}
public void updateTimerLabel(long seconds) {
timerLabel.setText("Time: " + seconds + "s");
}
public void updateMistakeLabel(int mistakes) {
mistakeLabel.setText("Mistakes: " + mistakes);
}
public void setPauseButtonText(String text) {
pauseButton.setText(text);
}
}

View File

@ -0,0 +1,116 @@
package GUI;
import domain.HitoriBoardLoader;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Window;
import java.util.Optional;
public class HitoriDialogManager {
private final Window owner;
public HitoriDialogManager(Window owner) {
this.owner = owner;
}
public Optional<String> showBoardSelectionDialog(HitoriBoardLoader boardLoader) {
Dialog<String> dialog = new Dialog<>();
dialog.setTitle("Select Hitori Board");
dialog.setHeaderText("Choose a board to play:");
if (owner != null && owner.getScene() != null) {
dialog.initOwner(owner);
dialog.initModality(Modality.APPLICATION_MODAL);
}
ButtonType selectButtonType = new ButtonType("Play", ButtonBar.ButtonData.OK_DONE);
ButtonType randomButtonType = new ButtonType("Random", ButtonBar.ButtonData.OTHER);
dialog.getDialogPane().getButtonTypes().addAll(selectButtonType, randomButtonType, ButtonType.CANCEL);
ChoiceBox<String> boardChoice = new ChoiceBox<>();
boardChoice.getItems().addAll(boardLoader.getAvailableBoardNames());
if (!boardChoice.getItems().isEmpty()) {
boardChoice.setValue(boardChoice.getItems().get(0));
}
VBox content = new VBox(10);
content.getChildren().add(boardChoice);
dialog.getDialogPane().setContent(content);
dialog.setResultConverter(dialogButton -> {
if (dialogButton == selectButtonType) {
return boardChoice.getValue();
} else if (dialogButton == randomButtonType) {
int random = (int) (Math.random() * boardChoice.getItems().size());
return boardChoice.getItems().get(random);
}
return null;
});
return dialog.showAndWait();
}
public void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(message);
alert.initOwner(owner);
alert.showAndWait();
}
public Optional<String> askForPlayerName() {
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("High Score");
dialog.setHeaderText("Congratulations! Enter your name:");
dialog.setContentText("Name:");
dialog.initOwner(owner);
return dialog.showAndWait();
}
public boolean confirmDeleteHighScores() {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Delete High Scores");
alert.setHeaderText("Are you sure?");
alert.setContentText("This will permanently delete all high scores.");
alert.initOwner(owner);
Optional<ButtonType> result = alert.showAndWait();
return result.isPresent() && result.get() == ButtonType.OK;
}
public boolean confirmNewGame() {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Game Complete");
alert.setHeaderText("Would you like to start a new game?");
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.initOwner(owner);
Optional<ButtonType> result = alert.showAndWait();
return result.isPresent() && result.get() == buttonTypeNew;
}
public boolean confirmLoadSavedGame() {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Saved Game Found");
alert.setHeaderText("Would you like to continue your saved game?");
alert.setContentText("Choose whether to load the saved game or start a new one.");
ButtonType buttonTypeYes = new ButtonType("Load Saved Game");
ButtonType buttonTypeNo = new ButtonType("Start New Game");
alert.getButtonTypes().setAll(buttonTypeYes, buttonTypeNo);
alert.initOwner(owner);
Optional<ButtonType> result = alert.showAndWait();
return result.isPresent() && result.get() == buttonTypeYes;
}
}

View File

@ -0,0 +1,39 @@
package GUI;
import GUI.GameUIController;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
public class HitoriScorePanel extends VBox {
private final GameUIController controller;
private final TextArea highScoreArea;
public HitoriScorePanel(GameUIController controller) {
this.controller = controller;
setPadding(new Insets(10));
setAlignment(Pos.TOP_CENTER);
Label highScoreLabel = new Label("High Scores");
highScoreArea = new TextArea();
highScoreArea.setEditable(false);
highScoreArea.setPrefRowCount(10);
highScoreArea.setPrefColumnCount(30);
Button saveButton = new Button("Save Game");
Button deleteHighScoresButton = new Button("Delete High Scores");
saveButton.setOnAction(e -> controller.saveGame());
deleteHighScoresButton.setOnAction(e -> controller.deleteHighScores());
getChildren().addAll(highScoreLabel, highScoreArea, saveButton, deleteHighScoresButton);
}
public void updateHighScores(String highScores) {
highScoreArea.setText(highScores);
}
}

View File

@ -0,0 +1,96 @@
package Main;
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;
import javafx.stage.Stage;
public class MainMethod extends Application {
private HitoriBoardLoader boardLoader;
private GameUIController controller;
private HitoriDialogManager dialogManager;
private String currentBoardName;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
boardLoader = new HitoriBoardLoader();
dialogManager = new HitoriDialogManager(primaryStage);
checkSavedGame(primaryStage);
}
private void checkSavedGame(Stage primaryStage) {
HitoriGameMain savedGame = HitoriGameMain.loadGameState();
if (savedGame != null) {
if (dialogManager.confirmLoadSavedGame()) {
initializeGame(savedGame.getBoard(), primaryStage);
} else {
showBoardSelectionDialog(primaryStage);
}
} else {
showBoardSelectionDialog(primaryStage);
}
}
private void showBoardSelectionDialog(Stage primaryStage) {
dialogManager.showBoardSelectionDialog(boardLoader).ifPresentOrElse(
boardName -> {
currentBoardName = boardName;
int[][] selectedBoard = boardLoader.getBoard(boardName);
if (selectedBoard != null) {
initializeGame(selectedBoard, primaryStage);
}
},
() -> System.exit(0)
);
}
private void initializeGame(int[][] board, Stage primaryStage) {
controller = new GameUIController(board, dialogManager);
createGameUI(primaryStage);
}
private void createGameUI(Stage primaryStage) {
primaryStage.setTitle("Hitori Game - " + currentBoardName);
BorderPane mainLayout = new BorderPane();
mainLayout.setCenter(controller.getBoardPanel());
mainLayout.setTop(controller.getControlPanel());
mainLayout.setRight(controller.getScorePanel());
Scene scene = new Scene(mainLayout, 800, 600);
primaryStage.setScene(scene);
// Window state listeners
primaryStage.iconifiedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue && !controller.isPaused()) {
controller.togglePause();
}
});
primaryStage.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue && !controller.isPaused()) {
controller.togglePause();
}
});
primaryStage.setOnCloseRequest(event -> controller.cleanup());
primaryStage.show();
}
@Override
public void stop() {
if (controller != null) {
controller.cleanup();
}
}
}

View File

@ -0,0 +1,48 @@
package domain;
import java.io.Serializable;
import java.util.Arrays;
public class GameBase implements Serializable {
protected int[][] board;
public boolean[][] blackCells;
public boolean[][] whiteCells;
protected int mistakeCount;
public GameBase(int[][] initialBoard) {
this.board = new int[initialBoard.length][initialBoard[0].length];
for (int i = 0; i < initialBoard.length; i++) {
this.board[i] = Arrays.copyOf(initialBoard[i], initialBoard[i].length);
}
this.blackCells = new boolean[board.length][board[0].length];
this.whiteCells = new boolean[board.length][board[0].length];
this.mistakeCount = 0;
}
public void reset() {
blackCells = new boolean[board.length][board[0].length];
whiteCells = new boolean[board.length][board[0].length];
mistakeCount = 0;
}
public int[][] getBoard() {
return board;
}
public int[][] getCurrentState() {
return board;
}
public boolean[][] getBlackCells() {
return blackCells;
}
public boolean[][] getWhiteCells() {
return whiteCells;
}
public int getMistakeCount() {
return mistakeCount;
}
}

View File

@ -0,0 +1,123 @@
package domain;
import java.util.*;
public class GameSolver extends domain.HitoriGameMoves {
public GameSolver(int[][] initialBoard) {
super(initialBoard);
}
public boolean isSolved() {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (!blackCells[i][j] && !whiteCells[i][j]) {
return false; // Found an unmarked cell
}
}
}
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (blackCells[i][j] && !isValidBlackMark(i, j)) {
return false;
}
}
}
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++) {
if (!blackCells[i][j]) {
if (seenInRow.contains(board[i][j])) {
return false;
}
seenInRow.add(board[i][j]);
}
if (!blackCells[j][i]) {
if (seenInCol.contains(board[j][i])) {
return false;
}
seenInCol.add(board[j][i]);
}
}
}
// Check connectivity of white cells
return isOrthogonallyConnected();
}
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] ||
col < board[0].length-1 && blackCells[row][col+1]) {
return false;
}
return true;
}
private boolean isOrthogonallyConnected() {
if (board.length == 0) return true;
boolean[][] visited = new boolean[board.length][board[0].length];
int[] start = findFirstWhiteCell();
if (start == null) return true;
dfs(start[0], start[1], visited);
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (!blackCells[i][j] && !visited[i][j]) {
return false;
}
}
}
return true;
}
private int[] findFirstWhiteCell() {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (!blackCells[i][j]) {
return new int[]{i, j};
}
}
}
return null;
}
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]) {
return;
}
visited[row][col] = true;
dfs(row - 1, col, visited);
dfs(row + 1, col, visited);
dfs(row, col - 1, visited);
dfs(row, col + 1, visited);
}
public List<int[]> findIncorrectBlackMarks() {
List<int[]> incorrectMarks = new ArrayList<>();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (blackCells[i][j] && !isValidBlackMark(i, j)) {
incorrectMarks.add(new int[]{i, j});
}
}
}
return incorrectMarks;
}
}

View File

@ -0,0 +1,124 @@
package domain;
import java.io.*;
import java.net.URL;
import java.util.*;
public class HitoriBoardLoader {
private Map<String, int[][]> availableBoards;
private Map<String, List<int[]>> solutions;
public HitoriBoardLoader() {
availableBoards = new HashMap<>();
solutions = new HashMap<>();
loadAllBoards();
}
private void loadAllBoards() {
try {
// Define all board file names
String[] boardFiles = {
"Hitori4x4_leicht.csv",
"Hitori5x5leicht.csv",
"Hitori8x8leicht.csv",
"Hitori8x8medium.csv",
"Hitori10x10medium.csv",
"Hitori15x15_medium.csv"
};
// Try to load each board
for (String fileName : boardFiles) {
try {
InputStream is = getClass().getResourceAsStream("/META-INF/" + fileName);
if (is == null) {
is = getClass().getResourceAsStream("/" + fileName);
}
if (is != null) {
loadBoard(fileName, is);
}
} catch (Exception e) {
System.out.println("Failed to load board: " + fileName);
e.printStackTrace();
}
}
if (availableBoards.isEmpty()) {
System.out.println("No board files found. Please ensure .csv files are in the resources folder.");
} else {
System.out.println("Successfully loaded " + availableBoards.size() + " boards.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
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);
}
// Find where the solution starts
int solutionIndex = -1;
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).contains("//")) {
solutionIndex = i;
break;
}
}
// 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(","));
}
}
if (!boardRows.isEmpty()) {
int rows = boardRows.size();
int cols = boardRows.get(0).length;
int[][] board = new int[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
board[i][j] = Integer.parseInt(boardRows.get(i)[j].trim());
}
}
availableBoards.put(fileName, board);
// Parse the solution if available
List<int[]> solutionCoordinates = new ArrayList<>();
if (solutionIndex != -1) {
for (int i = solutionIndex + 1; i < lines.size(); i++) {
line = lines.get(i).trim();
if (!line.isEmpty() && !line.startsWith("//")) {
String[] coords = line.split(",");
solutionCoordinates.add(new int[]{
Integer.parseInt(coords[0].trim()) - 1,
Integer.parseInt(coords[1].trim()) - 1
});
}
}
solutions.put(fileName, solutionCoordinates);
}
}
}
}
public Set<String> getAvailableBoardNames() {
return availableBoards.keySet();
}
public int[][] getBoard(String boardName) {
return availableBoards.get(boardName);
}
public List<int[]> getSolution(String boardName) {
return solutions.get(boardName);
}
}

View File

@ -0,0 +1,85 @@
package domain;
import domain.GameSolver;
import java.io.*;
public class HitoriGameMain extends GameSolver {
private static final long serialVersionUID = 1L;
private final domain.HitoriGameTimer timer;
private final domain.HitoriGameScores scores;
public HitoriGameMain(int[][] initialBoard) {
super(initialBoard);
this.timer = new domain.HitoriGameTimer();
this.scores = new HitoriGameScores();
}
// Timer delegation methods
public void startTimer() {
timer.startTimer();
}
public void pauseTimer() {
timer.pauseTimer();
}
public void stopTimer() {
timer.stopTimer();
}
public void resetTimer() {
timer.resetTimer();
}
public long getElapsedTimeInSeconds() {
return timer.getElapsedTimeInSeconds();
}
// High score delegation methods
public void addHighScore(String playerName, long timeInSeconds) {
scores.addHighScore(playerName, timeInSeconds, getMistakeCount());
}
public void deleteHighScores() {
scores.deleteHighScores();
}
public java.util.List<String> getHighScoresWithAverage() {
return scores.getHighScoresWithAverage();
}
public void loadHighScoresFromFile() {
scores.loadHighScoresFromFile();
}
public void saveHighScoresToFile() {
scores.saveHighScoresToFile();
}
// Game state persistence
public void saveGameState() {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("gamestate.dat"))) {
oos.writeObject(this);
} catch (IOException e) {
e.printStackTrace();
}
}
public static HitoriGameMain loadGameState() {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("gamestate.dat"))) {
return (HitoriGameMain) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
return null;
}
}
@Override
public void reset() {
super.reset();
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

@ -0,0 +1,82 @@
package domain;
import java.io.*;
import java.util.*;
public class HitoriGameScores implements Serializable {
private static final long serialVersionUID = 1L;
private Map<String, List<HighScoreEntry>> highScores;
private static class HighScoreEntry implements Serializable {
String playerName;
long time;
int mistakes;
HighScoreEntry(String playerName, long time, int mistakes) {
this.playerName = playerName;
this.time = time;
this.mistakes = mistakes;
}
}
public HitoriGameScores() {
this.highScores = new HashMap<>();
}
public void addHighScore(String playerName, long timeInSeconds, int mistakes) {
highScores.putIfAbsent(playerName, new ArrayList<>());
highScores.get(playerName).add(new HighScoreEntry(playerName, timeInSeconds, mistakes));
saveHighScoresToFile();
}
public void deleteHighScores() {
highScores.clear();
saveHighScoresToFile();
}
public List<String> getHighScoresWithAverage() {
List<String> result = new ArrayList<>();
List<HighScoreEntry> allEntries = new ArrayList<>();
for (List<HighScoreEntry> entries : highScores.values()) {
allEntries.addAll(entries);
}
allEntries.sort((a, b) -> Long.compare(a.time, b.time));
double avgTime = allEntries.stream()
.mapToLong(e -> e.time)
.average()
.orElse(0);
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));
return result;
}
public void saveHighScoresToFile() {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("highscores.dat"))) {
oos.writeObject(highScores);
} catch (IOException e) {
e.printStackTrace();
}
}
@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<>();
}
}
}

View File

@ -0,0 +1,56 @@
package domain;
import java.io.Serializable;
public class HitoriGameTimer implements Serializable {
private static final long serialVersionUID = 1L;
private transient long startTime;
private long elapsedTime;
private boolean isPaused;
public HitoriGameTimer() {
this.startTime = 0;
this.elapsedTime = 0;
this.isPaused = false;
}
public void startTimer() {
if (isPaused || startTime == 0) {
startTime = System.currentTimeMillis();
isPaused = false;
}
}
public void pauseTimer() {
if (!isPaused && startTime > 0) {
elapsedTime += System.currentTimeMillis() - startTime;
startTime = 0;
isPaused = true;
}
}
public void stopTimer() {
if (!isPaused && startTime > 0) {
elapsedTime += System.currentTimeMillis() - startTime;
startTime = 0;
}
}
public void resetTimer() {
startTime = 0;
elapsedTime = 0;
isPaused = false;
}
public long getElapsedTimeInSeconds() {
if (isPaused || startTime == 0) {
return elapsedTime / 1000;
}
return (elapsedTime + System.currentTimeMillis() - startTime) / 1000;
}
public boolean isPaused() {
return isPaused;
}
}

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

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: de.hs_mannheim.informatik.backend.HitoriGameGUI