Compare commits

..

16 commits

Author SHA1 Message Date
bdb698394a Re-Order Simulation Class 2018-10-23 21:02:42 +02:00
e40dddde52 Shorten toggleCell Function Name 2018-10-23 21:01:27 +02:00
2e7fd7c3d3 Revert "Add getCell and setCell method to Simulation"
This reverts commit 52b440351d.
2018-10-23 20:58:11 +02:00
52b440351d Add getCell and setCell method to Simulation 2018-10-23 20:41:54 +02:00
5116aff8a4 Refactor toggleCellState function 2018-10-23 20:31:44 +02:00
b1d96efc84 Fix toggleCell Function in Simulation 2018-10-23 20:30:20 +02:00
c8e2417ab8 Remove duplicate functions to set Grid Cells 2018-10-23 20:21:21 +02:00
e840a3e580 Remove unused variable dispersal 2018-10-23 14:47:23 +02:00
474adb740c Split up addRandomPattern function 2018-10-22 19:17:00 +02:00
0b5b1de454 Remove Duplicate Import 2018-10-21 14:06:36 +02:00
cb9da3bc54 Remove unnecessary CellPattern enum from Simulation 2018-10-21 14:01:34 +02:00
3a6e34945a Remove output argument from clearMap function 2018-10-21 14:00:53 +02:00
e115ed2f48 Make clear map function name more concise
Renam resetting the grid to clearMap.
2018-10-21 14:00:22 +02:00
66c273c783 Make Simulation dirty-flag public 2018-10-21 13:55:15 +02:00
1f435617da Re-Enable Simulation dirty-flag test
In preparation for refactoring, and exposing the dirty flag we re-enable the dirty flag test on resetMap
2018-10-21 11:38:57 +02:00
ae26496730 Fix grid resize test to check for object identity 2018-10-21 11:07:46 +02:00
3 changed files with 81 additions and 71 deletions

View file

@ -14,11 +14,11 @@ class SimulationService {
SimulationService(this._engine, [Simulation sim]) SimulationService(this._engine, [Simulation sim])
: this._sim = sim ?? Simulation(DEFAULT_GRID_SIZE, DEFAULT_GRID_SIZE) { : this._sim = sim ?? Simulation(DEFAULT_GRID_SIZE, DEFAULT_GRID_SIZE) {
_engine.simulation = _sim; _engine.simulation = _sim;
_sim.addRandomPattern(amount: 15, dispersal: 5); _sim.addRandomPattern(amount: 15);
} }
void reset() { void reset() {
_sim.reset(); _sim.clearMap();
} }
void addRandomPattern() { void addRandomPattern() {

View file

@ -1,87 +1,45 @@
import 'dart:html' as html; import 'dart:html' as html;
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:math';
import 'package:rules_of_living/src/Grid.dart'; import 'package:rules_of_living/src/Grid.dart';
import 'package:rules_of_living/src/rules/GameOfLife.dart'; import 'package:rules_of_living/src/rules/GameOfLife.dart';
import 'package:rules_of_living/src/rules/RuleSet.dart'; import 'package:rules_of_living/src/rules/RuleSet.dart';
enum CellPattern { SpaceShip, Blinker }
class Simulation { class Simulation {
Grid<bool> map; Grid<bool> map;
Grid<bool> _snapshot; Grid<bool> _snapshot;
final int _RANDOM_PATTERN_AMOUNT = 20;
final double _RANDOM_PATTERN_SPREAD_FROM_CENTER = 1 / 3;
RuleSet rules = GameOfLife(); RuleSet rules = GameOfLife();
bool _dirty = true; bool dirty = true;
bool get dirty => _dirty;
bool _renderEdges = true; bool _renderEdges = true;
bool get renderEdges => _renderEdges; bool get renderEdges => _renderEdges;
int _amount; math.Point get gridSize => math.Point<int>(map.width, map.height);
int _dispersal; void set gridSize(math.Point<int> value) {
Point get gridSize => Point<int>(map.width, map.height);
void set gridSize(Point<int> value) {
if (value.x <= 0 || value.y <= 0) if (value.x <= 0 || value.y <= 0)
throw ArgumentError("grid size must not be smaller than 1"); throw ArgumentError("grid size must not be smaller than 1");
map = Grid(value.x, value.y); map = Grid(value.x, value.y);
} }
Simulation(int w, int h) : this.map = new Grid(w, h) { Simulation(int w, int h) : this.map = new Grid(w, h) {
this.map = reset(); this.map = clearMap();
} }
Simulation.fromGrid(Grid<bool> map) : this.map = map; Simulation.fromGrid(Grid<bool> map) : this.map = map;
Grid<bool> reset([Grid<bool> map]) { Grid<bool> clearMap() {
map ??= this.map; dirty = true;
_dirty = true;
map.setAll(0, List.filled(map.length, false)); map.setAll(0, List.filled(map.length, false));
return map; return map;
} }
void addRandomPattern({int amount, int dispersal}) { void toggleCell(int x, int y) {
int _startingSeed = DateTime.now().millisecondsSinceEpoch; map.set(x, y, !map.get(x, y));
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<int, bool> update() { Map<int, bool> update() {
@ -91,7 +49,7 @@ class Simulation {
void mergeStateChanges(Map<int, bool> stateChanges) { void mergeStateChanges(Map<int, bool> stateChanges) {
stateChanges.forEach((i, el) => map[i] = el); stateChanges.forEach((i, el) => map[i] = el);
if (stateChanges.length != 0) _dirty = true; if (stateChanges.length != 0) dirty = true;
} }
Map<int, bool> calculateNextState(Grid<bool> oldState) { Map<int, bool> calculateNextState(Grid<bool> oldState) {
@ -118,16 +76,50 @@ class Simulation {
iy >= 0 && iy >= 0 &&
ix < map.width && ix < map.width &&
iy < map.height && iy < map.height &&
getCellState(ix, iy) == true && map.get(ix, iy) == true &&
!(x == ix && y == iy)) count++; !(x == ix && y == iy)) count++;
} }
} }
return count; return count;
} }
void addRandomPattern({int seed, int amount, num spreadFromCenter}) {
math.Random rng = _getRNG(seed ?? DateTime.now().millisecondsSinceEpoch);
amount ??= rng.nextInt(_RANDOM_PATTERN_AMOUNT);
spreadFromCenter ??= _RANDOM_PATTERN_SPREAD_FROM_CENTER;
int sanityCheck = 0;
Map<int, bool> changeSet = {};
for (var i = 0; i < (amount); i++) {
sanityCheck++;
math.Point cell = _getRandomPoint(rng, spreadFromCenter);
map.get(cell.x, cell.y)
? i--
: changeSet[map.toIndex(cell.x, cell.y)] = true;
if (sanityCheck > 100 && sanityCheck > i * 3) break;
}
mergeStateChanges(changeSet);
}
math.Random _getRNG(int seed) {
math.Random rng = new math.Random(seed);
return rng;
}
math.Point<int> _getRandomPoint(math.Random rng, num spreadFromCenter) {
math.Point absoluteSpread =
math.Point(map.width * spreadFromCenter, map.height * spreadFromCenter);
math.Point center = math.Point(map.width / 2, map.height / 2);
num cx = rng.nextInt(absoluteSpread.x.toInt()) +
(center.x - absoluteSpread.x / 2);
num cy = rng.nextInt(absoluteSpread.y.toInt()) +
(center.y - absoluteSpread.y / 2);
return math.Point<int>(cx.toInt(), cy.toInt());
}
void render(html.CanvasElement canvas, [num interp]) { void render(html.CanvasElement canvas, [num interp]) {
// only renders if any cells changed between renders // only renders if any cells changed between renders
if (!_dirty) return; if (!dirty) return;
html.CanvasRenderingContext2D ctx = canvas.getContext('2d'); html.CanvasRenderingContext2D ctx = canvas.getContext('2d');
int brickW = (canvas.width ~/ map.width); int brickW = (canvas.width ~/ map.width);
@ -146,18 +138,18 @@ class Simulation {
ctx.setFillColorRgb(0, 0, 0); ctx.setFillColorRgb(0, 0, 0);
ctx.fillRect(p.x * brickW, p.y * brickH, brickW, brickH); ctx.fillRect(p.x * brickW, p.y * brickH, brickW, brickH);
} }
_dirty = false; dirty = false;
} }
void set renderEdges(bool on) { void set renderEdges(bool on) {
_renderEdges = on; _renderEdges = on;
_dirty = true; dirty = true;
} }
void saveSnapshot() => _snapshot = Grid.from(map); void saveSnapshot() => _snapshot = Grid.from(map);
Grid<bool> loadSnapshot() { Grid<bool> loadSnapshot() {
map = Grid.from(_snapshot); map = Grid.from(_snapshot);
_dirty = true; dirty = true;
return map; return map;
} }
} }

View file

@ -1,9 +1,9 @@
import 'dart:math'; import 'dart:math';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:rules_of_living/src/Grid.dart'; import 'package:rules_of_living/src/Grid.dart';
import 'package:test/test.dart';
import 'package:rules_of_living/src/Simulation.dart'; import 'package:rules_of_living/src/Simulation.dart';
import 'package:test/test.dart';
class MockGrid extends Mock implements Grid<bool> {} class MockGrid extends Mock implements Grid<bool> {}
@ -24,18 +24,21 @@ void main() {
test("creates a new underlying grid on resizing", () { test("creates a new underlying grid on resizing", () {
var oldMap = sut.map; var oldMap = sut.map;
sut.gridSize = Point(10, 10); sut.gridSize = Point(10, 10);
expect(sut.map, isNot(oldMap)); expect(sut.map, isNot(same(oldMap)));
}, tags: "nobrowser");
}); });
}); group("resetMap", () {
group("reset", () { test("sets the internal map filled with 'false' ", () {
test("returns a map filled with 'false' ", () { sut.map.set(1, 1, true);
expect(sut.reset(), allOf(TypeMatcher<Grid>(), isNot(contains(true)))); sut.clearMap();
expect(sut.map, allOf(TypeMatcher<Grid>(), isNot(contains(true))));
}); });
test("sets the simulation to need re-rendering", () { test("sets the simulation to need re-rendering", () {
sut.reset(); sut.dirty = false;
sut.clearMap();
expect(sut.dirty, true); expect(sut.dirty, true);
}, skip: "can not find a way to set dirty to true first yet");
}); });
}, tags: "nobrowser");
group("save&load", () { group("save&load", () {
test( test(
"saves a copy of the map which does not change when the actual map changes", "saves a copy of the map which does not change when the actual map changes",
@ -46,5 +49,20 @@ void main() {
expect(sut.loadSnapshot(), isNot(equals(snapshot))); expect(sut.loadSnapshot(), isNot(equals(snapshot)));
}); });
}, tags: "nobrowser");
group("toggleCellState", () {
test("throws RangeError if outside the map bounds", () {
expect(() => sut.toggleCell(10, 9), throwsRangeError);
}, tags: const ["nobrowser"]);
test("sets the cell to false if currently true", () {
sut.map.set(1, 1, true);
sut.toggleCell(1, 1);
expect(sut.map.get(1, 1), false);
}, tags: const ["nobrowser"]);
test("sets the cell to true if currently false", () {
sut.map.set(1, 1, false);
sut.toggleCell(1, 1);
expect(sut.map.get(1, 1), true);
}, tags: const ["nobrowser"]);
}); });
} }