diff --git a/LICENSE.md b/LICENSE.md index 2246719..86a595d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,21 +1,2 @@ -The MIT License (MIT) - -Copyright (c) 2018 Marty Oehme - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file + +Copyright (C) 2018 diff --git a/analysis_options.yaml b/analysis_options.yaml index 78fb282..caef4c8 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,6 +1,6 @@ analyzer: exclude: [build/**] - + strong-mode: true errors: uri_has_not_been_generated: ignore plugins: diff --git a/dart_test.yaml b/dart_test.yaml deleted file mode 100644 index a7c6d02..0000000 --- a/dart_test.yaml +++ /dev/null @@ -1,10 +0,0 @@ -platforms: [chrome] - -tags: - nobrowser: - - bad: - - sad: - - happy: diff --git a/lib/app_component.css b/lib/app_component.css deleted file mode 100644 index 2e1a6f4..0000000 --- a/lib/app_component.css +++ /dev/null @@ -1,11 +0,0 @@ -#wrapper { - display: flex; -} - -#viewport { - flex: 0 0 65%; -} - -#sidebar { - flex: 1; -} \ No newline at end of file diff --git a/lib/app_component.dart b/lib/app_component.dart index 1656739..e5ece87 100644 --- a/lib/app_component.dart +++ b/lib/app_component.dart @@ -1,35 +1,47 @@ import 'package:angular/angular.dart'; -import 'package:angular_components/angular_components.dart'; -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/engine_service.dart'; -import 'package:rules_of_living/service/simulation_service.dart'; + +import 'dart:html' as html; +import 'package:rules_of_living/src/App.dart'; @Component( selector: 'my-app', templateUrl: "app_component.html", - directives: [ - coreDirectives, - MaterialButtonComponent, - MaterialIconComponent, - MaterialSliderComponent, - HeaderComponent, - SimulationComponent, - ControlsComponent, - ConfigurationComponent - ], - providers: [ - materialProviders, - ClassProvider(EngineService), - ClassProvider(SimulationService) - ], - styleUrls: const [ - 'package:angular_components/app_layout/layout.scss.css', - 'app_component.css' - ], + directives: [coreDirectives] ) -class AppComponent { +class AppComponent implements OnInit { var name = "World"; + + App engine; + + @ViewChild("caCanvas") + html.CanvasElement canvas; + + @override + void ngOnInit() { + canvas.context2D.setFillColorRgb(255, 0, 0); + canvas.context2D.fillRect(0, 0, 200, 150); + engine = new App(canvas); + + html.window.animationFrame.then(animFrame); + } + + void animFrame(num now) { + engine.process(now); + html.window.animationFrame.then(animFrame); + } + + void onStartClicked() { + engine.running = !engine.running; + } + + void onStepClicked() { + engine.running = false; + engine.update(); + } + + void onResetClicked() { + engine.reset(); + } + + void onRandomClicked() {} } diff --git a/lib/app_component.html b/lib/app_component.html index 8b57941..366ce7b 100644 --- a/lib/app_component.html +++ b/lib/app_component.html @@ -1,10 +1,20 @@ - -
-
- - -
- +

Cellular Automata - The Rules of Life

+
+ Ruleset: + +
+
+ +
+
+ + + + + Speed:
\ No newline at end of file diff --git a/lib/components/configuration_component.css b/lib/components/configuration_component.css deleted file mode 100644 index 2108951..0000000 --- a/lib/components/configuration_component.css +++ /dev/null @@ -1,4 +0,0 @@ -material-slider { - display: inline-block; - width: 150px; -} \ No newline at end of file diff --git a/lib/components/configuration_component.dart b/lib/components/configuration_component.dart deleted file mode 100644 index abbd2e5..0000000 --- a/lib/components/configuration_component.dart +++ /dev/null @@ -1,54 +0,0 @@ -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'; -import 'package:angular_components/material_input/material_input.dart'; -import 'package:angular_components/material_input/material_number_accessor.dart'; -import 'package:angular_components/material_slider/material_slider.dart'; -import 'package:angular_components/material_tooltip/material_tooltip.dart'; -import 'package:rules_of_living/service/engine_service.dart'; -import 'package:rules_of_living/service/simulation_service.dart'; - -@Component( - selector: "configuration", - templateUrl: "configuration_component.html", - styleUrls: [ - "configuration_component.css" - ], - directives: [ - MaterialButtonComponent, - MaterialIconComponent, - MaterialSliderComponent, - MaterialTooltipDirective, - materialInputDirectives, - materialNumberInputDirectives, - NgModel - ]) -class ConfigurationComponent { - final EngineService engine; - final SimulationService sim; - - int get width => sim.gridSize.x; - void set width(num value) { - if (value == null || value <= 0) return; - sim.gridSize = Point(value, sim.gridSize.y); - } - - int get height => sim.gridSize.y; - void set height(num value) { - if (value == null || value <= 0) return; - sim.gridSize = Point(sim.gridSize.x, value); - } - - int get simSpeed => engine.simSpeed; - void set simSpeed(int value) => engine.simSpeed = value; - - String get speedSliderTooltip => "Simulation Speed: $simSpeed"; - - ConfigurationComponent(this.engine, this.sim); - - void onEdgesClicked() { - sim.toggleGrid(); - } -} diff --git a/lib/components/configuration_component.html b/lib/components/configuration_component.html deleted file mode 100644 index d8a3e8b..0000000 --- a/lib/components/configuration_component.html +++ /dev/null @@ -1,21 +0,0 @@ -
- - - - - Ruleset: - - - -
\ No newline at end of file diff --git a/lib/components/controls_component.css b/lib/components/controls_component.css deleted file mode 100644 index e69de29..0000000 diff --git a/lib/components/controls_component.dart b/lib/components/controls_component.dart deleted file mode 100644 index 538bc27..0000000 --- a/lib/components/controls_component.dart +++ /dev/null @@ -1,48 +0,0 @@ -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', - templateUrl: "controls_component.html", - directives: [ - coreDirectives, - MaterialButtonComponent, - MaterialIconComponent, - MaterialTooltipDirective - ], - providers: [], - styleUrls: const ["controls_component.css"], -) -class ControlsComponent { - final EngineService engine; - final SimulationService sim; - - ControlsComponent(this.engine, this.sim); - - void onStartClicked() { - engine.toggleRunning(); - } - - void onStepClicked() { - engine.step(); - } - - void onSaveClicked() { - sim.save(); - } - - void onLoadClicked() { - sim.load(); - } - - void onRandomClicked() { - sim.addRandomPattern(); - engine.stop(); - } - - void onClearClicked() { - sim.reset(); - } -} diff --git a/lib/components/controls_component.html b/lib/components/controls_component.html deleted file mode 100644 index 764ca37..0000000 --- a/lib/components/controls_component.html +++ /dev/null @@ -1,13 +0,0 @@ -
- - - - - - - - - - - -
\ No newline at end of file diff --git a/lib/components/header_component.dart b/lib/components/header_component.dart deleted file mode 100644 index 83970fd..0000000 --- a/lib/components/header_component.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:angular/angular.dart'; -import 'package:angular_components/angular_components.dart'; - -@Component( - selector: 'app_header', - templateUrl: "header_component.html", - directives: [ - coreDirectives, - MaterialButtonComponent, - MaterialIconComponent, - MaterialSliderComponent - ], - providers: [], - styleUrls: const ['package:angular_components/app_layout/layout.scss.css'], -) -class HeaderComponent {} diff --git a/lib/components/header_component.html b/lib/components/header_component.html deleted file mode 100644 index 2f87a66..0000000 --- a/lib/components/header_component.html +++ /dev/null @@ -1,12 +0,0 @@ -
-
- - - - Cellular Automata -
- -
-
\ No newline at end of file diff --git a/lib/components/simulation_component.dart b/lib/components/simulation_component.dart deleted file mode 100644 index 1fcef71..0000000 --- a/lib/components/simulation_component.dart +++ /dev/null @@ -1,36 +0,0 @@ -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', - templateUrl: "simulation_component.html", - directives: [coreDirectives], - providers: [], -) -class SimulationComponent implements OnInit { - final EngineService engine; - final SimulationService sim; - - SimulationComponent(this.engine, this.sim); - - @override - void ngOnInit() { - html.CanvasElement canvas = html.CanvasElement()..id = "simulation"; - html.querySelector("#simulation")..append(canvas); - canvas.width = 500; - canvas.height = 500; - - canvas.context2D.setFillColorRgb(200, 0, 0); - canvas.context2D.fillRect(0, 0, canvas.width, canvas.height); - canvas.context2D.setFillColorRgb(0, 255, 0); - canvas.context2D.fillText(''' - If you see this - - the canvas did not load correctly :( - ''', canvas.width / 2 - 50, canvas.height / 2); - sim.canvas = canvas; - } -} diff --git a/lib/components/simulation_component.html b/lib/components/simulation_component.html deleted file mode 100644 index 1e5f113..0000000 --- a/lib/components/simulation_component.html +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file diff --git a/lib/service/engine_service.dart b/lib/service/engine_service.dart deleted file mode 100644 index b276d28..0000000 --- a/lib/service/engine_service.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:rules_of_living/src/Engine.dart'; -import 'package:rules_of_living/src/Simulation.dart'; - -class EngineService { - Engine _uncachedEngineAccess; - - EngineService() { - simSpeed = 5; - } - - Engine get engine => _uncachedEngineAccess ?? _setCachedAndReturn(Engine()); - void set engine(Engine newEngine) { - _uncachedEngineAccess = newEngine; - } - - Engine _setCachedAndReturn(Engine newEngine) { - engine = newEngine; - return newEngine; - } - - void run() { - engine.running = true; - } - - void stop() { - engine.running = false; - } - - void toggleRunning() { - engine.running = !engine.running; - } - - void step() { - engine.step(); - } - - /// Simulation Speed - /// - /// Sets the number of updates the simulation takes per second. Can range from - /// 1 to arbitrarily high numbers (though setting it too high can potentially - /// make the app brittle). - int get simSpeed => engine.stepsPerSecond; - void set simSpeed(int val) => engine.stepsPerSecond = val; - - bool get isRunning => engine.running; - - void set simulation(Simulation value) => engine.simulation = value; -} diff --git a/lib/service/simulation_service.dart b/lib/service/simulation_service.dart deleted file mode 100644 index 8e0c14f..0000000 --- a/lib/service/simulation_service.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:html' as html; -import 'dart:math'; - -import 'package:rules_of_living/service/engine_service.dart'; -import 'package:rules_of_living/src/Simulation.dart'; - -class SimulationService { - // DEFAULT VALUES - static final int DEFAULT_GRID_SIZE = 50; - - final EngineService _engine; - final Simulation _sim; - - SimulationService(this._engine, [Simulation sim]) - : this._sim = sim ?? Simulation(DEFAULT_GRID_SIZE, DEFAULT_GRID_SIZE) { - _engine.simulation = _sim; - _sim.addRandomPattern(amount: 15, dispersal: 5); - } - - void reset() { - _sim.reset(); - } - - void addRandomPattern() { - _sim.addRandomPattern(); - } - - Point get gridSize => _sim.gridSize; - void set gridSize(Point size) { - _sim.gridSize = size; - } - - //TODO split into RenderService when rendering is decoupled from engine. - html.CanvasElement get canvas => _engine.engine.canvas; - void set canvas(html.CanvasElement canvas) => _engine.engine.canvas = canvas; - - void toggleGrid() { - _sim.renderEdges = !_sim.renderEdges; - } - - void save() => _sim.saveSnapshot(); - void load() => _sim.loadSnapshot(); -} diff --git a/lib/src/App.dart b/lib/src/App.dart new file mode 100644 index 0000000..a571dba --- /dev/null +++ b/lib/src/App.dart @@ -0,0 +1,66 @@ +import 'dart:html' as html; + +import 'package:rules_of_living/src/Grid.dart'; + +class App { + // Elapsed Time Counter - useful for Safety Timeout + Stopwatch _elapsed = new Stopwatch(); + + // Game Tick Rate - *does* impact game speed + int _MS_PER_STEP = 1000 ~/ 3; + + // Max Frame (i.e. Rendering) rate - does *not* impact game speed + final int _MS_PER_FRAME = 1000 ~/ 30; + + // ms stuck in updateloop after which game will declare itself unresponsive + final int SAFETY_TIMEOUT = 2000; + + num _updateLag = 0.0; + num _drawLag = 0.0; + + + final html.CanvasElement canvas; + Grid grid = new Grid(100,100); + bool running = false; + + App(this.canvas) { + _elapsed.start(); + } + + void reset () { + grid = new Grid(100, 100); + running = false; + } + + void process(num now) { + _drawLag+= _elapsed.elapsedMilliseconds; + _updateLag += _elapsed.elapsedMilliseconds; + _elapsed.reset(); + + while (_updateLag >= _MS_PER_STEP) { + if (_elapsed.elapsedMilliseconds > SAFETY_TIMEOUT) { + // TODO stub - give warning etc when this occurs + print("ERROR STUCK IN UPDATE LOOP"); + break; + } + if (running == true) update(); + _updateLag -= _MS_PER_STEP; + } + + if (_drawLag >= _MS_PER_FRAME) { + render(_updateLag / _MS_PER_STEP); + _drawLag = 0; + } + } + + void update() { +// print("updating"); + grid.update(); + } + + + void render([num interp]) { +// print("rendering"); + grid.render(canvas, interp); + } +} \ No newline at end of file diff --git a/lib/src/Cell.dart b/lib/src/Cell.dart new file mode 100644 index 0000000..5301fb6 --- /dev/null +++ b/lib/src/Cell.dart @@ -0,0 +1,32 @@ +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; + + void advanceState() { + this.state = this.nextState; + this.nextState = false; + + this.dirty = true; + } + + void update(int neighbors) { + if (state == true) { + surviveRules.forEach( (Rule rule) { + if(rule.evaluate(neighbors) == true) this.nextState = true; + }); + } else { + birthRules.forEach((Rule rule) { + if (rule.evaluate(neighbors) == true) this.nextState = true; + }); + } + } +} diff --git a/lib/src/Engine.dart b/lib/src/Engine.dart deleted file mode 100644 index 5653bb9..0000000 --- a/lib/src/Engine.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'dart:html' as html; - -import 'package:rules_of_living/src/Simulation.dart'; - -class Engine { - // Elapsed Time Counter - useful for Safety Timeout - Stopwatch _elapsed = new Stopwatch(); - - /// Game Tick Rate - /// - /// *does* impact game speed; dictates how long each logic step takes. Only - /// interesting for engine internal calculations, to set simulation speed - /// from the outside use stepsPerSecond instead. - int _MS_PER_STEP = 1000 ~/ 3; - - /// Set Logic Updates per Second - /// - /// Dictates simulation speed. Sets the amount of (logic) updates per second. - /// Translations between number of updates and their timings is not exact so - /// comparing this variable to fixed ints might not yield the expected results. - /// Does not affect render frequency, which is handled by framesPerSecond. - int get stepsPerSecond => 1000 ~/ _MS_PER_STEP; - set stepsPerSecond(int val) => _MS_PER_STEP = 1000 ~/ val; - - // Max Frame (i.e. Rendering) rate - does *not* impact game speed - final int _MS_PER_FRAME = 1000 ~/ 30; - - // ms stuck in updateloop after which game will declare itself unresponsive - final int SAFETY_TIMEOUT = 2000; - - num _updateLag = 0.0; - num _drawLag = 0.0; - - /// The Canvas to Paint on - /// - /// Manually define or change the canvas the engine should render to. Should - /// be used if no canvas was defined at engine creation and it should be - /// rendered later. - html.CanvasElement canvas; - Simulation _simulation; - bool running = false; - - Engine() { - _elapsed.start(); - html.window.animationFrame.then(animFrame); - } - - void animFrame(num now) { - int elapsed = _elapsed.elapsedMilliseconds; - _elapsed.reset(); - process(elapsed, SAFETY_TIMEOUT, update: this.update, render: this.render); - html.window.animationFrame.then(animFrame); - } - - void process(int elapsed, int timeOut, {Function update, Function render}) { - _drawLag += elapsed; - _updateLag += elapsed; - - while (running == true && - _shouldUpdate(_updateLag, elapsed, timeOut) == true) { - _updateLag -= _MS_PER_STEP; - if (update == null) break; - update(); - } - - if (_drawLag >= _MS_PER_FRAME) { - _drawLag = 0; - if (render == null) return; - render(_updateLag / _MS_PER_STEP); - } - } - - bool _shouldUpdate(int updateLag, int elapsed, int timeOut) { - if (updateLag < _MS_PER_STEP) return false; - if (elapsed > timeOut) throw StackOverflowError; - - return true; - } - - /// Update Engine Logic - /// - /// Updates the logic of the engine by one tick. Should usually not be called - /// directly, since it is automatically taken care of by the processing function. - /// If simulation should be advanced manually one time, prefer using step(). - void update() { - if (_simulation == null) return; - - Map simulationUpdate = _simulation.update(); - _simulation.mergeStateChanges(simulationUpdate); - - if (simulationUpdate.length == 0) running = false; - } - - /// Advances Logic One Update - /// - /// Moves logic of the engine one step forward and pauses the - /// simulation. Does not automatically re-render the new state - /// (though this should usually not pose a problem). - void step() { - update(); - running = false; - } - - /// Renders the Current Simulation State - /// - /// Renders the simulation once. Will usually automatically be called by - /// the internal engine processing. Does not do anything if no canvas is - /// defined. - void render([num interp]) { - if (canvas == null || _simulation == null) return; - - _simulation.render(canvas, interp); - } - - void set simulation(Simulation value) => _simulation = value; -} diff --git a/lib/src/Grid.dart b/lib/src/Grid.dart index e2a62fc..b0d7e9f 100644 --- a/lib/src/Grid.dart +++ b/lib/src/Grid.dart @@ -1,68 +1,136 @@ -import 'dart:core'; -import 'dart:math'; +import 'dart:html' as html; -import 'package:collection/collection.dart'; +import 'package:rules_of_living/src/Cell.dart'; +import 'package:rules_of_living/src/Rule.dart'; -class Grid extends DelegatingList { - final List _internal; - final width; - final height; +class Grid { + final int w; + final int h; + final List> map; - Grid(int width, int height) : this._(List(width * height), width, height); + bool _dirty = true; - Grid.fill(int width, int height, E fillValue) - : this._(List.filled(width * height, fillValue), width, height); + Grid(int w, int h) + : this.w = w, + this.h = h, + this.map = new List() { + map.addAll(_buildGrid(w, h)); - Grid.from(Grid l) - : this._(List.from(l.getRange(0, l.length)), l.width, l.height); + // BLINKER +// map[5][5].state = true; +// map[5][6].state = true; +// map[6][5].state = true; +// map[6][6].state = true; +// +// map[7][7].state = true; +// map[7][8].state = true; +// map[8][7].state = true; +// map[8][8].state = true; - Grid.fromList(List l, int width) : this._(l, width, l.length ~/ width); + // SPACESHIP + setState(1 + 5, 0 + 5, true); + setState(2 + 5, 1 + 5, true); + setState(2 + 5, 2 + 5, true); + setState(1 + 5, 2 + 5, true); + setState(0 + 5, 2 + 5, true); - Grid._(l, int w, int h) - : _internal = l, - width = w, - height = h, - super(l); - - /// Return element at coordinate position - /// - /// Returns the corresponding element after checking the parameters - /// for the correct constraints along the width and height of the grid. - /// Throws [RangeError] if outside of constraints. Preferred method - /// to access elements via coordinates. - E get(int x, int y) { - int i = toIndex(x, y); - if (i >= length || x > width - 1) throw RangeError.index(i, this); - return _internal[i]; + print("Grid creation finished"); } - /// Sets element at coordinate position - /// - /// Sets the corresponding element to the [E] parameter [value] passed in. - /// Checks against the grid size constraints beforehand and throws - /// [RangeError] if outside of constraints. Preferred method to set - /// elements via coordinates. - void set(int x, int y, E value) { - int i = toIndex(x, y); - if (i >= length || x > width - 1) throw RangeError.index(i, this); - _internal[i] = value; + void setState(int x, int y, bool state) { + if (y < map.length && x < map[y].length) map[y][x].state = state; } - /// Calculate list index from coordinates - /// - /// Can be used to get the correct index from coordinates passed in. - /// Will only calculate the index, not take into consideration any grid size - /// constraints etc; use [get] for that (generally recommended). - int toIndex(int x, int y) => (x < 0 || y < 0) - ? throw RangeError("Coordinates for Grid Indexing must not be negative.") - : y * width + x; + List> _buildGrid(int w, int h) { + print("grid being created"); + List> grid = new List(h); + // GENERAL RULE LAYOUT + 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; + }); - /// Calculate coordinates from list index - /// - /// Calculates the 2-D array coordinates from the corresponding list index - /// passed in. Relies on grid width to calculate coordinates. Does not check - /// against grid size constraints; use [set] for that (generally recommended). - Point toCoordinates(int index) => (index < 0) - ? throw RangeError("Index for Grid Coordinates must not be negative") - : Point(index % width, index ~/ width); + // DEBUG RULE TESTING FOR PATTERNS + Rule coagSurvive = new Rule((int n) { + if (n==1) return true; + return false; + }); + Rule coagBirth = new Rule((int n) { + if (n==1) return true; + return false; + }); + + for (int y = 0; y < h; y++) { + grid[y] = new List(w); + for (int x = 0; x < w; x++) { + // GIVES RULES FOR CONWAY GAME OF LIFE BY DEFAULT S23/B3 + Cell cell = new Cell(); +// cell.surviveRules.add(twoTrue); + cell.surviveRules.add(coagSurvive); + cell.birthRules.add(coagBirth); + + grid[y][x] = cell; + } + } + return grid; + } + + void update() { + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + // DEFAULTS TO CONWAY GAME OF LIFE RANGE OF ONE + map[y][x].update(getSurroundingNeighbors(x, y, 1)); + if (!_dirty && map[y][x].dirty) _dirty = true; + } + } + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + // DEFAULTS TO CONWAY GAME OF LIFE RANGE OF ONE + map[y][x].advanceState(); + } + } + } + + int getSurroundingNeighbors(int x, int y, int range) { + int count = 0; + for (int iy = y - range; iy <= y + range; iy++) { + for (int ix = x - range; ix <= x + range; ix++) { + if (ix > 0 && + iy > 0 && + iy < map.length && + ix < map[iy].length && + map[iy][ix].state == true && + !(x == ix && y == iy)) { + count++; + } + } + } + return count; + } + + void render(html.CanvasElement canvas, [num interp]) { + // only renders if any cells changed between renders + if (!_dirty) return; + + html.CanvasRenderingContext2D ctx = canvas.getContext('2d'); + int brickW = (canvas.width ~/ map[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++) { + 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; + } } diff --git a/lib/src/Rule.dart b/lib/src/Rule.dart new file mode 100644 index 0000000..3a5eb1a --- /dev/null +++ b/lib/src/Rule.dart @@ -0,0 +1,5 @@ +class Rule { + final Function evaluate; + + Rule(this.evaluate); +} \ No newline at end of file diff --git a/lib/src/Simulation.dart b/lib/src/Simulation.dart deleted file mode 100644 index 0955c9a..0000000 --- a/lib/src/Simulation.dart +++ /dev/null @@ -1,163 +0,0 @@ -import 'dart:html' as html; -import 'dart:math' as math; -import 'dart:math'; - -import 'package:rules_of_living/src/Grid.dart'; -import 'package:rules_of_living/src/rules/GameOfLife.dart'; -import 'package:rules_of_living/src/rules/RuleSet.dart'; - -enum CellPattern { SpaceShip, Blinker } - -class Simulation { - Grid map; - Grid _snapshot; - - RuleSet rules = GameOfLife(); - - bool _dirty = true; - bool get dirty => _dirty; - - bool _renderEdges = true; - bool get renderEdges => _renderEdges; - - int _amount; - int _dispersal; - - Point get gridSize => Point(map.width, map.height); - void set gridSize(Point value) { - if (value.x <= 0 || value.y <= 0) - throw ArgumentError("grid size must not be smaller than 1"); - map = Grid(value.x, value.y); - } - - Simulation(int w, int h) : this.map = new Grid(w, h) { - this.map = reset(); - } - - Simulation.fromGrid(Grid map) : this.map = map; - - Grid reset([Grid map]) { - map ??= this.map; - _dirty = true; - map.setAll(0, List.filled(map.length, false)); - return map; - } - - void addRandomPattern({int amount, int dispersal}) { - int _startingSeed = DateTime.now().millisecondsSinceEpoch; - math.Random rng = new math.Random(_startingSeed); - _amount = amount ?? rng.nextInt(20); - _dispersal = dispersal ?? 10; - int cx = rng.nextInt(map.width ~/ 3) + (map.width ~/ 3); - int cy = rng.nextInt(map.height ~/ 3) + (map.height ~/ 3); - - int sanityCheck = 0; - for (var i = 0; i < (_amount); i++) { - sanityCheck++; - getCellState(cx, cy) - ? i-- - : setCellState( - cx + rng.nextInt(_dispersal), cy + rng.nextInt(_dispersal), true); - if (sanityCheck > 100 && sanityCheck > i * 3) break; - } - - _dirty = true; - } - - void setCellState(int x, int y, bool state) { - if (y >= map.height || x >= map.width) return null; - - state ? map.set(x, y, true) : map.set(x, y, false); - } - - bool getCellState(int x, int y) { - if (y >= map.height || x >= map.width) return null; - - return map.get(x, y); - } - - void toggleCellState(int x, int y) { - if (y >= map.height || x >= map.width) return null; - - getCellState(x, y) == null - ? setCellState(x, y, true) - : setCellState(x, y, false); - } - - Map update() { - Map stateChanges = calculateNextState(map); - return stateChanges; - } - - void mergeStateChanges(Map stateChanges) { - stateChanges.forEach((i, el) => map[i] = el); - if (stateChanges.length != 0) _dirty = true; - } - - Map calculateNextState(Grid oldState) { - Map stateChanges = Map(); - - for (int i = 0; i < map.length; i++) { - math.Point p = map.toCoordinates(i); - bool cell = map[i]; - int neighbors = getNeighbors(p.x, p.y, rules.range); - if (cell == false && rules.checkBirth(neighbors) == true) { - stateChanges[i] = true; - } else if (cell == true && rules.checkSurvival(neighbors) == false) { - stateChanges[i] = false; - } - } - return stateChanges; - } - - int getNeighbors(int x, int y, int range) { - int count = 0; - for (int ix = -range + x; ix <= range + x; ix++) { - for (int iy = -range + y; iy <= range + y; iy++) { - if (ix >= 0 && - iy >= 0 && - ix < map.width && - iy < map.height && - getCellState(ix, iy) == true && - !(x == ix && y == iy)) count++; - } - } - return count; - } - - void render(html.CanvasElement canvas, [num interp]) { - // only renders if any cells changed between renders - if (!_dirty) return; - - html.CanvasRenderingContext2D ctx = canvas.getContext('2d'); - int brickW = (canvas.width ~/ map.width); - int brickH = (canvas.height ~/ map.height); - ctx.clearRect(0, 0, canvas.width, canvas.height); - for (int i = 0; i < map.length; i++) { - math.Point p = map.toCoordinates(i); - if (_renderEdges) { - ctx.setStrokeColorRgb(100, 100, 100); - ctx.strokeRect(p.x * brickW, p.y * brickH, brickW, brickH); - } - - if (map[i] == true) - ctx.setFillColorRgb(155, 155, 255); - else - ctx.setFillColorRgb(0, 0, 0); - ctx.fillRect(p.x * brickW, p.y * brickH, brickW, brickH); - } - _dirty = false; - } - - void set renderEdges(bool on) { - _renderEdges = on; - _dirty = true; - } - - void saveSnapshot() => _snapshot = Grid.from(map); - Grid loadSnapshot() { - map = Grid.from(_snapshot); - _dirty = true; - return map; - } -} diff --git a/lib/src/rules/CellPattern.dart b/lib/src/rules/CellPattern.dart deleted file mode 100644 index ed4cae7..0000000 --- a/lib/src/rules/CellPattern.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'dart:math'; - -import 'package:collection/collection.dart'; - -class CellPattern extends DelegatingList { - final String _name; - CellPattern(String name, List base) - : _name = name, - super(base); - - String get name => _name; - - @override - String toString() { - return "$name: ${super.toString()}"; - } -} diff --git a/lib/src/rules/GameOfLife.dart b/lib/src/rules/GameOfLife.dart deleted file mode 100644 index edb2dde..0000000 --- a/lib/src/rules/GameOfLife.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:math'; - -import 'package:rules_of_living/src/rules/RuleSet.dart'; -import 'package:rules_of_living/src/rules/CellPattern.dart'; - -class GameOfLife implements RuleSet { - int range = 1; - List patterns = [ - // Two blocks, offset - // ## - // ## - CellPattern("Blinker", [ - Point(0, 0), - Point(1, 0), - Point(0, 1), - Point(1, 1), - Point(2, 2), - Point(3, 2), - Point(2, 3), - Point(3, 3) - ]), - // A 'gliding' Spaceship - // # - // # - // ### - CellPattern("SpaceShip", [ - Point(1, 0), - Point(2, 1), - Point(2, 2), - Point(1, 2), - Point(0, 2), - ]) - ]; - - bool checkSurvival(int neighbors) => - neighbors == 2 || neighbors == 3 ? true : false; - bool checkBirth(int neighbors) => neighbors == 3 ? true : false; -} diff --git a/lib/src/rules/RuleSet.dart b/lib/src/rules/RuleSet.dart deleted file mode 100644 index 03aeb36..0000000 --- a/lib/src/rules/RuleSet.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:rules_of_living/src/rules/CellPattern.dart'; - -abstract class RuleSet { - int range; - List patterns; - - bool checkSurvival(int neighbors); - bool checkBirth(int neighbors); -} diff --git a/pubspec.yaml b/pubspec.yaml index a63ea9a..9dc2faa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,16 +5,14 @@ author: Marty Oehme homepage: https://www.martyoehme.org/ environment: - sdk: '>=2.0.0' + sdk: '>=2.0.0-dev.66.0 <2.0.0' dependencies: angular: ^5.0.0-beta - angular_components: ^0.9.0-beta dev_dependencies: angular_test: ^2.0.0-beta build_runner: ^0.9.0 - build_test: ^0.10.3+1 + build_test: ^0.10.2 build_web_compilers: ^0.4.0 - test: ^1.3.0 - mockito: ^3.0.0 + test: ^1.0.0 diff --git a/test/app_test_disable.dart b/test/app_test.dart similarity index 100% rename from test/app_test_disable.dart rename to test/app_test.dart diff --git a/test/service/engine_service_test.dart b/test/service/engine_service_test.dart deleted file mode 100644 index a792b93..0000000 --- a/test/service/engine_service_test.dart +++ /dev/null @@ -1,41 +0,0 @@ -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; - - 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", () { - 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/service/simulation_service_test.dart b/test/service/simulation_service_test.dart deleted file mode 100644 index 3816099..0000000 --- a/test/service/simulation_service_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:rules_of_living/service/engine_service.dart'; -import 'package:rules_of_living/service/simulation_service.dart'; -import 'package:rules_of_living/src/Simulation.dart'; -import 'package:test/test.dart'; -import 'package:mockito/mockito.dart'; - -class MockSimulation extends Mock implements Simulation {} - -class MockEngineService extends Mock implements EngineService {} - -void main() { - SimulationService sut; - MockSimulation mockSim = MockSimulation(); - setUp(() => sut = SimulationService(MockEngineService(), mockSim)); - test("calling save calls through to Simulation.saveSnapshot", () { - sut.save(); - verify(mockSim.saveSnapshot()); - }); - test("calling load calls through to Simulation.loadSnapshot", () { - sut.load(); - verify(mockSim.loadSnapshot()); - }); -} diff --git a/test/simulation_test.dart b/test/simulation_test.dart deleted file mode 100644 index 7b8bf82..0000000 --- a/test/simulation_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:math'; -import 'package:mockito/mockito.dart'; -import 'package:rules_of_living/src/Grid.dart'; -import 'package:test/test.dart'; - -import 'package:rules_of_living/src/Simulation.dart'; - -class MockGrid extends Mock implements Grid {} - -void main() { - Simulation sut; - setUp(() { - sut = Simulation(10, 10); - }); - group("gridSize", () { - test( - "returns the width and height of the underlying grid", - () => expect( - sut.gridSize, equals(Point(sut.map.width, sut.map.height)))); - test("sets the underlying grid width and height", () { - sut.gridSize = Point(2, 3); - expect(sut.gridSize, equals(Point(2, 3))); - }); - test("creates a new underlying grid on resizing", () { - var oldMap = sut.map; - sut.gridSize = Point(10, 10); - expect(sut.map, isNot(oldMap)); - }); - }); - group("reset", () { - test("returns a map filled with 'false' ", () { - expect(sut.reset(), allOf(TypeMatcher(), isNot(contains(true)))); - }); - test("sets the simulation to need re-rendering", () { - sut.reset(); - expect(sut.dirty, true); - }, skip: "can not find a way to set dirty to true first yet"); - }); - group("save&load", () { - test( - "saves a copy of the map which does not change when the actual map changes", - () { - sut.saveSnapshot(); - sut.mergeStateChanges({1: true, 2: true}); - var snapshot = Grid.from(sut.map); - - expect(sut.loadSnapshot(), isNot(equals(snapshot))); - }); - }); -} diff --git a/test/src/engine_test.dart b/test/src/engine_test.dart deleted file mode 100644 index f602069..0000000 --- a/test/src/engine_test.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'dart:html' as html; -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'; - -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)); - }); - - 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("setCanvas allows setting a canvas for an engine at any point", () { - sut.canvas = new html.CanvasElement(); - expect(sut.canvas, isNotNull); - }); - }); -} diff --git a/test/src/grid_test.dart b/test/src/grid_test.dart deleted file mode 100644 index fce1b3f..0000000 --- a/test/src/grid_test.dart +++ /dev/null @@ -1,189 +0,0 @@ -import 'dart:math'; - -import 'package:rules_of_living/src/Grid.dart'; -import 'package:test/test.dart'; - -@Tags(const ["nobrowser"]) -void main() { - group("Instantiation", () { - List l; - setUp(() { - l = [ - "Hey", - "you", - "me", - "together", - "Hello", - "World", - "I", - "am", - "ready." - ]; - }); - test("gets created with the correct length for given quadratic gridsize", - () { - Grid sut = Grid(3, 3); - expect(sut.length, 9); - }, tags: const ["happy"]); - test("gets created with the correct length for given rectangular gridsize", - () { - Grid sut = Grid(87, 85); - expect(sut.length, 7395); - }, tags: const ["happy"]); - group(".from", () { - test("copies the content of another grid on .from Constructor call", () { - Grid original = Grid(2, 2); - original[0] = "Hey"; - original[1] = "you"; - original[2] = "me"; - original[3] = "together"; - - Grid sut = Grid.from(original); - expect(sut, containsAllInOrder(["Hey", "you", "me", "together"])); - }, tags: const ["happy"]); - test("copies the length of another grid on .from Constructor call", () { - Grid original = Grid(2, 2); - original[0] = "Hey"; - original[1] = "you"; - original[2] = "me"; - original[3] = "together"; - - Grid sut = Grid.from(original); - expect(sut.length, 4); - }, tags: const ["happy"]); - }); - group(".fromList", () { - test("sets the length for list passed in on .fromList Constructor call", - () { - Grid sut = Grid.fromList(l, 3); - - expect(sut.length, 9); - }, tags: const ["happy"]); - test("sets the contents of list passed in on .fromList Constructor call", - () { - Grid sut = Grid.fromList(l, 3); - - expect(sut[3], "together"); - }, tags: const ["happy"]); - test( - "sets the correct height for list passed in on .fromList Constructor call", - () { - Grid sut = Grid.fromList(l, 3); - - expect(sut.width, 3); - }, tags: const ["happy"]); - }); - group(".fill", () { - test("fills list with results of function passed in", () { - Grid sut = Grid.fill(3, 3, "testValue"); - expect( - sut, - containsAllInOrder([ - "testValue", - "testValue", - "testValue", - "testValue", - "testValue", - "testValue", - "testValue", - "testValue", - "testValue" - ])); - }, tags: const ["happy"]); - }); - }); - group("toIndex", () { - Grid sut; - setUp(() { - sut = Grid(3, 3); - }); - test("throws RangeError on negative x argument", () { - expect(() => sut.toIndex(-1, 2), throwsA(isRangeError)); - }, tags: const ["bad"]); - test("throws RangeError on negative y argument", () { - expect(() => sut.toIndex(2, -1), throwsA(isRangeError)); - }, tags: const ["bad"]); - test("calculates correct index for first element", () { - expect(sut.toIndex(0, 0), equals(0)); - }, tags: const ["happy"]); - test("calculates correct index for last element", () { - expect(sut.toIndex(2, 2), equals(8)); - }, tags: const ["happy"]); - test("calculates correct index for element on first row", () { - expect(sut.toIndex(2, 0), equals(2)); - }, tags: const ["happy"]); - test("calculates correct index for example element", () { - expect(sut.toIndex(1, 1), equals(4)); - }, tags: const ["happy"]); - }); - group("coordinates getter", () { - Grid sut; - setUp(() { - sut = Grid(3, 3); - sut.setAll(0, - ["Hey", "you", "me", "together", "Hello", null, "I", "am", "ready."]); - }); - test("returns null if no element exists at the position requested", () { - expect(sut.get(2, 1), null); - }, tags: const ["sad"]); - test("throws RangeError if requesting element outside of grid width", () { - expect(() => sut.get(4, 1), throwsRangeError); - }, tags: const ["bad"]); - test("throws RangeError if requesting element outside of grid height", () { - expect(() => sut.get(1, 4), throwsRangeError); - }, tags: const ["bad"]); - test("returns element at correct index", () { - expect(sut.get(1, 0), "you"); - }, tags: const ["happy"]); - test("returns last element correctly", () { - expect(sut.get(2, 2), "ready."); - }, tags: const ["happy"]); - }); - group("toCoords", () { - Grid sut; - setUp(() { - sut = Grid(3, 3); - }); - test("throws RangeError on negative index argument", () { - expect(() => sut.toCoordinates(-1), throwsA(isRangeError)); - }, tags: const ["bad"]); - test("calculates correct index for first element", () { - expect(sut.toCoordinates(0), equals(Point(0, 0))); - }, tags: const ["happy"]); - test("calculates correct index for last element", () { - expect(sut.toCoordinates(8), equals(Point(2, 2))); - }, tags: const ["happy"]); - test("calculates correct index for last element on first row", () { - expect(sut.toCoordinates(2), equals(Point(2, 0))); - }, tags: const ["happy"]); - test("calculates correct index for example element", () { - expect(sut.toCoordinates(6), equals(Point(0, 2))); - }, tags: const ["happy"]); - }); - group("coordinates setter", () { - Grid sut; - setUp(() { - sut = Grid(3, 3); - sut.setAll(0, - ["Hey", "you", "me", "together", "Hello", null, "I", "am", "ready."]); - }); - test("sets element to null if passing null in", () { - sut.set(1, 1, null); - expect(sut.get(1, 1), null); - }, tags: const ["sad"]); - test("throws RangeError if setting element outside of grid width", () { - expect(() => sut.set(4, 1, "testValue"), throwsRangeError); - }, tags: const ["bad"]); - test("throws RangeError if setting element outside of grid height", () { - expect(() => sut.set(1, 4, "testValue"), throwsRangeError); - }, tags: const ["bad"]); - test("sets element at correct index", () { - sut.set(1, 0, "testValue"); - expect(sut.get(1, 0), "testValue"); - }, tags: const ["happy"]); - test("sets last element correctly", () { - sut.set(2, 2, "testValue"); - expect(sut.get(2, 2), "testValue"); - }, tags: const ["happy"]); - }); -} diff --git a/test/src/rules/cellpattern_test.dart b/test/src/rules/cellpattern_test.dart deleted file mode 100644 index a7989f5..0000000 --- a/test/src/rules/cellpattern_test.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'dart:math'; - -import 'package:rules_of_living/src/rules/CellPattern.dart'; -import 'package:test/test.dart'; - -void main() { - CellPattern sut; - setUp(() { - sut = CellPattern("testPattern", [Point(1, 1), Point(0, 0), Point(-1, -1)]); - }); - group("Naming", () { - test("contains the name passed in for name variable", - () => expect(sut.name, "testPattern")); - test( - "Contains the name passed in on being formatted as String", - () => expect(sut.toString(), - "testPattern: [Point(1, 1), Point(0, 0), Point(-1, -1)]")); - }); -} diff --git a/test/src/rules/gameoflife_test.dart b/test/src/rules/gameoflife_test.dart deleted file mode 100644 index 565e4e1..0000000 --- a/test/src/rules/gameoflife_test.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:rules_of_living/src/rules/GameOfLife.dart'; -import 'package:test/test.dart'; - -void main() { - GameOfLife sut; - setUp(() { - sut = GameOfLife(); - }); - group("BirthRules", () { - test("will return true when being passed three neighbors", - () => expect(sut.checkBirth(3), true)); - test("will return false when being passed zero neighbors", - () => expect(sut.checkBirth(0), false)); - test("will return false when being passed two neighbors", - () => expect(sut.checkBirth(2), false)); - }); - group("SurviveRules", () { - test("will return true when being passed two neighbors", - () => expect(sut.checkSurvival(2), true)); - test("will return true when being passed three neighbors", - () => expect(sut.checkSurvival(3), true)); - test("will return false when being passed 0 neighbors", - () => expect(sut.checkSurvival(0), false)); - test("will return false when being passed more than 3 neighbors", - () => expect(sut.checkSurvival(4), false)); - }); -} diff --git a/web/index.html b/web/index.html index d5432b4..35bd3cb 100644 --- a/web/index.html +++ b/web/index.html @@ -15,10 +15,11 @@ - - + + + diff --git a/web/styles.css b/web/styles.css index e0b0d35..ef7a186 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1,126 +1,117 @@ @import url(https://fonts.googleapis.com/css?family=Roboto); @import url(https://fonts.googleapis.com/css?family=Material+Icons); -html, body { - height: 100%; - margin: 0; - padding: 0; - width: 100%; - font-family: 'Roboto', sans-serif; - +/* Master Styles */ +h1 { + color: #369; + font-family: Arial, Helvetica, sans-serif; + font-size: 250%; +} +h2, h3 { + color: #444; + font-family: Arial, Helvetica, sans-serif; + font-weight: lighter; +} +body { + margin: 2em; +} +body, input[text], button { + color: #888; + font-family: Cambria, Georgia; +} +a { + cursor: pointer; + cursor: hand; +} +button { + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} +button:disabled { + background-color: #eee; + color: #aaa; + cursor: auto; +} +label { + padding-right: 0.5em; +} +/* Navigation link styles */ +nav a { + padding: 5px 10px; + text-decoration: none; + margin-right: 10px; + margin-top: 10px; + display: inline-block; + background-color: #eee; + border-radius: 4px; +} +nav a:visited, a:link { + color: #607D8B; +} +nav a:hover { + color: #039be5; + background-color: #CFD8DC; +} +nav a.active { + color: #039be5; } -/*!* Master Styles *!*/ -/*h1 {*/ - /*color: #369;*/ - /*font-family: Arial, Helvetica, sans-serif;*/ - /*font-size: 250%;*/ -/*}*/ -/*h2, h3 {*/ - /*color: #444;*/ - /*font-family: Arial, Helvetica, sans-serif;*/ - /*font-weight: lighter;*/ -/*}*/ -/*body {*/ - /*margin: 2em;*/ -/*}*/ -/*body, input[text], button {*/ - /*color: #888;*/ - /*font-family: Cambria, Georgia;*/ -/*}*/ -/*a {*/ - /*cursor: pointer;*/ - /*cursor: hand;*/ -/*}*/ -/*button {*/ - /*font-family: Arial;*/ - /*background-color: #eee;*/ - /*border: none;*/ - /*padding: 5px 10px;*/ - /*border-radius: 4px;*/ - /*cursor: pointer;*/ - /*cursor: hand;*/ -/*}*/ -/*button:hover {*/ - /*background-color: #cfd8dc;*/ -/*}*/ -/*button:disabled {*/ - /*background-color: #eee;*/ - /*color: #aaa;*/ - /*cursor: auto;*/ -/*}*/ -/*label {*/ - /*padding-right: 0.5em;*/ -/*}*/ -/*!* Navigation link styles *!*/ -/*nav a {*/ - /*padding: 5px 10px;*/ - /*text-decoration: none;*/ - /*margin-right: 10px;*/ - /*margin-top: 10px;*/ - /*display: inline-block;*/ - /*background-color: #eee;*/ - /*border-radius: 4px;*/ -/*}*/ -/*nav a:visited, a:link {*/ - /*color: #607D8B;*/ -/*}*/ -/*nav a:hover {*/ - /*color: #039be5;*/ - /*background-color: #CFD8DC;*/ -/*}*/ -/*nav a.active {*/ - /*color: #039be5;*/ -/*}*/ - -/*!* items class *!*/ -/*.items {*/ - /*margin: 0 0 2em 0;*/ - /*list-style-type: none;*/ - /*padding: 0;*/ - /*width: 24em;*/ -/*}*/ -/*.items li {*/ - /*cursor: pointer;*/ - /*position: relative;*/ - /*left: 0;*/ - /*background-color: #EEE;*/ - /*margin: .5em;*/ - /*padding: .3em 0;*/ - /*height: 1.6em;*/ - /*border-radius: 4px;*/ -/*}*/ -/*.items li:hover {*/ - /*color: #607D8B;*/ - /*background-color: #DDD;*/ - /*left: .1em;*/ -/*}*/ -/*.items li.selected {*/ - /*background-color: #CFD8DC;*/ - /*color: white;*/ -/*}*/ -/*.items li.selected:hover {*/ - /*background-color: #BBD8DC;*/ -/*}*/ -/*.items .text {*/ - /*position: relative;*/ - /*top: -3px;*/ -/*}*/ -/*.items .badge {*/ - /*display: inline-block;*/ - /*font-size: small;*/ - /*color: white;*/ - /*padding: 0.8em 0.7em 0 0.7em;*/ - /*background-color: #607D8B;*/ - /*line-height: 1em;*/ - /*position: relative;*/ - /*left: -1px;*/ - /*top: -4px;*/ - /*height: 1.8em;*/ - /*margin-right: .8em;*/ - /*border-radius: 4px 0 0 4px;*/ -/*}*/ -/*!* everywhere else *!*/ -/** {*/ - /*font-family: Arial, Helvetica, sans-serif;*/ -/*}*/ +/* items class */ +.items { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 24em; +} +.items li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; +} +.items li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; +} +.items li.selected { + background-color: #CFD8DC; + color: white; +} +.items li.selected:hover { + background-color: #BBD8DC; +} +.items .text { + position: relative; + top: -3px; +} +.items .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; +} +/* everywhere else */ +* { + font-family: Arial, Helvetica, sans-serif; +}