diff --git a/lib/app_component.dart b/lib/app_component.dart new file mode 100644 index 0000000..e5ece87 --- /dev/null +++ b/lib/app_component.dart @@ -0,0 +1,47 @@ +import 'package:angular/angular.dart'; + +import 'dart:html' as html; +import 'package:rules_of_living/src/App.dart'; + +@Component( + selector: 'my-app', + templateUrl: "app_component.html", + directives: [coreDirectives] +) +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 new file mode 100644 index 0000000..366ce7b --- /dev/null +++ b/lib/app_component.html @@ -0,0 +1,20 @@ +

Cellular Automata - The Rules of Life

+
+ Ruleset: + +
+
+ +
+
+ + + + + Speed: +
\ No newline at end of file diff --git a/lib/App.dart b/lib/src/App.dart similarity index 78% rename from lib/App.dart rename to lib/src/App.dart index f77ab15..a571dba 100644 --- a/lib/App.dart +++ b/lib/src/App.dart @@ -1,7 +1,6 @@ import 'dart:html' as html; -import 'package:rules_of_living/Cell.dart'; -import 'package:rules_of_living/Grid.dart'; +import 'package:rules_of_living/src/Grid.dart'; class App { // Elapsed Time Counter - useful for Safety Timeout @@ -26,12 +25,11 @@ class App { App(this.canvas) { _elapsed.start(); - var runBtn = html.querySelector("#run"); - runBtn.onClick.forEach((html.MouseEvent mouse) { - running = !running; - if(running) runBtn.innerHtml = "Stop"; - if(!running) runBtn.innerHtml = "Start"; - }); + } + + void reset () { + grid = new Grid(100, 100); + running = false; } void process(num now) { @@ -45,7 +43,7 @@ class App { print("ERROR STUCK IN UPDATE LOOP"); break; } - update(); + if (running == true) update(); _updateLag -= _MS_PER_STEP; } @@ -57,7 +55,7 @@ class App { void update() { // print("updating"); - if (running) grid.update(); + grid.update(); } diff --git a/lib/Cell.dart b/lib/src/Cell.dart similarity index 77% rename from lib/Cell.dart rename to lib/src/Cell.dart index d9344ff..5301fb6 100644 --- a/lib/Cell.dart +++ b/lib/src/Cell.dart @@ -1,4 +1,4 @@ -import 'package:rules_of_living/Rule.dart'; +import 'package:rules_of_living/src/Rule.dart'; class Cell { bool state; @@ -6,11 +6,16 @@ class Cell { 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) { diff --git a/lib/Grid.dart b/lib/src/Grid.dart similarity index 92% rename from lib/Grid.dart rename to lib/src/Grid.dart index 52e0fea..b0d7e9f 100644 --- a/lib/Grid.dart +++ b/lib/src/Grid.dart @@ -1,13 +1,15 @@ import 'dart:html' as html; -import 'Cell.dart'; -import 'Rule.dart'; +import 'package:rules_of_living/src/Cell.dart'; +import 'package:rules_of_living/src/Rule.dart'; class Grid { final int w; final int h; final List> map; + bool _dirty = true; + Grid(int w, int h) : this.w = w, this.h = h, @@ -82,6 +84,7 @@ class Grid { 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++) { @@ -110,6 +113,9 @@ class Grid { } 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); @@ -124,5 +130,7 @@ class Grid { ctx.fillRect(x * brickW, y * brickH, brickW, brickH); } } + + _dirty = false; } } diff --git a/lib/Rule.dart b/lib/src/Rule.dart similarity index 100% rename from lib/Rule.dart rename to lib/src/Rule.dart diff --git a/pubspec.yaml b/pubspec.yaml index 2fe07bc..9dc2faa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,10 +7,12 @@ homepage: https://www.martyoehme.org/ environment: sdk: '>=2.0.0-dev.66.0 <2.0.0' -#dependencies: -# path: ^1.4.1 +dependencies: + angular: ^5.0.0-beta dev_dependencies: + angular_test: ^2.0.0-beta build_runner: ^0.9.0 + build_test: ^0.10.2 build_web_compilers: ^0.4.0 - test: ^1.2.0 + test: ^1.0.0 diff --git a/test/app_test.dart b/test/app_test.dart new file mode 100644 index 0000000..3e0614e --- /dev/null +++ b/test/app_test.dart @@ -0,0 +1,32 @@ +@TestOn('browser') + +import 'package:rules_of_living/app_component.dart'; +import 'package:rules_of_living/app_component.template.dart' as ng; +import 'package:angular_test/angular_test.dart'; +import 'package:test/test.dart'; + +void main() { + final testBed = + NgTestBed.forComponent(ng.AppComponentNgFactory); + NgTestFixture fixture; + + setUp(() async { + fixture = await testBed.create(); + }); + + tearDown(disposeAnyRunningTest); + + test('Default greeting', () { + expect(fixture.text, 'Hello Angular'); + }); + + test('Greet world', () async { + await fixture.update((c) => c.name = 'World'); + expect(fixture.text, 'Hello World'); + }); + + test('Greet world HTML', () { + final html = fixture.rootElement.innerHtml; + expect(html, '

Hello Angular

'); + }); +} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..dd18702 Binary files /dev/null and b/web/favicon.png differ diff --git a/web/index.html b/web/index.html index 6b86c2d..35bd3cb 100644 --- a/web/index.html +++ b/web/index.html @@ -1,29 +1,31 @@ - + - - - - - rules_of_living - - - + + + Hello Angular + + + + + + + + + - -
- -
-
- - - - - -
+ Loading... + diff --git a/web/main.dart b/web/main.dart index 8d236aa..3415d86 100644 --- a/web/main.dart +++ b/web/main.dart @@ -1,19 +1,7 @@ -import 'dart:html' as html; +import 'package:angular/angular.dart'; +import 'package:rules_of_living/app_component.template.dart' as ng; -import 'package:rules_of_living/App.dart'; - - -html.CanvasElement el; -App engine; void main() { - el = new html.CanvasElement(width: 500, height: 500); - html.querySelector('#output').append(el); - engine = new App(el); - html.window.animationFrame.then(animFrame); -} - -void animFrame(num now) { - engine.process(now); - html.window.animationFrame.then(animFrame); + runApp(ng.AppComponentNgFactory); } diff --git a/web/styles.css b/web/styles.css index cc035c9..ef7a186 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1,14 +1,117 @@ @import url(https://fonts.googleapis.com/css?family=Roboto); +@import url(https://fonts.googleapis.com/css?family=Material+Icons); -html, body { - width: 100%; - height: 100%; - margin: 0; +/* 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; - font-family: 'Roboto', sans-serif; + width: 24em; } - -#output { - padding: 20px; - text-align: center; +.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; }