From fcba2e842dea86b089810671c41c11422820f45c Mon Sep 17 00:00:00 2001 From: nicho <3013379@stud.hs-mannheim.de> Date: Tue, 7 Jan 2025 10:46:45 +0100 Subject: [PATCH] working --- gamestate.dat | Bin 867 -> 0 bytes src/main/java/GUI/GameUIController.java | 109 +++++++++++++++----- src/main/java/GUI/HitoriBoardPanel.java | 34 ++++-- src/main/java/GUI/HitoriDialogManager.java | 55 ++++++++-- src/main/java/Main/MainMethod.java | 17 +-- src/main/java/domain/GameBase.java | 6 +- src/main/java/domain/GameSolver.java | 16 +-- src/main/java/domain/HitoriBoardLoader.java | 2 +- src/main/java/domain/HitoriGameMain.java | 69 +++++++++++-- src/main/java/domain/HitoriGameMoves.java | 26 +++-- src/main/java/domain/HitoriGameTimer.java | 56 ++++++++-- src/test/java/GameUIControllerTest.java | 7 +- 12 files changed, 294 insertions(+), 103 deletions(-) delete mode 100644 gamestate.dat diff --git a/gamestate.dat b/gamestate.dat deleted file mode 100644 index 24ef3f574d6b85c39fe8dcce3cd061ca91aca245..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 867 zcmbu7PbdUY9LIk%yNg}&C;t-iC%IV{B~fS#qunSJNGv(;! zor{ut}1p zQ1}!T!qZSmEs}MqG0)OegKCqxn6^1GwFZ`V%BO9Eb8^DGs%tv^drn zMtII-oJ#mQ?118D3qAMU7n=w099cEh(cE=)*w&~5$fO{~RXw?|-#OH9w)jvAi+i46 zRp2K0>q)}3=GVuWmi@hv5=FwbM1HAvU2xDed#}JKQ zPW*MP*!f7S|DcUK4%rl@`@1*o2bbh10Ez}dmu*pLr;-!RDR`;%+dJwJk7 0 && blackCells[row-1][col] || + row < blackCells.length-1 && blackCells[row+1][col] || + col > 0 && blackCells[row][col-1] || + col < blackCells[0].length-1 && blackCells[row][col+1]) { + return false; + } + return true; } public void handleRightClick(int row, int col) { gameMoves.markCellAsWhite(row, col); updateUI(); - checkWin(); - } - - private void checkWin() { - if (gameSolver.isSolved()) { - handleWin(); - } } public void togglePause() { @@ -65,26 +87,44 @@ public class GameUIController { } public void resetGame() { - gameMoves.reset(); - gameTimer.resetTimer(); + gameSolver.reset(); + // Don't reset timer anymore updateUI(); } public void undo() { - if (gameMoves.undo()) { + if (gameSolver.undo()) { updateUI(); } } public void redo() { - if (gameMoves.redo()) { + if (gameSolver.redo()) { updateUI(); } } public void newGame() { + // Save current game state before starting new game + saveGame(); + + // Stop timer and cleanup current game stopTimer(); - // Main application will handle board selection + + // Start new game process + Platform.runLater(() -> { + // Close current window + primaryStage.close(); + + // Start fresh instance + Stage newStage = new Stage(); + Main.MainMethod mainMethod = new Main.MainMethod(); + try { + mainMethod.start(newStage); + } catch (Exception e) { + dialogManager.showAlert("Error", "Failed to start new game: " + e.getMessage()); + } + }); } public void checkSolution() { @@ -111,8 +151,11 @@ public class GameUIController { } public void saveGame() { - HitoriGameMain gameState = new HitoriGameMain(gameMoves.getBoard()); - gameState.saveGameState(); + HitoriGameMain saveState = new HitoriGameMain(gameMoves.getBoard()); + // Copy current state to save + saveState.setGameState(gameMoves.getBlackCells(), gameMoves.getWhiteCells(), + gameMoves.getMistakeCount(), gameTimer.getElapsedTimeInSeconds()); + saveState.saveGameState(); dialogManager.showAlert("Game Saved", "Your game has been saved successfully."); } @@ -128,12 +171,12 @@ public class GameUIController { stopTimer(); Optional playerName = dialogManager.askForPlayerName(); if (playerName.isPresent() && !playerName.get().trim().isEmpty()) { - gameScores.addHighScore(playerName.get(), gameTimer.getElapsedTimeInSeconds(), gameMoves.getMistakeCount()); + gameScores.addHighScore(playerName.get(), gameTimer.getElapsedTimeInSeconds(), gameSolver.getMistakeCount()); updateHighScoreDisplay(); dialogManager.showAlert("Congratulations!", String.format( "You've solved the puzzle!\nTime: %ds\nMistakes: %d", gameTimer.getElapsedTimeInSeconds(), - gameMoves.getMistakeCount() + gameSolver.getMistakeCount() )); isPaused = false; @@ -175,7 +218,9 @@ public class GameUIController { private void updateUI() { boardPanel.updateBoard(); updateMistakeLabel(); - checkWin(); + if (gameSolver.isSolved()) { + handleWin(); + } } private void updateTimerLabel() { @@ -183,7 +228,7 @@ public class GameUIController { } private void updateMistakeLabel() { - controlPanel.updateMistakeLabel(gameMoves.getMistakeCount()); + controlPanel.updateMistakeLabel(gameSolver.getMistakeCount()); } private void updateHighScoreDisplay() { @@ -195,6 +240,18 @@ public class GameUIController { updateHighScoreDisplay(); } + public void transferHighScores(HitoriGameScores targetScores) { + for (String score : gameScores.getHighScoresWithAverage()) { + if (!score.startsWith("Average Time:")) { + String[] parts = score.split(" - "); + String playerName = parts[0]; + long time = Long.parseLong(parts[1].substring(6, parts[1].length() - 1)); + int mistakes = Integer.parseInt(parts[2].substring(10)); + targetScores.addHighScore(playerName, time, mistakes); + } + } + } + public Set convertErrorsToSet(List errors) { Set errorPositions = new HashSet<>(); for (int[] pos : errors) { @@ -208,8 +265,8 @@ public class GameUIController { } public void cleanup() { - stopTimer(); saveGame(); + stopTimer(); } public HitoriBoardPanel getBoardPanel() { diff --git a/src/main/java/GUI/HitoriBoardPanel.java b/src/main/java/GUI/HitoriBoardPanel.java index dbc5e65..d660bdc 100644 --- a/src/main/java/GUI/HitoriBoardPanel.java +++ b/src/main/java/GUI/HitoriBoardPanel.java @@ -12,11 +12,14 @@ public class HitoriBoardPanel extends GridPane { private final HitoriGameMoves gameMoves; private final GameSolver gameSolver; private final GameUIController controller; + public boolean showingErrors; + private Set currentErrors; public HitoriBoardPanel(HitoriGameMoves gameMoves, GameSolver gameSolver, GameUIController controller) { this.gameMoves = gameMoves; this.gameSolver = gameSolver; this.controller = controller; + this.showingErrors = false; setHgap(5); setVgap(5); @@ -26,6 +29,15 @@ public class HitoriBoardPanel extends GridPane { } public void updateBoard() { + if (showingErrors) { + showErrors(); + } else { + displayNormalBoard(); + } + } + + private void displayNormalBoard() { + showingErrors = false; getChildren().clear(); int[][] boardState = gameMoves.getBoard(); boolean[][] blackCells = gameMoves.getBlackCells(); @@ -34,15 +46,16 @@ public class HitoriBoardPanel extends GridPane { for (int row = 0; row < boardState.length; row++) { for (int col = 0; col < boardState[row].length; col++) { Button cellButton = createCellButton(row, col, boardState[row][col], - blackCells[row][col], whiteCells[row][col]); + blackCells[row][col], whiteCells[row][col], false); add(cellButton, col, row); } } } public void showErrors() { + showingErrors = true; var errors = gameSolver.findIncorrectBlackMarks(); - Set errorPositions = controller.convertErrorsToSet(errors); + currentErrors = controller.convertErrorsToSet(errors); getChildren().clear(); int[][] boardState = gameMoves.getBoard(); @@ -52,21 +65,22 @@ public class HitoriBoardPanel extends GridPane { for (int row = 0; row < boardState.length; row++) { for (int col = 0; col < boardState[row].length; col++) { Button cellButton = createCellButton(row, col, boardState[row][col], - blackCells[row][col], whiteCells[row][col]); - - if (errorPositions.contains(row + "," + col) && blackCells[row][col]) { - cellButton.setStyle("-fx-background-color: red; -fx-text-fill: white;"); - } - + blackCells[row][col], whiteCells[row][col], + currentErrors.contains(row + "," + col)); add(cellButton, col, row); } } } - private Button createCellButton(int row, int col, int value, boolean isBlack, boolean isWhite) { + private Button createCellButton(int row, int col, int value, boolean isBlack, boolean isWhite, boolean isError) { Button cellButton = new Button(String.valueOf(value)); cellButton.setPrefSize(50, 50); - styleCellButton(cellButton, isBlack, isWhite); + + if (isError && isBlack) { + cellButton.setStyle("-fx-background-color: red; -fx-text-fill: white;"); + } else { + styleCellButton(cellButton, isBlack, isWhite); + } cellButton.setOnMouseClicked(event -> { if (!controller.isPaused()) { diff --git a/src/main/java/GUI/HitoriDialogManager.java b/src/main/java/GUI/HitoriDialogManager.java index f8d1197..0d4ac46 100644 --- a/src/main/java/GUI/HitoriDialogManager.java +++ b/src/main/java/GUI/HitoriDialogManager.java @@ -5,12 +5,13 @@ import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.VBox; import javafx.stage.Modality; +import javafx.stage.Stage; import javafx.stage.Window; import java.util.Optional; - public class HitoriDialogManager { private final Window owner; + private Stage currentDialog; public HitoriDialogManager(Window owner) { this.owner = owner; @@ -20,10 +21,10 @@ public class HitoriDialogManager { Dialog dialog = new Dialog<>(); dialog.setTitle("Select Hitori Board"); dialog.setHeaderText("Choose a board to play:"); + dialog.initModality(Modality.APPLICATION_MODAL); if (owner != null && owner.getScene() != null) { dialog.initOwner(owner); - dialog.initModality(Modality.APPLICATION_MODAL); } ButtonType selectButtonType = new ButtonType("Play", ButtonBar.ButtonData.OK_DONE); @@ -58,8 +59,13 @@ public class HitoriDialogManager { alert.setTitle(title); alert.setHeaderText(null); alert.setContentText(message); - alert.initOwner(owner); - alert.showAndWait(); + alert.initModality(Modality.NONE); // Don't block game updates + + if (owner != null && owner.getScene() != null) { + alert.initOwner(owner); + } + + alert.show(); // Use show() instead of showAndWait() to not block } public Optional askForPlayerName() { @@ -67,7 +73,12 @@ public class HitoriDialogManager { dialog.setTitle("High Score"); dialog.setHeaderText("Congratulations! Enter your name:"); dialog.setContentText("Name:"); - dialog.initOwner(owner); + dialog.initModality(Modality.APPLICATION_MODAL); + + if (owner != null && owner.getScene() != null) { + dialog.initOwner(owner); + } + return dialog.showAndWait(); } @@ -76,7 +87,11 @@ public class HitoriDialogManager { alert.setTitle("Delete High Scores"); alert.setHeaderText("Are you sure?"); alert.setContentText("This will permanently delete all high scores."); - alert.initOwner(owner); + alert.initModality(Modality.APPLICATION_MODAL); + + if (owner != null && owner.getScene() != null) { + alert.initOwner(owner); + } Optional result = alert.showAndWait(); return result.isPresent() && result.get() == ButtonType.OK; @@ -86,13 +101,17 @@ public class HitoriDialogManager { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle("Game Complete"); alert.setHeaderText("Would you like to start a new game?"); - alert.setContentText("Choose whether to start a new game or continue viewing this one."); + alert.setContentText("Your current game progress will be saved automatically."); ButtonType buttonTypeNew = new ButtonType("New Game"); ButtonType buttonTypeStay = new ButtonType("Stay Here", ButtonBar.ButtonData.CANCEL_CLOSE); alert.getButtonTypes().setAll(buttonTypeNew, buttonTypeStay); - alert.initOwner(owner); + alert.initModality(Modality.APPLICATION_MODAL); + + if (owner != null && owner.getScene() != null) { + alert.initOwner(owner); + } Optional result = alert.showAndWait(); return result.isPresent() && result.get() == buttonTypeNew; @@ -108,9 +127,27 @@ public class HitoriDialogManager { ButtonType buttonTypeNo = new ButtonType("Start New Game"); alert.getButtonTypes().setAll(buttonTypeYes, buttonTypeNo); - alert.initOwner(owner); + alert.initModality(Modality.APPLICATION_MODAL); + + if (owner != null && owner.getScene() != null) { + alert.initOwner(owner); + } Optional result = alert.showAndWait(); return result.isPresent() && result.get() == buttonTypeYes; } + + public void showBlockingAlert(String title, String message) { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(message); + alert.initModality(Modality.APPLICATION_MODAL); + + if (owner != null && owner.getScene() != null) { + alert.initOwner(owner); + } + + alert.showAndWait(); // Block until closed + } } \ No newline at end of file diff --git a/src/main/java/Main/MainMethod.java b/src/main/java/Main/MainMethod.java index 84021b2..d72ff22 100644 --- a/src/main/java/Main/MainMethod.java +++ b/src/main/java/Main/MainMethod.java @@ -3,11 +3,11 @@ package Main; import GUI.HitoriDialogManager; import domain.HitoriBoardLoader; import domain.HitoriGameMain; +import GUI.GameUIController; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.BorderPane; import javafx.stage.Stage; -import GUI.GameUIController; public class MainMethod extends Application { private HitoriBoardLoader boardLoader; @@ -24,14 +24,15 @@ public class MainMethod extends Application { boardLoader = new HitoriBoardLoader(); dialogManager = new HitoriDialogManager(primaryStage); - checkSavedGame(primaryStage); + HitoriGameMain savedGame = HitoriGameMain.loadGameState(); + checkSavedGame(primaryStage, savedGame); } - private void checkSavedGame(Stage primaryStage) { - HitoriGameMain savedGame = HitoriGameMain.loadGameState(); + private void checkSavedGame(Stage primaryStage, HitoriGameMain savedGame) { if (savedGame != null) { if (dialogManager.confirmLoadSavedGame()) { - initializeGame(savedGame.getBoard(), primaryStage); + // Load the saved game with all its state + initializeGame(primaryStage, savedGame); } else { showBoardSelectionDialog(primaryStage); } @@ -46,15 +47,15 @@ public class MainMethod extends Application { currentBoardName = boardName; int[][] selectedBoard = boardLoader.getBoard(boardName); if (selectedBoard != null) { - initializeGame(selectedBoard, primaryStage); + initializeGame(primaryStage, new HitoriGameMain(selectedBoard)); } }, () -> System.exit(0) ); } - private void initializeGame(int[][] board, Stage primaryStage) { - controller = new GameUIController(board, dialogManager); + private void initializeGame(Stage primaryStage, HitoriGameMain game) { + controller = new GameUIController(game.getBoard(), dialogManager, primaryStage, game); createGameUI(primaryStage); } diff --git a/src/main/java/domain/GameBase.java b/src/main/java/domain/GameBase.java index 02ed53e..dc54874 100644 --- a/src/main/java/domain/GameBase.java +++ b/src/main/java/domain/GameBase.java @@ -4,11 +4,10 @@ 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 int mistakeCount; public GameBase(int[][] initialBoard) { this.board = new int[initialBoard.length][initialBoard[0].length]; @@ -23,7 +22,6 @@ public class GameBase implements Serializable { public void reset() { blackCells = new boolean[board.length][board[0].length]; whiteCells = new boolean[board.length][board[0].length]; - mistakeCount = 0; } public int[][] getBoard() { @@ -45,4 +43,4 @@ public class GameBase implements Serializable { public int getMistakeCount() { return mistakeCount; } -} +} \ No newline at end of file diff --git a/src/main/java/domain/GameSolver.java b/src/main/java/domain/GameSolver.java index aa491d9..f2b8347 100644 --- a/src/main/java/domain/GameSolver.java +++ b/src/main/java/domain/GameSolver.java @@ -2,14 +2,14 @@ package domain; import java.util.*; -public class GameSolver extends domain.HitoriGameMoves { +public class GameSolver extends HitoriGameMoves { public GameSolver(int[][] initialBoard) { super(initialBoard); } public boolean isSolved() { - + // First check if all cells are marked (either black or white) for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { if (!blackCells[i][j] && !whiteCells[i][j]) { @@ -18,7 +18,7 @@ public class GameSolver extends domain.HitoriGameMoves { } } - + // Check for adjacent black cells for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { if (blackCells[i][j] && !isValidBlackMark(i, j)) { @@ -27,13 +27,13 @@ public class GameSolver extends domain.HitoriGameMoves { } } - + // Check for duplicates in rows and columns 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++) { - + // Check row if (!blackCells[i][j]) { if (seenInRow.contains(board[i][j])) { return false; @@ -41,7 +41,7 @@ public class GameSolver extends domain.HitoriGameMoves { seenInRow.add(board[i][j]); } - + // Check column if (!blackCells[j][i]) { if (seenInCol.contains(board[j][i])) { return false; @@ -55,7 +55,7 @@ public class GameSolver extends domain.HitoriGameMoves { return isOrthogonallyConnected(); } - public boolean isValidBlackMark(int row, int col) { + private 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] || @@ -120,4 +120,4 @@ public class GameSolver extends domain.HitoriGameMoves { } return incorrectMarks; } -} +} \ No newline at end of file diff --git a/src/main/java/domain/HitoriBoardLoader.java b/src/main/java/domain/HitoriBoardLoader.java index a65747b..b9d89d6 100644 --- a/src/main/java/domain/HitoriBoardLoader.java +++ b/src/main/java/domain/HitoriBoardLoader.java @@ -29,7 +29,7 @@ public class HitoriBoardLoader { // Try to load each board for (String fileName : boardFiles) { try { - InputStream is = getClass().getResourceAsStream("/META-INF/" + fileName); + InputStream is = getClass().getResourceAsStream("/" + fileName); if (is == null) { is = getClass().getResourceAsStream("/" + fileName); } diff --git a/src/main/java/domain/HitoriGameMain.java b/src/main/java/domain/HitoriGameMain.java index 9d22834..f9cfb04 100644 --- a/src/main/java/domain/HitoriGameMain.java +++ b/src/main/java/domain/HitoriGameMain.java @@ -1,19 +1,48 @@ 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; + private final HitoriGameTimer timer; + private final HitoriGameScores scores; + private long savedTime; public HitoriGameMain(int[][] initialBoard) { super(initialBoard); - this.timer = new domain.HitoriGameTimer(); + this.timer = new HitoriGameTimer(); this.scores = new HitoriGameScores(); + this.savedTime = 0; + } + + public HitoriGameTimer getTimer() { + return timer; + } + + public HitoriGameScores getScores() { + return scores; + } + + // Update serialization to handle full game state + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + // Save current state + out.writeLong(getElapsedTimeInSeconds()); + out.writeInt(mistakeCount); + out.writeObject(blackCells); + out.writeObject(whiteCells); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + // Restore state + long savedTime = in.readLong(); + this.mistakeCount = in.readInt(); + this.blackCells = (boolean[][]) in.readObject(); + this.whiteCells = (boolean[][]) in.readObject(); + // Initialize timer with saved time + this.timer.setElapsedTime(savedTime); } // Timer delegation methods @@ -34,7 +63,7 @@ public class HitoriGameMain extends GameSolver { } public long getElapsedTimeInSeconds() { - return timer.getElapsedTimeInSeconds(); + return savedTime > 0 ? savedTime : timer.getElapsedTimeInSeconds(); } // High score delegation methods @@ -59,6 +88,19 @@ public class HitoriGameMain extends GameSolver { } // Game state persistence + public void setGameState(boolean[][] blackCells, boolean[][] whiteCells, int mistakeCount, long elapsedTime) { + this.blackCells = new boolean[blackCells.length][blackCells[0].length]; + this.whiteCells = new boolean[whiteCells.length][whiteCells[0].length]; + + for (int i = 0; i < blackCells.length; i++) { + this.blackCells[i] = blackCells[i].clone(); + this.whiteCells[i] = whiteCells[i].clone(); + } + + this.mistakeCount = mistakeCount; + this.savedTime = elapsedTime; + } + public void saveGameState() { try (ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("gamestate.dat"))) { @@ -71,15 +113,26 @@ public class HitoriGameMain extends GameSolver { public static HitoriGameMain loadGameState() { try (ObjectInputStream ois = new ObjectInputStream( new FileInputStream("gamestate.dat"))) { - return (HitoriGameMain) ois.readObject(); + HitoriGameMain loadedGame = (HitoriGameMain) ois.readObject(); + // Initialize transient fields + loadedGame.timer.resetTimer(); + return loadedGame; } catch (IOException | ClassNotFoundException e) { return null; } } + public void restoreGameState(HitoriGameMain savedGame) { + if (savedGame != null) { + this.setGameState(savedGame.blackCells, savedGame.whiteCells, + savedGame.mistakeCount, savedGame.savedTime); + } + } + @Override public void reset() { super.reset(); - resetTimer(); + // Don't reset timer anymore + savedTime = 0; } } \ No newline at end of file diff --git a/src/main/java/domain/HitoriGameMoves.java b/src/main/java/domain/HitoriGameMoves.java index 1d341f5..9a4ccb7 100644 --- a/src/main/java/domain/HitoriGameMoves.java +++ b/src/main/java/domain/HitoriGameMoves.java @@ -29,9 +29,6 @@ public class HitoriGameMoves extends GameBase { } public void markCellAsBlack(int row, int col) { - if (!isValidBlackMark(row, col)) { - mistakeCount++; - } blackCells[row][col] = true; whiteCells[row][col] = false; saveState(); @@ -43,17 +40,7 @@ public class HitoriGameMoves extends GameBase { 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() { + protected void saveState() { while (history.size() > historyPointer + 1) { history.remove(history.size() - 1); } @@ -90,9 +77,20 @@ public class HitoriGameMoves extends GameBase { @Override public void reset() { + // Save mistake count before reset + int currentMistakes = mistakeCount; + + // Call parent reset which resets the board state super.reset(); + + // Clear history history.clear(); historyPointer = -1; + + // Restore mistake count + mistakeCount = currentMistakes; + + // Save initial state saveState(); } } \ No newline at end of file diff --git a/src/main/java/domain/HitoriGameTimer.java b/src/main/java/domain/HitoriGameTimer.java index 44655d9..d9b7998 100644 --- a/src/main/java/domain/HitoriGameTimer.java +++ b/src/main/java/domain/HitoriGameTimer.java @@ -1,30 +1,40 @@ package domain; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; 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; + private transient long startTime; // Time when timer was last started + private long elapsedTime; // Total accumulated time + private boolean isPaused; // Current pause state + private long lastPauseTime; // When the timer was last paused public HitoriGameTimer() { this.startTime = 0; this.elapsedTime = 0; - this.isPaused = false; + this.isPaused = true; // Start paused + this.lastPauseTime = 0; } public void startTimer() { - if (isPaused || startTime == 0) { + if (isPaused) { startTime = System.currentTimeMillis(); isPaused = false; + // If this is a resume after pause, handle accumulated time + if (lastPauseTime > 0) { + startTime = System.currentTimeMillis(); + } } } public void pauseTimer() { if (!isPaused && startTime > 0) { - elapsedTime += System.currentTimeMillis() - startTime; + lastPauseTime = System.currentTimeMillis(); + elapsedTime += lastPauseTime - startTime; startTime = 0; isPaused = true; } @@ -32,25 +42,51 @@ public class HitoriGameTimer implements Serializable { public void stopTimer() { if (!isPaused && startTime > 0) { - elapsedTime += System.currentTimeMillis() - startTime; + long stopTime = System.currentTimeMillis(); + elapsedTime += stopTime - startTime; startTime = 0; + isPaused = true; + lastPauseTime = stopTime; } } public void resetTimer() { startTime = 0; elapsedTime = 0; - isPaused = false; + isPaused = true; + lastPauseTime = 0; + } + + public void setElapsedTime(long timeInSeconds) { + this.elapsedTime = timeInSeconds * 1000; // Convert to milliseconds + this.lastPauseTime = System.currentTimeMillis(); } public long getElapsedTimeInSeconds() { - if (isPaused || startTime == 0) { + if (isPaused) { return elapsedTime / 1000; } - return (elapsedTime + System.currentTimeMillis() - startTime) / 1000; + long currentTime = System.currentTimeMillis(); + return (elapsedTime + (currentTime - startTime)) / 1000; } public boolean isPaused() { return isPaused; } + + // For serialization + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + // Save current elapsed time if timer is running + if (!isPaused && startTime > 0) { + elapsedTime += System.currentTimeMillis() - startTime; + } + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + // Initialize transient fields + startTime = 0; + isPaused = true; // Start paused when restored + } } \ No newline at end of file diff --git a/src/test/java/GameUIControllerTest.java b/src/test/java/GameUIControllerTest.java index 7466438..1ad53fa 100644 --- a/src/test/java/GameUIControllerTest.java +++ b/src/test/java/GameUIControllerTest.java @@ -1,5 +1,6 @@ import GUI.GameUIController; import GUI.HitoriDialogManager; +import domain.HitoriGameMain; import javafx.stage.Stage; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; @@ -28,11 +29,7 @@ class GameUIControllerTest { this.stage = stage; } - @BeforeEach - void setUp() { - dialogManager = mock(HitoriDialogManager.class); - controller = new GameUIController(TEST_BOARD, dialogManager); - } + @Test void testInitialization() {