Skip to content

Instantly share code, notes, and snippets.

@eisterman
Created June 1, 2022 16:47
Show Gist options
  • Save eisterman/9e55556c29394364a3f3c0312eb6bfb5 to your computer and use it in GitHub Desktop.
Save eisterman/9e55556c29394364a3f3c0312eb6bfb5 to your computer and use it in GitHub Desktop.
WPF Game of Life
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace WpfGameOfLife;
public class RectangleCell {
public double Left { get; internal set; }
public double Top { get; internal set; }
public Rectangle Rect { get; internal set; }
public int X { get; private set; }
public int Y { get; private set; }
public RectangleCell(int x, int y)
{
X = x;
Y = y;
}
}
public class CanvasGameOfLife {
public Brush LiveBrush { get; } = Brushes.Orange;
public Brush DeadBrush { get; } = Brushes.WhiteSmoke;
private double _rectangleWidth, _rectangleHeight;
private Canvas _canvas;
private RectangleCell[,] _cells;
public CanvasGameOfLife(Canvas canvas, int width, int height, GoLRule rule) {
Width = width;
Height = height;
Rule = rule;
_table = new int[width, height];
_canvas = canvas;
canvas.Children.Clear();
_rectangleWidth = _canvas.ActualWidth / width;
_rectangleHeight = _canvas.ActualHeight / height;
_rectangleWidth = _rectangleHeight = Math.Min(_rectangleWidth, _rectangleHeight);
_cells = new RectangleCell[width, height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
var cell = new RectangleCell(x, y);
cell.Left = x * _rectangleWidth;
cell.Top = y * _rectangleHeight;
cell.Rect = new Rectangle
{
Width = _rectangleWidth,
Height = _rectangleHeight,
Stroke = Brushes.LightGray,
StrokeThickness = 0.6,
// ToolTip = "0",
Tag = cell,
};
Canvas.SetLeft(cell.Rect, cell.Left);
Canvas.SetTop(cell.Rect, cell.Top);
canvas.Children.Add(cell.Rect);
cell.Rect.Fill = DeadBrush;
_cells[x, y] = cell;
}
}
}
public int Width { get; }
public int Height { get; }
public int this[int x, int y] {
get => _table[Math.Abs(x % Width), Math.Abs(y % Height)];
set => _table[Math.Abs(x % Width), Math.Abs(y % Height)] = value;
}
private int[,] _table;
public GoLRule Rule { get; set; }
public int Generation { get; private set; }
private int GetNeighbors(int x, int y) {
var neigh = 0;
for (var i = -1; i <= 1; i++) {
for (var j = -1; j <= 1; j++) {
if (i == 0 && j == 0) continue;
if (this[x + i, y + j] > 0) neigh++;
}
}
return neigh;
}
public void RefreshCellsColor() {
for (var i = 0; i < Width; i++) {
for (var j = 0; j < Height; j++) {
_cells[i, j].Rect.Fill = this[i, j] > 0 ? LiveBrush : DeadBrush;
}
}
}
public void RunOnce() {
var ntable = new int[Width, Height];
for (var i = 0; i < Width; i++) {
for (var j = 0; j < Height; j++) {
ntable[i, j] = (_table[i, j] == 0 ? Rule.Birth : Rule.Stay).Contains(GetNeighbors(i, j)) ? 1 : 0;
}
}
_table = ntable;
Generation++;
RefreshCellsColor();
}
public int[]? MouseToCoord(Point p) {
var x = (int)(p.X / _rectangleWidth);
var y = (int)(p.Y / _rectangleHeight);
if (x < 0 || x >= Width || y < 0 || y >= Height) {
return null;
}
return new int[] { x, y };
}
public override string ToString() {
var output = $"Generation:\t{Generation}\n";
for (var j = 0; j < Height; j++) {
var line = "";
for (var i = 0; i < Width; i++) {
line += _table[i, j] > 0 ? "#" : ".";
}
output += line + "\n";
}
return output;
}
public void Randomize(int seed) {
var random = new Random(seed);
for (var i = 0; i < Width; i++)
for (var j = 0; j < Height; j++) {
_table[i, j] = random.Next(0, 2);
}
RefreshCellsColor();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace WpfGameOfLife;
public interface IGoLRule {
public int[] Birth { get; }
public int[] Stay { get; }
}
public class GoLRule: IGoLRule {
public GoLRule() {
Birth = new[] { 3 };
Stay = new[] { 2, 3 };
}
public GoLRule(int[] birth, int[] stay) {
if (birth.Any(x => x is >= 0 and < 9)) throw new ArgumentOutOfRangeException(nameof(birth));
if (stay.Any(x => x is >= 0 and < 9)) throw new ArgumentOutOfRangeException(nameof(stay));
Birth = birth;
Stay = stay;
}
public GoLRule(string rule) {
var parts = rule.Split('/');
var strBirth = parts[0].ToCharArray();
var strStay = parts[1].ToCharArray();
var iaBirth = strBirth.Select(r => int.Parse(r.ToString()));
var iaStay = strStay.Select(r => int.Parse(r.ToString()));
Birth = iaBirth as int[] ?? throw new InvalidOperationException();
Stay = iaStay as int[] ?? throw new InvalidOperationException();
}
public int[] Birth { get;}
public int[] Stay { get; }
public override string ToString() {
var strBirth = string.Join("", Birth.Select(x => x.ToString()));
var strStay = string.Join("", Stay.Select(x => x.ToString()));
return string.Join("/", strBirth, strStay);
}
/*
public int ApplyRule(int cellstatus, IEnumerable<int> neighbors) {
var enumerable = neighbors as int[] ?? neighbors.ToArray();
if (enumerable.Length != 8) throw new ArgumentException("neighbors need to be 8");
return (cellstatus == 0 ? Birth : Stay).Contains(enumerable.Count(x => x > 0)) ? 1 : 0;
}
public int ApplyRule(int cellstatus, int numberalive) {
return (cellstatus == 0 ? Birth : Stay).Contains(numberalive) ? 1 : 0;
}
*/
}
<Window x:Class="WpfGameOfLife.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfGameOfLife"
mc:Ignorable="d"
Title="Conway's Game Of Life" Height="450" Width="800">
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="5 0"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="MinWidth" Value="50"/>
</Style>
<Style TargetType="ComboBox">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="MinWidth" Value="100"/>
<Setter Property="SelectedIndex" Value="0"/>
</Style>
<Style TargetType="Button">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="MinWidth" Value="100"/>
</Style>
</Window.Resources>
<Grid d:DataContext="{local:MainWindow}">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<DockPanel>
<TextBlock Text="Width:"></TextBlock>
<TextBox Name="TbWidth" Text="{Binding AreaWidth}"></TextBox>
<TextBlock Text="Height:"></TextBlock>
<TextBox Name="TbHeight" Text="{Binding AreaHeight}"></TextBox>
<TextBlock Text="Seed:"></TextBlock>
<TextBox Name="TbSeed" Text="{Binding Seed}"></TextBox>
<TextBlock Text="Rule:"></TextBlock>
<ComboBox Name="CbRule" ItemsSource="{Binding Rules}" SelectedItem="{Binding Rule}"></ComboBox>
<Button x:Name="BRun" Click="BRun_OnClick" HorizontalAlignment="Right" Width="100">Start</Button>
</DockPanel>
<Canvas x:Name="GoLCanvas" Grid.Row="1" Background="AliceBlue"
MouseLeftButtonDown="Canvas_OnMouseLeftButtonDown"
MouseMove="GoLCanvas_OnMouseMove"
MouseLeftButtonUp="GoLCanvas_OnMouseLeftButtonUp"
>
</Canvas>
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using System.Diagnostics;
using System.Linq;
namespace WpfGameOfLife {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
private CanvasGameOfLife? _gol;
private GoLRule _rule = new GoLRule();
private bool _isrunning = false;
public string Rule {
get => _rule.ToString();
set => _rule = new GoLRule(value);
}
private DispatcherTimer? _timer;
public int AreaWidth { get; set; } = 60;
public int AreaHeight { get; set; } = 60;
public int Seed { get; set; } = 1;
public string[] Rules { get; } = new string[] {
"3/23",
"1/1",
};
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void BRun_OnClick(object sender, RoutedEventArgs e) {
if (_isrunning) {
BRun.Content = "Run";
} else {
BRun.Content = "Stop";
if (_gol is null) InitGoL();
RunGoL();
}
_isrunning = !_isrunning;
}
private void InitGoL() {
_gol = new CanvasGameOfLife(GoLCanvas, AreaWidth, AreaHeight, _rule);
_gol.Randomize(Seed);
TbWidth.IsEnabled = false;
TbHeight.IsEnabled = false;
TbSeed.IsEnabled = false;
CbRule.IsEnabled = false;
}
private void RunGoL() {
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
_timer.Tick += (o, e) =>
{
_timer.Stop();
_gol!.RunOnce();
if (_isrunning)
{
_timer.Start();
}
};
_timer.Start();
}
private int[]? _lastChangedCoords;
private void DrawLineTick(Point p) {
var coords = _gol?.MouseToCoord(p);
if (coords is null) return;
if (_lastChangedCoords != null && _lastChangedCoords.SequenceEqual(coords)) return;
_gol![coords[0], coords[1]] = _gol![coords[0], coords[1]] > 0 ? 0 : 1;
_lastChangedCoords = coords;
_gol!.RefreshCellsColor();
}
private void Canvas_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
DrawLineTick(e.GetPosition(GoLCanvas));
}
private void GoLCanvas_OnMouseMove(object sender, MouseEventArgs e) {
if (e.LeftButton == MouseButtonState.Pressed) {
DrawLineTick(e.GetPosition(GoLCanvas));
} else {
_lastChangedCoords = null;
}
}
private void GoLCanvas_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
_lastChangedCoords = null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment