Compare commits
No commits in common. "58-refactor-simulation" and "master" have entirely different histories.
58-refacto
...
master
3 changed files with 71 additions and 81 deletions
|
@ -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);
|
_sim.addRandomPattern(amount: 15, dispersal: 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
_sim.clearMap();
|
_sim.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void addRandomPattern() {
|
void addRandomPattern() {
|
||||||
|
|
|
@ -1,45 +1,87 @@
|
||||||
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;
|
||||||
|
|
||||||
math.Point get gridSize => math.Point<int>(map.width, map.height);
|
int _amount;
|
||||||
void set gridSize(math.Point<int> value) {
|
int _dispersal;
|
||||||
|
|
||||||
|
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 = clearMap();
|
this.map = reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Simulation.fromGrid(Grid<bool> map) : this.map = map;
|
Simulation.fromGrid(Grid<bool> map) : this.map = map;
|
||||||
|
|
||||||
Grid<bool> clearMap() {
|
Grid<bool> reset([Grid<bool> map]) {
|
||||||
dirty = true;
|
map ??= this.map;
|
||||||
|
_dirty = true;
|
||||||
map.setAll(0, List.filled(map.length, false));
|
map.setAll(0, List.filled(map.length, false));
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleCell(int x, int y) {
|
void addRandomPattern({int amount, int dispersal}) {
|
||||||
map.set(x, y, !map.get(x, y));
|
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<int, bool> update() {
|
Map<int, bool> update() {
|
||||||
|
@ -49,7 +91,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) {
|
||||||
|
@ -76,50 +118,16 @@ class Simulation {
|
||||||
iy >= 0 &&
|
iy >= 0 &&
|
||||||
ix < map.width &&
|
ix < map.width &&
|
||||||
iy < map.height &&
|
iy < map.height &&
|
||||||
map.get(ix, iy) == true &&
|
getCellState(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);
|
||||||
|
@ -138,18 +146,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
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:rules_of_living/src/Simulation.dart';
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'package:rules_of_living/src/Simulation.dart';
|
||||||
|
|
||||||
class MockGrid extends Mock implements Grid<bool> {}
|
class MockGrid extends Mock implements Grid<bool> {}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -24,21 +24,18 @@ 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(same(oldMap)));
|
expect(sut.map, isNot(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.dirty = false;
|
sut.reset();
|
||||||
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",
|
||||||
|
@ -49,20 +46,5 @@ 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"]);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue