康威的人生游戏-滑翔机不动

时间:2018-07-20 19:44:13

标签: javascript html conways-game-of-life

所以我用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>

2 个答案:

答案 0 :(得分:0)

在整个代码中,您是通过x,然后是y坐标来访问网格。为了使它起作用,应该将网格定义为列的数组,而不是行的数组。

作为快速解决方案,我在xy中定义网格时简单地交换了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会指向旧状态,直到所有单元格都被更新为止,然后用新返回的单元格重写表。