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

Unified Diff: sky/examples/mine_digger/mine_digger.dart

Issue 1196213012: Add minedigger example (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: review (eric) Created 5 years, 6 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sky/examples/mine_digger/mine_digger.dart
diff --git a/sky/examples/mine_digger/mine_digger.dart b/sky/examples/mine_digger/mine_digger.dart
new file mode 100644
index 0000000000000000000000000000000000000000..584cd7d272566eb556830f4e9c084ed8a8c00197
--- /dev/null
+++ b/sky/examples/mine_digger/mine_digger.dart
@@ -0,0 +1,366 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:sky' as sky;
+import 'dart:math';
+
+import 'package:sky/rendering/flex.dart';
+import 'package:sky/widgets/basic.dart';
+import 'package:sky/painting/text_style.dart';
+
+// Classic minesweeper-inspired game. The mouse controls are standard except
+// for left + right combo which is not implemented. For touch, the duration of
+// the pointer determines probing versus flagging.
+//
+// There are only 3 classes to understand. Game, which is contains all the
+// logic and two UI classes: CoveredMineNode and ExposedMineNode, none of them
+// holding state.
+
+class Game {
+ static const int rows = 9;
+ static const int cols = 9;
+ static const int totalMineCount = 11;
+
+ static const int coveredCell = 0;
+ static const int explodedCell = 1;
+ static const int clearedCell = 2;
+ static const int flaggedCell = 3;
+ static const int shownCell = 4;
+
+ static final List<TextStyle> textStyles = new List<TextStyle>();
+
+ final App app;
+
+ bool alive;
+ bool hasWon;
+ int detectedCount;
+ int randomSeed;
+
+ // |cells| keeps track of the positions of the mines.
+ List<List<bool>> cells;
+ // |uiState| keeps track of the visible player progess.
+ List<List<int>> uiState;
+
+ Game(this.app) {
+ randomSeed = 22;
+ // Colors for each mine count:
+ // 0 - none, 1 - blue, 2-green, 3-red, 4-black, 5-dark red .. etc.
+ textStyles.add(
+ new TextStyle(color: const Color(0xFF555555), fontWeight: bold));
+ textStyles.add(
+ new TextStyle(color: const Color(0xFF0094FF), fontWeight: bold));
+ textStyles.add(
+ new TextStyle(color: const Color(0xFF13A023), fontWeight: bold));
+ textStyles.add(
+ new TextStyle(color: const Color(0xFFDA1414), fontWeight: bold));
+ textStyles.add(
+ new TextStyle(color: const Color(0xFF1E2347), fontWeight: bold));
+ textStyles.add(
+ new TextStyle(color: const Color(0xFF7F0037), fontWeight: bold));
+ textStyles.add(
+ new TextStyle(color: const Color(0xFFE93BE9), fontWeight: bold));
+ initialize();
+ }
+
+ void initialize() {
+ alive = true;
+ hasWon = false;
+ detectedCount = 0;
+ // Build the arrays.
+ cells = new List<List<bool>>();
+ uiState = new List<List<int>>();
+ for (int iy = 0; iy != rows; iy++) {
+ cells.add(new List<bool>());
+ uiState.add(new List<int>());
+ for (int ix = 0; ix != cols; ix++) {
+ cells[iy].add(false);
+ uiState[iy].add(coveredCell);
+ }
+ }
+ // Place the mines.
+ Random random = new Random(++randomSeed);
+ for (int mc = 0; mc != totalMineCount; mc++) {
+ int rx = random.nextInt(rows);
+ int ry = random.nextInt(cols);
+ if (cells[ry][rx]) {
+ // Mine already there. Try again.
+ --mc;
+ } else {
+ cells[ry][rx] = true;
+ }
+ }
+ }
+
+ Widget generateBoard() {
+ bool hasCoveredCell = false;
+ List<Flex> flexRows = new List<Flex>();
+ for (int iy = 0; iy != 9; iy++) {
+ List<Component> row = new List<Component>();
+ for (int ix = 0; ix != 9; ix++) {
+ int state = uiState[iy][ix];
+ int count = mineCount(ix, iy);
+
+ if (!alive) {
+ if (state != explodedCell)
+ state = cells[iy][ix] ? shownCell : state;
+ }
+
+ if (state == coveredCell) {
+ row.add(new CoveredMineNode(
+ this,
+ flagged: false,
+ posX: ix, posY: iy));
+ // Mutating |hasCoveredCell| here is hacky, but convenient, same
+ // goes for mutating |hasWon| below.
+ hasCoveredCell = true;
+ } else if (state == flaggedCell) {
+ row.add(new CoveredMineNode(
+ this,
+ flagged: true,
+ posX: ix, posY: iy));
+ } else {
+ row.add(new ExposedMineNode(
+ state: state,
+ count: count));
+ }
+ }
+ flexRows.add(
+ new Flex(
+ row,
+ direction: FlexDirection.horizontal,
+ justifyContent: FlexJustifyContent.center,
+ key: 'flex_row($iy)'
+ ));
+ }
+
+ if (!hasCoveredCell) {
+ // all cells uncovered. Are all mines flagged?
+ if ((detectedCount == totalMineCount) && alive) {
+ hasWon = true;
+ }
+ }
+
+ return new Container(
+ key: 'minefield',
+ padding: new EdgeDims.all(10.0),
+ margin: new EdgeDims.all(10.0),
+ decoration: new BoxDecoration(backgroundColor: const Color(0xFF6B6B6B)),
+ child: new Flex(
+ flexRows,
+ direction: FlexDirection.vertical,
+ key: 'flxv'));
+ }
+
+ Widget generateUI() {
+ Widget board = generateBoard();
+ String banner = hasWon ?
+ 'Awesome!!' : alive ?
+ 'Mine Digger [$detectedCount-$totalMineCount]': 'Kaboom! [press here]';
+
+ return new Flex([
+ new Container(
+ padding: new EdgeDims.all(10.0),
+ margin: new EdgeDims.all(10.0),
+ decoration: new BoxDecoration(backgroundColor: const Color(0xFFC0C0C0)),
+ child: new Listener(
+ onPointerDown: handleBannerPointerDown,
+ child: new Text(banner))),
+ board,
+ new Container(
+ height: 100.0, width: 100.0,
+ decoration: new BoxDecoration(backgroundColor: const Color(0xFFCC1111))
+ )
+ ],
+ direction: FlexDirection.vertical,
+ justifyContent: FlexJustifyContent.spaceAround);
+ }
+
+ void handleBannerPointerDown(sky.PointerEvent event) {
+ initialize();
+ app.setState((){});
+ }
+
+ // User action. The user uncovers the cell which can cause losing the game.
+ void probe(int x, int y) {
+ if (!alive)
+ return;
+ if (uiState[y][x] == flaggedCell)
+ return;
+ // Allowed to probe.
+ if (cells[y][x]) {
+ // Probed on a mine --> dead!!
+ uiState[y][x] = explodedCell;
+ alive = false;
+ } else {
+ // No mine, uncover nearby if possible.
+ cull(x, y);
+ }
+ app.setState((){});
+ }
+
+ // User action. The user is sure a mine is at this location.
+ void flag(int x, int y) {
+ if (uiState[y][x] == flaggedCell) {
+ uiState[y][x] = coveredCell;
+ --detectedCount;
+ } else {
+ uiState[y][x] = flaggedCell;
+ ++detectedCount;
+ }
+ app.setState((){});
+ }
+
+ // Recursively uncovers cells whose totalMineCount is zero.
+ void cull(int x, int y) {
+ if ((x < 0) || (x > rows - 1))
+ return;
+ if ((y < 0) || (y > cols - 1))
+ return;
+
+ if (uiState[y][x] == clearedCell)
+ return;
+ uiState[y][x] = clearedCell;
+
+ if (mineCount(x, y) > 0)
+ return;
+
+ cull(x - 1, y);
+ cull(x + 1, y);
+ cull(x, y - 1);
+ cull(x, y + 1 );
+ cull(x - 1, y - 1);
+ cull(x + 1, y + 1);
+ cull(x + 1, y - 1);
+ cull(x - 1, y + 1);
+ }
+
+ int mineCount(int x, int y) {
+ int count = 0;
+ int my = cols - 1;
+ int mx = rows - 1;
+
+ count += x > 0 ? bombs(x - 1, y) : 0;
+ count += x < mx ? bombs(x + 1, y) : 0;
+ count += y > 0 ? bombs(x, y - 1) : 0;
+ count += y < my ? bombs(x, y + 1 ) : 0;
+
+ count += (x > 0) && (y > 0) ? bombs(x - 1, y - 1) : 0;
+ count += (x < mx) && (y < my) ? bombs(x + 1, y + 1) : 0;
+ count += (x < mx) && (y > 0) ? bombs(x + 1, y - 1) : 0;
+ count += (x > 0) && (y < my) ? bombs(x - 1, y + 1) : 0;
+
+ return count;
+ }
+
+ int bombs(int x, int y) {
+ return cells[y][x] ? 1 : 0;
+ }
+}
+
+Widget makeCell(Widget widget) {
+ return new Container(
+ padding: new EdgeDims.all(1.0),
+ height: 27.0, width: 27.0,
+ decoration: new BoxDecoration(backgroundColor: const Color(0xFFC0C0C0)),
+ margin: new EdgeDims.all(2.0),
+ child: widget);
+}
+
+Widget makeInnerCell(Widget widget) {
+ return new Container(
+ padding: new EdgeDims.all(1.0),
+ margin: new EdgeDims.all(3.0),
+ height: 17.0, width: 17.0,
+ child: widget);
+}
+
+class CoveredMineNode extends Component {
+ final Game game;
+ final bool flagged;
+ final int posX;
+ final int posY;
+ Stopwatch stopwatch;
+
+ CoveredMineNode(this.game, {this.flagged, this.posX, this.posY});
+
+ void _handlePointerDown(sky.PointerEvent event) {
+ if (event.buttons == 1) {
+ game.probe(posX, posY);
+ } else if (event.buttons == 2) {
+ game.flag(posX, posY);
+ } else {
+ // Touch event.
+ stopwatch = new Stopwatch()..start();
+ }
+ }
+
+ void _handlePointerUp(sky.PointerEvent event) {
+ if (stopwatch == null)
+ return;
+ // Pointer down was a touch event.
+ var ms = stopwatch.elapsedMilliseconds;
+ if (stopwatch.elapsedMilliseconds < 250) {
+ game.probe(posX, posY);
+ } else {
+ // Long press flags.
+ game.flag(posX, posY);
+ }
+ stopwatch = null;
+ }
+
+ Widget build() {
+ Widget text = flagged ?
+ makeInnerCell(new StyledText(elements : [Game.textStyles[5], '\u2691'])) :
+ null;
+
+ Container inner = new Container(
+ margin: new EdgeDims.all(2.0),
+ height: 17.0, width: 17.0,
+ decoration: new BoxDecoration(backgroundColor: const Color(0xFFD9D9D9)),
+ child: text);
+
+ return makeCell(new Listener(
+ child: inner,
+ onPointerDown: _handlePointerDown,
+ onPointerUp: _handlePointerUp));
+ }
+}
+
+class ExposedMineNode extends Component {
+ final int state;
+ final int count;
+
+ ExposedMineNode({this.state, this.count});
+
+ Widget build() {
+ StyledText text;
+ if (state == Game.clearedCell) {
+ // Uncovered cell with nearby mine count.
+ if (count != 0)
+ text = new StyledText(elements : [Game.textStyles[count], '$count']);
+ } else {
+ // Exploded mine or shown mine for 'game over'.
+ int color = state == Game.explodedCell ? 3 : 0;
+ text = new StyledText(elements : [Game.textStyles[color], '\u2600']);
+ }
+
+ return makeCell(makeInnerCell(text));
+ }
+}
+
+class MineDiggerApp extends App {
+ Game game;
+
+ MineDiggerApp() {
+ game = new Game(this);
+ }
+
+ Widget build() {
+ return game.generateUI();
+ }
+}
+
+void main() {
+ runApp(new MineDiggerApp());
+}
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698