Initial commit
commit
1d806f85a3
|
|
@ -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
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings" defaultProject="true" />
|
||||
</project>
|
||||
Binary file not shown.
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
|
|
@ -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.
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Manifest-Version: 1.0
|
||||
Main-Class: de.hs_mannheim.informatik.backend.HitoriGameGUI
|
||||
|
||||
Loading…
Reference in New Issue