|
|
|
const ALIVE = 1; |
|
const EMPTY = 0; |
|
const PULSAR = [ |
|
[0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0], |
|
[0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0], |
|
[1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1], |
|
[1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1], |
|
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0], |
|
[0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0], |
|
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0], |
|
[1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1], |
|
[1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1], |
|
[0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0], |
|
[0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0] |
|
]; |
|
|
|
class Board { |
|
constructor(sizeX, sizeY) { |
|
if (arguments.length < 1) { |
|
throw new Error('You forget about board size again..'); |
|
} |
|
|
|
this.width = sizeX; |
|
this.height = sizeY; |
|
this.generation = 0; |
|
this.board = []; |
|
this.refill(); |
|
} |
|
|
|
refill() { |
|
this.generation = 0; |
|
for (let y = 0; y < this.height; y++) { |
|
this.board[y] = []; |
|
for (let x = 0; x < this.width; x++) { |
|
this.board[y][x] = EMPTY; |
|
} |
|
} |
|
} |
|
|
|
randomize() { |
|
this.board.map((row, y) => { |
|
return row.map((col, x) => { |
|
let point = this.checkPoint(x, y); |
|
if (!!Math.round(Math.random()*2)) { |
|
this.board[y][x] = !!Math.round(Math.random()*2) |
|
? ALIVE : EMPTY; |
|
} |
|
}); |
|
}); |
|
|
|
return this.board; |
|
} |
|
|
|
createPattern() { |
|
let startY = Math.round(this.height / 2 - PULSAR.length / 2); |
|
let startX = Math.round(this.width / 2 - PULSAR.length / 2); |
|
|
|
PULSAR.map((row, y) => { |
|
row.map((col, x) => { |
|
this.board[startY + y][startX + x] = row[x] === 0 ? EMPTY : ALIVE; |
|
}); |
|
}); |
|
|
|
return this; |
|
} |
|
|
|
/* |
|
If a cell (whether ON or OFF) has exactly 2 or 3 ON cells in the 8 cells surrounding it, then that cell becomes (or remains) ON. |
|
If an ON cell has fewer than 2 cells surrounding it as described above, it becomes OFF. |
|
If an ON cell has more than 3 cells surrounding it, it becomes OFF. |
|
|
|
|
|
|
|
Any live cell with fewer than two live neighbours dies, as if caused by under-population. |
|
Any live cell with two or three live neighbours lives on to the next generation. |
|
Any live cell with more than three live neighbours dies, as if by over-population. |
|
Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. |
|
*/ |
|
checkPoint(cx, cy) { |
|
const x = cx; |
|
const y = cy; |
|
const ni = this.pointNeighbors(x, y); |
|
const neighbors = ni.reduce((p,c) => c === ALIVE ? p + 1 : p, 0); |
|
|
|
if (neighbors < 2) { |
|
return EMPTY; |
|
} |
|
|
|
if (neighbors == 2) { |
|
return this.board[y][x]; |
|
} |
|
|
|
if (neighbors == 3) { |
|
return ALIVE; |
|
} |
|
|
|
if (neighbors > 3) { |
|
return EMPTY; |
|
} |
|
} |
|
|
|
pointNeighbors(cx ,cy) { |
|
let neighbors = new Array(8); |
|
const x = Number.parseInt(cx, 10); |
|
const y = Number.parseInt(cy, 10); |
|
const w = this.width - 1; |
|
const h = this.height - 1; |
|
let shiftX = [x-1, x, x+1], shiftY = [y - 1, y + 1]; |
|
|
|
|
|
if (x === 0) { shiftX[0] = w; shiftX[1] = 0; shiftX[2] = 1; } |
|
if (x === w) { shiftX[0] = w - 1; shiftX[1] = w; shiftX[2] = 0; } |
|
if (y === 0) { shiftY[0] = h; shiftY[1] = 1; } |
|
if (y === h) { shiftY[0] = 0; shiftY[1] = h -1; } |
|
|
|
neighbors[3] = this.board[y][shiftX[0]]; |
|
neighbors[4] = this.board[y][shiftX[2]]; |
|
neighbors[0] = this.board[shiftY[0]][shiftX[0]]; |
|
neighbors[1] = this.board[shiftY[0]][shiftX[1]]; |
|
neighbors[2] = this.board[shiftY[0]][shiftX[2]]; |
|
|
|
neighbors[5] = this.board[shiftY[1]][shiftX[0]]; |
|
neighbors[6] = this.board[shiftY[1]][shiftX[1]]; |
|
neighbors[7] = this.board[shiftY[1]][shiftX[2]]; |
|
|
|
return neighbors; |
|
} |
|
|
|
next() { |
|
let mutations = 0; |
|
|
|
this.board = this.board.map((row, y) => { |
|
return row.map((col, x) => { |
|
let point = this.checkPoint(x, y); |
|
if (this.board[y][x] !== point) { |
|
mutations++; |
|
} |
|
return point; |
|
}); |
|
}); |
|
|
|
if (mutations > 0) { |
|
this.generation++; |
|
} |
|
|
|
return this.board; |
|
} |
|
} |
|
|
|
|
|
class Screen extends React.Component { |
|
constructor() { |
|
super(); |
|
this.width = 29; |
|
this.height = 25; |
|
this.interval = null; |
|
|
|
this.state = { |
|
playing : false, |
|
board : [], |
|
generation : 0 |
|
}; |
|
} |
|
|
|
componentDidMount() { |
|
this.board = new Board(this.width, this.height); |
|
//this.board.createPattern(); |
|
this.setState({ board : this.board.randomize() }); |
|
this.onStart(); |
|
} |
|
|
|
onStart() { |
|
if (this.interval !== null) { |
|
return; |
|
} |
|
this.setState({playing : true}); |
|
this.interval = setInterval(() => { |
|
this.setState({ |
|
board : this.board.next(), |
|
generation : this.board.generation |
|
}); |
|
}, 210); |
|
} |
|
|
|
onRandom() { |
|
this.setState({ board : this.board.randomize() }); |
|
this.onStart(); |
|
} |
|
|
|
onPattern() { |
|
this.onClear(); |
|
this.board.createPattern(); |
|
this.setState({ |
|
board : this.board.board |
|
}); |
|
} |
|
|
|
onStop() { |
|
this.setState({playing: false}); |
|
clearInterval(this.interval); |
|
this.interval = null; |
|
} |
|
|
|
onClear() { |
|
this.board.refill(); |
|
this.setState({ |
|
board : this.board.board, |
|
generation: 0 |
|
}); |
|
} |
|
|
|
selectItem(e) { |
|
let idx = e.target.getAttribute('data-idx'); |
|
if (idx !== null) { |
|
let x = Number.parseInt(idx.split(',')[0], 10); |
|
let y = Number.parseInt(idx.split(',')[1], 10); |
|
this.state.board[y][x] = ALIVE; |
|
this.setState({ board : this.state.board }); |
|
} |
|
} |
|
|
|
representBoard() { |
|
const classes = ['item-empty', 'item-alive']; |
|
let items = []; |
|
|
|
for (let y = 0; y < this.state.board.length; y++) { |
|
let ys = []; |
|
for (let x = 0; x < this.state.board[y].length; x++) { |
|
let cls = classes[this.state.board[y][x]]; |
|
let idx = x +','+ y; |
|
ys.push( |
|
<div className={cls} data-idx={idx}></div> |
|
); |
|
} |
|
items.push( |
|
<div className="items-row"> |
|
{ys} |
|
</div> |
|
); |
|
} |
|
|
|
return items; |
|
} |
|
|
|
render() { |
|
return ( |
|
<div className="board-wrapper"> |
|
<div className="board-container"> |
|
<div className="board-controls text-center"> |
|
<h2 className="pull-left">GoL</h2> |
|
<span className="buttons-control"> |
|
{this.state.playing ? |
|
<span className="control" |
|
onClick={this.onStop.bind(this)}> |
|
<i className="fa fa-pause"></i> |
|
</span> |
|
: |
|
<span className="control" |
|
onClick={this.onStart.bind(this)}> |
|
<i className="fa fa-play"></i> |
|
</span> |
|
} |
|
<span className="control addon" |
|
onClick={this.onClear.bind(this)}> |
|
<i className="fa fa-times"></i> |
|
</span> |
|
|
|
<span className="control addon" |
|
onClick={this.onRandom.bind(this)}> |
|
<i className="fa fa-refresh"></i> |
|
</span> |
|
|
|
<span className="control addon" |
|
onClick={this.onPattern.bind(this)}> |
|
<i className="fa fa-puzzle-piece"></i> |
|
</span> |
|
|
|
</span> |
|
<h2 className="generation pull-right">{this.state.generation}</h2> |
|
</div> |
|
<div className="board" onClick={this.selectItem.bind(this)}> |
|
{this.representBoard.call(this)} |
|
</div> |
|
</div> |
|
<div className="copy"><a href="http://www.linuxenko.pro">© Svetlana Linuxenko</a></div> |
|
</div> |
|
) |
|
} |
|
} |
|
|
|
React.render(<Screen />, document.body); |