Last active
January 5, 2017 06:18
-
-
Save mikezila/162efb64a52a3771c0e6a332bc75159a to your computer and use it in GitHub Desktop.
The Game of Life in C#. Nothing special. Wanted to try rendering something using GDI+ in winforms as simply as possible while still being fast. Abusing a Timer in this way is kind of nasty, but that coupled with unsafe bitmap drawing is actually pretty performant for simple things.
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.Diagnostics; | |
using System.Drawing; | |
using System.Drawing.Drawing2D; | |
using System.Drawing.Imaging; | |
using System.Windows.Forms; | |
namespace chip8 | |
{ | |
public partial class MainForm : Form | |
{ | |
Graphics g; | |
Stopwatch watch = new Stopwatch(); | |
long logicTime = 0; | |
long drawTime = 0; | |
const int cellsWidth = 400; | |
const int cellsHeight = 300; | |
int[] cells = new int[cellsWidth * cellsHeight]; | |
static Random rand = new Random(); | |
Bitmap raster = new Bitmap(cellsWidth, cellsHeight, PixelFormat.Format24bppRgb); | |
public MainForm() | |
{ | |
InitializeComponent(); | |
this.ClientSize = new Size(cellsWidth*2,cellsHeight*2); | |
drawTimer.Interval = 1; | |
drawTimer.Tick += (TimerTick); | |
drawTimer.Start(); | |
g = this.CreateGraphics(); | |
g.SmoothingMode = SmoothingMode.None; | |
g.InterpolationMode = InterpolationMode.NearestNeighbor; | |
g.PixelOffsetMode = PixelOffsetMode.Half; | |
g.Clear(Color.White); | |
Seed(); | |
} | |
void Seed() | |
{ | |
for (int i = 0; i < cells.Length; i++) | |
cells[i] = rand.Next(0, 2); | |
} | |
void UpdateCells() | |
{ | |
var newCells = (int[])cells.Clone(); | |
for (int i = 0; i < cells.Length; i++) { | |
int liveNeighbors = 0; | |
liveNeighbors += cells.WrappedLookup(i - 1); //left | |
liveNeighbors += cells.WrappedLookup(i + 1); //right | |
liveNeighbors += cells.WrappedLookup(i + cellsWidth);//bottom | |
liveNeighbors += cells.WrappedLookup(i - cellsWidth);//top | |
liveNeighbors += cells.WrappedLookup(i - 1 - cellsWidth);//top-left | |
liveNeighbors += cells.WrappedLookup(i + 1 - cellsWidth);//top-right | |
liveNeighbors += cells.WrappedLookup(i - 1 + cellsWidth);//bottom-left | |
liveNeighbors += cells.WrappedLookup(i + 1 + cellsWidth);//bottom-right | |
if (cells[i] == 0 && liveNeighbors == 3) { | |
newCells[i] = 1; | |
continue; | |
} | |
if (liveNeighbors < 2) | |
newCells[i] = 0; | |
else if (liveNeighbors == 2 || liveNeighbors == 3) | |
continue; | |
else if (liveNeighbors > 3) | |
newCells[i] = 0; | |
} | |
cells = newCells; | |
} | |
int x = 0; | |
int y = 0; | |
void SafeDraw() | |
{ | |
for (int i = 0; i < cells.Length; i++) { | |
if (cells[i] == 0) { | |
raster.SetPixel(x++, y, Color.White); | |
} else if (cells[i] == 1) { | |
raster.SetPixel(x++, y, Color.Black); | |
} | |
if (x == cellsWidth) { | |
y++; | |
x = 0; | |
} | |
if (y == cellsHeight) | |
x = y = 0; | |
} | |
g.DrawImage(raster, 0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height); | |
} | |
unsafe void FastDraw() | |
{ | |
BitmapData bitmapData = raster.LockBits(new Rectangle(0, 0, raster.Width, raster.Height), ImageLockMode.ReadWrite, raster.PixelFormat); | |
var ptrFirstPixel = (byte*)bitmapData.Scan0; | |
int ptrSeek = 0; | |
for (int i = 0; i < cells.Length; i++) { | |
if (cells[i] == 1) { | |
ptrFirstPixel[ptrSeek++] = (byte)0; | |
ptrFirstPixel[ptrSeek++] = (byte)0; | |
ptrFirstPixel[ptrSeek++] = (byte)0; | |
} else { | |
ptrFirstPixel[ptrSeek++] = (byte)255; | |
ptrFirstPixel[ptrSeek++] = (byte)255; | |
ptrFirstPixel[ptrSeek++] = (byte)255; | |
} | |
} | |
raster.UnlockBits(bitmapData); | |
g.DrawImage(raster, 0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height); | |
} | |
bool fastDrawing = true; | |
void TimerTick(object sender, EventArgs e) | |
{ | |
watch.Start(); | |
UpdateCells(); | |
logicTime = watch.ElapsedMilliseconds; | |
watch.Restart(); | |
if (fastDrawing) | |
FastDraw(); | |
else | |
SafeDraw(); | |
drawTime = watch.ElapsedMilliseconds; | |
if (fastDrawing) | |
Text = "Life - " + logicTime.ToString() + "ms logic, " + drawTime + "ms draw" + " (unsafe)"; | |
else | |
Text = "Life - " + logicTime.ToString() + "ms logic, " + drawTime + "ms draw" + " (safe)"; | |
watch.Reset(); | |
} | |
void Clicked(object sender, MouseEventArgs e) | |
{ | |
if (e.Button == MouseButtons.Left) | |
Seed(); | |
else if (e.Button == MouseButtons.Right) | |
fastDrawing = !fastDrawing; | |
} | |
} | |
public static class Util | |
{ | |
public static int WrappedLookup(this int[] array, int index) | |
{ | |
if (index < 0) { | |
return array[(array.Length - 1) + (index % (array.Length - 1))]; | |
} else | |
return array[index % (array.Length - 1)]; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment