我写了一个Conway的生命游戏的快速实现,但它运行速度非常慢,主要是因为我检查相邻单元格的方法再次循环遍历整个单元格网格,现在我已经改变了检查相邻单元格的方法但遗憾的是它不再正确更新,它似乎工作得很好,除了它不会产生几乎和一样多的新单元格。
现在我花了几个小时手动调试代码,使用断点进行检查并尝试比较值和调用,但它看起来好像我的GetNeighbours()
方法正常工作,所以我向你承认,我无法弄清楚自己出了什么问题。我已经提交了以下代码以获取帮助。
编辑:有些人指出我无法按照我的方式复制Grid.cells
数组。我已将其更改为使用Array.Copy()
,但不幸的是它仍无法完全正常工作。我无法弄明白,但它似乎仍然无法在所有情况下创建新单元格。
MainForm.cs
public partial class MainFom : Form
{
Grid formGrid;
CancellationTokenSource tokenSrc = new CancellationTokenSource();
public MainFom()
{
InitializeComponent();
}
private void MainFom_Load(object sender, EventArgs e)
{
formGrid = new Grid();
for (int i = 0; i < 50; i++)
{
int xCoord = 10 * i + 12;
Controls.Add(new Label()
{
AutoSize = true,
Text = i.ToString(),
Location = new Point(xCoord, 0),
Font = new Font(Font.FontFamily, 6)
});
for (int s = 0; s < 50; s++)
{
int yCoord = 10 * s + 12;
Controls.Add(new Label()
{
AutoSize = true,
Text = s.ToString(),
Location = new Point(0, yCoord),
Font = new Font(Font.FontFamily, 6)
});
}
}
}
private void MainFom_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(formGrid.toBitmap(), 0, 0);
e.Graphics.Dispose();
}
private void startBtn_Click(object sender, EventArgs e)
{
Task tempTask = Task.Factory.StartNew(
(x) =>
{
while (!tokenSrc.IsCancellationRequested)
{
formGrid.UpdateGrid();
Graphics graphics = this.CreateGraphics();
graphics.Clear(this.BackColor);
graphics.DrawImage(formGrid.toBitmap(), 0, 0);
graphics.Dispose();
}
}, tokenSrc);
startBtn.Hide();
Button stopBtn = new Button() { Text = "Stop", Location = startBtn.Location, Size = startBtn.Size };
this.Controls.Add(stopBtn);
stopBtn.Click += new EventHandler(
(x, y) =>
{
tokenSrc.Cancel();
stopBtn.Hide();
startBtn.Show();
tempTask.Wait();
tokenSrc = new CancellationTokenSource();
});
}
}
Grid.cs
class Grid
{
#region Properties/Fields
const int MAX_CELLS = 50;
Random RNG = new Random();
Cell[,] cells;
int generations = new int();
#endregion
public Grid()
{
cells = new Cell[MAX_CELLS, MAX_CELLS];
for (int x = 0; x < MAX_CELLS; x++)
{
int xCoord = 10 * x + 12;
for (int y = 0; y < MAX_CELLS; y++)
{
int yCoord = 10 * y + 12;
Point point = new Point(xCoord, yCoord);
if (RNG.Next(100) < 20) {
cells[x, y] = new Cell(point, true); }
else {
cells[x, y] = new Cell(point, false);
}
}
}
}
public void UpdateGrid()
{
Cell[,] copy = cells;
for (int x = 0; x < MAX_CELLS; x++)
{
for (int y = 0; y < MAX_CELLS; y++)
{
int neighboursCtr = GetNeighbours(x, y);
//Rule 1: Any live cell with fewer than two live neighbours dies, as if caused by under-population.
if (cells[x, y].IsAlive && neighboursCtr < 2)
{
copy[x, y].Kill();
}
//Rule 2: Any live cell with more than three live neighbours dies, as if by overcrowding.
if (cells[x, y].IsAlive && neighboursCtr > 3)
{
copy[x, y].Kill();
}
//Rule 3: Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
if (!cells[x, y].IsAlive && neighboursCtr == 3)
{
copy[x, y].Alive();
}
}
}
cells = copy;
generations++;
}
public Bitmap toBitmap()
{
Bitmap gridBmp = new Bitmap(1000, 1000); // TODO: Find optimal size for bmp
Size cellSize = new Size(10, 10);
using (Graphics gfxObj = Graphics.FromImage(gridBmp))
{
// Draw grid here and Dispose() on Pen, gfxObj is implicitly disposed
Pen myPen = new Pen(Color.LightGray);
SolidBrush myBrush = new SolidBrush(Color.Black);
for (int x = 0; x < MAX_CELLS; x++)
{
for (int y = 0; y < MAX_CELLS; y++)
{
if (!cells[x, y].IsAlive)
{
gfxObj.DrawRectangle(myPen, new Rectangle(cells[x, y].point, cellSize));
} else
{
gfxObj.FillRectangle(myBrush, new Rectangle(cells[x, y].point, cellSize));
}
}
}
myPen.Dispose();
myBrush.Dispose();
}
return gridBmp;
}
private int GetNeighbours(int column, int row)
{
int neighbours = new int();
int[] starts = new int[] { Math.Max(0 ,column - 1), Math.Max(0, row - 1) };
int[] ends = new int[] { Math.Min(49, column + 1), Math.Min(49, row + 1) };
double colAndRow = column + row/10;
for (int x = starts[0]; x < ends[0]+1; x++)
{
for (int y = starts[1]; y < ends[1]+1; y++)
{
double xAndY = x + y/10;
if (cells[x, y].IsAlive && xAndY != colAndRow)
{
neighbours++;
}
}
}
return neighbours;
}
}
Cell.cs
struct Cell
{
public bool IsAlive { get; private set; }
public readonly Point point;
public Cell(Point point, bool isAlive) : this()
{
this.point = point;
IsAlive = isAlive;
}
public void Alive()
{
IsAlive = true;
}
public void Kill()
{
IsAlive = false;
}
}
答案 0 :(得分:1)
问题出在您的UpdateGrid()
方法中。您只需将原始数组的引用分配给新变量:
Cell[,] copy = cells;
但这仍然是同一个对象;特别是,调用copy[x, y].Kill()
和cells[x, y].Kill()
之间没有区别。因此,您在计算过程中修改状态,这会影响代码的逻辑。
使用Array.Copy
制作原件的副本,它应该正常工作(您的算法似乎没有任何其他问题)。
答案 1 :(得分:1)
数组是引用类型,表示
Cell[,] copy = cells;
并不是你想要做的事情。它不是源数组的副本,所以它会在分析邻居的同时操纵它,这将导致错误的结果。
使用Array.Copy
。
答案 2 :(得分:0)
可以做很多改进。 请查看Optimizing Conway's 'Game of Life'和Hashlife
您可以首先使用LockBits来更快地使用位图。
您可以使用并行编程来改善循环:Save time with parallel FOR loop
您还可以改进算法,每次都避免整个矩阵扫描,而是维护一个活细胞列表,并且只通过这些细胞和它的邻居。 我在以下C# Game of Life Code中实现了这样的算法。