commit 1d806f85a3eb4680f064c8b59bdb04d543d8fbe6
Author: nicho <3013379@stud.hs-mannheim.de>
Date: Mon Jan 6 21:04:04 2025 +0100
Initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/.gitignore
@@ -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
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..d843f34
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/gamestate.dat b/gamestate.dat
new file mode 100644
index 0000000..24ef3f5
Binary files /dev/null and b/gamestate.dat differ
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..20e51db
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,182 @@
+
+
+ 4.0.0
+
+ de.hs_mannheim.informatik.backend
+ HitoriFinal
+ 1.0-SNAPSHOT
+
+
+ 20
+ 20
+ UTF-8
+ 20.0.2
+ 20
+
+
+
+
+ central
+ https://repo.maven.apache.org/maven2
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.8.1
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.8.1
+ test
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.24.2
+
+
+
+
+ org.openjfx
+ javafx-controls
+ ${javafx.version}
+ compile
+
+
+ org.openjfx
+ javafx-fxml
+ ${javafx.version}
+ compile
+
+
+ org.openjfx
+ javafx-base
+ ${javafx.version}
+ compile
+
+
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+
+
+
+
+ org.openjfx
+ javafx-maven-plugin
+ 0.0.1
+
+ Main.MainMethod
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+ false
+
+
+
+ package
+
+ shade
+
+
+
+
+ Main.MainMethod
+
+
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.12
+
+
+
+ prepare-agent
+
+
+
+ report
+ test
+
+ report
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-pmd-plugin
+ 3.26.0
+
+ false
+ true
+
+
+
+ verify
+
+ check
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.11.2
+
+ private
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.6.0
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/GUI/GameUIController.java b/src/main/java/GUI/GameUIController.java
new file mode 100644
index 0000000..c170533
--- /dev/null
+++ b/src/main/java/GUI/GameUIController.java
@@ -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 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 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 convertErrorsToSet(List errors) {
+ Set 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;
+ }
+}
diff --git a/src/main/java/GUI/HitoriBoardPanel.java b/src/main/java/GUI/HitoriBoardPanel.java
new file mode 100644
index 0000000..490b093
--- /dev/null
+++ b/src/main/java/GUI/HitoriBoardPanel.java
@@ -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 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;");
+ }
+ }
+}
+
diff --git a/src/main/java/GUI/HitoriControlPanel.java b/src/main/java/GUI/HitoriControlPanel.java
new file mode 100644
index 0000000..58c7aca
--- /dev/null
+++ b/src/main/java/GUI/HitoriControlPanel.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/GUI/HitoriDialogManager.java b/src/main/java/GUI/HitoriDialogManager.java
new file mode 100644
index 0000000..f8d1197
--- /dev/null
+++ b/src/main/java/GUI/HitoriDialogManager.java
@@ -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 showBoardSelectionDialog(HitoriBoardLoader boardLoader) {
+ Dialog 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 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 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 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 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 result = alert.showAndWait();
+ return result.isPresent() && result.get() == buttonTypeYes;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/GUI/HitoriScorePanel.java b/src/main/java/GUI/HitoriScorePanel.java
new file mode 100644
index 0000000..204091b
--- /dev/null
+++ b/src/main/java/GUI/HitoriScorePanel.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/Main/MainMethod.java b/src/main/java/Main/MainMethod.java
new file mode 100644
index 0000000..6d6cb9e
--- /dev/null
+++ b/src/main/java/Main/MainMethod.java
@@ -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();
+ }
+ }
+}
diff --git a/src/main/java/domain/GameBase.java b/src/main/java/domain/GameBase.java
new file mode 100644
index 0000000..02ed53e
--- /dev/null
+++ b/src/main/java/domain/GameBase.java
@@ -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;
+ }
+}
diff --git a/src/main/java/domain/GameSolver.java b/src/main/java/domain/GameSolver.java
new file mode 100644
index 0000000..aa491d9
--- /dev/null
+++ b/src/main/java/domain/GameSolver.java
@@ -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 seenInRow = new HashSet<>();
+ Set 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 findIncorrectBlackMarks() {
+ List 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;
+ }
+}
diff --git a/src/main/java/domain/HitoriBoardLoader.java b/src/main/java/domain/HitoriBoardLoader.java
new file mode 100644
index 0000000..a65747b
--- /dev/null
+++ b/src/main/java/domain/HitoriBoardLoader.java
@@ -0,0 +1,124 @@
+package domain;
+
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+
+public class HitoriBoardLoader {
+ private Map availableBoards;
+ private Map> 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 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 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 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 getAvailableBoardNames() {
+ return availableBoards.keySet();
+ }
+
+ public int[][] getBoard(String boardName) {
+ return availableBoards.get(boardName);
+ }
+
+ public List getSolution(String boardName) {
+ return solutions.get(boardName);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/domain/HitoriGameMain.java b/src/main/java/domain/HitoriGameMain.java
new file mode 100644
index 0000000..9d22834
--- /dev/null
+++ b/src/main/java/domain/HitoriGameMain.java
@@ -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 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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/domain/HitoriGameMoves.java b/src/main/java/domain/HitoriGameMoves.java
new file mode 100644
index 0000000..1d341f5
--- /dev/null
+++ b/src/main/java/domain/HitoriGameMoves.java
@@ -0,0 +1,98 @@
+package domain;
+
+import java.io.Serializable;
+import java.util.*;
+
+public class HitoriGameMoves extends GameBase {
+ private List 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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/domain/HitoriGameScores.java b/src/main/java/domain/HitoriGameScores.java
new file mode 100644
index 0000000..482373a
--- /dev/null
+++ b/src/main/java/domain/HitoriGameScores.java
@@ -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> 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 getHighScoresWithAverage() {
+ List result = new ArrayList<>();
+ List allEntries = new ArrayList<>();
+
+ for (List 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>) ois.readObject();
+ } catch (IOException | ClassNotFoundException e) {
+ highScores = new HashMap<>();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/domain/HitoriGameTimer.java b/src/main/java/domain/HitoriGameTimer.java
new file mode 100644
index 0000000..44655d9
--- /dev/null
+++ b/src/main/java/domain/HitoriGameTimer.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/META-INF/Hitori10x10medium.csv b/src/main/resources/META-INF/Hitori10x10medium.csv
new file mode 100644
index 0000000..b65f935
--- /dev/null
+++ b/src/main/resources/META-INF/Hitori10x10medium.csv
@@ -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
\ No newline at end of file
diff --git a/src/main/resources/META-INF/Hitori15x15_medium.csv b/src/main/resources/META-INF/Hitori15x15_medium.csv
new file mode 100644
index 0000000..6db903b
--- /dev/null
+++ b/src/main/resources/META-INF/Hitori15x15_medium.csv
@@ -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
\ No newline at end of file
diff --git a/src/main/resources/META-INF/Hitori4x4_leicht.csv b/src/main/resources/META-INF/Hitori4x4_leicht.csv
new file mode 100644
index 0000000..d4126ed
--- /dev/null
+++ b/src/main/resources/META-INF/Hitori4x4_leicht.csv
@@ -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
\ No newline at end of file
diff --git a/src/main/resources/META-INF/Hitori5x5leicht.csv b/src/main/resources/META-INF/Hitori5x5leicht.csv
new file mode 100644
index 0000000..4275794
--- /dev/null
+++ b/src/main/resources/META-INF/Hitori5x5leicht.csv
@@ -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
\ No newline at end of file
diff --git a/src/main/resources/META-INF/Hitori8x8leicht.csv b/src/main/resources/META-INF/Hitori8x8leicht.csv
new file mode 100644
index 0000000..6020688
--- /dev/null
+++ b/src/main/resources/META-INF/Hitori8x8leicht.csv
@@ -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
\ No newline at end of file
diff --git a/src/main/resources/META-INF/Hitori8x8medium.csv b/src/main/resources/META-INF/Hitori8x8medium.csv
new file mode 100644
index 0000000..9116a6e
--- /dev/null
+++ b/src/main/resources/META-INF/Hitori8x8medium.csv
@@ -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
\ No newline at end of file
diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..445bc24
--- /dev/null
+++ b/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: de.hs_mannheim.informatik.backend.HitoriGameGUI
+