所以我用Javascript和画布实现了一个简单的生活游戏,我认为它运行得很好(固定的时间步长,一个临时的“下一块板”来存储更改,直到需要时,等等),但是当我添加了一个“滑翔机”模式,其行为与预期不同。他们稍微移动但是停下来。
我已经看了一百遍代码,看不到任何错误,但是我敢肯定,这是我在某个地方犯的一个简单错误。下面的代码。任何建议,不胜感激!
更新:
正如乔纳斯(Jonas)指出的那样,我未能对阵列进行深度复制。我现在已经解决了这一问题,并且模拟现在可以按照“人生游戏”的预期进行工作。 (感谢乔纳斯!)
下面的更新代码。不幸的是,滑翔机问题仍然存在-它们在模拟的第一帧中正确移动,然后完全停止。如果有人能发现剩余的错误,我将不胜感激。
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
const tableSize = 64;
const cellSize = 4;
let tickDelay = 60;
let table = [];
let loop;
let deadChance = 0.5;
const colors = {
alive: '#f2b630',
dead: '#333'
};
function init() {
//build table
table = [];
for (let y = 0; y < tableSize; y++) {
let row = [];
for (let x = 0; x < tableSize; x++) {
let randomAlive = true;
if (Math.random() > deadChance) {
randomAlive = false;
}
let cell = new Cell(x, y, randomAlive);
row.push(cell);
}
table.push(row);
}
}
function tick() {
console.log("tick");
table = table.map(row => row.map(cell => cell.tick()));
render();
}
function render() {
for (let y = 0; y < tableSize; y++) {
for (let x = 0; x < tableSize; x++) {
table[x][y].draw();
}
}
}
function start() {
console.log("Starting");
loop = setInterval(tick, tickDelay);
}
function stop() {
console.log("Stopping");
clearInterval(loop);
}
function reset() {
console.log("Resetting");
clearInterval(loop);
init();
render();
}
class Cell {
constructor(x, y, isAlive) {
//The x and y values are table indices, not pixel values
this.x = x;
this.y = y;
this.isAlive = isAlive;
}
tick() {
let currentNeighbours = getNeighbours(this.x, this.y);
let numAliveNeighbours = 0;
for (let i = 0; i < currentNeighbours.length; i++) {
if (currentNeighbours[i].isAlive) {
numAliveNeighbours++;
}
}
switch (numAliveNeighbours) {
case 0: this.makeDead(); break;
case 1: this.makeDead(); break;
case 2: break;
case 3: this.makeAlive(); break;
case 4: this.makeDead(); break;
case 5: this.makeDead(); break;
case 6: this.makeDead(); break;
case 7: this.makeDead(); break;
case 8: this.makeDead(); break;
}
return new Cell(this.x, this.y, this.isAlive);
}
draw() {
if (this.isAlive) {
ctx.fillStyle = colors.alive;
} else {
ctx.fillStyle = colors.dead;
}
let margin = 1;
ctx.fillRect(this.x * cellSize + (this.x * margin), this.y * cellSize + (this.y * margin), cellSize, cellSize);
}
makeAlive() {
this.isAlive = true;
}
makeDead() {
this.isAlive = false;
}
}
//Helper functions
function getNeighbours(x, y) {
//return a list of all eight neighbours of this cell in North-East-South-West (NESW) order
let result = [];
//wrap at the edges of the table for each neighbour
let targetX;
let targetY;
//get NORTH neighbour
targetX = x;
targetY = y-1;
if (targetY < 0)
targetY = tableSize-1;
result.push(table[targetX][targetY]);
//get NORTHEAST neighbour
targetX = x+1;
targetY = y-1;
if (targetY < 0)
targetY = tableSize-1;
if (targetX > tableSize-1)
targetX = 0;
result.push(table[targetX][targetY]);
//get EAST neighbour
targetX = x+1;
targetY = y;
if (targetX >= tableSize)
targetX = 0;
result.push(table[targetX][targetY]);
//get SOUTHEAST neighbour
targetX = x+1;
targetY = y+1;
if (targetY > tableSize-1)
targetY = 0;
if (targetX > tableSize-1)
targetX = 0;
result.push(table[targetX][targetY]);
//get SOUTH neighbour
targetX = x;
targetY = y+1;
if (targetY >= tableSize)
targetY = 0;
result.push(table[targetX][targetY]);
//get SOUTHWEST neighbour
targetX = x-1;
targetY = y+1;
if (targetY > tableSize-1)
targetY = 0;
if (targetX < 0)
targetX = tableSize-1;
result.push(table[targetX][targetY]);
//get WEST neighbour
targetX = x-1;
targetY = y;
if (targetX < 0)
targetX = tableSize-1;
result.push(table[targetX][targetY]);
//get NORTHWEST neighbour
targetX = x-1;
targetY = y-1;
if (targetY < 0)
targetY = tableSize-1;
if (targetX < 0)
targetX = tableSize-1;
result.push(table[targetX][targetY]);
return result;
}
//Patterns
function pattern() {
//Set up the board using a random preset pattern
console.log("Creating pattern");
clearInterval(loop);
//build dead table
table = [];
for (let y = 0; y < tableSize; y++) {
let row = [];
for (let x = 0; x < tableSize; x++) {
let cell = new Cell(x, y, false);
row.push(cell);
}
table.push(row);
}
//add living cells for patterns
//Blinker
table[1][0].isAlive = true;
table[2][0].isAlive = true;
table[3][0].isAlive = true;
/*
//Glider
table[1][1].isAlive = true;
table[2][2].isAlive = true;
table[2][3].isAlive = true;
table[3][2].isAlive = true;
table[3][1].isAlive = true;
table[12][12].isAlive = true;
table[13][13].isAlive = true;
table[14][13].isAlive = true;
table[13][14].isAlive = true;
table[12][14].isAlive = true;
*/
render();
}
//Build board and render initial state
init();
render();
html {
background: slategray;
}
.game {
background: #ddc;
border-radius: 2px;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
margin-top: 10%;
display: block;
}
h1 {
color: white;
text-align: center;
font-family: sans-serif;
}
button {
text-align: center;
padding: 12px;
border-radius: 2px;
font-size: 1.2em;
margin-left: auto;
margin-right: auto;
margin-top: 12px;
display: block;
}
.controls {
display: flex;
width: 300px;
margin: auto;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Conway's Game of Life</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<h1>Conway's Game of Life</h1>
<canvas id="canvas" class="game" width="319px" height="319px"></canvas>
<div class="controls">
<button onclick='start()'>Start</button>
<button onclick='stop()'>Stop</button>
<button onclick='reset()'>Reset</button>
<button onclick='pattern()'>Pattern</button>
</div>
<script src="js/game.js"></script>
</body>
</html>
答案 0 :(得分:0)
在整个代码中,您是通过x
,然后是y
坐标来访问网格。为了使它起作用,应该将网格定义为列的数组,而不是行的数组。
作为快速解决方案,我在x
和y
中定义网格时简单地交换了init()
和pattern()
。您可能需要重命名变量以反映意图。
tick
函数的这一部分存在一个大问题。您要在检查其他图块的未来状态之前, 更改图块的isAlive
属性的值。
switch (numAliveNeighbours) { case 0: this.makeDead(); break; case 1: this.makeDead(); break; case 2: break; case 3: this.makeAlive(); break; case 4: this.makeDead(); break; case 5: this.makeDead(); break; case 6: this.makeDead(); break; case 7: this.makeDead(); break; case 8: this.makeDead(); break; } return new Cell(this.x, this.y, this.isAlive);
我根据个人喜好使用以下一种衬纸对其进行了修复,只要不直接更改现有图块,就可以保留switch语句。
const isAlive = this.isAlive ? (numAliveNeighbours === 2 || numAliveNeighbours === 3) : (numAliveNeighbours === 3)
return new Cell(this.x, this.y, isAlive);
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
const tableSize = 64;
const cellSize = 4;
let tickDelay = 60;
let table = [];
let loop;
let deadChance = 0.5;
const colors = {
alive: '#f2b630',
dead: '#333'
};
function init() {
//build table
table = [];
for (let y = 0; y < tableSize; y++) {
let row = [];
for (let x = 0; x < tableSize; x++) {
let randomAlive = true;
if (Math.random() > deadChance) {
randomAlive = false;
}
let cell = new Cell(y, x, randomAlive);
row.push(cell);
}
table.push(row);
}
}
function tick() {
//console.log("tick");
table = table.map(row => row.map(cell => cell.tick()));
render();
}
function render() {
for (let y = 0; y < tableSize; y++) {
for (let x = 0; x < tableSize; x++) {
table[x][y].draw();
}
}
}
function start() {
console.log("Starting");
loop = setInterval(tick, tickDelay);
}
function stop() {
console.log("Stopping");
clearInterval(loop);
}
function reset() {
console.log("Resetting");
clearInterval(loop);
init();
render();
}
class Cell {
constructor(x, y, isAlive) {
//The x and y values are table indices, not pixel values
this.x = x;
this.y = y;
this.isAlive = isAlive;
}
tick() {
let currentNeighbours = getNeighbours(this.x, this.y);
let numAliveNeighbours = 0;
for (let i = 0; i < currentNeighbours.length; i++) {
if (currentNeighbours[i].isAlive) {
numAliveNeighbours++;
}
}
const isAlive = this.isAlive ? (numAliveNeighbours === 2 || numAliveNeighbours === 3) : (numAliveNeighbours === 3)
return new Cell(this.x, this.y, isAlive);
}
draw() {
if (this.isAlive) {
ctx.fillStyle = colors.alive;
} else {
ctx.fillStyle = colors.dead;
}
let margin = 1;
ctx.fillRect(this.x * cellSize + (this.x * margin), this.y * cellSize + (this.y * margin), cellSize, cellSize);
}
makeAlive() {
this.isAlive = true;
}
makeDead() {
this.isAlive = false;
}
}
//Helper functions
function getNeighbours(x, y) {
//return a list of all eight neighbours of this cell in North-East-South-West (NESW) order
let result = [];
//wrap at the edges of the table for each neighbour
let targetX;
let targetY;
//get NORTH neighbour
targetX = x;
targetY = y-1;
if (targetY < 0)
targetY = tableSize-1;
result.push(table[targetX][targetY]);
//get NORTHEAST neighbour
targetX = x+1;
targetY = y-1;
if (targetY < 0)
targetY = tableSize-1;
if (targetX > tableSize-1)
targetX = 0;
result.push(table[targetX][targetY]);
//get EAST neighbour
targetX = x+1;
targetY = y;
if (targetX >= tableSize)
targetX = 0;
result.push(table[targetX][targetY]);
//get SOUTHEAST neighbour
targetX = x+1;
targetY = y+1;
if (targetY > tableSize-1)
targetY = 0;
if (targetX > tableSize-1)
targetX = 0;
result.push(table[targetX][targetY]);
//get SOUTH neighbour
targetX = x;
targetY = y+1;
if (targetY >= tableSize)
targetY = 0;
result.push(table[targetX][targetY]);
//get SOUTHWEST neighbour
targetX = x-1;
targetY = y+1;
if (targetY > tableSize-1)
targetY = 0;
if (targetX < 0)
targetX = tableSize-1;
result.push(table[targetX][targetY]);
//get WEST neighbour
targetX = x-1;
targetY = y;
if (targetX < 0)
targetX = tableSize-1;
result.push(table[targetX][targetY]);
//get NORTHWEST neighbour
targetX = x-1;
targetY = y-1;
if (targetY < 0)
targetY = tableSize-1;
if (targetX < 0)
targetX = tableSize-1;
result.push(table[targetX][targetY]);
return result;
}
//Patterns
function pattern() {
//Set up the board using a random preset pattern
console.log("Creating pattern");
clearInterval(loop);
//build dead table
table = [];
for (let y = 0; y < tableSize; y++) {
let row = [];
for (let x = 0; x < tableSize; x++) {
let cell = new Cell(y, x, false);
row.push(cell);
}
table.push(row);
}
//add living cells for patterns
//Glider
table[1][1].isAlive = true;
table[2][2].isAlive = true;
table[2][3].isAlive = true;
table[3][2].isAlive = true;
table[3][1].isAlive = true;
render();
}
//Build board and render initial state
pattern();
render();
html {
background: slategray;
}
.game {
background: #ddc;
border-radius: 2px;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
margin-top: 10%;
display: block;
}
h1 {
color: white;
text-align: center;
font-family: sans-serif;
}
button {
text-align: center;
padding: 12px;
border-radius: 2px;
font-size: 1.2em;
margin-left: auto;
margin-right: auto;
margin-top: 12px;
display: block;
}
.controls {
display: flex;
width: 300px;
margin: auto;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Conway's Game of Life</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<h1>Conway's Game of Life</h1>
<canvas id="canvas" class="game" width="319px" height="319px"></canvas>
<div class="controls">
<button onclick='start()'>Start</button>
<button onclick='stop()'>Stop</button>
<button onclick='reset()'>Reset</button>
<button onclick='pattern()'>Pattern</button>
</div>
<script src="js/game.js"></script>
</body>
</html>
答案 1 :(得分:-1)
slice()
仅对数组进行浅拷贝,这意味着
nextTable[this.x][this.y] === this // true
因此,您实际上使用的是一组单元格,而不适用于Convays Game,因为它要求这些单元格根据当前状态进行更新,并且如果只有一张表,则某些单元格会根据已经更新的邻居之一。要更改此设置,我将更改Cell的tick()
方法,以使其返回下一个单元格:
tick() {
//...
return new Cell(this.x, this.y, true /* false */);
}
现在在您的主要tick()
函数中,只需将当前表映射到一个新表即可:
table = table.map(row => row.map(cell => cell.tick()));
由于您根本不需要nextTable
,因为table
会指向旧状态,直到所有单元格都被更新为止,然后用新返回的单元格重写表。