diff --git a/lib/src/Cell.dart b/lib/src/Cell.dart deleted file mode 100644 index 4e12f04..0000000 --- a/lib/src/Cell.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:rules_of_living/src/Rule.dart'; - -class Cell { - /// For determining if render updates are necessary in [Grid].render() function - bool dirty = false; -} diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index 36ab89a..3653353 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -32,11 +32,11 @@ class Engine { /// Grid Size /// /// Number of cells on x coordinate and y coordinate. Can be set individually. - Point get gridSize => Point(_grid.w, _grid.h); + Point get gridSize => Point(_simulation.w, _simulation.h); void set gridSize(Point value) { if (value.x <= 0 || value.y <= 0) throw ArgumentError("grid size must not be smaller than 1"); - _grid = Simulation(value.x, value.y); + _simulation = Simulation(value.x, value.y); } num _updateLag = 0.0; @@ -48,14 +48,14 @@ class Engine { /// be used if no canvas was defined at engine creation and it should be /// rendered later. html.CanvasElement canvas; - Simulation _grid; + Simulation _simulation; bool running = false; Engine([x = 100, y = 100, this.canvas]) { - _grid = Simulation(x, y); + _simulation = Simulation(x, y); _elapsed.start(); - _grid.addPattern(amount: 15, dispersal: 5); + _simulation.addRandomPattern(amount: 15, dispersal: 5); html.window.animationFrame.then(animFrame); } @@ -65,12 +65,12 @@ class Engine { } void reset() { - _grid.reset(); + _simulation.reset(); running = false; } void clear() { - _grid = new Simulation(gridSize.x, gridSize.y); + _simulation = new Simulation(gridSize.x, gridSize.y); running = false; } @@ -101,8 +101,10 @@ class Engine { /// directly, since it is automatically taken care of by the processing function. /// If simulation should be advanced manually one time, prefer using step(). void update() { - // TODO: create hasUpdated/hasAdvanced method in simulation to abstract actual updating away - if (_grid.update().length == 0) running = false; + Map simulationUpdate = _simulation.update(); + _simulation.mergeStateChanges(simulationUpdate); + + if (simulationUpdate.length == 0) running = false; } /// Advances Logic One Update @@ -111,8 +113,8 @@ class Engine { /// simulation. Does not automatically re-render the new state /// (though this should usually not pose a problem). void step() { + update(); running = false; - _grid.update(); } /// Renders the Current Simulation State @@ -121,26 +123,16 @@ class Engine { /// the internal engine processing. Does not do anything if no canvas is /// defined. void render([num interp]) { - if (canvas != null) _grid.render(canvas, interp); + if (canvas == null) return; + + _simulation.render(canvas, interp); } - void addPattern( - {CellPattern pattern, - int x, - int y, - int amount, - int dispersal, - int seed}) { - _grid.addPattern( - pattern: pattern, - x: x, - y: y, - amount: amount, - dispersal: dispersal, - seed: seed); + void addPattern({int amount, int dispersal}) { + _simulation.addRandomPattern(amount: amount, dispersal: dispersal); } void toggleEdgeRendering() { - _grid.renderEdges = !_grid.renderEdges; + _simulation.renderEdges = !_simulation.renderEdges; } } diff --git a/lib/src/Rule.dart b/lib/src/Rule.dart deleted file mode 100644 index 723b4d0..0000000 --- a/lib/src/Rule.dart +++ /dev/null @@ -1,5 +0,0 @@ -class Rule { - final Function evaluate; - - Rule(this.evaluate); -} diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index 02c3c3a..42df2a4 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -2,21 +2,20 @@ import 'dart:html' as html; import 'dart:math' as 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 { final Grid map; + RuleSet rules = GameOfLife(); bool _dirty = true; bool _renderEdges = true; - int _startingSeed; - int _x; - int _y; int _amount; int _dispersal; - CellPattern _pattern; int get w => map.width; int get h => map.height; @@ -28,71 +27,27 @@ class Simulation { void reset() { map.setAll(0, List.filled(map.length, false)); - if (_startingSeed != null) - addPattern( - pattern: _pattern, - dispersal: _dispersal, - amount: _amount, - seed: _startingSeed, - x: _x, - y: _y); _dirty = true; } - void addPattern( - {CellPattern pattern, - int x, - int y, - int amount, - int dispersal, - int seed}) { - _startingSeed = seed ?? DateTime.now().millisecondsSinceEpoch; + void addRandomPattern({int amount, int dispersal}) { + int _startingSeed = DateTime.now().millisecondsSinceEpoch; math.Random rng = new math.Random(_startingSeed); - _x = x; - _y = y; _amount = amount ?? rng.nextInt(20); _dispersal = dispersal ?? 10; - _pattern = pattern; - int cx = x ?? rng.nextInt(map.width ~/ 3) + (map.width ~/ 3); - int cy = y ?? rng.nextInt(map.height ~/ 3) + (map.height ~/ 3); - switch (pattern) { - // Two blocks, offset - // ## - // ## - case CellPattern.Blinker: - setCellState(cx, cy, true); - setCellState(cx + 1, cy, true); - setCellState(cx, cy + 1, true); - setCellState(cx + 1, cy + 1, true); + int cx = rng.nextInt(map.width ~/ 3) + (map.width ~/ 3); + int cy = rng.nextInt(map.height ~/ 3) + (map.height ~/ 3); - setCellState(cx + 2, cy + 2, true); - setCellState(cx + 3, cy + 2, true); - setCellState(cx + 2, cy + 3, true); - setCellState(cx + 3, cy + 3, true); - break; - // A 'gliding' Spaceship - // # - // # - // ### - case CellPattern.SpaceShip: - setCellState(1 + cx, 0 + cy, true); - setCellState(2 + cx, 1 + cy, true); - setCellState(2 + cx, 2 + cy, true); - setCellState(1 + cx, 2 + cy, true); - setCellState(0 + cx, 2 + cy, true); - break; - default: - 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; - } - break; + 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; } @@ -118,29 +73,31 @@ class Simulation { Map update() { Map stateChanges = calculateNextState(map); - - stateChanges.forEach((i, el) => map[i] = el); - stateChanges.length != 0 ? _dirty = true : false; 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 = getSurroundingNeighbors(p.x, p.y, 1); - if (cell == false && neighbors == 3) { + int neighbors = getNeighbors(p.x, p.y, rules.range); + if (cell == false && rules.checkBirth(neighbors) == true) { stateChanges[i] = true; - } else if (cell == true && neighbors != 2 && neighbors != 3) { + } else if (cell == true && rules.checkSurvival(neighbors) == false) { stateChanges[i] = false; } } return stateChanges; } - int getSurroundingNeighbors(int x, int y, int range) { + 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++) { diff --git a/lib/src/rules/CellPattern.dart b/lib/src/rules/CellPattern.dart new file mode 100644 index 0000000..9798948 --- /dev/null +++ b/lib/src/rules/CellPattern.dart @@ -0,0 +1,10 @@ +import 'package:collection/collection.dart'; + +class CellPattern extends DelegatingList { + final String _name; + CellPattern(String name, List base) + : _name = name, + super(base); + + String get name => _name; +} diff --git a/lib/src/rules/GameOfLife.dart b/lib/src/rules/GameOfLife.dart new file mode 100644 index 0000000..edb2dde --- /dev/null +++ b/lib/src/rules/GameOfLife.dart @@ -0,0 +1,38 @@ +import 'dart:math'; + +import 'package:rules_of_living/src/rules/RuleSet.dart'; +import 'package:rules_of_living/src/rules/CellPattern.dart'; + +class GameOfLife implements RuleSet { + int range = 1; + List patterns = [ + // Two blocks, offset + // ## + // ## + CellPattern("Blinker", [ + Point(0, 0), + Point(1, 0), + Point(0, 1), + Point(1, 1), + Point(2, 2), + Point(3, 2), + Point(2, 3), + Point(3, 3) + ]), + // A 'gliding' Spaceship + // # + // # + // ### + CellPattern("SpaceShip", [ + Point(1, 0), + Point(2, 1), + Point(2, 2), + Point(1, 2), + Point(0, 2), + ]) + ]; + + bool checkSurvival(int neighbors) => + neighbors == 2 || neighbors == 3 ? true : false; + bool checkBirth(int neighbors) => neighbors == 3 ? true : false; +} diff --git a/lib/src/rules/RuleSet.dart b/lib/src/rules/RuleSet.dart new file mode 100644 index 0000000..03aeb36 --- /dev/null +++ b/lib/src/rules/RuleSet.dart @@ -0,0 +1,9 @@ +import 'package:rules_of_living/src/rules/CellPattern.dart'; + +abstract class RuleSet { + int range; + List patterns; + + bool checkSurvival(int neighbors); + bool checkBirth(int neighbors); +}