In the C# Player's Guide book, one of the challenges was to make the game Game of Life in the console.
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
namespace GameOfLife
{
class Program
{
static void Main(string[] args)
{
string filePath = @"C:\Users\example\Documents\Visual Studio 2015\Projects\GameOfLife\GameOfLife\Grid.txt";
// We use ReadAllLines because Windows adds \r\n and this gets rid of \r
string gridText = string.Join("\n", File.ReadAllLines(filePath));
int interval = 50;
int generation = 0;
Game game = new Game(gridText);
while(true)
{
Console.Clear();
Console.WriteLine($"Generation: {generation++}");
Console.WriteLine(game.ShowGrid());
game.NextGeneration();
// Console.ReadKey()
Thread.Sleep(interval);
}
// End
}
}
}
Game.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameOfLife
{
class Game
{
private bool[,] Grid { get; set; }
private int Rows { get; }
private int Cols { get; }
public Game(string initialGridText)
{
this.Grid = MakeGrid(initialGridText);
this.Rows = this.Grid.GetLength(0);
this.Cols = this.Grid.GetLength(1);
}
public void NextGeneration() {
Func<int, int, bool> getCellStatus =
(row, col) =>
{
int aliveNeighbours = GetNeighbours(row, col)
.Where(x => x)
.Count();
// If it's alive, it dies if it doesn't have 2 or 3 neighbours
if (this.Grid[row, col])
return aliveNeighbours > 1 && aliveNeighbours < 4;
// If it's dead, it turns alive if it has 3 neighbours
return aliveNeighbours == 3;
};
bool[,] newGrid = new bool[this.Rows, this.Cols];
loopIndex(this.Rows, rowIndex =>
loopIndex(this.Cols, colIndex =>
newGrid[rowIndex, colIndex] = getCellStatus(rowIndex, colIndex)));
this.Grid = newGrid;
}
public bool[] GetNeighbours(int row, int col)
{
// Left upper corner
if (col == 0 && row == 0)
return new bool[3] {
this.Grid[0, 1],
this.Grid[1, 0],
this.Grid[1, 1]
};
// Left bottom corner
if (col == 0 && row == this.Rows - 1)
return new bool[3] {
this.Grid[row, 1],
this.Grid[row - 1, 0],
this.Grid[row - 1, 1]
};
// Right upper corner
if (col == this.Cols - 1 && row == 0)
return new bool[3] {
this.Grid[row, col - 1],
this.Grid[row + 1, col],
this.Grid[row + 1, col - 1]
};
// Right bottom corner
if (col == this.Cols - 1 && row == this.Rows - 1)
return new bool[3] {
this.Grid[row - 1, col],
this.Grid[row, col - 1],
this.Grid[row - 1, col - 1]
};
// Left side
if (col == 0)
return new bool[5] {
this.Grid[row - 1, col],
this.Grid[row + 1, col],
this.Grid[row, col + 1],
this.Grid[row - 1, col + 1],
this.Grid[row + 1, col + 1]
};
// Right side
if (col == this.Cols - 1)
return new bool[5] {
this.Grid[row - 1, col],
this.Grid[row + 1, col],
this.Grid[row, col - 1],
this.Grid[row + 1, col - 1],
this.Grid[row - 1, col - 1]
};
// Top side
if (row == 0)
return new bool[5] {
this.Grid[row + 1, col],
this.Grid[row, col - 1],
this.Grid[row, col + 1],
this.Grid[row + 1, col - 1],
this.Grid[row + 1, col + 1]
};
// Down side
if (row == this.Rows - 1)
return new bool[5] {
this.Grid[row - 1, col],
this.Grid[row, col - 1],
this.Grid[row, col + 1],
this.Grid[row - 1, col + 1],
this.Grid[row - 1, col - 1]
};
// Middle
return new bool[8] {
this.Grid[row - 1, col],
this.Grid[row + 1, col],
this.Grid[row, col - 1],
this.Grid[row, col + 1],
this.Grid[row + 1, col + 1],
this.Grid[row + 1, col - 1],
this.Grid[row - 1, col + 1],
this.Grid[row - 1, col - 1]
};
}
public string ShowGrid()
{
string grid = "";
loopIndex(this.Rows, rowIndex =>
{
loopIndex(this.Cols, colIndex =>
{
// We use 2 characters because console shows us
// twice as tall as they are wide
grid += this.Grid[rowIndex, colIndex] ? "XX" : "..";
});
grid += '\n';
});
return grid;
}
private static bool[,] MakeGrid(string gridText)
{
string[] rowsText = gridText.Split('\n');
int rows = rowsText.Length;
int cols = rowsText[0].Length;
bool[,] newGrid = new bool[rows, cols];
loopIndex(rows, rowIndex =>
loopIndex(cols, colIndex =>
newGrid[rowIndex, colIndex] =
rowsText[rowIndex][colIndex] != '.'));
return newGrid;
}
private static void loopIndex(int slots, Action<int> map) =>
Enumerable.Range(0, slots).ToList().ForEach(map);
}
}
Example of Grid.txt (Die Hard)
- How can I improve my code?
-
\$\begingroup\$ To me it looks like a use-only-lambdas-game ;-) Lambdas are good but you definitely overused them. \$\endgroup\$t3chb0t– t3chb0t2016年07月04日 04:08:15 +00:00Commented Jul 4, 2016 at 4:08
-
\$\begingroup\$ @t3chb0t How did I? Suggest another way \$\endgroup\$Afonso Matos– Afonso Matos2016年07月04日 18:28:07 +00:00Commented Jul 4, 2016 at 18:28
-
\$\begingroup\$ getCellStatus should just be a function, loopindex nested inside itself could be done as Action<int, int> \$\endgroup\$Caleth– Caleth2016年07月08日 23:52:05 +00:00Commented Jul 8, 2016 at 23:52
1 Answer 1
For GetNeighbors()
, you can filter out the indexes that are out of bounds instead of hard coding the indexes for each edge case. So we get the indexes that are in bounds, iterate through the combinations, and ignore the combination where the indexes equal row and col.
public bool[] GetNeighbours(int row, int col)
{
var xIndexes = (new[] { col - 1, col, col + 1 })
.Where(x => x >= 0 && x < this.Cols);
var yIndexes = (new[] { row - 1, row, row + 1 })
.Where(y => y >= 0 && y < this.Rows)
.ToArray();
return
(from x in xIndexes
from y in yIndexes
where !(x == col && y == row)
select this.Grid[x, y])
.ToArray();
}
If you want to use LINQ to create an array with an x and y, you can use jagged arrays instead of multidimensional arrays.
-
\$\begingroup\$ 1) Even better, you could create an
isBetween
lambda for the predicates in theWhere
s. 2) Why do you useToArray
on theyIndexes
and not on thexIndexes
? I think you don't need it. \$\endgroup\$t3chb0t– t3chb0t2016年07月04日 04:31:46 +00:00Commented Jul 4, 2016 at 4:31 -
1\$\begingroup\$ @t3chb0t 1) Good idea. 2) I use
ToArray
on theyIndexes
because they get iterated for each x inxIndexes
, soWhere
gets called once when initializing the array instead of three times in the final LINQ query. Perhaps it's a micro-optimiztion, but I think it's a good habit to avoid calculating an IEnumerable more than once. \$\endgroup\$Risky Martin– Risky Martin2016年07月04日 04:39:37 +00:00Commented Jul 4, 2016 at 4:39