Skip to content

Instantly share code, notes, and snippets.

@dartlab-user
Forked from anonymous/.metadata.json
Created February 3, 2015 16:30
Show Gist options
  • Save dartlab-user/8be086652cebd7802498 to your computer and use it in GitHub Desktop.
Save dartlab-user/8be086652cebd7802498 to your computer and use it in GitHub Desktop.
Conway's Game of Life
{
"origin": "dartlab.org",
"url": "http://dartlab.org/#:gistId",
"history": []
}
<h1 id="title">Conway's Game of Life</h1>
<p id="infoBar">
Generation: <span id="generationValue"></span>
# Cells: <span id="cellsValue"></span>
<input id="clearButton" type="button" value="Clear" title="Kill everyone!" />
<input id="randomButton" type="button" value="Random" title="Hold random button to create live cells" />
</p>
<div id="toolbar">
<p id="speedPanel">
<label for="speed">Speed: </label>
<input id="speed" type="range" name="Speed" min="-1" max="10" step="1" value="2">
<span id="speedValue">1x</span>
</p>
<p id="patternPanel">
<!-- TODO: pattern -->
</p>
</div>
<div id="canvasPanel">
<canvas id="canvas" width="256" height="256"></canvas>
</div>
import 'dart:html';
import 'dart:async';
import 'dart:math';
int lifeTime = 1000;
DivElement canvasPanel = querySelector("#canvasPanel");
CanvasElement canvas = querySelector("#canvas");
GridPresenter gridPresenter = new GridPresenter(new Engine(), new Grid(canvas));
InputElement clearButton = querySelector("#clearButton");
InputElement randomButton = querySelector("#randomButton");
InputElement speed = querySelector("#speed");
SpanElement speedValue = querySelector("#speedValue");
SpanElement generationValue = querySelector("#generationValue");
SpanElement cellsValue = querySelector("#cellsValue");
Timer generationTimer = new Timer(Duration.ZERO, () {});
void main() {
window.onLoad.listen(_autoResizeCanvas);
window.onResize.listen(_autoResizeCanvas);
window.onMouseWheel.listen(_zoom);
speed.onChange.listen(onSpeedChange);
clearButton.onClick.listen(_clear);
Timer _randomTimer = new Timer(Duration.ZERO, () {});
randomButton.onMouseDown.listen((e) => _randomTimer = _randomStart());
randomButton.onMouseUp.listen((e) => _randomTimer.cancel());
randomButton.onMouseOut.listen((e) => _randomTimer.cancel());
animate();
}
animate() => window.requestAnimationFrame(_animate);
_animate(num time) {
generationTimer.cancel();
if(lifeTime > 0) {
Duration duration = new Duration(milliseconds: max(0, lifeTime - (time.toInt() % lifeTime)));
generationTimer = new Timer(duration, () {
nextGeneration();
animate();
});
}
}
nextGeneration() {
gridPresenter.nextGeneration();
drawGeneration();
}
drawGeneration() {
gridPresenter.drawGeneration();
refreshInfo();
}
refreshInfo() {
generationValue.text = gridPresenter.generationCount.toString();
cellsValue.text = gridPresenter.generation.aliveCells.length.toString();
}
onSpeedChange(e) {
int value = speed.valueAsNumber.toInt();
num x = 1;
if(value < 0) {
x = -1;
speedValue.text = "Stop";
} else if(value >= 10) {
x = 1000;
speedValue.text = "Max";
} else {
x = 0.25 * pow(2, value);
speedValue.text = "${x}x";
}
lifeTime = 1000 ~/ x;
animate();
}
_clear(e) {
gridPresenter.clear();
refreshInfo();
}
_autoResizeCanvas(e) {
canvas.style.display = "none";
canvas..width = canvasPanel.offsetWidth - 30 ..height = canvasPanel.offsetHeight - 30;
canvas.style.display = "block";
gridPresenter.refresh();
}
_zoom(WheelEvent e) {
int delta = (e.detail != 0 ? e.detail / 3 : e.deltaY / 120).toInt();
gridPresenter.zoom(delta);
}
Timer _randomStart() => new Timer.periodic(const Duration(milliseconds: 100), (t) {
Random rand = new Random(new DateTime.now().millisecondsSinceEpoch);
int cellWidth = gridPresenter.grid.cellWidth;
int cellHeight = gridPresenter.grid.cellHeight;
num number = cellWidth * cellHeight * 0.01;
for(int i = 0; i < number; i++) {
Cell cell = new Cell(rand.nextInt(cellWidth), rand.nextInt(cellHeight));
gridPresenter[cell] = true;
refreshInfo();
}
});
class Cell {
static const int _maxCache = 2048;
static Map<int, Map<int, Cell>> _cache = new Map();
final int x, y;
factory Cell(int x, int y) {
if(x.abs() > _maxCache || y.abs() > _maxCache) {
return new Cell._internal(x, y);
} else if(!_cache.containsKey(x)) {
_cache[x] = new Map();
return _cache[x][y] = new Cell._internal(x, y);
} else if(!_cache[x].containsKey(y)) {
return _cache[x][y] = new Cell._internal(x, y);
} else {
return _cache[x][y];
}
}
const Cell._internal(this.x, this.y);
int get hashCode => x ^ y;
bool operator ==(other) => x == other.x && y == other.y;
String toString() => "[$x, $y]";
List<Cell> neighbours() => [
new Cell(x-1, y-1), new Cell(x, y-1), new Cell(x+1, y-1),
new Cell(x-1, y ), new Cell(x+1, y ),
new Cell(x-1, y+1), new Cell(x, y+1), new Cell(x+1, y+1),
];
}
class Generation {
Set<Cell> _aliveCells = new Set();
Set<Cell> _deadCells = new Set();
Generation();
Generation.from(Iterable<Cell> aliveCell) : this._aliveCells = new Set.from(aliveCell);
int aliveNeighbours(Cell cell) {
return cell.neighbours().where(_aliveCells.contains).length;
}
Set<Cell> aliveCellsAndNeighbours() {
Set cells = new Set();
_aliveCells.forEach((cell) {
cells.add(cell);
cells.addAll(cell.neighbours());
});
return cells;
}
bool operator [](Cell cell) => _aliveCells.contains(cell);
void operator []=(Cell cell, bool value) {
if(value) {
_aliveCells.add(cell);
_deadCells.remove(cell);
} else {
_aliveCells.remove(cell);
_deadCells.add(cell);
}
}
Iterable<Cell> get aliveCells => _aliveCells;
Iterable<Cell> get deadCells => _deadCells;
String toString() => "Generation{aliveCell: $_aliveCells, deadCells: $_deadCells}";
}
class Engine {
Cell min;
Cell max;
Engine({this.min, this.max});
Generation nextGeneration(Generation current) {
Generation next = new Generation();
current.aliveCellsAndNeighbours().forEach((cell) => processCell(current, next, cell));
return next;
}
processCell(Generation current, Generation next, Cell cell) {
int aliveNeighbours = current.aliveNeighbours(cell);
next[cell] = aliveNeighbours == 3 || (aliveNeighbours == 2 && current[cell]);
}
}
typedef CellClick(Cell currentCell, {Cell startCell});
class Grid {
CanvasElement _canvas;
CanvasRenderingContext2D _context;
num cellSize = 10;
num lineWidth = 1;
String backgroundColor = "white";
String lineColor = "white";
num _scale = 1;
Cell _startMouseDown;
Cell _currentMouseMove;
CellClick cellClick;
Grid(CanvasElement canvas) {
_canvas = canvas;
_canvas.onMouseDown.listen(_onMouseDown);
_canvas.onMouseUp.listen(_onMouseUp);
_canvas.onMouseMove.listen(_onMouseMove);
_context = canvas.getContext("2d");
clear();
}
void operator []=(Cell cell, bool value) => value ? drawCell(cell, "red") : clearCell(cell);
drawCell(Cell cell, [String color = "red"]) {
num lineWidth = this.lineWidth * scale;
_context..beginPath()
..rect(cell.x * cellSize, cell.y * cellSize, cellSize, cellSize)
..fillStyle = color
..fill()
..lineWidth = lineWidth
..strokeStyle = lineColor
..stroke()
..closePath();
}
clear() {
_context..beginPath()
..rect(0, 0, width, height)
..fillStyle = backgroundColor
..closePath()
..fill();
drawGrid();
}
clearCell(Cell cell) => drawCell(cell, "white");
drawGrid() {
if(lineColor != backgroundColor) {
for(num x = 0; x < width; x+= cellSize) {
drawLine(x, 0, x, height);
}
for(num y = 0; y < height; y+= cellSize) {
drawLine(0, y, width, y);
}
}
}
drawLine(num x1, num y1, num x2, num y2) {
_context..beginPath()
..strokeStyle = lineColor
..moveTo(x1, y1)
..lineTo(x2, y2)
..lineWidth = lineWidth * scale ..stroke();
}
drawGeneration(Generation generation) {
generation.deadCells.forEach((cell) => clearCell(cell));
generation.aliveCells.forEach((cell) => drawCell(cell, "red"));
}
_onMouseDown(MouseEvent event) =>_startMouseDown = _mouseEventToCell(event);
_onMouseUp(MouseEvent event) {
_startMouseDown = null;
_dispatchCellClick(_mouseEventToCell(event));
_currentMouseMove = null;
}
_onMouseMove(MouseEvent event) {
// FIX: vérifier que la souris est bien DOWN
if(_startMouseDown != null) {
var cell = _mouseEventToCell(event);
if(_currentMouseMove != cell) {
_dispatchCellClick(cell);
}
}
}
_dispatchCellClick(Cell cell) {
if(cellClick != null && _currentMouseMove != cell) {
_currentMouseMove = cell;
if(_currentMouseMove == _startMouseDown) {
cellClick(_currentMouseMove);
} else {
cellClick(_currentMouseMove, startCell: _startMouseDown);
}
}
}
_mouseEventToCell(MouseEvent event) {
int x = event.offset.x ~/ (cellSize * scale);
int y = event.offset.y ~/ (cellSize * scale);
return new Cell(x, y);
}
num get width => _canvas.width / scale;
num get height => _canvas.height / scale;
int get cellWidth => (width / cellSize).ceil().toInt();
int get cellHeight => (height / cellSize).ceil().toInt();
num get scale => _scale;
set scale(num scale) {
print("Scale: $scale");
_context.scale(scale / _scale, scale / _scale);
_scale = scale;
}
}
class GridPresenter {
Engine engine;
Grid grid;
Generation generation = new Generation();
int generationCount = 0;
bool _clickState = true;
GridPresenter(this.engine, this.grid) {
grid.cellClick = _gridCellClick;
}
nextGeneration() {
generation = engine.nextGeneration(generation);
if(generation.aliveCells.length > 0) {
generationCount++;
}
}
drawGeneration() {
grid.drawGeneration(generation);
}
refresh() {
grid.clear();
grid.drawGeneration(generation);
}
clear() {
generation = new Generation();
generationCount = 0;
grid.clear();
}
_gridCellClick(Cell current, {Cell startCell}) {
if(startCell == null) {
_clickState = generation[current] = !generation[current];
} else {
generation[current] = _clickState;
}
grid[current] = generation[current];
}
bool operator [](Cell cell) => generation[cell];
void operator []=(Cell cell, bool value) {
generation[cell] = true;
if(value) {
grid.drawCell(cell);
} else {
grid.clearCell(cell);
}
}
zoom(int delta) {
grid.scale = max(1 / 8, min(grid.scale * (delta < 0 ? -0.5 : 2) * delta, 8));
grid.clear();
grid.drawGeneration(generation);
}
}
html, body {height: 100%;}
body {
display: table;
position: relative;
width: 100%;
background-color: #F8F8F8;
font-family: 'Open Sans', sans-serif;
font-size: 14px;
font-weight: normal;
line-height: 1.2em;
margin: 15px;
overflow: hidden;
-webkit-user-select: none;
user-select: none;
}
canvas {
cursor:default;
}
p {
color: #333;
}
#title, #infoBar, #canvasPanel {
display: table-row;
position: relative;
}
#toolbar {
display: table;
position: relative;
width: 100%;
table-layout: fixed;
}
#title, #infoBar, #toolbar {
height: 30px;
white-space: nowrap;
}
#canvasPanel {
height: 100%;
}
#speedPanel, #patternPanel {
display: table-cell;
}
#speedPanel #speed {
vertical-align: middle;
width: 75%;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment