Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(647)

Side by Side Diff: dart/samples/webcomponents/game_of_life/components/components.dart

Issue 10918082: adding game_of_life sample (Closed) Base URL: git@github.com:samhopkins/bleeding_edge.git@master
Patch Set: addressing sigmund's remarks Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 #library('game_of_life_components');
6
7 #import('dart:html');
8 #import('dart:math', prefix: 'Math');
9 #import('package:dart-web-components/lib/js_polyfill/web_components.dart');
10
11 /** Functions used to propogate a tick to cells. */
12 typedef void Ping();
13
14 // We've done things this way because we can't have default values for fields
15 // inside a web component right now (see bug 4957).
16
17 /** How big should the (square) board be by default? Measured in cells/side. */
18 final int DEFAULT_GAME_SIZE = 40;
19
20 /**
21 * How many pixels long is the side of a cell by default?
22 * (Note: must match the CSS!)
23 */
24 final int DEFAULT_CELL_SIZE = 20;
25
26 /** How many pixels from the game should the control panel be by default? */
27 final int DEFAULT_PANEL_OFFSET = 20;
28
29 /** How many milliseconds between steps by default? */
30 final int DEFAULT_STEP_TIME = 1000;
31
32 /**
33 * Maps tag names to Dart constructors for components in this library.
34 * Singleton.
35 */
36 Map<String, WebComponentFactory> get CTOR_MAP {
37 if (_CTOR_MAP == null) {
38 _CTOR_MAP = {
39 'x-cell' : () => new Cell.component(),
40 'x-control-panel' : () => new ControlPanel.component(),
41 'x-game-of-life' : () => new GameOfLife.component()
42 };
43 }
44 return _CTOR_MAP;
45 }
46
47 Map<String, WebComponentFactory> _CTOR_MAP;
48
49 /**
50 * If the importing code uses only the components in this library,
51 * this function will do all necessary component initialization.
52 */
53 void gameOfLifeComponentsSetup() {
54 initializeComponents((String name) => CTOR_MAP[name], true);
55 }
56
57 /**
58 * A single cell in the Game Of Life. Listens to a GameOfLife parent component
59 * to get a clock tick, and interacts on its neighbors on every tick to move the
60 * game one step forward.
61 */
62 class Cell extends DivElementImpl implements WebComponent, Hashable {
63 Collection<Cell> neighbors;
64 ShadowRoot _root;
65 GameOfLife game;
66 bool aliveThisStep;
67 bool aliveNextStep;
68
69 // BEGIN AUTOGENERATED CODE
70 static WebComponentFactory _$constr;
71 factory Cell.component() {
72 if(_$constr == null) {
73 _$constr = () => new Cell._internal();
74 }
75 var t1 = new DivElement();
76 t1.attributes['is'] = 'x-cell';
77 rewirePrototypeChain(t1, _$constr, 'Cell');
78 return t1;
79 }
80
81 factory Cell() {
82 return manager.expandHtml('<div is="x-cell"></div>');
83 }
84
85 Cell._internal();
86 // END AUTOGENERATED CODE
87
88 void created(ShadowRoot root) {
89 _root = root;
90 neighbors = <Cell>[];
91 classes.add('cell');
92
93 // Cells start dead.
94 aliveThisStep = false;
95 }
96
97 void inserted() { }
98
99 /**
100 * Set up event listeners and populate [neighbors] by querying [game] for this
101 * cell's neighbors. Event listeners can be done here rather than dealt with
102 * in [inserted] and [removed] because cells will always be gc'd if removed
103 * from the DOM.
104 */
105 void bound() {
106 on.click.add((event) {
107 classes.toggle('alive');
108 aliveThisStep = !aliveThisStep;
109 });
110
111 game.on.step.add(step);
112 game.on.resolve.add(resolve);
113
114 // find neighbors
115 var parsedCoordinates = this.id.substring(1).split('y');
116 var x = Math.parseInt(parsedCoordinates[0]);
117 var y = Math.parseInt(parsedCoordinates[1]);
118 for (int dx = -1; dx <= 1; dx++) {
119 for (int dy = -1; dy <= 1; dy++) {
120 if (game.inGrid(x + dx, y + dy) && !(dx == 0 && dy == 0)) {
121 var neighbor = game._query('#x${x + dx}y${y + dy}');
122 neighbors.add(neighbor);
123 }
124 }
125 }
126 }
127
128
129 void attributeChanged(String name, String oldValue, String newValue) { }
130
131 void removed() { }
132
133 /**
134 * Each turn of the game is broken into a step and a resolve. On a step, the
135 * cell queries its neighbors current states and decides whether or not will
136 * be alive or dead next turn.
137 */
138 void step() {
139 var numAlive = neighbors.filter((n) => n.aliveThisStep).length;
140 // We could compress this into one line, but it's clearer this way.
141 aliveNextStep = false;
142 if (aliveThisStep) {
143 if (numAlive == 2 || numAlive == 3) {
144 aliveNextStep = true;
145 }
146 } else {
147 if (numAlive == 3) {
148 aliveNextStep = true;
149 }
150 }
151 }
152
153 /**
154 * Each turn of the game is broken in a step and a resolve. On a resolve, the
155 * cell uses the information collected in the step phase to update its state
156 * and appearance -- black if alive this turn, white if dead this turn.
157 */
158 void resolve() {
159 if (aliveNextStep) {
160 classes.add('alive');
161 } else {
162 classes.remove('alive');
163 }
164 aliveThisStep = aliveNextStep;
165 }
166
167 }
168
169 /**
170 * A control panel for the Game of Life. Has start, stop, and step buttons which
171 * start the game, stop the game, and move the game one turn forward,
172 * respectively.
173 */
174 class ControlPanel extends DivElementImpl implements WebComponent {
175 ShadowRoot _root;
176 GameOfLife game;
177
178 // BEGIN AUTOGENERATED CODE
179 static WebComponentFactory _$constr;
180 factory ControlPanel.component() {
181 if(_$constr == null) {
182 _$constr = () => new ControlPanel._internal();
183 }
184 var t1 = new DivElement();
185 t1.attributes['is'] = 'x-control-panel';
186 rewirePrototypeChain(t1, _$constr, 'ControlPanel');
187 return t1;
188 }
189
190 factory ControlPanel() {
191 return manager.expandHtml('<div is="x-control-panel"></div>');
192 }
193
194 ControlPanel._internal();
195 // END AUTOGENERATED CODE
196
197 void created(ShadowRoot root) {
198 _root = root;
199 }
200
201 void inserted() { }
202
203 /**
204 * Sets up event listeners for the buttons. This must be done here rather than
205 * in [inserted] because the events must propogate up to [game].
206 */
207 void bound() {
208 _root.query('#start').on.click.add((e) => game.run());
209 _root.query('#stop').on.click.add((e) => game.stop());
210 _root.query('#step').on.click.add((e) => game.step());
211 }
212
213 void attributeChanged(String name, String oldValue, String newValue) { }
214
215 void removed() { }
216 }
217
218 /**
219 * A Game of Life component, containing an interactive implementation of
220 * Conway's Game of Life.
221 */
222 class GameOfLife extends DivElementImpl implements WebComponent {
223
224 // Implementation Notes: The game consists of a control panel and a board
225 // composed of cells. Each cell is a web component, and the control panel is a
226 // web component. The top-level widget populates the board with cells and
227 // provides a clock tick to which the cells listen. It exposes API to stop and
228 // start that tick, which the control panel binds to its buttons. Aside
229 // from the tick, no state is maintained in the top level widget -- each cell
230 // maintains its own state and talks to its neighbors to move the game
231 // forward.
232
233 // TODO(samhop): implement wraparound on the board.
234 ShadowRoot _root;
235 GameOfLifeEvents on;
236 Timer timer;
237 int lastRefresh;
238 bool _stop;
239 StyleElement computedStyles;
240
241 // These cannot be initialized here right now -- see bug 4957.
242
243 /** How big should the (square) board be? Measured in cells/side. */
244 int GAME_SIZE;
245
246 /** How many pixels long is the side of a cell? (Note: must match the CSS!) */
247 int CELL_SIZE;
248
249 /** How many pixels from the game should the control panel be? */
250 int PANEL_OFFSET;
251
252 /** How many milliseconds between steps? */
253 int _stepTime;
254
255 void set stepTime(int time) {
256 _stepTime = time;
257 }
258
259 // BEGIN AUTOGENERATED CODE
260 static WebComponentFactory _$constr;
261 factory GameOfLife.component() {
262 if(_$constr == null) {
263 _$constr = () => new GameOfLife._internal();
264 }
265 var t1 = new DivElement();
266 t1.attributes['is'] = 'x-game-of-life';
267 rewirePrototypeChain(t1, _$constr, 'GameOfLife');
268 return t1;
269 }
270
271 factory GameOfLife() {
272 return manager.expandHtml('<div is="x-game-of-life"></div>');
273 }
274
275 GameOfLife._internal();
276 // END AUTOGENERATED CODE
277
278 /** On creation, initialize fields and then populate the game. */
279 void created(ShadowRoot root) {
280 _root = root;
281 on = new GameOfLifeEvents();
282 lastRefresh = 0;
283
284 // At present we must do this initialization here -- see bug 4957.
285 GAME_SIZE = DEFAULT_GAME_SIZE;
286 CELL_SIZE = DEFAULT_CELL_SIZE;
287 PANEL_OFFSET = DEFAULT_PANEL_OFFSET;
288 _stepTime = DEFAULT_STEP_TIME;
289
290 _populate();
291 }
292
293 void inserted() { }
294
295 void attributeChanged(String name, String oldValue, String newValue) { }
296
297 void removed() { }
298
299 /**
300 * Returns the results of querying on [selector] beneath [_root]. Needed by
301 * Cells to determine their neighbors.
302 */
303 _query(String selector) {
304 return _root.query(selector);
305 }
306
307 /** Stop ticking. */
308 void stop() {
309 _stop = true;
310 }
311
312 /**
313 * Tick once, if it has been at least _stepTime milliseconds since the last
314 * tick. Then, if we haven't been told to stop, call set up a
315 * requestAnimationFrame callback to tick again.
316 */
317 void _increment(int time) {
318 if (new Date.now().millisecondsSinceEpoch - lastRefresh >= _stepTime) {
319 on.step.forEach((f) => f());
320 on.resolve.forEach((f) => f());
321 lastRefresh = new Date.now().millisecondsSinceEpoch;
322 }
323 if (!_stop) {
324 window.requestAnimationFrame(_increment);
325 }
326 }
327
328 /** Start the game. */
329 void run() {
330 _stop = false;
331 window.requestAnimationFrame(_increment);
332 }
333
334 /**
335 * Move the game one step forward. If the game was running, stop the game
336 * beforehand.
337 */
338 void step() {
339 _stop = true;
340 _increment(null);
341 }
342
343 /**
344 * Fill the game board with cells, position them appropriately, position the
345 * control panel, and bind all subcomponents.
346 */
347 void _populate() {
348 // set up position styles
349 computedStyles = new StyleElement();
350 _root.nodes.add(computedStyles);
351 var computedStylesBuffer = new StringBuffer();
352 _forEachCell((i, j) => _addPositionId(computedStylesBuffer, i, j));
353
354 // position the control panel
355 var panelStyle =
356 '''
357 #panel {
358 top: ${CELL_SIZE * GAME_SIZE + PANEL_OFFSET}px;
359 left: ${PANEL_OFFSET}px;
360 }
361 ''';
362 computedStylesBuffer.add('${computedStyles.innerHTML}\n$panelStyle');
363
364 computedStyles.innerHTML = computedStylesBuffer.toString();
365
366 // add cells
367 _forEachCell((i, j) {
368 var cell = new Cell();
369 cell.game = this;
370 cell.id = _generatePositionString(i, j);
371 _root.nodes.add(cell);
372 });
373
374 // bind the control panel
375 var controlPanel = _root.query('div[is="x-control-panel"]');
376 controlPanel.game = this;
377 controlPanel.bound();
378
379 // TODO(samhop): fix webcomponents.dart so that attributes are preserved.
380 _root.query('div').id = 'panel';
381
382 // TODO(samhop) fix webcomponents.dart so we don't have to do this
383 _root.queryAll('.cell').forEach((cell) => cell.bound());
384 }
385
386 /**
387 * Calls f exactly once on all pairs (i, j) for ints i, j between 0 and
388 * [GAME_SIZE] - 1, inclusive.
389 */
390 void _forEachCell(f) {
391 for (var i = 0; i < GAME_SIZE; i++) {
392 for (var j = 0; j < GAME_SIZE; j++) {
393 f(i, j);
394 }
395 }
396 }
397
398 /**
399 * Appends correct cell positioning information for cell ([i], [j]) to [curr].
400 */
401 String _addPositionId(StringBuffer curr, int i, int j) =>
402 curr.add(
403 '''
404 #${_generatePositionString(i, j)} {
405 left: ${CELL_SIZE * i}px;
406 top: ${CELL_SIZE * j}px;
407 }
408 ''');
409
410 /** Returns the cell id corresponding to ([i], [j]). */
411 String _generatePositionString(int i, int j) => 'x${i}y${j}';
412
413 /**
414 * Is the coordinate ([x],[y]) in the game grid, given the current
415 * [GAME_SIZE]?
416 */
417 bool inGrid(x, y) =>
418 (x >=0 && y >=0 && x < GAME_SIZE && y < GAME_SIZE);
419
420 /**
421 * Set cell ([i],[j])'s aliveness to [alive]. Throws an
422 * IllegalArgumentException if ([i],[j]) is not a valid cell (i.e. if it is
423 * outside of the grid).
424 */
425 void setAliveness(int i, int j, bool alive) {
426 _validateGridPosition(i, j);
427 _query('#${_generatePositionString(i, j)}').aliveThisStep = alive;
428 }
429
430 /**
431 * Is cell ([i], [j]) currently alive? Throws an IllegalArgumentException if
432 * ([i], [j]) is not a valid cell (i.e. it is outside the grid).
433 */
434 bool isAlive(int i, int j) {
435 _validateGridPosition(i, j);
436 return _query('#${_generatePositionString(i,j)}').aliveThisStep;
437 }
438
439 /** Throw an IllegalArgumentException if ([i], [j]) is not a valid cell. */
440 void _validateGridPosition(int i, int j) {
441 if (i < 0 || j < 0 || !inGrid(i,j)) {
442 throw new IllegalArgumentException('(${i}, ${j}) is a bad coordinate');
443 }
444 assert(inGrid(i,j) && i >= 0 && j >= 0);
445 }
446 }
447
448 /** Events container for a GameOfLife. */
449 class GameOfLifeEvents implements Events {
450 List<Ping> _step_list;
451 List<Ping> _resolve_list;
452
453 GameOfLifeEvents()
454 : _step_list = <Ping>[],
455 _resolve_list = <Ping>[];
456
457 List<Ping> get step => _step_list;
458 List<Ping> get resolve => _resolve_list;
459 }
OLDNEW
« no previous file with comments | « dart/samples/webcomponents/game_of_life/README.md ('k') | dart/samples/webcomponents/game_of_life/components/components.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698