好的,所以有很多“康威的生命游戏”问题,但这个问题非常具体。我将不得不首先向您抛出一堆代码,将其分解并告诉您问题所在。
所以这是迄今为止我的Conway的生命游戏实现,现在它仅限于调试控制台(JSfiddle - http://jsfiddle.net/georeith/C9Gyr/8/ - 启动它,打开你的控制台):
var utils = {};
/*
* utils.extend()
* - Extend initial object with all properties of following objects, objects later in the argument list take precedence.
*/
utils.extend = function(obj) {
var args = Array.prototype.slice.call(arguments, 1);
for (var i = args.length; i--;) {
for (var prop in args[i]) {
obj[prop] = args[i][prop];
}
}
return obj;
}
/*
* utils.defaults()
* - Overwrite initial object with properties of following objects only if key is present in the initial object.
*/
utils.defaults = function(obj) {
var args = Array.prototype.slice.call(arguments, 1);
for (var i = args.length; i--;) {
for (var prop in args[i]) {
if (obj.hasOwnProperty(prop)) {
obj[prop] = args[i][prop];
}
}
}
return obj;
}
/* no-wrap positioning functions */
var calcPos = {
ul: function(cell) {
return [cell.x - 1, cell.y - 1];
},
um: function(cell) {
return [cell.x, cell.y - 1];
},
ur: function(cell) {
return [cell.x + 1, cell.y - 1];
},
l: function(cell) {
return [cell.x - 1, cell.y];
},
r: function(cell) {
return [cell.x + 1, cell.y];
},
ll: function(cell) {
return [cell.x - 1, cell.y + 1];
},
lm: function(cell) {
return [cell.x, cell.y + 1];
},
lr: function(cell) {
return [cell.x + 1, cell.y + 1];
}
}
var worldDefaults = {
rows: 50,
columns: 50,
wrap: true, // left edge is mirrored on right, top edge is mirrored on bottom. Vice versa
speed: -1, // milliseconds (minimum time, waits until end of last tick to calculate from)
grid: []
}
var World = function (opts) {
this.settings = utils.defaults(worldDefaults, opts);
this.maxX = this.settings.columns - 1;
this.maxY = this.settings.rows -1;
for (var y = 0, yLen = this.settings.rows; y < yLen; ++y) {
for (var x = 0, xLen = this.settings.columns; x < xLen; ++x) {
if (y === 0) {
this.cellList.push([]);
if (this.settings.grid.length <= x) {
this.settings.grid.push([]);
}
}
var cell = new Cell();
cell.x = x;
cell.y = y;
cell.alive = !!this.settings.grid[x][y];
if (cell.alive) {
this.lifeList.push(cell);
}
var lx = (x) ? x - 1 : this.maxX;
var uy = (y) ? y - 1 : this.maxY;
var ux = (x == this.maxX) ? 0 : x + 1;
var ly = (y == this.maxY) ? 0 : y + 1;
cell.neighbourCoords = (this.settings.wrap) ?
[
[lx, uy], [x, uy], [ux, uy],
[lx, y], /*[x, y]*/ [ux, y],
[lx, ly], [x, ly], [ux, ly]
]
:
[
calcPos.ul, calcPos.um, calcPos.ur,
calcPos.l, calcPos.r,
calcPos.ll, calcPos.lm, calcPos.lr
]
;
this.cellList[x][y] = cell;
}
}
}
World.prototype.generation = 0;
World.prototype.cellList = [];
World.prototype.lifeList = [];
World.prototype.changeList = [];
World.prototype.nextTick = null;
/* Progresses the world */
World.prototype.tick = function() {
var newLifeList = [];
this.changeList = [];
// This hash goes out of scope after each tick allowing any dead shadowCells to be garbage collected
if (!this.settings.wrap) {
var shadowCellHash = {};
}
for (var i = 0, iLen = this.lifeList.length; i < iLen; ++i) {
var cell = this.lifeList[i];
if (cell.key) {
shadowCellHash[cell.key] = cell;
}
cell.neighbours = 0;
cell.lastIterated = this.generation;
for (var j = 0, jLen = cell.neighbourCoords.length; j < jLen; ++j) {
var coords;
var neighbour;
if (this.settings.wrap) {
coords = cell.neighbourCoords[j];
neighbour = this.cellList[coords[0]][coords[1]];
} else {
coords = cell.neighbourCoords[j](cell);
if (coords[0] > this.maxX || coords[0] < 0 || coords[1] > this.maxY || coords[1] < 0) {
// This neighbour is off the screen so will require a shadowCell
var key = ''+coords[0]+','+coords[1];
if (!shadowCellHash[key]) {
neighbour = shadowCellHash[key] = new ShadowCell(coords[0], coords[1]);
neighbour.neighbourCoords = cell.neighbourCoords;
} else {
neighbour = shadowCellHash[key];
}
} else {
neighbour = this.cellList[coords[0]][coords[1]];
}
}
if (neighbour.lastIterated !== this.generation) {
neighbour.neighbours = 0;
neighbour.lastIterated = this.generation;
}
if (neighbour.alive !== neighbour.changed) {
// neighbour started as alive
++cell.neighbours;
} else {
// neighbour started as dead
++neighbour.neighbours;
if (neighbour.neighbours === 3) {
neighbour.alive = true;
neighbour.changed = true;
neighbour.changeIndex = this.changeList.push(neighbour) - 1;
} else if (neighbour.neighbours === 4) {
// neighbour has reverted to dead
neighbour.alive = false;
neighbour.changed = false;
neighbour.changeIndex = -1;
this.changeList[neighbour.changeIndex] = undefined;
}
}
}
if (cell.neighbours < 2 || cell.neighbours > 3) {
cell.changed = true;
cell.alive = false;
cell.changeIndex = this.changeList.push(cell) - 1;
} else {
newLifeList.push(cell);
}
}
for (var i = 0, iLen = this.changeList.length; i < iLen; ++i) {
var cell = this.changeList[i];
if (cell !== undefined) {
cell.changeIndex = -1;
if (cell.alive) {
newLifeList.push(cell);
}
cell.update();
cell.changed = false;
}
}
this.lifeList = newLifeList;
++this.generation;
this.onTick();
var that = this;
if (this.settings.speed >= 0) {
this.nextTick = setTimeout(function() {
that.tick();
}, this.settings.speed);
}
return this;
}
World.prototype.out = function() {
var s = '';
for (var y = 0, yLen = this.settings.rows; y < yLen; ++y) {
for (var x = 0, xLen = this.settings.columns; x < xLen; ++x) {
s += (this.cellList[x][y].alive)? '\u2B1B' : '\u2B1C';
}
s += '\n';
}
s += '\u21B3 Generation: ' + this.generation + ' -- Cells: ' + this.lifeList.length + ' \u21B5';
s += '\n';
return s;
}
World.prototype.stop = function() {
this.speed = -1;
}
World.prototype.onTick = function() {
return this;
}
var Cell = function() {
return this;
}
Cell.prototype.x = 0;
Cell.prototype.y = 0;
Cell.prototype.neighbours = 0;
Cell.prototype.alive = false;
Cell.prototype.changed = false;
Cell.prototype.changeIndex = -1;
Cell.prototype.lastIterated = -1;
/*
* ShadowCell
* - non rendered cell for use in no-wrap
*/
var ShadowCell = function(x,y) {
this.x = x;
this.y = y;
this.key = ''+this.x+','+this.y;
return this;
}
ShadowCell.prototype = utils.extend({}, Cell.prototype);
ShadowCell.prototype.isShadow = true;
ShadowCell.prototype.update = function(){
return this;
};
/*
* Cell.update()
* - Update cell after tick
*/
Cell.prototype.update = function() {
this.render();
return this;
}
/*
* Cell.render()
* - Placeholder function to be overwritten by rendering engine
*/
Cell.prototype.render = function() {
return this;
}
我选择的方法涉及在每一代开始时活着的所有细胞的数组。然后我遍历他们的8个邻居中的每一个并决定是否创建/删除它们。
当我将wrap: false
传递给World
构造函数(请参阅JSfiddle实现)时,这很有效,这告诉它镜像边并且不允许溢出。然而,这种布局风格打破了很多模式,因为它会导致细胞回归自身,所以我也想让它在网格之外进行计算。
为此我创建了ShadowCell
类,其行为与Cell
类大致相同(每个网格单元死或活是它的一个实例),除了ShadowClass
是仅在网格外部需要不存在的单元格时才创建,并且在不再需要时提供垃圾收集(如果在每一代之后它已经死亡)。否则,它会模仿Cell
类属性,并直接适合Cell
所做的相同逻辑。
如果您在控制台输出中转到“第4代”,您可能会注意到它不太正确......
我已将此问题缩小到ShadowCell
实现,因为如果我在形状周围提供足够的填充以使其不会溢出网格(这是ShadowCell
启动时),这是有效的,尽管就像我之前所说的那样ShadowCell
是Cell
类的副本,它具有相同的属性并被传入,就像它是Cell
一样。
因为我希望这些被垃圾收集,所以我不在整个网格数组World.cellList
中包含这些...这让我相信问题出在这部分代码中:
// This hash goes out of scope after each tick allowing any dead shadowCells to be garbage collected
if (!this.settings.wrap) {
var shadowCellHash = {};
}
for (var i = 0, iLen = this.lifeList.length; i < iLen; ++i) {
var cell = this.lifeList[i];
if (cell.key) {
shadowCellHash[cell.key] = cell;
}
cell.neighbours = 0;
cell.lastIterated = this.generation;
for (var j = 0, jLen = cell.neighbourCoords.length; j < jLen; ++j) {
var coords;
var neighbour;
if (this.settings.wrap) {
coords = cell.neighbourCoords[j];
neighbour = this.cellList[coords[0]][coords[1]];
} else {
coords = cell.neighbourCoords[j](cell);
if (coords[0] > this.maxX || coords[0] < 0 || coords[1] > this.maxY || coords[1] < 0) {
// This neighbour is off the screen so will require a shadowCell
var key = ''+coords[0]+','+coords[1];
if (!shadowCellHash[key]) {
// ShadowCell not in hash, let's create one
neighbour = shadowCellHash[key] = new ShadowCell(coords[0], coords[1]);
neighbour.neighbourCoords = cell.neighbourCoords;
// NOTE: neighbourCoords are a set of functions that return values relative to the cell you pass to them. I am not literally giving the `ShadowCell` the same neighbour positions here.
} else {
neighbour = shadowCellHash[key];
}
} else {
// This neighbour is on screen, grab its cell.
neighbour = this.cellList[coords[0]][coords[1]];
}
}
...
注意:活着的ShadowCell
不会被垃圾收集,因为它们与其他单元一起存储在一个数组中(我从调试中确定这一点,请参阅您的控制台输出并计算可见单元格。)
由于某种原因,ShadowCell
类似乎导致不正确的邻居报告。我试图通过在每一代中跟踪每个单独细胞的创建,删除和计数邻居来调试它,但是我的大脑在它可以将它们组合在一起之前死亡。对于我的所有调试工作,我不明白为什么会出现这种情况。 ShadowCell
与使用它的其他所有内容Cell
几乎相同(它们使用完全相同的位置函数.etc),它不会被渲染的事实不应该是此
对于第4代,我通过记录阴影贴图的创建得到以下输出,我可以看到每一代都创建了一次(注意:该类没有显示,因为我使用utils.extend()
创建了他们的快照):
Object {x: 5, y: -1, key: "5,-1", neighbourCoords: Array[8], neighbours: 0…}
Object {x: 6, y: -1, key: "6,-1", neighbourCoords: Array[8], neighbours: 0…}
Object {x: 7, y: -1, key: "7,-1", neighbourCoords: Array[8], neighbours: 0…}
Object {x: 4, y: -1, key: "4,-1", neighbourCoords: Array[8], neighbours: 0…}
Object {x: -1, y: 1, key: "-1,1", neighbourCoords: Array[8], neighbours: 0…}
Object {x: -1, y: 2, key: "-1,2", neighbourCoords: Array[8], neighbours: 0…}
Object {x: -1, y: 3, key: "-1,3", neighbourCoords: Array[8], neighbours: 0…}
Object {x: 5, y: -2, key: "5,-2", neighbourCoords: Array[8], neighbours: 0…}
Object {x: 6, y: -2, key: "6,-2", neighbourCoords: Array[8], neighbours: 0…}
Object {x: 7, y: -2, key: "7,-2", neighbourCoords: Array[8], neighbours: 0…}
Object {x: -1, y: 4, key: "-1,4", neighbourCoords: Array[8], neighbours: 0…}
登录第152行,如下:
if (!shadowCellHash[key]) {
neighbour = shadowCellHash[key] = new ShadowCell(coords[0], coords[1]);
neighbour.neighbourCoords = cell.neighbourCoords;
console.log(utils.extend({}, neighbour));
} else {
答案 0 :(得分:4)
shadowCellHash
未使用所有ShadowCell
进行初始化。当循环检查[5,-1]
邻居时,它找不到[6,-1]
,因为它不在shadowCellHash
中。由于找不到[6,-1]
,因此创建了一个新的 dead [6,-1]
,并且[5,-1]
未生成,因为它没有足够的活动邻居。
我认为我已经通过在每个shadowCellHash
World.tick
来解决您的问题
// This hash goes out of scope after each tick allowing any dead shadowCells to be garbage collected
if (!this.settings.wrap) {
var shadowCellHash = {};
for (var i = 0; i < this.lifeList.length; i++) {
var cell = this.lifeList[i];
if (cell.key) {
shadowCellHash[cell.key] = cell;
}
}
}