import 'dart:html' as html; import 'dart:math' as math; import 'dart:math'; import 'package:rules_of_living/src/Grid.dart'; import 'package:rules_of_living/src/rules/GameOfLife.dart'; import 'package:rules_of_living/src/rules/RuleSet.dart'; enum CellPattern { SpaceShip, Blinker } class Simulation { Grid map; Grid _snapshot; RuleSet rules = GameOfLife(); bool _dirty = true; bool get dirty => _dirty; bool _renderEdges = true; bool get renderEdges => _renderEdges; int _amount; int _dispersal; Point get gridSize => Point(map.width, map.height); void set gridSize(Point value) { if (value.x <= 0 || value.y <= 0) throw ArgumentError("grid size must not be smaller than 1"); map = Grid(value.x, value.y); } Simulation(int w, int h) : this.map = new Grid(w, h) { this.map = reset(); } Simulation.fromGrid(Grid map) : this.map = map; Grid reset([Grid map]) { map ??= this.map; _dirty = true; map.setAll(0, List.filled(map.length, false)); return map; } void addRandomPattern({int amount, int dispersal}) { int _startingSeed = DateTime.now().millisecondsSinceEpoch; math.Random rng = new math.Random(_startingSeed); _amount = amount ?? rng.nextInt(20); _dispersal = dispersal ?? 10; int cx = rng.nextInt(map.width ~/ 3) + (map.width ~/ 3); int cy = rng.nextInt(map.height ~/ 3) + (map.height ~/ 3); int sanityCheck = 0; for (var i = 0; i < (_amount); i++) { sanityCheck++; getCellState(cx, cy) ? i-- : setCellState( cx + rng.nextInt(_dispersal), cy + rng.nextInt(_dispersal), true); if (sanityCheck > 100 && sanityCheck > i * 3) break; } _dirty = true; } void setCellState(int x, int y, bool state) { if (y >= map.height || x >= map.width) return null; state ? map.set(x, y, true) : map.set(x, y, false); } bool getCellState(int x, int y) { if (y >= map.height || x >= map.width) return null; return map.get(x, y); } void toggleCellState(int x, int y) { if (y >= map.height || x >= map.width) return null; getCellState(x, y) == null ? setCellState(x, y, true) : setCellState(x, y, false); } Map update() { Map stateChanges = calculateNextState(map); return stateChanges; } void mergeStateChanges(Map stateChanges) { stateChanges.forEach((i, el) => map[i] = el); if (stateChanges.length != 0) _dirty = true; } Map calculateNextState(Grid oldState) { Map stateChanges = Map(); for (int i = 0; i < map.length; i++) { math.Point p = map.toCoordinates(i); bool cell = map[i]; int neighbors = getNeighbors(p.x, p.y, rules.range); if (cell == false && rules.checkBirth(neighbors) == true) { stateChanges[i] = true; } else if (cell == true && rules.checkSurvival(neighbors) == false) { stateChanges[i] = false; } } return stateChanges; } int getNeighbors(int x, int y, int range) { int count = 0; for (int ix = -range + x; ix <= range + x; ix++) { for (int iy = -range + y; iy <= range + y; iy++) { if (ix >= 0 && iy >= 0 && ix < map.width && iy < map.height && getCellState(ix, iy) == true && !(x == ix && y == iy)) count++; } } return count; } void render(html.CanvasElement canvas, [num interp]) { // only renders if any cells changed between renders if (!_dirty) return; html.CanvasRenderingContext2D ctx = canvas.getContext('2d'); int brickW = (canvas.width ~/ map.width); int brickH = (canvas.height ~/ map.height); ctx.clearRect(0, 0, canvas.width, canvas.height); for (int i = 0; i < map.length; i++) { math.Point p = map.toCoordinates(i); if (_renderEdges) { ctx.setStrokeColorRgb(100, 100, 100); ctx.strokeRect(p.x * brickW, p.y * brickH, brickW, brickH); } if (map[i] == true) ctx.setFillColorRgb(155, 155, 255); else ctx.setFillColorRgb(0, 0, 0); ctx.fillRect(p.x * brickW, p.y * brickH, brickW, brickH); } _dirty = false; } void set renderEdges(bool on) { _renderEdges = on; _dirty = true; } void saveSnapshot() => _snapshot = Grid.from(map); Grid loadSnapshot() { map = Grid.from(_snapshot); _dirty = true; return map; } }