Last active
July 21, 2024 13:30
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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