diff --git a/analysis_options.yaml b/analysis_options.yaml
index 78fb282..caef4c8 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,6 +1,6 @@
analyzer:
exclude: [build/**]
-
+ strong-mode: true
errors:
uri_has_not_been_generated: ignore
plugins:
diff --git a/lib/app_component.dart b/lib/app_component.dart
index 1656739..ae8a931 100644
--- a/lib/app_component.dart
+++ b/lib/app_component.dart
@@ -4,8 +4,9 @@ import 'package:rules_of_living/components/configuration_component.dart';
import 'package:rules_of_living/components/controls_component.dart';
import 'package:rules_of_living/components/header_component.dart';
import 'package:rules_of_living/components/simulation_component.dart';
+import 'package:rules_of_living/service/configuration_service.dart';
+import 'package:rules_of_living/service/control_service.dart';
import 'package:rules_of_living/service/engine_service.dart';
-import 'package:rules_of_living/service/simulation_service.dart';
@Component(
selector: 'my-app',
@@ -23,7 +24,8 @@ import 'package:rules_of_living/service/simulation_service.dart';
providers: [
materialProviders,
ClassProvider(EngineService),
- ClassProvider(SimulationService)
+ ClassProvider(ConfigurationService),
+ ClassProvider(ControlService)
],
styleUrls: const [
'package:angular_components/app_layout/layout.scss.css',
diff --git a/lib/components/configuration_component.dart b/lib/components/configuration_component.dart
index abbd2e5..cc1ecd1 100644
--- a/lib/components/configuration_component.dart
+++ b/lib/components/configuration_component.dart
@@ -1,5 +1,3 @@
-import 'dart:math';
-
import 'package:angular/angular.dart';
import 'package:angular_components/material_button/material_button.dart';
import 'package:angular_components/material_icon/material_icon.dart';
@@ -7,8 +5,7 @@ import 'package:angular_components/material_input/material_input.dart';
import 'package:angular_components/material_input/material_number_accessor.dart';
import 'package:angular_components/material_slider/material_slider.dart';
import 'package:angular_components/material_tooltip/material_tooltip.dart';
-import 'package:rules_of_living/service/engine_service.dart';
-import 'package:rules_of_living/service/simulation_service.dart';
+import 'package:rules_of_living/service/configuration_service.dart';
@Component(
selector: "configuration",
@@ -26,29 +23,28 @@ import 'package:rules_of_living/service/simulation_service.dart';
NgModel
])
class ConfigurationComponent {
- final EngineService engine;
- final SimulationService sim;
+ final ConfigurationService config;
- int get width => sim.gridSize.x;
+ int get width => config.gridSize.x;
void set width(num value) {
if (value == null || value <= 0) return;
- sim.gridSize = Point(value, sim.gridSize.y);
+ config.setGridSize(x: value.toInt());
}
- int get height => sim.gridSize.y;
+ int get height => config.gridSize.y;
void set height(num value) {
if (value == null || value <= 0) return;
- sim.gridSize = Point(sim.gridSize.x, value);
+ config.setGridSize(y: value.toInt());
}
- int get simSpeed => engine.simSpeed;
- void set simSpeed(int value) => engine.simSpeed = value;
+ int get simSpeed => config.simSpeed;
+ void set simSpeed(int value) => config.simSpeed = value;
String get speedSliderTooltip => "Simulation Speed: $simSpeed";
- ConfigurationComponent(this.engine, this.sim);
+ ConfigurationComponent(this.config);
void onEdgesClicked() {
- sim.toggleGrid();
+ config.toggleGrid();
}
}
diff --git a/lib/components/controls_component.dart b/lib/components/controls_component.dart
index 538bc27..954d94b 100644
--- a/lib/components/controls_component.dart
+++ b/lib/components/controls_component.dart
@@ -1,7 +1,6 @@
import 'package:angular/angular.dart';
import 'package:angular_components/angular_components.dart';
-import 'package:rules_of_living/service/engine_service.dart';
-import 'package:rules_of_living/service/simulation_service.dart';
+import 'package:rules_of_living/service/control_service.dart';
@Component(
selector: 'sim-controls',
@@ -16,33 +15,27 @@ import 'package:rules_of_living/service/simulation_service.dart';
styleUrls: const ["controls_component.css"],
)
class ControlsComponent {
- final EngineService engine;
- final SimulationService sim;
+ final ControlService ctrl;
- ControlsComponent(this.engine, this.sim);
+ ControlsComponent(this.ctrl);
void onStartClicked() {
- engine.toggleRunning();
+ ctrl.toggleRunning();
}
void onStepClicked() {
- engine.step();
+ ctrl.step();
}
- void onSaveClicked() {
- sim.save();
- }
-
- void onLoadClicked() {
- sim.load();
+ void onResetClicked() {
+ ctrl.reset();
}
void onRandomClicked() {
- sim.addRandomPattern();
- engine.stop();
+ ctrl.addRandomPattern();
}
void onClearClicked() {
- sim.reset();
+ ctrl.clear();
}
}
diff --git a/lib/components/controls_component.html b/lib/components/controls_component.html
index 764ca37..b4c43f3 100644
--- a/lib/components/controls_component.html
+++ b/lib/components/controls_component.html
@@ -1,8 +1,7 @@
-
-
+
-
+
diff --git a/lib/components/simulation_component.dart b/lib/components/simulation_component.dart
index 1fcef71..312eef4 100644
--- a/lib/components/simulation_component.dart
+++ b/lib/components/simulation_component.dart
@@ -1,8 +1,7 @@
import 'dart:html' as html;
import 'package:angular/angular.dart';
-import 'package:rules_of_living/service/engine_service.dart';
-import 'package:rules_of_living/service/simulation_service.dart';
+import 'package:rules_of_living/service/configuration_service.dart';
@Component(
selector: 'gol-simulation',
@@ -11,10 +10,9 @@ import 'package:rules_of_living/service/simulation_service.dart';
providers: [],
)
class SimulationComponent implements OnInit {
- final EngineService engine;
- final SimulationService sim;
+ final ConfigurationService config;
- SimulationComponent(this.engine, this.sim);
+ SimulationComponent(this.config);
@override
void ngOnInit() {
@@ -31,6 +29,6 @@ class SimulationComponent implements OnInit {
the canvas did not load correctly :(
''', canvas.width / 2 - 50, canvas.height / 2);
- sim.canvas = canvas;
+ config.canvas = canvas;
}
}
diff --git a/lib/service/configuration_service.dart b/lib/service/configuration_service.dart
new file mode 100644
index 0000000..e11a5e8
--- /dev/null
+++ b/lib/service/configuration_service.dart
@@ -0,0 +1,43 @@
+import 'dart:html' as html;
+import 'dart:math';
+
+import 'package:rules_of_living/service/engine_service.dart';
+
+class ConfigurationService {
+ final EngineService _es;
+
+ bool showGrid;
+
+ int _simSpeed;
+
+ ConfigurationService(this._es) {
+ showGrid = false;
+ simSpeed = 5;
+ }
+
+ /// Simulation Speed
+ ///
+ /// Sets the number of updates the simulation takes per second. Can range from
+ /// 1 to arbitrarily high numbers (though setting it too high can potentially
+ /// make the app brittle).
+ int get simSpeed => _simSpeed;
+ void set simSpeed(int val) {
+ _simSpeed = val;
+ _es.engine.stepsPerSecond = simSpeed;
+ }
+
+ void set canvas(html.CanvasElement canvas) => _es.engine.canvas = canvas;
+ html.CanvasElement get canvas => _es.engine.canvas;
+
+ void toggleGrid() {
+ showGrid = !showGrid;
+ }
+
+ void setGridSize({int x, int y}) {
+ x = x ?? _es.engine.gridSize.x;
+ y = y ?? _es.engine.gridSize.y;
+ _es.engine.gridSize = Point(x, y);
+ }
+
+ Point get gridSize => _es.engine.gridSize;
+}
diff --git a/lib/service/control_service.dart b/lib/service/control_service.dart
new file mode 100644
index 0000000..0066106
--- /dev/null
+++ b/lib/service/control_service.dart
@@ -0,0 +1,38 @@
+import 'package:rules_of_living/service/engine_service.dart';
+
+class ControlService {
+ EngineService _es;
+
+ ControlService(this._es);
+
+ void run() {
+ _es.engine.running = true;
+ }
+
+ void stop() {
+ _es.engine.running = false;
+ }
+
+ void toggleRunning() {
+ _es.engine.running = !_es.engine.running;
+ }
+
+ void step() {
+ _es.engine.step();
+ }
+
+ void reset() {
+ _es.engine.reset();
+ }
+
+ void addRandomPattern() {
+ _es.engine.running = false;
+ _es.engine.addPattern();
+ }
+
+ void clear() {
+ _es.engine.clear();
+ }
+
+ bool get isRunning => _es.engine.running;
+}
diff --git a/lib/service/engine_service.dart b/lib/service/engine_service.dart
index b276d28..8168735 100644
--- a/lib/service/engine_service.dart
+++ b/lib/service/engine_service.dart
@@ -1,13 +1,8 @@
import 'package:rules_of_living/src/Engine.dart';
-import 'package:rules_of_living/src/Simulation.dart';
class EngineService {
Engine _uncachedEngineAccess;
- EngineService() {
- simSpeed = 5;
- }
-
Engine get engine => _uncachedEngineAccess ?? _setCachedAndReturn(Engine());
void set engine(Engine newEngine) {
_uncachedEngineAccess = newEngine;
@@ -17,32 +12,4 @@ class EngineService {
engine = newEngine;
return newEngine;
}
-
- void run() {
- engine.running = true;
- }
-
- void stop() {
- engine.running = false;
- }
-
- void toggleRunning() {
- engine.running = !engine.running;
- }
-
- void step() {
- engine.step();
- }
-
- /// Simulation Speed
- ///
- /// Sets the number of updates the simulation takes per second. Can range from
- /// 1 to arbitrarily high numbers (though setting it too high can potentially
- /// make the app brittle).
- int get simSpeed => engine.stepsPerSecond;
- void set simSpeed(int val) => engine.stepsPerSecond = val;
-
- bool get isRunning => engine.running;
-
- void set simulation(Simulation value) => engine.simulation = value;
}
diff --git a/lib/service/simulation_service.dart b/lib/service/simulation_service.dart
deleted file mode 100644
index 8e0c14f..0000000
--- a/lib/service/simulation_service.dart
+++ /dev/null
@@ -1,43 +0,0 @@
-import 'dart:html' as html;
-import 'dart:math';
-
-import 'package:rules_of_living/service/engine_service.dart';
-import 'package:rules_of_living/src/Simulation.dart';
-
-class SimulationService {
- // DEFAULT VALUES
- static final int DEFAULT_GRID_SIZE = 50;
-
- final EngineService _engine;
- final Simulation _sim;
-
- SimulationService(this._engine, [Simulation sim])
- : this._sim = sim ?? Simulation(DEFAULT_GRID_SIZE, DEFAULT_GRID_SIZE) {
- _engine.simulation = _sim;
- _sim.addRandomPattern(amount: 15, dispersal: 5);
- }
-
- void reset() {
- _sim.reset();
- }
-
- void addRandomPattern() {
- _sim.addRandomPattern();
- }
-
- Point get gridSize => _sim.gridSize;
- void set gridSize(Point size) {
- _sim.gridSize = size;
- }
-
- //TODO split into RenderService when rendering is decoupled from engine.
- html.CanvasElement get canvas => _engine.engine.canvas;
- void set canvas(html.CanvasElement canvas) => _engine.engine.canvas = canvas;
-
- void toggleGrid() {
- _sim.renderEdges = !_sim.renderEdges;
- }
-
- void save() => _sim.saveSnapshot();
- void load() => _sim.loadSnapshot();
-}
diff --git a/lib/src/Cell.dart b/lib/src/Cell.dart
new file mode 100644
index 0000000..b10142b
--- /dev/null
+++ b/lib/src/Cell.dart
@@ -0,0 +1,36 @@
+import 'package:rules_of_living/src/Rule.dart';
+
+class Cell {
+ bool state;
+ bool nextState = false;
+ List surviveRules = new List();
+ List birthRules = new List();
+
+ /// For determining if render updates are necessary in [Grid].render() function
+ bool dirty = false;
+
+ Cell([bool state = false]) : this.state = state;
+
+ // Sets the actual state to what it should be next update
+ // Returns the newly assumed actual state of the cell.
+ bool advanceState() {
+ this.state = this.nextState;
+ this.nextState = false;
+
+ this.dirty = true;
+
+ return this.state;
+ }
+
+ void update(int neighbors) {
+ if (state == true) {
+ surviveRules.forEach((Rule rule) {
+ if (rule.evaluate(neighbors) == true) this.nextState = true;
+ });
+ } else {
+ birthRules.forEach((Rule rule) {
+ if (rule.evaluate(neighbors) == true) this.nextState = true;
+ });
+ }
+ }
+}
diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart
index 5653bb9..17f474a 100644
--- a/lib/src/Engine.dart
+++ b/lib/src/Engine.dart
@@ -1,5 +1,7 @@
import 'dart:html' as html;
+import 'dart:math';
+import 'package:rules_of_living/src/Render.dart';
import 'package:rules_of_living/src/Simulation.dart';
class Engine {
@@ -28,67 +30,104 @@ class Engine {
// ms stuck in updateloop after which game will declare itself unresponsive
final int SAFETY_TIMEOUT = 2000;
+ bool running = false;
+ Render _render;
+
+ /// Grid Size
+ ///
+ /// Number of cells on x coordinate and y coordinate. Can be set individually.
+ 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");
+ _simulation = Simulation(value.x, value.y);
+ }
+
num _updateLag = 0.0;
num _drawLag = 0.0;
+ Simulation __simulation;
+ Simulation get _simulation => __simulation;
+ void set _simulation(Simulation value) {
+ __simulation = value;
+ _resetRenderer();
+ }
+
/// The Canvas to Paint on
///
/// Manually define or change the canvas the engine should render to. Should
/// be used if no canvas was defined at engine creation and it should be
/// rendered later.
- html.CanvasElement canvas;
- Simulation _simulation;
- bool running = false;
+ html.CanvasElement _canvas;
+ html.CanvasElement get canvas => _canvas;
+ void set canvas(html.CanvasElement canvas) {
+ _canvas = canvas;
+ _resetRenderer();
+ }
+
+ void _resetRenderer() {
+ if (this.canvas == null || _simulation == null) return;
+ _render = Render(canvas, gridSize);
+ }
+
+ Engine([x = 100, y = 100, canvas]) {
+ this.canvas = canvas;
+ _simulation = Simulation(x, y);
- Engine() {
_elapsed.start();
+ _simulation.addPattern(amount: 15, dispersal: 5);
html.window.animationFrame.then(animFrame);
}
void animFrame(num now) {
- int elapsed = _elapsed.elapsedMilliseconds;
- _elapsed.reset();
- process(elapsed, SAFETY_TIMEOUT, update: this.update, render: this.render);
+ process(now);
html.window.animationFrame.then(animFrame);
}
- void process(int elapsed, int timeOut, {Function update, Function render}) {
- _drawLag += elapsed;
- _updateLag += elapsed;
+ void reset() {
+ _simulation.reset();
+ running = false;
+ }
- while (running == true &&
- _shouldUpdate(_updateLag, elapsed, timeOut) == true) {
+ void clear() {
+ _simulation = new Simulation(gridSize.x, gridSize.y);
+ running = false;
+ }
+
+ void process(num now) {
+ _drawLag += _elapsed.elapsedMilliseconds;
+ _updateLag += _elapsed.elapsedMilliseconds;
+ _elapsed.reset();
+
+ while (_updateLag >= _MS_PER_STEP) {
+ if (_elapsed.elapsedMilliseconds > SAFETY_TIMEOUT) {
+ // TODO stub - give warning etc when this occurs
+ print("ERROR STUCK IN UPDATE LOOP");
+ break;
+ }
+ if (running == true) update();
_updateLag -= _MS_PER_STEP;
- if (update == null) break;
- update();
}
if (_drawLag >= _MS_PER_FRAME) {
- _drawLag = 0;
- if (render == null) return;
render(_updateLag / _MS_PER_STEP);
+ _drawLag = 0;
}
}
- bool _shouldUpdate(int updateLag, int elapsed, int timeOut) {
- if (updateLag < _MS_PER_STEP) return false;
- if (elapsed > timeOut) throw StackOverflowError;
-
- return true;
- }
-
/// Update Engine Logic
///
/// Updates the logic of the engine by one tick. Should usually not be called
/// 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() {
- if (_simulation == null) return;
-
- Map simulationUpdate = _simulation.update();
- _simulation.mergeStateChanges(simulationUpdate);
-
- if (simulationUpdate.length == 0) running = false;
+ Map stateChanges = _simulation.update();
+ if (stateChanges.length == 0) {
+ running = false;
+ return;
+ } else {
+ _render.mergeChanges(stateChanges);
+ }
}
/// Advances Logic One Update
@@ -97,8 +136,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;
+ _simulation.update();
}
/// Renders the Current Simulation State
@@ -106,11 +145,25 @@ class Engine {
/// Renders the simulation once. Will usually automatically be called by
/// the internal engine processing. Does not do anything if no canvas is
/// defined.
- void render([num interp]) {
- if (canvas == null || _simulation == null) return;
+ void render([num interp]) {}
- _simulation.render(canvas, interp);
+ void addPattern(
+ {CellPattern pattern,
+ int x,
+ int y,
+ int amount,
+ int dispersal,
+ int seed}) {
+ _simulation.addPattern(
+ pattern: pattern,
+ x: x,
+ y: y,
+ amount: amount,
+ dispersal: dispersal,
+ seed: seed);
}
- void set simulation(Simulation value) => _simulation = value;
+ void toggleEdgeRendering() {
+// _grid.renderEdges = !_grid.renderEdges;
+ }
}
diff --git a/lib/src/Render.dart b/lib/src/Render.dart
new file mode 100644
index 0000000..7eb1a04
--- /dev/null
+++ b/lib/src/Render.dart
@@ -0,0 +1,121 @@
+import 'dart:html' as html;
+import 'dart:math';
+import 'package:stagexl/stagexl.dart' as sxl;
+
+class ColorScheme {
+ //TODO make iterable (perhaps through backing list which gets accesses by the variables)
+ final int ON;
+ final int EXPANSION;
+ final int CORE;
+ final int OFF;
+
+ const ColorScheme(this.ON, this.EXPANSION, this.CORE,
+ [this.OFF = sxl.Color.Transparent]);
+}
+
+class Render {
+ final sxl.Stage _stage;
+ final sxl.RenderLoop _renderLoop = sxl.RenderLoop();
+ Map _colorMap;
+ final sxl.BitmapContainer _renderContainer = sxl.BitmapContainer();
+
+ bool transparentBG = false;
+ bool _renderEdges = false;
+ bool get renderEdges => _renderEdges;
+ void set renderEdges(bool value) {
+ _renderEdges = value;
+ _colorMap = _getColorMap(_cellSize, _colorScheme);
+ }
+
+ Point _gridSize;
+ Point get _cellSize => Point(
+ _stage.stageWidth ~/ _gridSize.x, _stage.stageHeight ~/ _gridSize.y);
+
+ final ColorScheme _colorScheme;
+ //TODO replace with scheme data structure to enable different color scheme injection
+ static const defaultScheme =
+ ColorScheme(sxl.Color.Blue, 1, 1, sxl.Color.Black);
+
+ // TODO gridSize rendering can only be scaled uniformly currently - switch to Point(w,h)?
+ Render(html.CanvasElement canvas, Point gridSize,
+ [ColorScheme colorScheme = defaultScheme])
+ : _stage = _createStage(canvas),
+ _colorScheme = colorScheme,
+ _gridSize = gridSize {
+ _colorMap = _getColorMap(_cellSize, colorScheme);
+ _initRenderGrid(_cellSize, gridSize).forEach((sxl.Bitmap bm) {
+ _renderContainer.addChild(bm);
+ });
+ _renderLoop.addStage(_stage);
+ _stage.addChild(_renderContainer);
+ setDirty();
+ }
+
+ static sxl.Stage _createStage(html.CanvasElement canvas) {
+ sxl.StageXL.stageOptions.renderEngine = sxl.RenderEngine.WebGL;
+ sxl.StageXL.stageOptions.stageRenderMode = sxl.StageRenderMode.AUTO_INVALID;
+// sxl.StageXL.stageOptions.backgroundColor = sxl.Color.Yellow;
+ return sxl.Stage(canvas);
+ }
+
+ Map _getColorMap(Point size, ColorScheme scheme) {
+ print("${size.toString()}, ${scheme.toString()}");
+
+ // Creates a shape with color of each quad in scheme
+ sxl.Shape shape = sxl.Shape();
+ // ON
+ shape.graphics.beginPath();
+ shape.graphics.rect(0 * size.x, 0, size.x, size.y);
+ shape.graphics.fillColor(scheme.ON ?? sxl.Color.Transparent);
+ shape.graphics
+ .strokeColor(renderEdges ? sxl.Color.DarkGray : sxl.Color.Transparent);
+ shape.graphics.closePath();
+ // OFF
+ shape.graphics.beginPath();
+ shape.graphics.rect(1 * size.x, 0, size.x, size.y);
+ shape.graphics.fillColor(scheme.OFF ?? sxl.Color.Transparent);
+ shape.graphics
+ .strokeColor(renderEdges ? sxl.Color.DarkGray : sxl.Color.Transparent);
+ shape.graphics.closePath();
+
+ // creates one texture out of shape
+ sxl.BitmapData texture = sxl.BitmapData(2 * size.x, size.y);
+ texture.draw(shape);
+ // re-slice texture into individual BitmapDatas
+ Map colorMap = Map();
+ List colorBitmaps = texture.sliceIntoFrames(size.x, size.y);
+ print("Found ${colorBitmaps.length} colors.");
+ // TODO more elegant way through iterables etc; also include EXPANSION, CORE functionality
+ colorMap[scheme.ON] = colorBitmaps[0];
+ colorMap[scheme.OFF] = colorBitmaps[1];
+
+ return colorMap;
+ }
+
+ List _initRenderGrid(Point cellSize, Point gridSize) {
+ List grid = List();
+ for (int y = 0; y < gridSize.y; y++) {
+ for (int x = 0; x < gridSize.x; x++) {
+ sxl.Bitmap bm = sxl.Bitmap();
+
+ bm.bitmapData = _colorMap[_colorScheme.OFF];
+ bm.x = x * cellSize.x;
+ bm.y = y * cellSize.y;
+ grid.add(bm);
+ }
+ }
+
+ return grid;
+ }
+
+ void setDirty() => _stage.invalidate();
+
+ void mergeChanges(Map stateChanges) {
+ if (stateChanges.length == 0) return;
+ stateChanges.forEach((int i, bool state) {
+ _renderContainer.getChildAt(i).bitmapData =
+ (state ? _colorMap[_colorScheme.ON] : _colorMap[_colorScheme.OFF]);
+ });
+ setDirty();
+ }
+}
diff --git a/lib/src/Rule.dart b/lib/src/Rule.dart
new file mode 100644
index 0000000..723b4d0
--- /dev/null
+++ b/lib/src/Rule.dart
@@ -0,0 +1,5 @@
+class Rule {
+ final Function evaluate;
+
+ Rule(this.evaluate);
+}
diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart
index 0955c9a..a10c16f 100644
--- a/lib/src/Simulation.dart
+++ b/lib/src/Simulation.dart
@@ -1,116 +1,148 @@
import 'dart:html' as html;
import 'dart:math' as math;
-import 'dart:math';
+import 'package:rules_of_living/src/Cell.dart';
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';
+import 'package:rules_of_living/src/Rule.dart';
enum CellPattern { SpaceShip, Blinker }
class Simulation {
- Grid map;
- Grid _snapshot;
-
- RuleSet rules = GameOfLife();
+ final Grid map;
bool _dirty = true;
- bool get dirty => _dirty;
-
bool _renderEdges = true;
- bool get renderEdges => _renderEdges;
+ int _startingSeed;
+ int _x;
+ int _y;
int _amount;
int _dispersal;
+ CellPattern _pattern;
- 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);
- }
+ int get w => map.width;
+ int get h => map.height;
Simulation(int w, int h) : this.map = new Grid(w, h) {
- this.map = reset();
+ for (int i = 0; i < map.length; i++) {
+ map[i] = _getGOLCell();
+ }
+ print("Grid creation finished");
}
- Simulation.fromGrid(Grid map) : this.map = map;
+ Cell _getGOLCell([bool defaultState = false]) {
+ Cell cell = Cell(defaultState);
+ Rule threeTrue = new Rule((int n) {
+ if (n == 3) return true;
+ return false;
+ });
+ Rule twoTrue = new Rule((int n) {
+ if (n == 2) return true;
+ return false;
+ });
+ cell.surviveRules.add(twoTrue);
+ cell.surviveRules.add(threeTrue);
+ cell.birthRules.add(threeTrue);
+ return cell;
+ }
- Grid reset([Grid map]) {
- map ??= this.map;
+ void reset() {
+ map.setAll(0, List.filled(map.length, _getGOLCell()));
+ if (_startingSeed != null)
+ addPattern(
+ pattern: _pattern,
+ dispersal: _dispersal,
+ amount: _amount,
+ seed: _startingSeed,
+ x: _x,
+ y: _y);
_dirty = true;
- map.setAll(0, List.filled(map.length, false));
- return map;
}
- void addRandomPattern({int amount, int dispersal}) {
- int _startingSeed = DateTime.now().millisecondsSinceEpoch;
+ void addPattern(
+ {CellPattern pattern,
+ int x,
+ int y,
+ int amount,
+ int dispersal,
+ int seed}) {
+ _startingSeed = seed ?? DateTime.now().millisecondsSinceEpoch;
math.Random rng = new math.Random(_startingSeed);
+ _x = x;
+ _y = y;
_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);
+ _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 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;
+ 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;
}
-
_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);
+ if (y < map.height && x < map.width) map.get(x, y).state = state;
}
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);
+ if (y < map.height && x < map.width) return map.get(x, y).state;
+ return null;
}
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;
- }
+ map[i].update(getSurroundingNeighbors(p.x, p.y, 1));
}
+ // TODO when implementing changeSet we can remove this second loop and add to changeSet in the first
+ for (int i = 0; i < map.length; i++) {
+ Cell el = map[i];
+ if (el.state != el.nextState) stateChanges[i] = el.nextState;
+ el.advanceState();
+ }
+ (stateChanges.length > 0) ? _dirty = true : false;
return stateChanges;
}
- int getNeighbors(int x, int y, int range) {
+ int getSurroundingNeighbors(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++) {
@@ -118,7 +150,7 @@ class Simulation {
iy >= 0 &&
ix < map.width &&
iy < map.height &&
- getCellState(ix, iy) == true &&
+ map.get(ix, iy).state == true &&
!(x == ix && y == iy)) count++;
}
}
@@ -140,7 +172,7 @@ class Simulation {
ctx.strokeRect(p.x * brickW, p.y * brickH, brickW, brickH);
}
- if (map[i] == true)
+ if (map[i].state == true)
ctx.setFillColorRgb(155, 155, 255);
else
ctx.setFillColorRgb(0, 0, 0);
@@ -154,10 +186,5 @@ class Simulation {
_dirty = true;
}
- void saveSnapshot() => _snapshot = Grid.from(map);
- Grid loadSnapshot() {
- map = Grid.from(_snapshot);
- _dirty = true;
- return map;
- }
+ bool get renderEdges => _renderEdges;
}
diff --git a/lib/src/rules/CellPattern.dart b/lib/src/rules/CellPattern.dart
deleted file mode 100644
index ed4cae7..0000000
--- a/lib/src/rules/CellPattern.dart
+++ /dev/null
@@ -1,17 +0,0 @@
-import 'dart:math';
-
-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;
-
- @override
- String toString() {
- return "$name: ${super.toString()}";
- }
-}
diff --git a/lib/src/rules/GameOfLife.dart b/lib/src/rules/GameOfLife.dart
deleted file mode 100644
index edb2dde..0000000
--- a/lib/src/rules/GameOfLife.dart
+++ /dev/null
@@ -1,38 +0,0 @@
-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
deleted file mode 100644
index 03aeb36..0000000
--- a/lib/src/rules/RuleSet.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'package:rules_of_living/src/rules/CellPattern.dart';
-
-abstract class RuleSet {
- int range;
- List patterns;
-
- bool checkSurvival(int neighbors);
- bool checkBirth(int neighbors);
-}
diff --git a/pubspec.yaml b/pubspec.yaml
index a63ea9a..94b94ee 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -10,6 +10,7 @@ environment:
dependencies:
angular: ^5.0.0-beta
angular_components: ^0.9.0-beta
+ stagexl: ^1.4.0+1
dev_dependencies:
angular_test: ^2.0.0-beta
diff --git a/test/service/configuration_service_test.dart b/test/service/configuration_service_test.dart
new file mode 100644
index 0000000..44e23b2
--- /dev/null
+++ b/test/service/configuration_service_test.dart
@@ -0,0 +1,46 @@
+import 'dart:math';
+
+import 'package:mockito/mockito.dart';
+import 'package:rules_of_living/service/configuration_service.dart';
+import 'package:rules_of_living/service/engine_service.dart';
+import 'package:rules_of_living/src/Engine.dart';
+@TestOn('browser')
+import 'package:test/test.dart';
+
+class MockEngine extends Mock implements Engine {}
+
+void main() {
+ ConfigurationService sut;
+ EngineService engineService;
+ MockEngine me;
+ setUp(() {
+ me = MockEngine();
+ engineService = EngineService();
+ engineService.engine = me;
+ sut = ConfigurationService(engineService);
+ });
+
+ group("simulation speed", () {
+ test("speed changes propagate to engine", () {
+ sut.simSpeed = 312;
+ verify(me.stepsPerSecond = 312);
+ });
+ });
+
+ group("grid size", () {
+ test("grid changes are sent to engine", () {
+ sut.setGridSize(x: 512, y: 388);
+ verify(me.gridSize = Point(512, 388));
+ });
+ test("grid can be changed solely on x axis", () {
+ when(me.gridSize).thenReturn(Point(100, 100));
+ sut.setGridSize(x: 555);
+ verify(me.gridSize = Point(555, 100));
+ });
+ test("grid can be changed solely on y axis", () {
+ when(me.gridSize).thenReturn(Point(100, 100));
+ sut.setGridSize(y: 556);
+ verify(me.gridSize = Point(100, 556));
+ });
+ });
+}
diff --git a/test/service/simulation_service_test.dart b/test/service/simulation_service_test.dart
deleted file mode 100644
index 3816099..0000000
--- a/test/service/simulation_service_test.dart
+++ /dev/null
@@ -1,23 +0,0 @@
-import 'package:rules_of_living/service/engine_service.dart';
-import 'package:rules_of_living/service/simulation_service.dart';
-import 'package:rules_of_living/src/Simulation.dart';
-import 'package:test/test.dart';
-import 'package:mockito/mockito.dart';
-
-class MockSimulation extends Mock implements Simulation {}
-
-class MockEngineService extends Mock implements EngineService {}
-
-void main() {
- SimulationService sut;
- MockSimulation mockSim = MockSimulation();
- setUp(() => sut = SimulationService(MockEngineService(), mockSim));
- test("calling save calls through to Simulation.saveSnapshot", () {
- sut.save();
- verify(mockSim.saveSnapshot());
- });
- test("calling load calls through to Simulation.loadSnapshot", () {
- sut.load();
- verify(mockSim.loadSnapshot());
- });
-}
diff --git a/test/simulation_test.dart b/test/simulation_test.dart
deleted file mode 100644
index 7b8bf82..0000000
--- a/test/simulation_test.dart
+++ /dev/null
@@ -1,50 +0,0 @@
-import 'dart:math';
-import 'package:mockito/mockito.dart';
-import 'package:rules_of_living/src/Grid.dart';
-import 'package:test/test.dart';
-
-import 'package:rules_of_living/src/Simulation.dart';
-
-class MockGrid extends Mock implements Grid {}
-
-void main() {
- Simulation sut;
- setUp(() {
- sut = Simulation(10, 10);
- });
- group("gridSize", () {
- test(
- "returns the width and height of the underlying grid",
- () => expect(
- sut.gridSize, equals(Point(sut.map.width, sut.map.height))));
- test("sets the underlying grid width and height", () {
- sut.gridSize = Point(2, 3);
- expect(sut.gridSize, equals(Point(2, 3)));
- });
- test("creates a new underlying grid on resizing", () {
- var oldMap = sut.map;
- sut.gridSize = Point(10, 10);
- expect(sut.map, isNot(oldMap));
- });
- });
- group("reset", () {
- test("returns a map filled with 'false' ", () {
- expect(sut.reset(), allOf(TypeMatcher(), isNot(contains(true))));
- });
- test("sets the simulation to need re-rendering", () {
- sut.reset();
- expect(sut.dirty, true);
- }, skip: "can not find a way to set dirty to true first yet");
- });
- group("save&load", () {
- test(
- "saves a copy of the map which does not change when the actual map changes",
- () {
- sut.saveSnapshot();
- sut.mergeStateChanges({1: true, 2: true});
- var snapshot = Grid.from(sut.map);
-
- expect(sut.loadSnapshot(), isNot(equals(snapshot)));
- });
- });
-}
diff --git a/test/src/engine_test.dart b/test/src/engine_test.dart
index f602069..fcfefc2 100644
--- a/test/src/engine_test.dart
+++ b/test/src/engine_test.dart
@@ -1,106 +1,15 @@
import 'dart:html' as html;
-import 'package:rules_of_living/src/Simulation.dart';
-import 'package:test/test.dart';
-import 'package:mockito/mockito.dart';
+import 'dart:math';
@TestOn('browser')
import 'package:rules_of_living/src/Engine.dart';
-
-class MockSimulation extends Mock implements Simulation {
- int updateNum = 0;
- bool hasChanges = false;
-
- @override
- Map update() {
- updateNum++;
- return hasChanges ? {1: true, 2: false} : {};
- }
-}
+import 'package:test/test.dart';
void main() {
Engine sut;
setUp(() {
sut = Engine();
});
- group("process", () {
- setUp(() => sut.running = true);
- test("errors out if updating takes too long",
- () => expect(() => sut.process(5000, 10), throwsA(StackOverflowError)));
- test("does not update if not enough time elapsed to pass ms per step", () {
- bool result = false;
- sut.stepsPerSecond = 1000;
-
- sut.process(999, 2000,
- update: () => result = true, render: (double interp) => null);
-
- expect(result, true);
- });
- test("updates only when the ms per step threshold is crossed", () {
- int updateNum = 0;
- sut.stepsPerSecond = 1;
-
- sut.process(1001, 2000, update: () => updateNum++);
- expect(updateNum, equals(1));
- });
- test("updates until updateLag has been resolved", () {
- int updateNum = 0;
- sut.stepsPerSecond = 1;
-
- sut.process(2999, 5000, update: () => updateNum++);
- expect(updateNum, equals(2));
- });
- test("works without passing in update or render function arguments", () {
- sut.stepsPerSecond = 1000;
- expect(() => sut.process(500, 5000), isNot(throwsA(anything)));
- });
- });
- group("update", () {
- MockSimulation mockSim;
- setUp(() {
- mockSim = MockSimulation();
- sut.simulation = mockSim;
- });
- test("does not error out if no simulation variable is set", () {
- sut.simulation = null;
- expect(() => sut.update(), isNot(throwsA(anything)));
- });
- test("updates simulation one tick for every time it is called", () {
- sut.update();
- sut.update();
- sut.update();
- expect(mockSim.updateNum, equals(3));
- });
- test("sets running to false when simulation returns no changes", () {
- sut.running = true;
- sut.update();
- expect(sut.running, equals(false));
- });
- test("keeps running when simulation returns changes", () {
- sut.running = true;
- mockSim.hasChanges = true;
-
- sut.update();
-
- expect(sut.running, equals(true));
- });
- });
- group("step", () {
- MockSimulation mockSim;
- setUp(() {
- mockSim = MockSimulation();
- sut.simulation = mockSim;
- });
- test("advances the simulation by one update", () {
- sut.step();
- expect(mockSim.updateNum, equals(1));
- });
- test("turns off continuous engine updates", () {
- sut.running = true;
- sut.step();
-
- expect(sut.running, equals(false));
- });
- });
group("canvas", () {
test("Engine can be instantiated without canvas", () {
expect(sut, isNot(throwsNoSuchMethodError));
@@ -122,4 +31,12 @@ void main() {
expect(sut.canvas, isNotNull);
});
});
+ group("gridSize", () {
+ test("zero gridSizes throw ArgumentErrors", () {
+ expect(() => sut.gridSize = Point(0, 5), throwsArgumentError);
+ });
+ test("negative gridSizes throw ArgumentErrors", () {
+ expect(() => sut.gridSize = Point(1, -5), throwsArgumentError);
+ });
+ });
}
diff --git a/test/src/render_test.dart b/test/src/render_test.dart
new file mode 100644
index 0000000..3c39387
--- /dev/null
+++ b/test/src/render_test.dart
@@ -0,0 +1,15 @@
+import 'dart:html' as html;
+import 'dart:math';
+
+@TestOn('browser')
+import 'package:rules_of_living/src/Render.dart';
+import 'package:test/test.dart';
+
+void main() {
+ Render sut;
+ setUp(() {
+ html.CanvasElement canvas = html.CanvasElement();
+ sut = Render(canvas, 8);
+ });
+ group("colorMap", () {});
+}
diff --git a/test/src/rules/cellpattern_test.dart b/test/src/rules/cellpattern_test.dart
deleted file mode 100644
index a7989f5..0000000
--- a/test/src/rules/cellpattern_test.dart
+++ /dev/null
@@ -1,19 +0,0 @@
-import 'dart:math';
-
-import 'package:rules_of_living/src/rules/CellPattern.dart';
-import 'package:test/test.dart';
-
-void main() {
- CellPattern sut;
- setUp(() {
- sut = CellPattern("testPattern", [Point(1, 1), Point(0, 0), Point(-1, -1)]);
- });
- group("Naming", () {
- test("contains the name passed in for name variable",
- () => expect(sut.name, "testPattern"));
- test(
- "Contains the name passed in on being formatted as String",
- () => expect(sut.toString(),
- "testPattern: [Point(1, 1), Point(0, 0), Point(-1, -1)]"));
- });
-}
diff --git a/test/src/rules/gameoflife_test.dart b/test/src/rules/gameoflife_test.dart
deleted file mode 100644
index 565e4e1..0000000
--- a/test/src/rules/gameoflife_test.dart
+++ /dev/null
@@ -1,27 +0,0 @@
-import 'package:rules_of_living/src/rules/GameOfLife.dart';
-import 'package:test/test.dart';
-
-void main() {
- GameOfLife sut;
- setUp(() {
- sut = GameOfLife();
- });
- group("BirthRules", () {
- test("will return true when being passed three neighbors",
- () => expect(sut.checkBirth(3), true));
- test("will return false when being passed zero neighbors",
- () => expect(sut.checkBirth(0), false));
- test("will return false when being passed two neighbors",
- () => expect(sut.checkBirth(2), false));
- });
- group("SurviveRules", () {
- test("will return true when being passed two neighbors",
- () => expect(sut.checkSurvival(2), true));
- test("will return true when being passed three neighbors",
- () => expect(sut.checkSurvival(3), true));
- test("will return false when being passed 0 neighbors",
- () => expect(sut.checkSurvival(0), false));
- test("will return false when being passed more than 3 neighbors",
- () => expect(sut.checkSurvival(4), false));
- });
-}
|