Skip to content

Instantly share code, notes, and snippets.

@waf
Last active July 21, 2024 13:30
Show Gist options
  • Save waf/5c6a04899e8250cb9a89406b978c9bcc to your computer and use it in GitHub Desktop.
Save waf/5c6a04899e8250cb9a89406b978c9bcc to your computer and use it in GitHub Desktop.
Complete program that plays MineSweeper in the terminal in 100 non-whitespace lines of code. Ported from https://radanskoric.com/experiments/minesweeper-100-lines-of-clean-ruby
if (args.Length == 3)
{
Play(args.Select(int.Parse).ToArray());
}
else
{
Console.WriteLine("Usage: Minesweeper width height mineCount (e.g. Minesweeper 12 6 6)");
}
static void Play(params int[] args)
{
var game = new Game(Board.GenerateRandom(args[0], args[1], args[2]));
var renderer = new AsciiRenderer(game);
renderer.Render();
while (true)
{
Console.Write("Type click coordinate as 'x, y' (0 based)> ");
if (Console.ReadLine().Split(",") is [var x, var y])
{
var result = game.Reveal(new Coordinate(int.Parse(x), int.Parse(y)));
renderer.Render();
if (result is Game.State.Win or Game.State.Lose)
{
Console.WriteLine($"You {result}!");
return;
}
}
}
}
record Coordinate(int X, int Y)
{
static readonly Coordinate[] neighbors = (
from x in new[] { -1, 0, 1 }
from y in new[] { -1, 0, 1 }
select new Coordinate(x, y)
).Except([new(0, 0)]).ToArray();
public bool IsNeighbor(Coordinate other) =>
new[] { Math.Abs(X - other.X), Math.Abs(Y - other.Y) }.Max() <= 1;
public IEnumerable<Coordinate> Neighbors(int boardWidth, int boardHeight) =>
neighbors
.Select(neighbor => this + neighbor)
.Where(neighbor => !(neighbor.X < 0 || neighbor.X >= boardWidth || neighbor.Y < 0 || neighbor.Y >= boardHeight));
public static Coordinate operator +(Coordinate a, Coordinate b) => new(a.X + b.X, a.Y + b.Y);
}
record Board(int Width, int Height, Coordinate[] Mines)
{
public record Mine() : BoardCell;
public record Empty(int NeighborMines) : BoardCell;
public record BoardCell();
public static Board GenerateRandom(int width, int height, int mineCount)
{
var fullBoard = from x in Enumerable.Range(0, width)
from y in Enumerable.Range(0, height)
select new Coordinate(x, y);
return new Board(width, height, Random.Shared.GetItems(fullBoard.ToArray(), mineCount));
}
public BoardCell Cell(Coordinate coordinate) =>
Mines.Contains(coordinate) ? new Mine() : new Empty(CountNeighbors(coordinate));
int CountNeighbors(Coordinate coordinate) => Mines.Count(mine => mine.IsNeighbor(coordinate));
}
class Game(Board board)
{
public enum State { Play, Win, Lose }
public Board Board => board;
readonly Board.BoardCell[] cells = new Board.BoardCell[board.Height * board.Width];
readonly Board.Empty CellWithNoAdjacentMines = new(0);
public State Reveal(Coordinate coordinate)
{
var index = CellIndex(coordinate);
if (cells[index] is not null) return State.Play;
cells[index] = board.Cell(coordinate);
if (cells[index] is Board.Mine) return State.Lose;
if (cells[index] == CellWithNoAdjacentMines) RevealNeighbors(coordinate);
return cells.Count(c => c is null) == board.Mines.Length ? State.Win : State.Play;
}
public Board.BoardCell Cell(Coordinate coordinate) => cells[CellIndex(coordinate)];
public int CellIndex(Coordinate coordinate) => coordinate.Y * board.Width + coordinate.X;
public void RevealNeighbors(Coordinate coordinate) =>
coordinate.Neighbors(board.Width, board.Height).ToList().ForEach(n => Reveal(n));
}
class AsciiRenderer(Game grid)
{
public void Render(TextWriter? stdout = null)
{
stdout ??= Console.Out;
foreach (var y in Enumerable.Range(0, grid.Board.Height))
{
foreach (var x in Enumerable.Range(0, grid.Board.Width))
{
stdout.Write(grid.Cell(new Coordinate(x, y)) switch
{
null => "#",
Board.Mine => "*",
Board.Empty { NeighborMines: 0 } => "_",
Board.Empty { NeighborMines: int mineCount } => mineCount.ToString(),
});
}
stdout.WriteLine();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment