From 78f63a0ea06985291f93332c1dd7202e1270530a Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 28 Aug 2018 16:02:43 +0200 Subject: [PATCH 01/38] Begin Refactor StageXL into Engine --- lib/src/Engine.dart | 42 ++++++++++--- lib/src/Grid.dart | 49 +-------------- lib/src/Render.dart | 121 ++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 1 + test/src/render_test.dart | 15 +++++ 5 files changed, 173 insertions(+), 55 deletions(-) create mode 100644 lib/src/Render.dart create mode 100644 test/src/render_test.dart diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index b06e53a..1d75066 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -2,6 +2,7 @@ import 'dart:html' as html; import 'dart:math'; import 'package:rules_of_living/src/Grid.dart'; +import 'package:rules_of_living/src/Render.dart'; class Engine { // Elapsed Time Counter - useful for Safety Timeout @@ -29,6 +30,9 @@ 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. @@ -42,17 +46,33 @@ class Engine { num _updateLag = 0.0; num _drawLag = 0.0; + Grid __grid; + Grid get _grid => __grid; + void set _grid(Grid value) { + __grid = 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; - Grid _grid; - bool running = false; + html.CanvasElement _canvas; + html.CanvasElement get canvas => _canvas; + void set canvas(html.CanvasElement canvas) { + _canvas = canvas; + _resetRenderer(); + } - Engine([x = 100, y = 100, this.canvas]) { + void _resetRenderer() { + if (canvas == null || _grid == null) return; + _render = Render(canvas, gridSize); + } + + Engine([x = 100, y = 100, canvas]) { _grid = Grid(x, y); + this.canvas = canvas; _elapsed.start(); _grid.addPattern(amount: 15, dispersal: 5); @@ -101,7 +121,13 @@ 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() { - if (!_grid.update()) running = false; + Map stateChanges = _grid.update(); + if (stateChanges.length == 0) { + running = false; + return; + } else { + _render.mergeChanges(stateChanges); + } } /// Advances Logic One Update @@ -119,9 +145,7 @@ 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) _grid.render(canvas, interp); - } + void render([num interp]) {} void addPattern( {CellPattern pattern, @@ -140,6 +164,6 @@ class Engine { } void toggleEdgeRendering() { - _grid.renderEdges = !_grid.renderEdges; +// _grid.renderEdges = !_grid.renderEdges; } } diff --git a/lib/src/Grid.dart b/lib/src/Grid.dart index d95c25e..edeb1ad 100644 --- a/lib/src/Grid.dart +++ b/lib/src/Grid.dart @@ -1,4 +1,3 @@ -import 'dart:html' as html; import 'dart:math' as math; import 'package:rules_of_living/src/Cell.dart'; @@ -11,9 +10,6 @@ class Grid { final int h; final List> map; - bool _dirty = true; - bool _renderEdges = true; - int _startingSeed; int _x; int _y; @@ -40,7 +36,6 @@ class Grid { seed: _startingSeed, x: _x, y: _y); - _dirty = true; } void addPattern( @@ -97,7 +92,6 @@ class Grid { } break; } - _dirty = true; } void setCellState(int x, int y, bool state) { @@ -148,8 +142,8 @@ class Grid { return grid; } - bool update() { - bool stateChanges = false; + Map update() { + Map stateChanges = Map(); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { // DEFAULTS TO CONWAY GAME OF LIFE RANGE OF ONE @@ -159,10 +153,8 @@ class Grid { for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { Cell c = map[y][x]; - if (c.state != c.nextState) stateChanges = true; + if (c.state != c.nextState) stateChanges[y * w + x] = c.nextState; c.advanceState(); - - if (!_dirty && map[y][x].dirty) _dirty = true; } } return stateChanges; @@ -184,39 +176,4 @@ class Grid { } 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[0].length); - int brickH = (canvas.height ~/ map.length); - ctx.clearRect(0, 0, canvas.width, canvas.height); - - for (int y = 0; y < map.length; y++) { - for (int x = 0; x < map[y].length; x++) { - if (_renderEdges) { - ctx.setStrokeColorRgb(100, 100, 100); - ctx.strokeRect(x * brickW, y * brickH, brickW, brickH); - } - - Cell c = map[y][x]; - if (c.state == true) - ctx.setFillColorRgb(155, 155, 255); - else - ctx.setFillColorRgb(0, 0, 0); - ctx.fillRect(x * brickW, y * brickH, brickW, brickH); - } - } - - _dirty = false; - } - - void set renderEdges(bool on) { - _renderEdges = on; - _dirty = true; - } - - bool get renderEdges => _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/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/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", () {}); +} From 6b0fae44b6c7dd42494bd81ac9fc63d7d789cfc3 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 28 Aug 2018 16:02:43 +0200 Subject: [PATCH 02/38] Begin Refactor StageXL into Engine --- lib/src/Engine.dart | 58 ++++++++++++------ lib/src/Render.dart | 121 ++++++++++++++++++++++++++++++++++++++ lib/src/Simulation.dart | 13 ++-- pubspec.yaml | 1 + test/src/render_test.dart | 15 +++++ 5 files changed, 185 insertions(+), 23 deletions(-) create mode 100644 lib/src/Render.dart create mode 100644 test/src/render_test.dart diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index 84d52e8..17f474a 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -1,6 +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 { @@ -29,33 +30,52 @@ 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(_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; 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 _grid; - bool running = false; + html.CanvasElement _canvas; + html.CanvasElement get canvas => _canvas; + void set canvas(html.CanvasElement canvas) { + _canvas = canvas; + _resetRenderer(); + } - Engine([x = 100, y = 100, this.canvas]) { - _grid = Simulation(x, y); + 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); _elapsed.start(); - _grid.addPattern(amount: 15, dispersal: 5); + _simulation.addPattern(amount: 15, dispersal: 5); html.window.animationFrame.then(animFrame); } @@ -65,12 +85,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,7 +121,13 @@ 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() { - if (!_grid.update()) running = false; + Map stateChanges = _simulation.update(); + if (stateChanges.length == 0) { + running = false; + return; + } else { + _render.mergeChanges(stateChanges); + } } /// Advances Logic One Update @@ -111,7 +137,7 @@ class Engine { /// (though this should usually not pose a problem). void step() { running = false; - _grid.update(); + _simulation.update(); } /// Renders the Current Simulation State @@ -119,9 +145,7 @@ 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) _grid.render(canvas, interp); - } + void render([num interp]) {} void addPattern( {CellPattern pattern, @@ -130,7 +154,7 @@ class Engine { int amount, int dispersal, int seed}) { - _grid.addPattern( + _simulation.addPattern( pattern: pattern, x: x, y: y, @@ -140,6 +164,6 @@ class Engine { } void toggleEdgeRendering() { - _grid.renderEdges = !_grid.renderEdges; +// _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/Simulation.dart b/lib/src/Simulation.dart index ca7bb71..a10c16f 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -125,19 +125,20 @@ class Simulation { return null; } - bool update() { - bool stateChanges = false; + Map update() { + Map stateChanges = Map(); for (int i = 0; i < map.length; i++) { math.Point p = map.toCoordinates(i); 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 - map.forEach((Cell el) { - if (el.state != el.nextState) stateChanges = true; + for (int i = 0; i < map.length; i++) { + Cell el = map[i]; + if (el.state != el.nextState) stateChanges[i] = el.nextState; el.advanceState(); - }); - stateChanges ? _dirty = true : false; + } + (stateChanges.length > 0) ? _dirty = true : false; return stateChanges; } 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/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", () {}); +} From c50e92fb19705ebc4a3fe49df5150c6dd71fcbb3 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 2 Oct 2018 14:55:39 +0200 Subject: [PATCH 03/38] Add simple stateChanges map into Simulation --- lib/src/Engine.dart | 3 ++- lib/src/Simulation.dart | 16 +++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index 84d52e8..9855d14 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -101,7 +101,8 @@ 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() { - if (!_grid.update()) running = false; + // TODO: create hasUpdated/hasAdvanced method in simulation to abstract actual updating away + if (_grid.update().length != 0) running = false; } /// Advances Logic One Update diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index ca7bb71..e451bf0 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -125,19 +125,17 @@ class Simulation { return null; } - bool update() { - bool stateChanges = false; + Map update() { + Map stateChanges = Map(); for (int i = 0; i < map.length; i++) { math.Point p = map.toCoordinates(i); - map[i].update(getSurroundingNeighbors(p.x, p.y, 1)); + Cell el = map[i]; + el.update(getSurroundingNeighbors(p.x, p.y, 1)); + stateChanges[i] = el; } - // TODO when implementing changeSet we can remove this second loop and add to changeSet in the first - map.forEach((Cell el) { - if (el.state != el.nextState) stateChanges = true; - el.advanceState(); - }); - stateChanges ? _dirty = true : false; + stateChanges.forEach((_, el) => el.advanceState()); + stateChanges.length != 0 ? _dirty = true : false; return stateChanges; } From 08155b70a5ab0a7df15dffb999f473f075a5f4e2 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 15 Oct 2018 17:16:09 +0200 Subject: [PATCH 04/38] Fix Pausing after every Update Updates would pause when any change has happened, not when no change has happened. --- lib/src/Engine.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index 9855d14..36ab89a 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -102,7 +102,7 @@ class Engine { /// 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; + if (_grid.update().length == 0) running = false; } /// Advances Logic One Update From 71f4df85af8a63f1bacb08b4c62ecaadaf9c3106 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 15 Oct 2018 17:28:09 +0200 Subject: [PATCH 05/38] Refactor Simulation to be List of dumb cells Cells are now only an empty struct, they carry no information beyond needing to be re-rendered. All Simulation logic is handled in the Simulation Class. --- lib/src/Cell.dart | 30 ------------------------------ lib/src/Simulation.dart | 38 +++++++++++++++++--------------------- 2 files changed, 17 insertions(+), 51 deletions(-) diff --git a/lib/src/Cell.dart b/lib/src/Cell.dart index b10142b..4e12f04 100644 --- a/lib/src/Cell.dart +++ b/lib/src/Cell.dart @@ -1,36 +1,6 @@ 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/Simulation.dart b/lib/src/Simulation.dart index e451bf0..4f6a864 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -3,7 +3,6 @@ import 'dart:math' as math; import 'package:rules_of_living/src/Cell.dart'; import 'package:rules_of_living/src/Grid.dart'; -import 'package:rules_of_living/src/Rule.dart'; enum CellPattern { SpaceShip, Blinker } @@ -31,18 +30,7 @@ class Simulation { } 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); + Cell cell = Cell(); return cell; } @@ -117,12 +105,15 @@ class Simulation { } void setCellState(int x, int y, bool state) { - if (y < map.height && x < map.width) map.get(x, y).state = state; + if (y >= map.height || x >= map.width) return; + + state ? map.set(x, y, Cell()) : map.set(x, y, null); } bool getCellState(int x, int y) { - if (y < map.height && x < map.width) return map.get(x, y).state; - return null; + if (y >= map.height || x >= map.width) return null; + + return map.get(x, y) == null ? false : true; } Map update() { @@ -131,10 +122,15 @@ class Simulation { for (int i = 0; i < map.length; i++) { math.Point p = map.toCoordinates(i); Cell el = map[i]; - el.update(getSurroundingNeighbors(p.x, p.y, 1)); - stateChanges[i] = el; + bool changed = false; + int neighbors = getSurroundingNeighbors(p.x, p.y, 1); + if (el == null && neighbors == 3) { + stateChanges[i] = Cell(); + } else if (el != null && neighbors != 2 && neighbors != 3) { + stateChanges[i] = null; + } } - stateChanges.forEach((_, el) => el.advanceState()); + stateChanges.forEach((i, el) => map[i] = el); stateChanges.length != 0 ? _dirty = true : false; return stateChanges; } @@ -147,7 +143,7 @@ class Simulation { iy >= 0 && ix < map.width && iy < map.height && - map.get(ix, iy).state == true && + map.get(ix, iy) != null && !(x == ix && y == iy)) count++; } } @@ -169,7 +165,7 @@ class Simulation { ctx.strokeRect(p.x * brickW, p.y * brickH, brickW, brickH); } - if (map[i].state == true) + if (map[i] != null) ctx.setFillColorRgb(155, 155, 255); else ctx.setFillColorRgb(0, 0, 0); From 245d9a22c2b844c6d4169ff131cebbdc40913732 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 16 Oct 2018 18:21:01 +0200 Subject: [PATCH 06/38] Remove Cell Data Structure Cells are only boolean values of true or false for now. --- lib/src/Simulation.dart | 59 ++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index 4f6a864..02c3c3a 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -1,13 +1,12 @@ import 'dart:html' as html; import 'dart:math' as math; -import 'package:rules_of_living/src/Cell.dart'; import 'package:rules_of_living/src/Grid.dart'; enum CellPattern { SpaceShip, Blinker } class Simulation { - final Grid map; + final Grid map; bool _dirty = true; bool _renderEdges = true; @@ -23,19 +22,12 @@ class Simulation { int get h => map.height; Simulation(int w, int h) : this.map = new Grid(w, h) { - for (int i = 0; i < map.length; i++) { - map[i] = _getGOLCell(); - } - print("Grid creation finished"); - } - - Cell _getGOLCell([bool defaultState = false]) { - Cell cell = Cell(); - return cell; + reset(); + print("Grid Created"); } void reset() { - map.setAll(0, List.filled(map.length, _getGOLCell())); + map.setAll(0, List.filled(map.length, false)); if (_startingSeed != null) addPattern( pattern: _pattern, @@ -105,33 +97,46 @@ class Simulation { } void setCellState(int x, int y, bool state) { - if (y >= map.height || x >= map.width) return; + if (y >= map.height || x >= map.width) return null; - state ? map.set(x, y, Cell()) : map.set(x, y, 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) == null ? false : true; + return map.get(x, y); } - Map update() { - Map stateChanges = Map(); + 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); + + stateChanges.forEach((i, el) => map[i] = el); + stateChanges.length != 0 ? _dirty = true : false; + return stateChanges; + } + + Map calculateNextState(Grid oldState) { + Map stateChanges = Map(); for (int i = 0; i < map.length; i++) { math.Point p = map.toCoordinates(i); - Cell el = map[i]; - bool changed = false; + bool cell = map[i]; int neighbors = getSurroundingNeighbors(p.x, p.y, 1); - if (el == null && neighbors == 3) { - stateChanges[i] = Cell(); - } else if (el != null && neighbors != 2 && neighbors != 3) { - stateChanges[i] = null; + if (cell == false && neighbors == 3) { + stateChanges[i] = true; + } else if (cell == true && neighbors != 2 && neighbors != 3) { + stateChanges[i] = false; } } - stateChanges.forEach((i, el) => map[i] = el); - stateChanges.length != 0 ? _dirty = true : false; return stateChanges; } @@ -143,7 +148,7 @@ class Simulation { iy >= 0 && ix < map.width && iy < map.height && - map.get(ix, iy) != null && + getCellState(ix, iy) == true && !(x == ix && y == iy)) count++; } } @@ -165,7 +170,7 @@ class Simulation { ctx.strokeRect(p.x * brickW, p.y * brickH, brickW, brickH); } - if (map[i] != null) + if (map[i] == true) ctx.setFillColorRgb(155, 155, 255); else ctx.setFillColorRgb(0, 0, 0); From 27d4879b1bca3f11cbeb16efae3b295b6a9df9ea Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 17 Oct 2018 20:56:30 +0200 Subject: [PATCH 07/38] Extract RuleSet Class from Simulation --- lib/src/RuleSet.dart | 27 ++++++++++++++++++++++++++ lib/src/Simulation.dart | 43 ++++++----------------------------------- 2 files changed, 33 insertions(+), 37 deletions(-) create mode 100644 lib/src/RuleSet.dart diff --git a/lib/src/RuleSet.dart b/lib/src/RuleSet.dart new file mode 100644 index 0000000..edfc5a9 --- /dev/null +++ b/lib/src/RuleSet.dart @@ -0,0 +1,27 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; + +abstract class RuleSet { + int checkRange; + + bool checkSurvival(int neighbors); + bool checkBirth(int neighbors); +} + +class Pattern extends DelegatingList { + final String _name; + Pattern(String name, List base) + : _name = name, + super(base); + + String get name => _name; +} + +class GameOfLife implements RuleSet { + int checkRange = 1; + + bool checkSurvival(int neighbors) => + neighbors == 2 || neighbors == 3 ? true : false; + bool checkBirth(int neighbors) => neighbors == 3 ? true : false; +} diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index 02c3c3a..05db54b 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -2,11 +2,13 @@ import 'dart:html' as html; import 'dart:math' as math; import 'package:rules_of_living/src/Grid.dart'; +import 'package:rules_of_living/src/RuleSet.dart'; enum CellPattern { SpaceShip, Blinker } class Simulation { final Grid map; + RuleSet rules = GameOfLife(); bool _dirty = true; bool _renderEdges = true; @@ -28,14 +30,6 @@ 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; } @@ -56,31 +50,6 @@ class Simulation { 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); - - 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++) { @@ -130,17 +99,17 @@ class Simulation { 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, 1); + 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++) { From 9b2b5f3e55060bd500e1af6eb9faff7492d1272e Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 17 Oct 2018 21:00:14 +0200 Subject: [PATCH 08/38] Remove unnecessary Simulation variables --- lib/src/Simulation.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index 05db54b..bfdfa2c 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -14,11 +14,8 @@ class Simulation { 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; @@ -42,11 +39,8 @@ class Simulation { 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; - _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) { From e6e82f78f24ab7c434e16f4ea66b5438352baced Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 17 Oct 2018 21:07:48 +0200 Subject: [PATCH 09/38] Remove unnecessary Switch Case in Simulation --- lib/src/Simulation.dart | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index bfdfa2c..8342d7c 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -43,19 +43,17 @@ class Simulation { _dispersal = dispersal ?? 10; int cx = x ?? rng.nextInt(map.width ~/ 3) + (map.width ~/ 3); int cy = y ?? rng.nextInt(map.height ~/ 3) + (map.height ~/ 3); - switch (pattern) { - 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; } From 6d7120650fdb2ab2757cb8052bc18cd481cdf3db Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 17 Oct 2018 21:08:55 +0200 Subject: [PATCH 10/38] Add Special Patterns to RuleSet --- lib/src/RuleSet.dart | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/src/RuleSet.dart b/lib/src/RuleSet.dart index edfc5a9..f115699 100644 --- a/lib/src/RuleSet.dart +++ b/lib/src/RuleSet.dart @@ -4,6 +4,7 @@ import 'package:collection/collection.dart'; abstract class RuleSet { int checkRange; + List patterns; bool checkSurvival(int neighbors); bool checkBirth(int neighbors); @@ -20,6 +21,32 @@ class Pattern extends DelegatingList { class GameOfLife implements RuleSet { int checkRange = 1; + List patterns = [ + // Two blocks, offset + // ## + // ## + Pattern("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 + // # + // # + // ### + Pattern("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; From bac65ef1163c6479675e6c2fbf5d1d0ce2e7d194 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 09:57:08 +0200 Subject: [PATCH 11/38] Remove unnecessary pattern parameters --- lib/src/Engine.dart | 16 ++-------------- lib/src/Simulation.dart | 15 ++++----------- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index 36ab89a..73ab7ea 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -124,20 +124,8 @@ class Engine { if (canvas != null) _grid.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}) { + _grid.addPattern(amount: amount, dispersal: dispersal); } void toggleEdgeRendering() { diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index 8342d7c..b0bbb46 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -13,7 +13,6 @@ class Simulation { bool _dirty = true; bool _renderEdges = true; - int _startingSeed; int _amount; int _dispersal; @@ -30,19 +29,13 @@ class Simulation { _dirty = true; } - void addPattern( - {CellPattern pattern, - int x, - int y, - int amount, - int dispersal, - int seed}) { - _startingSeed = seed ?? DateTime.now().millisecondsSinceEpoch; + void addPattern({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 = x ?? rng.nextInt(map.width ~/ 3) + (map.width ~/ 3); - int cy = y ?? rng.nextInt(map.height ~/ 3) + (map.height ~/ 3); + 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++) { From e16085153aa26670d0d8c7e3dab1791352341a12 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 09:59:26 +0200 Subject: [PATCH 12/38] Rename Simulation in Engine Object --- lib/src/Engine.dart | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index 73ab7ea..e00b50f 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.addPattern(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; } @@ -102,7 +102,7 @@ class Engine { /// 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; + if (_simulation.update().length == 0) running = false; } /// Advances Logic One Update @@ -112,7 +112,7 @@ class Engine { /// (though this should usually not pose a problem). void step() { running = false; - _grid.update(); + _simulation.update(); } /// Renders the Current Simulation State @@ -121,14 +121,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({int amount, int dispersal}) { - _grid.addPattern(amount: amount, dispersal: dispersal); + _simulation.addPattern(amount: amount, dispersal: dispersal); } void toggleEdgeRendering() { - _grid.renderEdges = !_grid.renderEdges; + _simulation.renderEdges = !_simulation.renderEdges; } } From de1aa46743709fe45057a8972cfe9ac1f829b22c Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 10:58:06 +0200 Subject: [PATCH 13/38] Separate Simulation calculating updates and merging Simulation updates were one step of calculation and merging the calculations into the map in one function. Separating the two allows checking for a new update without affecting the grid, allows passing the last Update around and allows custom changes to the grid by passing changes to the merge function that were not derived from the update function. --- lib/src/Engine.dart | 5 ++++- lib/src/Simulation.dart | 8 +++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index e00b50f..59bcc66 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -102,7 +102,10 @@ class Engine { /// 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 (_simulation.update().length == 0) running = false; + Map simulationUpdate = _simulation.update(); + _simulation.mergeStateChanges(simulationUpdate); + + if (simulationUpdate.length == 0) running = false; } /// Advances Logic One Update diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index b0bbb46..8cc04ef 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -72,12 +72,14 @@ 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(); From c3244b085e152278c34739fcda864ca6de430e50 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 11:04:23 +0200 Subject: [PATCH 14/38] Fix single steps not updating simulation --- lib/src/Engine.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index 59bcc66..e5190cf 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -101,7 +101,6 @@ 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 Map simulationUpdate = _simulation.update(); _simulation.mergeStateChanges(simulationUpdate); @@ -114,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; - _simulation.update(); } /// Renders the Current Simulation State From 4f63947ab9a999253119a8943651197863d821c9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 11:19:04 +0200 Subject: [PATCH 15/38] Delete unused Cell and Rule Classes They were used under the old system of every gridspace being its own cell data structure with its own rules to observe. Replaced by the RuleSet class. Cell has vanished in favor of simple boolean values filling the grid. --- lib/src/Cell.dart | 6 ------ lib/src/Rule.dart | 5 ----- 2 files changed, 11 deletions(-) delete mode 100644 lib/src/Cell.dart delete mode 100644 lib/src/Rule.dart 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/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); -} From fbdf114fed22f9fe1fe8585d4234a546f25abba1 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 11:21:09 +0200 Subject: [PATCH 16/38] Move RuleSet to its own directory --- lib/src/Simulation.dart | 2 +- lib/src/{ => rules}/RuleSet.dart | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/src/{ => rules}/RuleSet.dart (100%) diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index 8cc04ef..2f167a3 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -2,7 +2,7 @@ import 'dart:html' as html; import 'dart:math' as math; import 'package:rules_of_living/src/Grid.dart'; -import 'package:rules_of_living/src/RuleSet.dart'; +import 'package:rules_of_living/src/rules/RuleSet.dart'; enum CellPattern { SpaceShip, Blinker } diff --git a/lib/src/RuleSet.dart b/lib/src/rules/RuleSet.dart similarity index 100% rename from lib/src/RuleSet.dart rename to lib/src/rules/RuleSet.dart From 0aa3df30b4e0f3cc261247c9c97a7a6275a89c64 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 11:27:44 +0200 Subject: [PATCH 17/38] Extract CellPattern and GameOfLife into own files --- lib/src/Simulation.dart | 1 + lib/src/rules/CellPattern.dart | 10 +++++++ lib/src/rules/GameOfLife.dart | 38 ++++++++++++++++++++++++++ lib/src/rules/RuleSet.dart | 49 ++-------------------------------- 4 files changed, 51 insertions(+), 47 deletions(-) create mode 100644 lib/src/rules/CellPattern.dart create mode 100644 lib/src/rules/GameOfLife.dart diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index 2f167a3..a3f99c5 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -2,6 +2,7 @@ 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 } 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..f812233 --- /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 checkRange = 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 index f115699..ad2bb70 100644 --- a/lib/src/rules/RuleSet.dart +++ b/lib/src/rules/RuleSet.dart @@ -1,54 +1,9 @@ -import 'dart:math'; - -import 'package:collection/collection.dart'; +import 'package:rules_of_living/src/rules/CellPattern.dart'; abstract class RuleSet { int checkRange; - List patterns; + List patterns; bool checkSurvival(int neighbors); bool checkBirth(int neighbors); } - -class Pattern extends DelegatingList { - final String _name; - Pattern(String name, List base) - : _name = name, - super(base); - - String get name => _name; -} - -class GameOfLife implements RuleSet { - int checkRange = 1; - List patterns = [ - // Two blocks, offset - // ## - // ## - Pattern("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 - // # - // # - // ### - Pattern("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; -} From e13962f3718ca307022e8ca195a35e2e121bfa57 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 11:29:55 +0200 Subject: [PATCH 18/38] Shorten RuleSet variable for their checked range Range is self-explanatory and not as confusing as checkRange. --- lib/src/rules/GameOfLife.dart | 2 +- lib/src/rules/RuleSet.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/rules/GameOfLife.dart b/lib/src/rules/GameOfLife.dart index f812233..edb2dde 100644 --- a/lib/src/rules/GameOfLife.dart +++ b/lib/src/rules/GameOfLife.dart @@ -4,7 +4,7 @@ import 'package:rules_of_living/src/rules/RuleSet.dart'; import 'package:rules_of_living/src/rules/CellPattern.dart'; class GameOfLife implements RuleSet { - int checkRange = 1; + int range = 1; List patterns = [ // Two blocks, offset // ## diff --git a/lib/src/rules/RuleSet.dart b/lib/src/rules/RuleSet.dart index ad2bb70..03aeb36 100644 --- a/lib/src/rules/RuleSet.dart +++ b/lib/src/rules/RuleSet.dart @@ -1,7 +1,7 @@ import 'package:rules_of_living/src/rules/CellPattern.dart'; abstract class RuleSet { - int checkRange; + int range; List patterns; bool checkSurvival(int neighbors); From f1399064a20c04feb89cc9a416bac3ee129c7d5e Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 11:30:14 +0200 Subject: [PATCH 19/38] Fix Simulation using RuleSet range for neighbor checks --- lib/src/Simulation.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index a3f99c5..90969ce 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -87,7 +87,7 @@ class Simulation { 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, 1); + 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) { From 4f92c69a82794b97365d5186eadb437a8a9bbaf7 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 11:32:00 +0200 Subject: [PATCH 20/38] Rename Simulation function adding random patterns Rename from addPattern to addRandomPattern to more clearly signify its purpose. --- lib/src/Engine.dart | 4 ++-- lib/src/Simulation.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index e5190cf..3653353 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -55,7 +55,7 @@ class Engine { _simulation = Simulation(x, y); _elapsed.start(); - _simulation.addPattern(amount: 15, dispersal: 5); + _simulation.addRandomPattern(amount: 15, dispersal: 5); html.window.animationFrame.then(animFrame); } @@ -129,7 +129,7 @@ class Engine { } void addPattern({int amount, int dispersal}) { - _simulation.addPattern(amount: amount, dispersal: dispersal); + _simulation.addRandomPattern(amount: amount, dispersal: dispersal); } void toggleEdgeRendering() { diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index 90969ce..42df2a4 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -30,7 +30,7 @@ class Simulation { _dirty = true; } - void addPattern({int amount, int dispersal}) { + void addRandomPattern({int amount, int dispersal}) { int _startingSeed = DateTime.now().millisecondsSinceEpoch; math.Random rng = new math.Random(_startingSeed); _amount = amount ?? rng.nextInt(20); From 17697070ee4df34a9d7ed3f90cedd4ae06e9f418 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 11:58:38 +0200 Subject: [PATCH 21/38] Move ControlService tasks into EngineService All ControlService was used for was a redirection to the engine service. This will be further split up in the future into more logical units of responsibility. --- lib/app_component.dart | 2 -- lib/components/controls_component.dart | 4 +-- lib/service/control_service.dart | 38 -------------------------- lib/service/engine_service.dart | 31 +++++++++++++++++++++ 4 files changed, 33 insertions(+), 42 deletions(-) delete mode 100644 lib/service/control_service.dart diff --git a/lib/app_component.dart b/lib/app_component.dart index ae8a931..2d4557f 100644 --- a/lib/app_component.dart +++ b/lib/app_component.dart @@ -5,7 +5,6 @@ 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'; @Component( @@ -25,7 +24,6 @@ import 'package:rules_of_living/service/engine_service.dart'; materialProviders, ClassProvider(EngineService), ClassProvider(ConfigurationService), - ClassProvider(ControlService) ], styleUrls: const [ 'package:angular_components/app_layout/layout.scss.css', diff --git a/lib/components/controls_component.dart b/lib/components/controls_component.dart index 954d94b..f4b5264 100644 --- a/lib/components/controls_component.dart +++ b/lib/components/controls_component.dart @@ -1,6 +1,6 @@ import 'package:angular/angular.dart'; import 'package:angular_components/angular_components.dart'; -import 'package:rules_of_living/service/control_service.dart'; +import 'package:rules_of_living/service/engine_service.dart'; @Component( selector: 'sim-controls', @@ -15,7 +15,7 @@ import 'package:rules_of_living/service/control_service.dart'; styleUrls: const ["controls_component.css"], ) class ControlsComponent { - final ControlService ctrl; + final EngineService ctrl; ControlsComponent(this.ctrl); diff --git a/lib/service/control_service.dart b/lib/service/control_service.dart deleted file mode 100644 index 0066106..0000000 --- a/lib/service/control_service.dart +++ /dev/null @@ -1,38 +0,0 @@ -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 8168735..df1492c 100644 --- a/lib/service/engine_service.dart +++ b/lib/service/engine_service.dart @@ -12,4 +12,35 @@ 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(); + } + + void reset() { + engine.reset(); + } + + void addRandomPattern() { + engine.running = false; + engine.addPattern(); + } + + void clear() { + engine.clear(); + } + + bool get isRunning => engine.running; } From 72ce25a8066f2d1a5f970d584644796dee77420d Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 11:59:36 +0200 Subject: [PATCH 22/38] Rename Controls component variable accessing engine --- lib/components/controls_component.dart | 14 +++++++------- lib/components/controls_component.html | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/components/controls_component.dart b/lib/components/controls_component.dart index f4b5264..511a7d4 100644 --- a/lib/components/controls_component.dart +++ b/lib/components/controls_component.dart @@ -15,27 +15,27 @@ import 'package:rules_of_living/service/engine_service.dart'; styleUrls: const ["controls_component.css"], ) class ControlsComponent { - final EngineService ctrl; + final EngineService engine; - ControlsComponent(this.ctrl); + ControlsComponent(this.engine); void onStartClicked() { - ctrl.toggleRunning(); + engine.toggleRunning(); } void onStepClicked() { - ctrl.step(); + engine.step(); } void onResetClicked() { - ctrl.reset(); + engine.reset(); } void onRandomClicked() { - ctrl.addRandomPattern(); + engine.addRandomPattern(); } void onClearClicked() { - ctrl.clear(); + engine.clear(); } } diff --git a/lib/components/controls_component.html b/lib/components/controls_component.html index b4c43f3..42e79f7 100644 --- a/lib/components/controls_component.html +++ b/lib/components/controls_component.html @@ -1,7 +1,7 @@
- + From 6b4786fdd08e250da3cd8ac60dfab47d4d7b73db Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 12:05:02 +0200 Subject: [PATCH 23/38] Add SimulationService to controls Will eventually attach to the Simulation directly without first going through Engine. For now just redirects calls to EngineService to keep functions intact. --- lib/app_component.dart | 2 ++ lib/components/controls_component.dart | 10 ++++++---- lib/service/simulation_service.dart | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 lib/service/simulation_service.dart diff --git a/lib/app_component.dart b/lib/app_component.dart index 2d4557f..eaa2e41 100644 --- a/lib/app_component.dart +++ b/lib/app_component.dart @@ -6,6 +6,7 @@ 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/engine_service.dart'; +import 'package:rules_of_living/service/simulation_service.dart'; @Component( selector: 'my-app', @@ -24,6 +25,7 @@ import 'package:rules_of_living/service/engine_service.dart'; materialProviders, ClassProvider(EngineService), ClassProvider(ConfigurationService), + ClassProvider(SimulationService) ], styleUrls: const [ 'package:angular_components/app_layout/layout.scss.css', diff --git a/lib/components/controls_component.dart b/lib/components/controls_component.dart index 511a7d4..e524e60 100644 --- a/lib/components/controls_component.dart +++ b/lib/components/controls_component.dart @@ -1,6 +1,7 @@ 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'; @Component( selector: 'sim-controls', @@ -16,8 +17,9 @@ import 'package:rules_of_living/service/engine_service.dart'; ) class ControlsComponent { final EngineService engine; + final SimulationService sim; - ControlsComponent(this.engine); + ControlsComponent(this.engine, this.sim); void onStartClicked() { engine.toggleRunning(); @@ -28,14 +30,14 @@ class ControlsComponent { } void onResetClicked() { - engine.reset(); + sim.reset(); } void onRandomClicked() { - engine.addRandomPattern(); + sim.addRandomPattern(); } void onClearClicked() { - engine.clear(); + sim.clear(); } } diff --git a/lib/service/simulation_service.dart b/lib/service/simulation_service.dart new file mode 100644 index 0000000..1081bf0 --- /dev/null +++ b/lib/service/simulation_service.dart @@ -0,0 +1,19 @@ +import 'package:rules_of_living/service/engine_service.dart'; + +class SimulationService { + final EngineService engine; + + SimulationService(this.engine); + + void reset() { + engine.reset(); + } + + void addRandomPattern() { + engine.addRandomPattern(); + } + + void clear() { + engine.clear(); + } +} From 7729da3a40be6d65e951190a5a7061828dd34766 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 12:16:47 +0200 Subject: [PATCH 24/38] Split ConfigurationService to use SimulationService Methods concerning engine make use of EngineService, those concerning grid and patterns make use of SimulationService. --- lib/service/configuration_service.dart | 19 ++++++++++--------- lib/service/simulation_service.dart | 18 +++++++++++++----- test/service/configuration_service_test.dart | 4 +++- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/service/configuration_service.dart b/lib/service/configuration_service.dart index e11a5e8..045e66c 100644 --- a/lib/service/configuration_service.dart +++ b/lib/service/configuration_service.dart @@ -2,15 +2,17 @@ import 'dart:html' as html; import 'dart:math'; import 'package:rules_of_living/service/engine_service.dart'; +import 'package:rules_of_living/service/simulation_service.dart'; class ConfigurationService { - final EngineService _es; + final EngineService _engine; + final SimulationService _sim; bool showGrid; int _simSpeed; - ConfigurationService(this._es) { + ConfigurationService(this._engine, this._sim) { showGrid = false; simSpeed = 5; } @@ -23,21 +25,20 @@ class ConfigurationService { int get simSpeed => _simSpeed; void set simSpeed(int val) { _simSpeed = val; - _es.engine.stepsPerSecond = simSpeed; + //TODO make method in EngineService to respect Demeter + _engine.engine.stepsPerSecond = simSpeed; } - void set canvas(html.CanvasElement canvas) => _es.engine.canvas = canvas; - html.CanvasElement get canvas => _es.engine.canvas; + void set canvas(html.CanvasElement canvas) => _engine.engine.canvas = canvas; + html.CanvasElement get canvas => _engine.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); + _sim.gridSize = Point(x ?? gridSize.x, y ?? gridSize.y); } - Point get gridSize => _es.engine.gridSize; + Point get gridSize => _sim.gridSize; } diff --git a/lib/service/simulation_service.dart b/lib/service/simulation_service.dart index 1081bf0..45a7b09 100644 --- a/lib/service/simulation_service.dart +++ b/lib/service/simulation_service.dart @@ -1,19 +1,27 @@ +import 'dart:math'; + import 'package:rules_of_living/service/engine_service.dart'; class SimulationService { - final EngineService engine; + final EngineService _engine; - SimulationService(this.engine); + SimulationService(this._engine); void reset() { - engine.reset(); + _engine.reset(); } void addRandomPattern() { - engine.addRandomPattern(); + _engine.addRandomPattern(); } void clear() { - engine.clear(); + _engine.clear(); } + + void set gridSize(Point size) { + _engine.engine.gridSize = size; + } + + Point get gridSize => _engine.engine.gridSize; } diff --git a/test/service/configuration_service_test.dart b/test/service/configuration_service_test.dart index 44e23b2..9aa4893 100644 --- a/test/service/configuration_service_test.dart +++ b/test/service/configuration_service_test.dart @@ -3,6 +3,7 @@ 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/service/simulation_service.dart'; import 'package:rules_of_living/src/Engine.dart'; @TestOn('browser') import 'package:test/test.dart'; @@ -12,12 +13,13 @@ class MockEngine extends Mock implements Engine {} void main() { ConfigurationService sut; EngineService engineService; + SimulationService simService; MockEngine me; setUp(() { me = MockEngine(); engineService = EngineService(); engineService.engine = me; - sut = ConfigurationService(engineService); + sut = ConfigurationService(engineService, simService); }); group("simulation speed", () { From 99ead8691bc22b1dfe3676e61ded0757409f70be Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 12:29:46 +0200 Subject: [PATCH 25/38] Make gridSize in Services pass correct signature Both need to conform to Point to be accepted by the engine. --- lib/service/configuration_service.dart | 2 +- lib/service/simulation_service.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/service/configuration_service.dart b/lib/service/configuration_service.dart index 045e66c..ffd0b78 100644 --- a/lib/service/configuration_service.dart +++ b/lib/service/configuration_service.dart @@ -37,7 +37,7 @@ class ConfigurationService { } void setGridSize({int x, int y}) { - _sim.gridSize = Point(x ?? gridSize.x, y ?? gridSize.y); + _sim.gridSize = Point(x ?? gridSize.x, y ?? gridSize.y); } Point get gridSize => _sim.gridSize; diff --git a/lib/service/simulation_service.dart b/lib/service/simulation_service.dart index 45a7b09..0c967ef 100644 --- a/lib/service/simulation_service.dart +++ b/lib/service/simulation_service.dart @@ -19,7 +19,7 @@ class SimulationService { _engine.clear(); } - void set gridSize(Point size) { + void set gridSize(Point size) { _engine.engine.gridSize = size; } From 58971016dab2f92d0584548918b8262bf36bb2ad Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 12:51:35 +0200 Subject: [PATCH 26/38] Remove ConfigurationService Replaced with direct access to both EngineService and SimulationService. --- lib/app_component.dart | 2 - lib/components/configuration_component.dart | 24 ++++++---- lib/components/controls_component.dart | 1 + lib/components/simulation_component.dart | 8 ++-- lib/service/configuration_service.dart | 44 ------------------ lib/service/engine_service.dart | 22 +++++++++ test/service/configuration_service_test.dart | 48 -------------------- 7 files changed, 41 insertions(+), 108 deletions(-) delete mode 100644 lib/service/configuration_service.dart delete mode 100644 test/service/configuration_service_test.dart diff --git a/lib/app_component.dart b/lib/app_component.dart index eaa2e41..1656739 100644 --- a/lib/app_component.dart +++ b/lib/app_component.dart @@ -4,7 +4,6 @@ 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/engine_service.dart'; import 'package:rules_of_living/service/simulation_service.dart'; @@ -24,7 +23,6 @@ import 'package:rules_of_living/service/simulation_service.dart'; providers: [ materialProviders, ClassProvider(EngineService), - ClassProvider(ConfigurationService), ClassProvider(SimulationService) ], styleUrls: const [ diff --git a/lib/components/configuration_component.dart b/lib/components/configuration_component.dart index cc1ecd1..c72106c 100644 --- a/lib/components/configuration_component.dart +++ b/lib/components/configuration_component.dart @@ -1,3 +1,5 @@ +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'; @@ -5,7 +7,8 @@ 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/configuration_service.dart'; +import 'package:rules_of_living/service/engine_service.dart'; +import 'package:rules_of_living/service/simulation_service.dart'; @Component( selector: "configuration", @@ -23,28 +26,29 @@ import 'package:rules_of_living/service/configuration_service.dart'; NgModel ]) class ConfigurationComponent { - final ConfigurationService config; + final EngineService engine; + final SimulationService sim; - int get width => config.gridSize.x; + int get width => sim.gridSize.x; void set width(num value) { if (value == null || value <= 0) return; - config.setGridSize(x: value.toInt()); + sim.gridSize = Point(value, sim.gridSize.y); } - int get height => config.gridSize.y; + int get height => sim.gridSize.y; void set height(num value) { if (value == null || value <= 0) return; - config.setGridSize(y: value.toInt()); + sim.gridSize = Point(sim.gridSize.x, value); } - int get simSpeed => config.simSpeed; - void set simSpeed(int value) => config.simSpeed = value; + int get simSpeed => engine.simSpeed; + void set simSpeed(int value) => engine.simSpeed = value; String get speedSliderTooltip => "Simulation Speed: $simSpeed"; - ConfigurationComponent(this.config); + ConfigurationComponent(this.engine, this.sim); void onEdgesClicked() { - config.toggleGrid(); + engine.toggleGrid(); } } diff --git a/lib/components/controls_component.dart b/lib/components/controls_component.dart index e524e60..3f31741 100644 --- a/lib/components/controls_component.dart +++ b/lib/components/controls_component.dart @@ -35,6 +35,7 @@ class ControlsComponent { void onRandomClicked() { sim.addRandomPattern(); + engine.stop(); } void onClearClicked() { diff --git a/lib/components/simulation_component.dart b/lib/components/simulation_component.dart index 312eef4..91812fb 100644 --- a/lib/components/simulation_component.dart +++ b/lib/components/simulation_component.dart @@ -1,7 +1,7 @@ import 'dart:html' as html; import 'package:angular/angular.dart'; -import 'package:rules_of_living/service/configuration_service.dart'; +import 'package:rules_of_living/service/engine_service.dart'; @Component( selector: 'gol-simulation', @@ -10,9 +10,9 @@ import 'package:rules_of_living/service/configuration_service.dart'; providers: [], ) class SimulationComponent implements OnInit { - final ConfigurationService config; + final EngineService engine; - SimulationComponent(this.config); + SimulationComponent(this.engine); @override void ngOnInit() { @@ -29,6 +29,6 @@ class SimulationComponent implements OnInit { the canvas did not load correctly :( ''', canvas.width / 2 - 50, canvas.height / 2); - config.canvas = canvas; + engine.canvas = canvas; } } diff --git a/lib/service/configuration_service.dart b/lib/service/configuration_service.dart deleted file mode 100644 index ffd0b78..0000000 --- a/lib/service/configuration_service.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'dart:html' as html; -import 'dart:math'; - -import 'package:rules_of_living/service/engine_service.dart'; -import 'package:rules_of_living/service/simulation_service.dart'; - -class ConfigurationService { - final EngineService _engine; - final SimulationService _sim; - - bool showGrid; - - int _simSpeed; - - ConfigurationService(this._engine, this._sim) { - 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; - //TODO make method in EngineService to respect Demeter - _engine.engine.stepsPerSecond = simSpeed; - } - - void set canvas(html.CanvasElement canvas) => _engine.engine.canvas = canvas; - html.CanvasElement get canvas => _engine.engine.canvas; - - void toggleGrid() { - showGrid = !showGrid; - } - - void setGridSize({int x, int y}) { - _sim.gridSize = Point(x ?? gridSize.x, y ?? gridSize.y); - } - - Point get gridSize => _sim.gridSize; -} diff --git a/lib/service/engine_service.dart b/lib/service/engine_service.dart index df1492c..6d40681 100644 --- a/lib/service/engine_service.dart +++ b/lib/service/engine_service.dart @@ -1,8 +1,14 @@ +import 'dart:html' as html; + import 'package:rules_of_living/src/Engine.dart'; class EngineService { Engine _uncachedEngineAccess; + EngineService() { + simSpeed = 5; + } + Engine get engine => _uncachedEngineAccess ?? _setCachedAndReturn(Engine()); void set engine(Engine newEngine) { _uncachedEngineAccess = newEngine; @@ -29,6 +35,22 @@ class EngineService { 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; + + //TODO split into RenderService when rendering is decoupled from engine. + html.CanvasElement get canvas => engine.canvas; + void set canvas(html.CanvasElement canvas) => engine.canvas = canvas; + + void toggleGrid() { + engine.toggleEdgeRendering(); + } + void reset() { engine.reset(); } diff --git a/test/service/configuration_service_test.dart b/test/service/configuration_service_test.dart deleted file mode 100644 index 9aa4893..0000000 --- a/test/service/configuration_service_test.dart +++ /dev/null @@ -1,48 +0,0 @@ -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/service/simulation_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; - SimulationService simService; - MockEngine me; - setUp(() { - me = MockEngine(); - engineService = EngineService(); - engineService.engine = me; - sut = ConfigurationService(engineService, simService); - }); - - 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)); - }); - }); -} From 45e8f01acb2205896cd53387a5e5586e2cd34a72 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 14:41:57 +0200 Subject: [PATCH 27/38] Move Render Methods into SimulationService --- lib/components/configuration_component.dart | 2 +- lib/components/simulation_component.dart | 4 +++- lib/service/engine_service.dart | 10 ---------- lib/service/simulation_service.dart | 9 +++++++++ 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/components/configuration_component.dart b/lib/components/configuration_component.dart index c72106c..abbd2e5 100644 --- a/lib/components/configuration_component.dart +++ b/lib/components/configuration_component.dart @@ -49,6 +49,6 @@ class ConfigurationComponent { ConfigurationComponent(this.engine, this.sim); void onEdgesClicked() { - engine.toggleGrid(); + sim.toggleGrid(); } } diff --git a/lib/components/simulation_component.dart b/lib/components/simulation_component.dart index 91812fb..1135865 100644 --- a/lib/components/simulation_component.dart +++ b/lib/components/simulation_component.dart @@ -2,6 +2,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'; @Component( selector: 'gol-simulation', @@ -11,6 +12,7 @@ import 'package:rules_of_living/service/engine_service.dart'; ) class SimulationComponent implements OnInit { final EngineService engine; + final SimulationService sim; SimulationComponent(this.engine); @@ -29,6 +31,6 @@ class SimulationComponent implements OnInit { the canvas did not load correctly :( ''', canvas.width / 2 - 50, canvas.height / 2); - engine.canvas = canvas; + sim.canvas = canvas; } } diff --git a/lib/service/engine_service.dart b/lib/service/engine_service.dart index 6d40681..a76fedb 100644 --- a/lib/service/engine_service.dart +++ b/lib/service/engine_service.dart @@ -1,5 +1,3 @@ -import 'dart:html' as html; - import 'package:rules_of_living/src/Engine.dart'; class EngineService { @@ -43,14 +41,6 @@ class EngineService { int get simSpeed => engine.stepsPerSecond; void set simSpeed(int val) => engine.stepsPerSecond = val; - //TODO split into RenderService when rendering is decoupled from engine. - html.CanvasElement get canvas => engine.canvas; - void set canvas(html.CanvasElement canvas) => engine.canvas = canvas; - - void toggleGrid() { - engine.toggleEdgeRendering(); - } - void reset() { engine.reset(); } diff --git a/lib/service/simulation_service.dart b/lib/service/simulation_service.dart index 0c967ef..b5a9021 100644 --- a/lib/service/simulation_service.dart +++ b/lib/service/simulation_service.dart @@ -1,3 +1,4 @@ +import 'dart:html' as html; import 'dart:math'; import 'package:rules_of_living/service/engine_service.dart'; @@ -24,4 +25,12 @@ class SimulationService { } Point get gridSize => _engine.engine.gridSize; + + //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() { + _engine.engine.toggleEdgeRendering(); + } } From bbfb2f735b26b3112f0773072b14bc5fb72e1138 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 14:41:57 +0200 Subject: [PATCH 28/38] Move Render Methods into SimulationService --- lib/components/configuration_component.dart | 2 +- lib/components/simulation_component.dart | 6 ++++-- lib/service/engine_service.dart | 10 ---------- lib/service/simulation_service.dart | 9 +++++++++ 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/components/configuration_component.dart b/lib/components/configuration_component.dart index c72106c..abbd2e5 100644 --- a/lib/components/configuration_component.dart +++ b/lib/components/configuration_component.dart @@ -49,6 +49,6 @@ class ConfigurationComponent { ConfigurationComponent(this.engine, this.sim); void onEdgesClicked() { - engine.toggleGrid(); + sim.toggleGrid(); } } diff --git a/lib/components/simulation_component.dart b/lib/components/simulation_component.dart index 91812fb..1fcef71 100644 --- a/lib/components/simulation_component.dart +++ b/lib/components/simulation_component.dart @@ -2,6 +2,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'; @Component( selector: 'gol-simulation', @@ -11,8 +12,9 @@ import 'package:rules_of_living/service/engine_service.dart'; ) class SimulationComponent implements OnInit { final EngineService engine; + final SimulationService sim; - SimulationComponent(this.engine); + SimulationComponent(this.engine, this.sim); @override void ngOnInit() { @@ -29,6 +31,6 @@ class SimulationComponent implements OnInit { the canvas did not load correctly :( ''', canvas.width / 2 - 50, canvas.height / 2); - engine.canvas = canvas; + sim.canvas = canvas; } } diff --git a/lib/service/engine_service.dart b/lib/service/engine_service.dart index 6d40681..a76fedb 100644 --- a/lib/service/engine_service.dart +++ b/lib/service/engine_service.dart @@ -1,5 +1,3 @@ -import 'dart:html' as html; - import 'package:rules_of_living/src/Engine.dart'; class EngineService { @@ -43,14 +41,6 @@ class EngineService { int get simSpeed => engine.stepsPerSecond; void set simSpeed(int val) => engine.stepsPerSecond = val; - //TODO split into RenderService when rendering is decoupled from engine. - html.CanvasElement get canvas => engine.canvas; - void set canvas(html.CanvasElement canvas) => engine.canvas = canvas; - - void toggleGrid() { - engine.toggleEdgeRendering(); - } - void reset() { engine.reset(); } diff --git a/lib/service/simulation_service.dart b/lib/service/simulation_service.dart index 0c967ef..b5a9021 100644 --- a/lib/service/simulation_service.dart +++ b/lib/service/simulation_service.dart @@ -1,3 +1,4 @@ +import 'dart:html' as html; import 'dart:math'; import 'package:rules_of_living/service/engine_service.dart'; @@ -24,4 +25,12 @@ class SimulationService { } Point get gridSize => _engine.engine.gridSize; + + //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() { + _engine.engine.toggleEdgeRendering(); + } } From 32a3676d95aaef417342936ffdc6302ffd1d4464 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 15:19:54 +0200 Subject: [PATCH 29/38] Remove deprecated strict mode --- analysis_options.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index caef4c8..78fb282 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: From 8db9cd6ff1c363b21913b468ffa83be14db79055 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 18 Oct 2018 15:21:50 +0200 Subject: [PATCH 30/38] Move Simulation Accesses to Simulation Class Everything has been refactored away from engine, which now only controls updating & rendering within a specific timestep. (As well as stepping forward by calling a single update) Everything regarding grids, patterns and cells has been moved into the simulation and the Services have been updated to reflect that. --- lib/service/engine_service.dart | 16 +++---------- lib/service/simulation_service.dart | 25 ++++++++++++------- lib/src/Engine.dart | 37 ++++------------------------- lib/src/Simulation.dart | 10 +++++++- test/src/engine_test.dart | 8 ------- 5 files changed, 33 insertions(+), 63 deletions(-) diff --git a/lib/service/engine_service.dart b/lib/service/engine_service.dart index a76fedb..b276d28 100644 --- a/lib/service/engine_service.dart +++ b/lib/service/engine_service.dart @@ -1,4 +1,5 @@ import 'package:rules_of_living/src/Engine.dart'; +import 'package:rules_of_living/src/Simulation.dart'; class EngineService { Engine _uncachedEngineAccess; @@ -41,18 +42,7 @@ class EngineService { int get simSpeed => engine.stepsPerSecond; void set simSpeed(int val) => engine.stepsPerSecond = val; - void reset() { - engine.reset(); - } - - void addRandomPattern() { - engine.running = false; - engine.addPattern(); - } - - void clear() { - engine.clear(); - } - 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 index b5a9021..396290b 100644 --- a/lib/service/simulation_service.dart +++ b/lib/service/simulation_service.dart @@ -2,35 +2,42 @@ 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 { - final EngineService _engine; + // DEFAULT VALUES + static final int DEFAULT_GRID_SIZE = 50; - SimulationService(this._engine); + final EngineService _engine; + final Simulation _sim = Simulation(DEFAULT_GRID_SIZE, DEFAULT_GRID_SIZE); + + SimulationService(this._engine) { + _engine.simulation = _sim; + _sim.addRandomPattern(amount: 15, dispersal: 5); + } void reset() { - _engine.reset(); + _sim.reset(); } void addRandomPattern() { - _engine.addRandomPattern(); + _sim.addRandomPattern(); } void clear() { - _engine.clear(); + _sim.reset(); } + Point get gridSize => _sim.gridSize; void set gridSize(Point size) { - _engine.engine.gridSize = size; + _sim.gridSize = size; } - Point get gridSize => _engine.engine.gridSize; - //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() { - _engine.engine.toggleEdgeRendering(); + _sim.renderEdges = !_sim.renderEdges; } } diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index 3653353..ad101ca 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -29,16 +29,6 @@ class Engine { // ms stuck in updateloop after which game will declare itself unresponsive final int SAFETY_TIMEOUT = 2000; - /// 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; @@ -51,11 +41,8 @@ class Engine { Simulation _simulation; bool running = false; - Engine([x = 100, y = 100, this.canvas]) { - _simulation = Simulation(x, y); - + Engine() { _elapsed.start(); - _simulation.addRandomPattern(amount: 15, dispersal: 5); html.window.animationFrame.then(animFrame); } @@ -64,16 +51,6 @@ class Engine { html.window.animationFrame.then(animFrame); } - void reset() { - _simulation.reset(); - running = false; - } - - void clear() { - _simulation = new Simulation(gridSize.x, gridSize.y); - running = false; - } - void process(num now) { _drawLag += _elapsed.elapsedMilliseconds; _updateLag += _elapsed.elapsedMilliseconds; @@ -101,6 +78,8 @@ 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() { + if (_simulation == null) return; + Map simulationUpdate = _simulation.update(); _simulation.mergeStateChanges(simulationUpdate); @@ -123,16 +102,10 @@ class Engine { /// the internal engine processing. Does not do anything if no canvas is /// defined. void render([num interp]) { - if (canvas == null) return; + if (canvas == null || _simulation == null) return; _simulation.render(canvas, interp); } - void addPattern({int amount, int dispersal}) { - _simulation.addRandomPattern(amount: amount, dispersal: dispersal); - } - - void toggleEdgeRendering() { - _simulation.renderEdges = !_simulation.renderEdges; - } + void set simulation(Simulation value) => _simulation = value; } diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index 42df2a4..fd8bbcb 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -1,5 +1,6 @@ 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'; @@ -8,7 +9,7 @@ import 'package:rules_of_living/src/rules/RuleSet.dart'; enum CellPattern { SpaceShip, Blinker } class Simulation { - final Grid map; + Grid map; RuleSet rules = GameOfLife(); bool _dirty = true; @@ -20,6 +21,13 @@ class Simulation { int get w => map.width; int get h => map.height; + Point get gridSize => Point(w, h); + 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) { reset(); print("Grid Created"); diff --git a/test/src/engine_test.dart b/test/src/engine_test.dart index fcfefc2..bd184a4 100644 --- a/test/src/engine_test.dart +++ b/test/src/engine_test.dart @@ -31,12 +31,4 @@ 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); - }); - }); } From 2993b33d9e9e8135297f171b055d2c607f0fcd7f Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 19 Oct 2018 08:23:00 +0200 Subject: [PATCH 31/38] Add tests for CellPattern & GameOfLife classes --- lib/src/rules/CellPattern.dart | 11 +++++++++-- test/src/rules/cellpattern_test.dart | 19 +++++++++++++++++++ test/src/rules/gameoflife_test.dart | 27 +++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 test/src/rules/cellpattern_test.dart create mode 100644 test/src/rules/gameoflife_test.dart diff --git a/lib/src/rules/CellPattern.dart b/lib/src/rules/CellPattern.dart index 9798948..ed4cae7 100644 --- a/lib/src/rules/CellPattern.dart +++ b/lib/src/rules/CellPattern.dart @@ -1,10 +1,17 @@ +import 'dart:math'; + import 'package:collection/collection.dart'; -class CellPattern extends DelegatingList { +class CellPattern extends DelegatingList { final String _name; - CellPattern(String name, List base) + CellPattern(String name, List base) : _name = name, super(base); String get name => _name; + + @override + String toString() { + return "$name: ${super.toString()}"; + } } diff --git a/test/src/rules/cellpattern_test.dart b/test/src/rules/cellpattern_test.dart new file mode 100644 index 0000000..a7989f5 --- /dev/null +++ b/test/src/rules/cellpattern_test.dart @@ -0,0 +1,19 @@ +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 new file mode 100644 index 0000000..565e4e1 --- /dev/null +++ b/test/src/rules/gameoflife_test.dart @@ -0,0 +1,27 @@ +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)); + }); +} From e8c1e6ed8b36f7b0ed6fede88916eb1dfebb4f86 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 19 Oct 2018 11:50:34 +0200 Subject: [PATCH 32/38] Refactor Engine Methods Extract method checking for update necessity. --- lib/src/Engine.dart | 33 ++++++++------ test/src/engine_test.dart | 95 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 16 deletions(-) diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index ad101ca..5653bb9 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -1,5 +1,4 @@ import 'dart:html' as html; -import 'dart:math'; import 'package:rules_of_living/src/Simulation.dart'; @@ -47,31 +46,37 @@ class Engine { } void animFrame(num now) { - process(now); + int elapsed = _elapsed.elapsedMilliseconds; + _elapsed.reset(); + process(elapsed, SAFETY_TIMEOUT, update: this.update, render: this.render); html.window.animationFrame.then(animFrame); } - void process(num now) { - _drawLag += _elapsed.elapsedMilliseconds; - _updateLag += _elapsed.elapsedMilliseconds; - _elapsed.reset(); + void process(int elapsed, int timeOut, {Function update, Function render}) { + _drawLag += elapsed; + _updateLag += elapsed; - 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(); + while (running == true && + _shouldUpdate(_updateLag, elapsed, timeOut) == true) { _updateLag -= _MS_PER_STEP; + if (update == null) break; + update(); } if (_drawLag >= _MS_PER_FRAME) { - render(_updateLag / _MS_PER_STEP); _drawLag = 0; + if (render == null) return; + render(_updateLag / _MS_PER_STEP); } } + 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 diff --git a/test/src/engine_test.dart b/test/src/engine_test.dart index bd184a4..f602069 100644 --- a/test/src/engine_test.dart +++ b/test/src/engine_test.dart @@ -1,15 +1,106 @@ import 'dart:html' as html; -import 'dart:math'; +import 'package:rules_of_living/src/Simulation.dart'; +import 'package:test/test.dart'; +import 'package:mockito/mockito.dart'; @TestOn('browser') import 'package:rules_of_living/src/Engine.dart'; -import 'package:test/test.dart'; + +class MockSimulation extends Mock implements Simulation { + int updateNum = 0; + bool hasChanges = false; + + @override + Map update() { + updateNum++; + return hasChanges ? {1: true, 2: false} : {}; + } +} 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)); From 2169de16fdd391e43e3670c2c388d7b3b63dc722 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 19 Oct 2018 19:35:09 +0200 Subject: [PATCH 33/38] Add Tests to Simulation --- lib/src/Simulation.dart | 21 +++++++++++---------- test/simulation_test.dart | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 test/simulation_test.dart diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index fd8bbcb..54c7efa 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -13,15 +13,15 @@ class Simulation { RuleSet rules = GameOfLife(); bool _dirty = true; + bool get dirty => _dirty; + bool _renderEdges = true; + bool get renderEdges => _renderEdges; int _amount; int _dispersal; - int get w => map.width; - int get h => map.height; - - Point get gridSize => Point(w, h); + 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"); @@ -29,13 +29,16 @@ class Simulation { } Simulation(int w, int h) : this.map = new Grid(w, h) { - reset(); - print("Grid Created"); + this.map = reset(); } - void reset() { - map.setAll(0, List.filled(map.length, false)); + 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}) { @@ -148,6 +151,4 @@ class Simulation { _renderEdges = on; _dirty = true; } - - bool get renderEdges => _renderEdges; } diff --git a/test/simulation_test.dart b/test/simulation_test.dart new file mode 100644 index 0000000..b8f5db6 --- /dev/null +++ b/test/simulation_test.dart @@ -0,0 +1,37 @@ +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'; + +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"); + }); +} From 0da2d08b74dccbeb630882e23d0cb017a27198c8 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 19 Oct 2018 19:59:00 +0200 Subject: [PATCH 34/38] Add Simulation Saving and Loading Methods --- lib/src/Simulation.dart | 5 +++++ test/simulation_test.dart | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index 54c7efa..5243b84 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -10,6 +10,8 @@ enum CellPattern { SpaceShip, Blinker } class Simulation { Grid map; + Grid _snapshot; + RuleSet rules = GameOfLife(); bool _dirty = true; @@ -151,4 +153,7 @@ class Simulation { _renderEdges = on; _dirty = true; } + + void saveSnapshot() => _snapshot = map; + Grid loadSnapshot() => map = _snapshot; } diff --git a/test/simulation_test.dart b/test/simulation_test.dart index b8f5db6..d137f97 100644 --- a/test/simulation_test.dart +++ b/test/simulation_test.dart @@ -5,6 +5,8 @@ import 'package:test/test.dart'; import 'package:rules_of_living/src/Simulation.dart'; +class MockGrid extends Mock implements Grid {} + void main() { Simulation sut; setUp(() { @@ -34,4 +36,13 @@ void main() { expect(sut.dirty, true); }, skip: "can not find a way to set dirty to true first yet"); }); + group("save&load", () { + test("saves the current map, can be loaded by loadSnapshot", () { + var snapshot = sut.map; + sut.saveSnapshot(); + sut.map = MockGrid(); + + expect(sut.loadSnapshot(), equals(snapshot)); + }); + }); } From 37bff59f83f7a4e08254ff9ec34e565df4dff7ff Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 19 Oct 2018 20:01:52 +0200 Subject: [PATCH 35/38] Remove clear function: Code Duplication --- lib/components/controls_component.dart | 2 +- lib/service/simulation_service.dart | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/components/controls_component.dart b/lib/components/controls_component.dart index 3f31741..1d7ee17 100644 --- a/lib/components/controls_component.dart +++ b/lib/components/controls_component.dart @@ -39,6 +39,6 @@ class ControlsComponent { } void onClearClicked() { - sim.clear(); + sim.reset(); } } diff --git a/lib/service/simulation_service.dart b/lib/service/simulation_service.dart index 396290b..5f538ec 100644 --- a/lib/service/simulation_service.dart +++ b/lib/service/simulation_service.dart @@ -24,10 +24,6 @@ class SimulationService { _sim.addRandomPattern(); } - void clear() { - _sim.reset(); - } - Point get gridSize => _sim.gridSize; void set gridSize(Point size) { _sim.gridSize = size; From 2ea974bbd53d99600678a5da7b46e0f31ff03c56 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 19 Oct 2018 20:49:59 +0200 Subject: [PATCH 36/38] Fix Snapshot Load and Save Functions Were just pointers to the map before. Fix to be actual clones of the map. --- lib/src/Simulation.dart | 8 ++++++-- test/simulation_test.dart | 10 ++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart index 5243b84..0955c9a 100644 --- a/lib/src/Simulation.dart +++ b/lib/src/Simulation.dart @@ -154,6 +154,10 @@ class Simulation { _dirty = true; } - void saveSnapshot() => _snapshot = map; - Grid loadSnapshot() => map = _snapshot; + void saveSnapshot() => _snapshot = Grid.from(map); + Grid loadSnapshot() { + map = Grid.from(_snapshot); + _dirty = true; + return map; + } } diff --git a/test/simulation_test.dart b/test/simulation_test.dart index d137f97..7b8bf82 100644 --- a/test/simulation_test.dart +++ b/test/simulation_test.dart @@ -37,12 +37,14 @@ void main() { }, skip: "can not find a way to set dirty to true first yet"); }); group("save&load", () { - test("saves the current map, can be loaded by loadSnapshot", () { - var snapshot = sut.map; + test( + "saves a copy of the map which does not change when the actual map changes", + () { sut.saveSnapshot(); - sut.map = MockGrid(); + sut.mergeStateChanges({1: true, 2: true}); + var snapshot = Grid.from(sut.map); - expect(sut.loadSnapshot(), equals(snapshot)); + expect(sut.loadSnapshot(), isNot(equals(snapshot))); }); }); } From 22dabda987fcc3217274831df8c2a6e42a60bd7d Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 19 Oct 2018 20:50:24 +0200 Subject: [PATCH 37/38] Add Save and Load Functions to SimService --- lib/service/simulation_service.dart | 8 ++++++-- test/service/simulation_service_test.dart | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 test/service/simulation_service_test.dart diff --git a/lib/service/simulation_service.dart b/lib/service/simulation_service.dart index 5f538ec..8e0c14f 100644 --- a/lib/service/simulation_service.dart +++ b/lib/service/simulation_service.dart @@ -9,9 +9,10 @@ class SimulationService { static final int DEFAULT_GRID_SIZE = 50; final EngineService _engine; - final Simulation _sim = Simulation(DEFAULT_GRID_SIZE, DEFAULT_GRID_SIZE); + final Simulation _sim; - SimulationService(this._engine) { + SimulationService(this._engine, [Simulation sim]) + : this._sim = sim ?? Simulation(DEFAULT_GRID_SIZE, DEFAULT_GRID_SIZE) { _engine.simulation = _sim; _sim.addRandomPattern(amount: 15, dispersal: 5); } @@ -36,4 +37,7 @@ class SimulationService { void toggleGrid() { _sim.renderEdges = !_sim.renderEdges; } + + void save() => _sim.saveSnapshot(); + void load() => _sim.loadSnapshot(); } diff --git a/test/service/simulation_service_test.dart b/test/service/simulation_service_test.dart new file mode 100644 index 0000000..3816099 --- /dev/null +++ b/test/service/simulation_service_test.dart @@ -0,0 +1,23 @@ +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()); + }); +} From 92e147028e3a134fcbd64a3c0fdd396d07f47b43 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 19 Oct 2018 20:51:14 +0200 Subject: [PATCH 38/38] Add Save and Load Buttons to Interface Fully functional and tested. --- lib/components/controls_component.dart | 8 ++++++-- lib/components/controls_component.html | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/components/controls_component.dart b/lib/components/controls_component.dart index 1d7ee17..538bc27 100644 --- a/lib/components/controls_component.dart +++ b/lib/components/controls_component.dart @@ -29,8 +29,12 @@ class ControlsComponent { engine.step(); } - void onResetClicked() { - sim.reset(); + void onSaveClicked() { + sim.save(); + } + + void onLoadClicked() { + sim.load(); } void onRandomClicked() { diff --git a/lib/components/controls_component.html b/lib/components/controls_component.html index 42e79f7..764ca37 100644 --- a/lib/components/controls_component.html +++ b/lib/components/controls_component.html @@ -1,5 +1,6 @@
- + +