diff --git a/lib/app_component.dart b/lib/app_component.dart index e9b44cc..ae8a931 100644 --- a/lib/app_component.dart +++ b/lib/app_component.dart @@ -5,6 +5,7 @@ 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( @@ -20,8 +21,16 @@ import 'package:rules_of_living/service/engine_service.dart'; ControlsComponent, ConfigurationComponent ], - providers: [materialProviders, ClassProvider(EngineService), ClassProvider(ConfigurationService)], - styleUrls: const ['package:angular_components/app_layout/layout.scss.css', 'app_component.css'], + providers: [ + materialProviders, + ClassProvider(EngineService), + ClassProvider(ConfigurationService), + ClassProvider(ControlService) + ], + styleUrls: const [ + 'package:angular_components/app_layout/layout.scss.css', + 'app_component.css' + ], ) class AppComponent { var name = "World"; diff --git a/lib/components/configuration_component.dart b/lib/components/configuration_component.dart index bd3ae0c..cc1ecd1 100644 --- a/lib/components/configuration_component.dart +++ b/lib/components/configuration_component.dart @@ -1,6 +1,8 @@ import 'package:angular/angular.dart'; import 'package:angular_components/material_button/material_button.dart'; import 'package:angular_components/material_icon/material_icon.dart'; +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'; @@ -8,18 +10,35 @@ import 'package:rules_of_living/service/configuration_service.dart'; @Component( selector: "configuration", templateUrl: "configuration_component.html", - styleUrls: ["configuration_component.css"], + styleUrls: [ + "configuration_component.css" + ], directives: [ MaterialButtonComponent, MaterialIconComponent, MaterialSliderComponent, - MaterialTooltipDirective + MaterialTooltipDirective, + materialInputDirectives, + materialNumberInputDirectives, + NgModel ]) class ConfigurationComponent { final ConfigurationService config; + int get width => config.gridSize.x; + void set width(num value) { + if (value == null || value <= 0) return; + config.setGridSize(x: value.toInt()); + } + + int get height => config.gridSize.y; + void set height(num value) { + if (value == null || value <= 0) return; + config.setGridSize(y: value.toInt()); + } + int get simSpeed => config.simSpeed; - int set simSpeed(int value) => config.simSpeed = value; + void set simSpeed(int value) => config.simSpeed = value; String get speedSliderTooltip => "Simulation Speed: $simSpeed"; diff --git a/lib/components/configuration_component.html b/lib/components/configuration_component.html index a17261d..d8a3e8b 100644 --- a/lib/components/configuration_component.html +++ b/lib/components/configuration_component.html @@ -1,5 +1,21 @@
- - + + + + Ruleset: + + +
\ No newline at end of file diff --git a/lib/components/controls_component.dart b/lib/components/controls_component.dart index 511a7d4..954d94b 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/engine_service.dart'; +import 'package:rules_of_living/service/control_service.dart'; @Component( selector: 'sim-controls', @@ -15,27 +15,27 @@ import 'package:rules_of_living/service/engine_service.dart'; styleUrls: const ["controls_component.css"], ) class ControlsComponent { - final EngineService engine; + final ControlService ctrl; - ControlsComponent(this.engine); + ControlsComponent(this.ctrl); void onStartClicked() { - engine.toggleRunning(); + ctrl.toggleRunning(); } void onStepClicked() { - engine.step(); + ctrl.step(); } void onResetClicked() { - engine.reset(); + ctrl.reset(); } void onRandomClicked() { - engine.addRandomPattern(); + ctrl.addRandomPattern(); } void onClearClicked() { - engine.clear(); + ctrl.clear(); } } diff --git a/lib/components/controls_component.html b/lib/components/controls_component.html index 42e79f7..b4c43f3 100644 --- a/lib/components/controls_component.html +++ b/lib/components/controls_component.html @@ -1,7 +1,7 @@
- + diff --git a/lib/components/simulation_component.dart b/lib/components/simulation_component.dart index 12b7844..312eef4 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/engine_service.dart'; +import 'package:rules_of_living/service/configuration_service.dart'; @Component( selector: 'gol-simulation', @@ -10,9 +10,9 @@ import 'package:rules_of_living/service/engine_service.dart'; providers: [], ) class SimulationComponent implements OnInit { - final EngineService engineService; + final ConfigurationService config; - SimulationComponent(this.engineService); + SimulationComponent(this.config); @override void ngOnInit() { @@ -29,6 +29,6 @@ class SimulationComponent implements OnInit { the canvas did not load correctly :( ''', canvas.width / 2 - 50, canvas.height / 2); - engineService.canvas = canvas; + config.canvas = canvas; } } diff --git a/lib/service/configuration_service.dart b/lib/service/configuration_service.dart index 93b89d7..e11a5e8 100644 --- a/lib/service/configuration_service.dart +++ b/lib/service/configuration_service.dart @@ -1,12 +1,20 @@ +import 'dart:html' as html; +import 'dart:math'; + import 'package:rules_of_living/service/engine_service.dart'; class ConfigurationService { - final EngineService engineService; + final EngineService _es; bool showGrid; int _simSpeed; + ConfigurationService(this._es) { + showGrid = false; + simSpeed = 5; + } + /// Simulation Speed /// /// Sets the number of updates the simulation takes per second. Can range from @@ -15,15 +23,21 @@ class ConfigurationService { int get simSpeed => _simSpeed; void set simSpeed(int val) { _simSpeed = val; - engineService.engine.stepsPerSecond = simSpeed; + _es.engine.stepsPerSecond = simSpeed; } - ConfigurationService(this.engineService) { - showGrid = false; - simSpeed = 5; - } + void set canvas(html.CanvasElement canvas) => _es.engine.canvas = canvas; + html.CanvasElement get canvas => _es.engine.canvas; void toggleGrid() { showGrid = !showGrid; } -} \ No newline at end of file + + void setGridSize({int x, int y}) { + x = x ?? _es.engine.gridSize.x; + y = y ?? _es.engine.gridSize.y; + _es.engine.gridSize = Point(x, y); + } + + Point get gridSize => _es.engine.gridSize; +} diff --git a/lib/service/control_service.dart b/lib/service/control_service.dart new file mode 100644 index 0000000..0066106 --- /dev/null +++ b/lib/service/control_service.dart @@ -0,0 +1,38 @@ +import 'package:rules_of_living/service/engine_service.dart'; + +class ControlService { + EngineService _es; + + ControlService(this._es); + + void run() { + _es.engine.running = true; + } + + void stop() { + _es.engine.running = false; + } + + void toggleRunning() { + _es.engine.running = !_es.engine.running; + } + + void step() { + _es.engine.step(); + } + + void reset() { + _es.engine.reset(); + } + + void addRandomPattern() { + _es.engine.running = false; + _es.engine.addPattern(); + } + + void clear() { + _es.engine.clear(); + } + + bool get isRunning => _es.engine.running; +} diff --git a/lib/service/engine_service.dart b/lib/service/engine_service.dart index d7b0bed..8168735 100644 --- a/lib/service/engine_service.dart +++ b/lib/service/engine_service.dart @@ -1,49 +1,15 @@ -import 'dart:html' as html; - import 'package:rules_of_living/src/Engine.dart'; class EngineService { - Engine _engine; + Engine _uncachedEngineAccess; - Engine get engine => _engine ?? createEngine(null); - - Engine createEngine(html.CanvasElement canvas) { - _engine = Engine(canvas); - return _engine; + Engine get engine => _uncachedEngineAccess ?? _setCachedAndReturn(Engine()); + void set engine(Engine newEngine) { + _uncachedEngineAccess = newEngine; } - void set canvas(html.CanvasElement canvas) => engine.canvas = canvas; - html.CanvasElement get canvas => engine.canvas; - - void run() { - engine.running = true; + Engine _setCachedAndReturn(Engine newEngine) { + engine = newEngine; + return newEngine; } - - 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; - } diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart index be3f31f..b06e53a 100644 --- a/lib/src/Engine.dart +++ b/lib/src/Engine.dart @@ -1,4 +1,5 @@ import 'dart:html' as html; +import 'dart:math'; import 'package:rules_of_living/src/Grid.dart'; @@ -28,9 +29,15 @@ class Engine { // ms stuck in updateloop after which game will declare itself unresponsive final int SAFETY_TIMEOUT = 2000; - // Grid Size TODO add as configurable option - static final GRID_X = 100; - static final GRID_Y = 100; + /// Grid Size + /// + /// Number of cells on x coordinate and y coordinate. Can be set individually. + Point get gridSize => Point(_grid.w, _grid.h); + void set gridSize(Point value) { + if (value.x <= 0 || value.y <= 0) + throw ArgumentError("grid size must not be smaller than 1"); + _grid = Grid(value.x, value.y); + } num _updateLag = 0.0; num _drawLag = 0.0; @@ -41,10 +48,12 @@ class Engine { /// be used if no canvas was defined at engine creation and it should be /// rendered later. html.CanvasElement canvas; - Grid _grid = new Grid(GRID_X, GRID_Y); + Grid _grid; bool running = false; - Engine([this.canvas]) { + Engine([x = 100, y = 100, this.canvas]) { + _grid = Grid(x, y); + _elapsed.start(); _grid.addPattern(amount: 15, dispersal: 5); html.window.animationFrame.then(animFrame); @@ -61,7 +70,7 @@ class Engine { } void clear() { - _grid = new Grid(GRID_X, GRID_Y); + _grid = new Grid(gridSize.x, gridSize.y); running = false; } @@ -111,7 +120,7 @@ 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) _grid.render(canvas, interp); } void addPattern( diff --git a/test/service/configuration_service_test.dart b/test/service/configuration_service_test.dart index 1afa50a..44e23b2 100644 --- a/test/service/configuration_service_test.dart +++ b/test/service/configuration_service_test.dart @@ -1,30 +1,46 @@ -import 'package:rules_of_living/src/Engine.dart'; -@TestOn('browser') +import 'dart:math'; -import 'package:test/test.dart'; +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:mockito/mockito.dart'; +import 'package:rules_of_living/src/Engine.dart'; +@TestOn('browser') +import 'package:test/test.dart'; class MockEngine extends Mock implements Engine {} -class MockEngineService extends Mock implements EngineService { - MockEngine _engine = MockEngine(); - @override - Engine get engine => _engine; -} void main() { - group("simulation speed", () { - ConfigurationService sut; - MockEngineService mes; - setUp(() { - mes = MockEngineService(); - sut = ConfigurationService(mes); - }); + ConfigurationService sut; + EngineService engineService; + MockEngine me; + setUp(() { + me = MockEngine(); + engineService = EngineService(); + engineService.engine = me; + sut = ConfigurationService(engineService); + }); + group("simulation speed", () { test("speed changes propagate to engine", () { sut.simSpeed = 312; - verify(mes.engine.stepsPerSecond=312); + verify(me.stepsPerSecond = 312); + }); + }); + + group("grid size", () { + test("grid changes are sent to engine", () { + sut.setGridSize(x: 512, y: 388); + verify(me.gridSize = Point(512, 388)); + }); + test("grid can be changed solely on x axis", () { + when(me.gridSize).thenReturn(Point(100, 100)); + sut.setGridSize(x: 555); + verify(me.gridSize = Point(555, 100)); + }); + test("grid can be changed solely on y axis", () { + when(me.gridSize).thenReturn(Point(100, 100)); + sut.setGridSize(y: 556); + verify(me.gridSize = Point(100, 556)); }); }); } diff --git a/test/service/engine_service_test.dart b/test/service/engine_service_test.dart index 9d4a226..a792b93 100644 --- a/test/service/engine_service_test.dart +++ b/test/service/engine_service_test.dart @@ -1,19 +1,41 @@ -@TestOn('browser') - -import 'package:test/test.dart'; +import 'package:mockito/mockito.dart'; import 'package:rules_of_living/service/engine_service.dart'; +import 'package:rules_of_living/src/Engine.dart'; +@TestOn('browser') +import 'package:test/test.dart'; + +class MockEngine extends Mock implements Engine {} void main() { EngineService sut; + MockEngine me; setUp(() { + me = MockEngine(); sut = EngineService(); }); + group("Dependency Injection", () { + test("EngineService can be passed a custom Engine", () { + sut.engine = me; - test("EngineService creates an engine on demand", () { - expect(sut.engine, isNotNull); + Engine result = sut.engine; + expect(result, equals(me)); + }); }); + group("caching", () { + test("EngineService creates an engine on demand", () { + Engine result = sut.engine; + expect(result, TypeMatcher()); + }); - test("EngineService returns the cached engine on subsequent requests", () { - expect(sut.engine, allOf(isNotNull, equals(sut.engine))); + test("EngineService returns the cached engine on subsequent requests", () { + Engine result = sut.engine; + expect(sut.engine, equals(result)); + }); + test("caching can be overriden by providing a custom engine", () { + Engine first = sut.engine; + sut.engine = me; + Engine second = sut.engine; + expect(second, isNot(equals(first))); + }); }); } diff --git a/test/src/engine_test.dart b/test/src/engine_test.dart index 43ea13e..fcfefc2 100644 --- a/test/src/engine_test.dart +++ b/test/src/engine_test.dart @@ -1,7 +1,7 @@ import 'dart:html' as html; +import 'dart:math'; @TestOn('browser') - import 'package:rules_of_living/src/Engine.dart'; import 'package:test/test.dart'; @@ -10,23 +10,33 @@ void main() { setUp(() { sut = Engine(); }); + group("canvas", () { + test("Engine can be instantiated without canvas", () { + expect(sut, isNot(throwsNoSuchMethodError)); + }); - test("Engine can be instantiated without canvas", () { - expect(sut, isNot(throwsNoSuchMethodError)); - }); + test("Engine does not throw errors when calling render directly", () { + // anonymous function necessary since throws can not use functions with args + expect(() => sut.render, isNot(throwsNoSuchMethodError)); + }); - test("Engine does not throw errors when calling render directly", () { - // anonymous function necessary since throws can not use functions with args - expect(() => sut.render, isNot(throwsNoSuchMethodError)); - }); + test("Engine does not throw errors when processing without attached canvas", + () { + // anonymous function necessary since throws can not use functions with args + expect(() => sut.process, isNot(throwsNoSuchMethodError)); + }); - test("Engine does not throw errors when processing without attached canvas", () { - // anonymous function necessary since throws can not use functions with args - expect(() => sut.process, isNot(throwsNoSuchMethodError)); + test("setCanvas allows setting a canvas for an engine at any point", () { + sut.canvas = new html.CanvasElement(); + expect(sut.canvas, isNotNull); + }); }); - - test("setCanvas allows setting a canvas for an engine at any point", () { - sut.canvas = new html.CanvasElement(); - 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); + }); }); -} \ No newline at end of file +}