Created
March 4, 2016 11:59
-
-
Save margusmartsepp/3cb220557822f6ed2a01 to your computer and use it in GitHub Desktop.
Conway's Game of Life
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
using System; | |
using System.Drawing; | |
using System.Drawing.Imaging; | |
using System.Text; | |
namespace ConwaysGameOfLife | |
{ | |
class Program | |
{ | |
public class GameOfLifeConsoleUi | |
{ | |
private readonly GameOfLife _life; | |
public GameOfLifeConsoleUi(GameOfLife life) | |
{ | |
_life = life; | |
Console.BackgroundColor = ConsoleColor.Gray; | |
Console.Clear(); | |
Console.CursorVisible = false; | |
var width = Math.Max(life.Dimensions.Width, 8) * 2 + 1; | |
var height = Math.Max(life.Dimensions.Height, 8) + 1; | |
Console.SetWindowSize(width, height); | |
Console.SetBufferSize(width, height); | |
const ConsoleColor deadColor = ConsoleColor.White; | |
const ConsoleColor liveColor = ConsoleColor.Black; | |
Console.BackgroundColor = deadColor; | |
Console.ForegroundColor = liveColor; | |
} | |
public void DrawBoard() | |
{ | |
Console.SetCursorPosition(0, 0); | |
Console.Write(_life); | |
} | |
} | |
public class GameOfLife | |
{ | |
private bool[,] Board { get; set; } | |
public bool LoopEdges { get; set; } | |
public int Generation { get; private set; } | |
public readonly Size Dimensions; | |
public DateTime ZeroGenerationDateTime { get; } | |
public GameOfLife(Size dimensions, bool loop) | |
{ | |
ZeroGenerationDateTime = DateTime.Now; | |
Generation = 0; | |
Dimensions = dimensions; | |
Board = new bool[dimensions.Width, dimensions.Height]; | |
LoopEdges = loop; | |
} | |
public override string ToString() | |
{ | |
var builder = new StringBuilder(); | |
for (var y = 0; y < Board.GetLength(0); y++) | |
{ | |
for (var x = 0; x < Board.GetLength(1); x++) | |
{ | |
var c = Board[x, y] ? '\u2588' : ' '; | |
builder.Append(c).Append(c); | |
} | |
builder.Append('\n'); | |
} | |
return builder.ToString(); | |
} | |
public void Randomize() | |
{ | |
var random = new Random(); | |
for (var y = 0; y < Board.GetLength(0); y++) | |
for (var x = 0; x < Board.GetLength(1); x++) | |
Board[x, y] = random.NextDouble() >= 0.5; | |
} | |
public void Update() | |
{ | |
Board = GetNextGeneration(Board, LoopEdges); | |
Generation++; | |
} | |
public static bool[,] GetNextGeneration(bool[,] current, bool loopEdges) | |
{ | |
var newBoard = new bool[current.GetLength(0), current.GetLength(1)]; | |
for (var y = 0; y < current.GetLength(0); y++) | |
{ | |
for (var x = 0; x < current.GetLength(1); x++) | |
{ | |
var liveNeighbors = CountLiveNeighbors(current, x, y, loopEdges); | |
var currentState = current[x, y]; | |
newBoard[x, y] = WillCellBeAlive(currentState, liveNeighbors); | |
} | |
} | |
return newBoard; | |
} | |
private static int CountLiveNeighbors(bool[,] current, int x, int y, bool loopEdges) | |
{ | |
var liveNeighborsCount = 0; | |
const int relativeFrom = -1; | |
const int relativeTo = 1; | |
for (var j = relativeFrom; j <= relativeTo; j++) | |
{ | |
if (IsOffBoard(y, loopEdges, j, current.GetLength(1))) | |
continue; | |
for (var i = relativeFrom; i <= relativeTo; i++) | |
{ | |
if (IsOffBoard(x, loopEdges, i, current.GetLength(0))) | |
continue; | |
var relativeConjugationY = (y + j + current.GetLength(1)) % current.GetLength(1); | |
var relativeConjugationX = (x + i + current.GetLength(0)) % current.GetLength(0); | |
liveNeighborsCount += current[relativeConjugationX, relativeConjugationY] ? 1 : 0; | |
} | |
} | |
return current[x, y] ? liveNeighborsCount - 1: liveNeighborsCount; | |
} | |
private static bool IsOffBoard(int currentPosition, bool loopEdges, int relativePosition, int upperBound, int lowerBound = 0) | |
{ | |
return !loopEdges | |
&& currentPosition + relativePosition < lowerBound | |
|| currentPosition + relativePosition >= upperBound; | |
} | |
private static bool WillCellBeAlive(bool currentState, int liveNeighbors) | |
{ | |
return currentState | |
&& (liveNeighbors == 2 || liveNeighbors == 3) | |
|| !currentState && liveNeighbors == 3; | |
} | |
public void StoreGenerationImage() | |
{ | |
var image = GetGenerationImage(); | |
var filename = $"GoL_{ZeroGenerationDateTime:HH_mm_ss_fff}_{Generation}.png"; | |
image.Save(filename, ImageFormat.Png); | |
} | |
private Bitmap GetGenerationImage() | |
{ | |
var generationBitmap = new Bitmap(Dimensions.Width, Dimensions.Height); | |
using (var generationGraphics = Graphics.FromImage(generationBitmap)) | |
{ | |
var aBrush = Brushes.Black; | |
var bBrush = Brushes.Yellow; | |
for (var y = 0; y < Board.GetLength(0); y++) | |
for (var x = 0; x < Board.GetLength(1); x++) | |
generationGraphics.FillRectangle(Board[x, y] ? aBrush : bBrush, x, y, 1, 1); | |
} | |
return generationBitmap; | |
} | |
} | |
static void Main(string[] args) | |
{ | |
var life = new GameOfLife(new Size(1024, 1024), loop: true); | |
life.Randomize(); | |
//var ui = new GameOfLifeConsoleUi(life); | |
//var delay = 50; | |
// Run the game until the Escape key is pressed. | |
while (!Console.KeyAvailable || Console.ReadKey(true).Key != ConsoleKey.Escape) | |
{ | |
//ui.DrawBoard(); | |
life.Update(); | |
life.StoreGenerationImage(); | |
Console.WriteLine(life.Generation); | |
// Wait for a bit between updates. | |
//Thread.Sleep(delay); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment