From 972ef0c8d9ae47ad59450eacdd63bb5d371271bd Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Sat, 29 Nov 2025 14:50:15 -0800 Subject: [PATCH] add unit tests for minesweeper logic and score handling --- .../bcit/comp2522/project/MinesScoreTest.java | 90 +++++ .../ca/bcit/comp2522/project/MinesTest.java | 314 ++++++++++++++++-- 2 files changed, 373 insertions(+), 31 deletions(-) create mode 100644 src/tests/ca/bcit/comp2522/project/MinesScoreTest.java diff --git a/src/tests/ca/bcit/comp2522/project/MinesScoreTest.java b/src/tests/ca/bcit/comp2522/project/MinesScoreTest.java new file mode 100644 index 0000000..06c3e63 --- /dev/null +++ b/src/tests/ca/bcit/comp2522/project/MinesScoreTest.java @@ -0,0 +1,90 @@ +package ca.bcit.comp2522.project; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.File; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * MinesScore test to test score file appending, + * reading, high-score comparisons + * + * @author Braeden Sowinski + * @version 1.0.0 + */ +public class MinesScoreTest +{ + @Test + public void testAppendScoreThenRead(@TempDir Path tempDir) + { + final File file; + file = tempDir.resolve("scores.txt").toFile(); + + final LocalDateTime now; + now = LocalDateTime.now(); + + final MinesScore score; + score = new MinesScore(now, 45, MinesScore.DIFFICULTY_EASY, true); + + MinesScore.appendScoreToFile(score, file.getAbsolutePath()); + + final List scores; + scores = MinesScore.readScoresFromFile(file.getAbsolutePath()); + + assertEquals(1, scores.size()); + assertEquals(45, scores.get(0).getSeconds()); + assertEquals(MinesScore.DIFFICULTY_EASY, scores.get(0).getDifficulty()); + assertTrue(scores.get(0).getRandomMode()); + } + + @ParameterizedTest + @ValueSource(strings = { "easy", "medium", "hard" }) + public void testDifficultyValidationAcceptsValidValues(final String difficulty) + { + final LocalDateTime now; + now = LocalDateTime.now(); + + final MinesScore score; + score = new MinesScore(now, 10, difficulty, false); + + assertEquals(difficulty, score.getDifficulty()); + } + + @Test + public void testHighScoreComparisonCorrect() + { + final LocalDateTime now; + now = LocalDateTime.now(); + + final MinesScore best; + best = new MinesScore(now, 20, MinesScore.DIFFICULTY_HARD, false); + + final MinesScore challenger; + challenger = new MinesScore(now, 10, MinesScore.DIFFICULTY_HARD, false); + + final List scores; + scores = List.of(best); + + assertTrue(MinesScore.isHighScore(challenger, scores)); + assertFalse(MinesScore.isHighScore(best, scores)); + } + + @Test + public void testReadScoresHandlesBlankFile(@TempDir Path tempDir) + { + final File file; + file = tempDir.resolve("empty.txt").toFile(); + + final List result; + result = MinesScore.readScoresFromFile(file.getAbsolutePath()); + + assertTrue(result.isEmpty()); + } +} diff --git a/src/tests/ca/bcit/comp2522/project/MinesTest.java b/src/tests/ca/bcit/comp2522/project/MinesTest.java index d9ab9fe..ca1b07d 100644 --- a/src/tests/ca/bcit/comp2522/project/MinesTest.java +++ b/src/tests/ca/bcit/comp2522/project/MinesTest.java @@ -1,61 +1,313 @@ package ca.bcit.comp2522.project; -import ca.bcit.comp2522.project.Mines; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.lang.reflect.Field; + import static org.junit.jupiter.api.Assertions.*; /** - * Lesson 10: unit tests for MinesGame. + * MinesTest game logic, field array logic such as + * popFieldVoid, validates correct cells are revealed, + * flag cycling and tracking, invalid moves such as + * revealing a flagged cell, revealing mine triggers + * loss, etc. + * + * @author Braeden Sowinski + * @version 1.0.0 */ public class MinesTest { - private static final int TEST_WIDTH = 5; - private static final int TEST_HEIGHT = 5; - private static final int TEST_MINES = 3; + private Mines mines; + + @BeforeEach + public void setUp() + { + final int width; + final int height; + final int totalMines; + final boolean randomMode; + + width = 3; + height = 3; + totalMines = 1; + randomMode = false; + + mines = new Mines(width, height, totalMines, randomMode); + } + + // --- Reflection helpers ------------------------------------------------- + + private int[] getFieldArray(final Mines game) throws Exception + { + final Field fieldField; + fieldField = Mines.class.getDeclaredField("field"); + fieldField.setAccessible(true); + + final Object value; + value = fieldField.get(game); + + return (int[]) value; + } + + private void setFieldArray(final Mines game, final int[] newField) throws Exception + { + final Field fieldField; + fieldField = Mines.class.getDeclaredField("field"); + fieldField.setAccessible(true); + fieldField.set(game, newField); + } + + // --- Basic tests --------------------------------------------------------- @Test - public void newGameHasCorrectMineCount() + public void testFieldArraySizeMatchesWidthTimesHeight() throws Exception { - final Mines game; - int mineCount; + final int[] fieldArray; + fieldArray = getFieldArray(mines); - game = new Mines(TEST_WIDTH, TEST_HEIGHT, TEST_MINES, false); - mineCount = 0; + final int expectedSize; + expectedSize = mines.getWidth() * mines.getHeight(); - for (int i = 0; i < TEST_WIDTH * TEST_HEIGHT; i++) - { - if (game.isMine(i)) - { - mineCount++; - } - } - - assertEquals(TEST_MINES, mineCount); + assertEquals(expectedSize, fieldArray.length); } @Test - public void revealNonMineDoesNotLose() - throws Exception + public void testToggleFlagCyclesCorrectly() { - final Mines game; - int safeIndex; + final int index; + index = 0; - game = new Mines(TEST_WIDTH, TEST_HEIGHT, TEST_MINES, false); - safeIndex = -1; + final int result1; + result1 = mines.toggleFlag(index); + assertEquals(Mines.FLAG, result1); - for (int i = 0; i < TEST_WIDTH * TEST_HEIGHT; i++) + final int result2; + result2 = mines.toggleFlag(index); + assertEquals(Mines.FLAG_QUESTION, result2); + + final int result3; + result3 = mines.toggleFlag(index); + assertEquals(Mines.NO_FLAG, result3); + } + + @Test + public void testRevealThrowsOnFlaggedCell() throws Exception + { + final int index; + index = 0; + + mines.toggleFlag(index); + + assertThrows(InvalidMoveException.class, () -> mines.reveal(index)); + } + + @Test + public void testRevealMineReturnsTrue() throws Exception + { + final int size; + size = mines.getWidth() * mines.getHeight(); + + boolean foundMine; + foundMine = false; + + for (int i = 0; i < size; i++) { - if (!game.isMine(i)) + if (mines.isMine(i)) { - safeIndex = i; + final boolean hitMine; + hitMine = mines.reveal(i); + assertTrue(hitMine); + foundMine = true; break; } } - assertNotEquals(-1, safeIndex); + assertTrue(foundMine); + } - assertFalse(game.reveal(safeIndex)); - assertTrue(game.isRevealed(safeIndex)); + @Test + public void testHasWonDetectsWinState() throws Exception + { + final int size; + size = mines.getWidth() * mines.getHeight(); + + for (int i = 0; i < size; i++) + { + if (!mines.isMine(i)) + { + mines.reveal(i); + } + } + + assertTrue(mines.hasWon()); + } + + // --- Advanced tests (using reflection to control the board) ------------- + + @Test + public void testPopVoidExpansionRevealsConnectedRegion() throws Exception + { + /* + Board layout (4x4), indices: + 0 1 2 3 + 4 5 6 7 + 8 9 10 11 + 12 13 14 15 + + Values: + 0 0 1 M + 0 0 1 1 + 0 0 0 0 + M 1 0 0 + + When revealing index 0, all connected 0s should be revealed. + */ + + final int width; + final int height; + final int totalMines; + final boolean randomMode; + + width = 4; + height = 4; + totalMines = 0; + randomMode = false; + + final Mines board; + board = new Mines(width, height, totalMines, randomMode); + + final int[] field; + field = new int[]{ + 0, 0, 1, -1, + 0, 0, 1, 1, + 0, 0, 0, 0, + -1, 1, 0, 0 + }; + + setFieldArray(board, field); + + final int revealIndex; + revealIndex = 0; + + board.reveal(revealIndex); + + // verify zero region auto-expands + assertTrue(board.isRevealed(0)); + assertTrue(board.isRevealed(1)); + assertTrue(board.isRevealed(4)); + assertTrue(board.isRevealed(5)); + assertTrue(board.isRevealed(6)); + assertTrue(board.isRevealed(9)); + assertTrue(board.isRevealed(10)); + assertTrue(board.isRevealed(14)); + } + + @Test + public void testRandomizeRemainingKeepsFlaggedMines() throws Exception + { + final int width; + final int height; + final int totalMines; + final boolean randomMode; + + width = 4; + height = 4; + totalMines = 0; + randomMode = true; // must be true for randomizeRemaining to do anything + + final Mines board; + board = new Mines(width, height, totalMines, randomMode); + + final int[] field; + field = new int[]{ + -1, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, -1, 0, + 0, 0, 0, 0 + }; + + setFieldArray(board, field); + + final int flaggedMineIndex; + flaggedMineIndex = 0; + + board.toggleFlag(flaggedMineIndex); + + board.randomizeRemaining(); + + // flagged mine should still be a mine + assertTrue(board.isMine(flaggedMineIndex)); + } + + @Test + public void testHasWonTrueWhenAllNonMinesRevealedOnCustomBoard() throws Exception + { + final int width; + final int height; + final int totalMines; + final boolean randomMode; + + width = 3; + height = 3; + totalMines = 1; + randomMode = false; + + final Mines board; + board = new Mines(width, height, totalMines, randomMode); + + final int[] field; + field = new int[]{ + -1, 1, 0, + 1, 1, 0, + 0, 0, 0 + }; + + setFieldArray(board, field); + + for (int i = 0; i < 9; i++) + { + if (!board.isMine(i)) + { + board.reveal(i); + } + } + + assertTrue(board.hasWon()); + } + + @Test + public void testRevealMineReturnsTrueOnCustomBoard() throws Exception + { + final int width; + final int height; + final int totalMines; + final boolean randomMode; + + width = 3; + height = 3; + totalMines = 1; + randomMode = false; + + final Mines board; + board = new Mines(width, height, totalMines, randomMode); + + final int[] field; + field = new int[]{ + 0, -1, 0, + 0, 0, 0, + 0, 0, 0 + }; + + setFieldArray(board, field); + + final int mineIndex; + mineIndex = 1; + + final boolean hitMine; + hitMine = board.reveal(mineIndex); + + assertTrue(hitMine); } }