Files
minesweeper-codex/tests/Minesweeper.Tests/MinesweeperGameTests.cs
T
drobor-ai 2ba405b19c Initial commit: terminal Minesweeper in C# for .NET 10
A playable console Minesweeper (arrow keys, chording, flags, question marks,
pause, difficulty presets) plus a deduction-and-guess solver that can play the
game headlessly. Game logic lives in src/Minesweeper/Game and has no console
dependencies, so it is covered by 65 unit tests.

Includes publishing helpers for self-contained, trimmed single-file binaries
(publish.sh) and an .editorconfig that the sources conform to: UTF-8 BOM and
4-space indents for C#, 2-space indents for project files, LF endings and
2-space indents for shell scripts, System-first usings, no redundant `this.`,
and s_/ _ prefixed private static and instance fields.
2026-09-08 07:12:51 +00:00

153 lines
4.6 KiB
C#

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);
}
}