using Minesweeper.Game; using Xunit; namespace Minesweeper.Tests; public class MinesweeperGameTests { private static readonly string[] s_twinMineLayout = [ "x.x", "...", "...", ]; private static readonly string[] s_cornerMineLayout = [ "x..", "...", "...", ]; [Fact] public void TheFirstClickPlantsMinesAndIsSafe() { var board = new Board(9, 9, 10); var game = new MinesweeperGame(board, new Random(7)); Assert.False(game.HasOpenedFirstCell); var result = game.Open(new CellRef(4, 4)); Assert.True(board.MinesPlanted); Assert.True(game.HasOpenedFirstCell); Assert.False(result.HitMine); Assert.True(result.Opened.Count > 1, "the default safe zone should open an area"); Assert.Equal(GameStatus.Playing, game.Status); } [Fact] public void EverySafeSquareOpeningWinsAndFlagsTheMines() { var game = TestBoards.Game(1, s_cornerMineLayout); var result = game.Open(new CellRef(2, 0)); Assert.Equal(8, result.Opened.Count); Assert.Equal(GameStatus.Won, game.Status); Assert.Equal(1, game.FlagsPlaced); Assert.Equal(0, game.MinesRemaining); Assert.All(game.Board.MineCells(), mine => Assert.True(game.Board.At(mine).IsFlagged)); } [Fact] public void SteppingOnAMineEndsTheRoundAndShowsTheBoard() { var game = TestBoards.SingleMine(4, 4); game.ToggleFlag(new CellRef(3, 3)); var result = game.Open(new CellRef(0, 0)); Assert.True(result.HitMine); Assert.Equal(GameStatus.Lost, game.Status); Assert.Equal(new CellRef(0, 0), game.Explosion!.Value); Assert.True(game.Board.At(0, 0).Exploded); Assert.All(game.Board.MineCells(), mine => Assert.True(game.Board.At(mine).IsRevealed || game.Board.At(mine).IsFlagged)); // A flag on safe ground stays put so the renderer can mark it as wrong. Assert.True(game.Board.At(3, 3).IsFlagged); Assert.False(game.Board.At(3, 3).IsMine); } [Fact] public void FinishedRoundsIgnoreFurtherMoves() { var game = TestBoards.SingleMine(); game.Open(new CellRef(0, 0)); var revealed = game.RevealedCount; Assert.False(game.Open(new CellRef(2, 2)).AnyOpened); Assert.False(game.ToggleFlag(new CellRef(2, 3))); Assert.Equal(revealed, game.RevealedCount); } [Fact] public void FlagsMoveTheCounterBothWays() { var game = TestBoards.SingleMine(); Assert.Equal(1, game.MinesRemaining); Assert.True(game.ToggleFlag(new CellRef(1, 1))); Assert.Equal(0, game.MinesRemaining); Assert.True(game.ToggleFlag(new CellRef(2, 2))); Assert.Equal(-1, game.MinesRemaining); Assert.True(game.ToggleFlag(new CellRef(1, 1))); Assert.Equal(0, game.MinesRemaining); Assert.True(game.ToggleFlag(new CellRef(1, 1)), "a cleared square can be flagged again"); Assert.Equal(-1, game.MinesRemaining); } [Fact] public void FlaggedSquaresCannotBeOpened() { var game = TestBoards.SingleMine(); game.ToggleFlag(new CellRef(1, 1)); Assert.False(game.Open(new CellRef(1, 1)).AnyOpened); Assert.True(game.Board.At(1, 1).IsFlagged); } [Fact] public void QuestionMarksAreOptionalAndStayOpenable() { var game = TestBoards.SingleMine(); Assert.False(game.ToggleQuestion(new CellRef(1, 1), questionMarksEnabled: false)); Assert.True(game.ToggleQuestion(new CellRef(1, 1), questionMarksEnabled: true)); Assert.Equal(CellState.Question, game.Board.At(1, 1).State); Assert.True(game.Open(new CellRef(1, 1)).AnyOpened); Assert.True(game.Board.At(1, 1).IsRevealed); } [Fact] public void ChordingThroughTheGameCountsTheNewSquares() { var game = TestBoards.Game(1, s_twinMineLayout); game.Open(new CellRef(1, 0)); game.ToggleFlag(new CellRef(0, 0)); game.ToggleFlag(new CellRef(2, 0)); var result = game.Chord(new CellRef(1, 0)); Assert.Equal(3, result.Opened.Count); Assert.Equal(4, game.RevealedCount); Assert.Equal(GameStatus.Playing, game.Status); } [Fact] public void RevealSolutionOnlyWorksAfterTheRound() { var game = TestBoards.SingleMine(); game.RevealSolution(); Assert.False(game.Board.At(0, 0).IsRevealed); game.Open(new CellRef(0, 0)); game.RevealSolution(); Assert.True(game.Board.At(0, 0).IsRevealed); } }