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

Side by Side 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, 5 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
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 import 'dart:sky' as sky;
6 import 'dart:math';
7
8 import 'package:sky/rendering/flex.dart';
9 import 'package:sky/widgets/basic.dart';
10 import 'package:sky/painting/text_style.dart';
11
12 // Classic minesweeper-inspired game. The mouse controls are standard except
13 // for left + right combo which is not implemented. For touch, the duration of
14 // the pointer determines probing versus flagging.
15 //
16 // There are only 3 classes to understand. Game, which is contains all the
17 // logic and two UI classes: CoveredMineNode and ExposedMineNode, none of them
18 // holding state.
19
20 class Game {
21 static const int rows = 9;
22 static const int cols = 9;
23 static const int totalMineCount = 11;
24
25 static const int coveredCell = 0;
26 static const int explodedCell = 1;
27 static const int clearedCell = 2;
28 static const int flaggedCell = 3;
29 static const int shownCell = 4;
30
31 static final List<TextStyle> textStyles = new List<TextStyle>();
32
33 final App app;
34
35 bool alive;
36 bool hasWon;
37 int detectedCount;
38 int randomSeed;
39
40 // |cells| keeps track of the positions of the mines.
41 List<List<bool>> cells;
42 // |uiState| keeps track of the visible player progess.
43 List<List<int>> uiState;
44
45 Game(this.app) {
46 randomSeed = 22;
47 // Colors for each mine count:
48 // 0 - none, 1 - blue, 2-green, 3-red, 4-black, 5-dark red .. etc.
49 textStyles.add(
50 new TextStyle(color: const Color(0xFF555555), fontWeight: bold));
51 textStyles.add(
52 new TextStyle(color: const Color(0xFF0094FF), fontWeight: bold));
53 textStyles.add(
54 new TextStyle(color: const Color(0xFF13A023), fontWeight: bold));
55 textStyles.add(
56 new TextStyle(color: const Color(0xFFDA1414), fontWeight: bold));
57 textStyles.add(
58 new TextStyle(color: const Color(0xFF1E2347), fontWeight: bold));
59 textStyles.add(
60 new TextStyle(color: const Color(0xFF7F0037), fontWeight: bold));
61 textStyles.add(
62 new TextStyle(color: const Color(0xFFE93BE9), fontWeight: bold));
63 initialize();
64 }
65
66 void initialize() {
67 alive = true;
68 hasWon = false;
69 detectedCount = 0;
70 // Build the arrays.
71 cells = new List<List<bool>>();
72 uiState = new List<List<int>>();
73 for (int iy = 0; iy != rows; iy++) {
74 cells.add(new List<bool>());
75 uiState.add(new List<int>());
76 for (int ix = 0; ix != cols; ix++) {
77 cells[iy].add(false);
78 uiState[iy].add(coveredCell);
79 }
80 }
81 // Place the mines.
82 Random random = new Random(++randomSeed);
83 for (int mc = 0; mc != totalMineCount; mc++) {
84 int rx = random.nextInt(rows);
85 int ry = random.nextInt(cols);
86 if (cells[ry][rx]) {
87 // Mine already there. Try again.
88 --mc;
89 } else {
90 cells[ry][rx] = true;
91 }
92 }
93 }
94
95 Widget generateBoard() {
96 bool hasCoveredCell = false;
97 List<Flex> flexRows = new List<Flex>();
98 for (int iy = 0; iy != 9; iy++) {
99 List<Component> row = new List<Component>();
100 for (int ix = 0; ix != 9; ix++) {
101 int state = uiState[iy][ix];
102 int count = mineCount(ix, iy);
103
104 if (!alive) {
105 if (state != explodedCell)
106 state = cells[iy][ix] ? shownCell : state;
107 }
108
109 if (state == coveredCell) {
110 row.add(new CoveredMineNode(
111 this,
112 flagged: false,
113 posX: ix, posY: iy));
114 // Mutating |hasCoveredCell| here is hacky, but convenient, same
115 // goes for mutating |hasWon| below.
116 hasCoveredCell = true;
117 } else if (state == flaggedCell) {
118 row.add(new CoveredMineNode(
119 this,
120 flagged: true,
121 posX: ix, posY: iy));
122 } else {
123 row.add(new ExposedMineNode(
124 state: state,
125 count: count));
126 }
127 }
128 flexRows.add(
129 new Flex(
130 row,
131 direction: FlexDirection.horizontal,
132 justifyContent: FlexJustifyContent.center,
133 key: 'flex_row($iy)'
134 ));
135 }
136
137 if (!hasCoveredCell) {
138 // all cells uncovered. Are all mines flagged?
139 if ((detectedCount == totalMineCount) && alive) {
140 hasWon = true;
141 }
142 }
143
144 return new Container(
145 key: 'minefield',
146 padding: new EdgeDims.all(10.0),
147 margin: new EdgeDims.all(10.0),
148 decoration: new BoxDecoration(backgroundColor: const Color(0xFF6B6B6B)),
149 child: new Flex(
150 flexRows,
151 direction: FlexDirection.vertical,
152 key: 'flxv'));
153 }
154
155 Widget generateUI() {
156 Widget board = generateBoard();
157 String banner = hasWon ?
158 'Awesome!!' : alive ?
159 'Mine Digger [$detectedCount-$totalMineCount]': 'Kaboom! [press here]';
160
161 return new Flex([
162 new Container(
163 padding: new EdgeDims.all(10.0),
164 margin: new EdgeDims.all(10.0),
165 decoration: new BoxDecoration(backgroundColor: const Color(0xFFC0C0C0)),
166 child: new Listener(
167 onPointerDown: handleBannerPointerDown,
168 child: new Text(banner))),
169 board,
170 new Container(
171 height: 100.0, width: 100.0,
172 decoration: new BoxDecoration(backgroundColor: const Color(0xFFCC1111))
173 )
174 ],
175 direction: FlexDirection.vertical,
176 justifyContent: FlexJustifyContent.spaceAround);
177 }
178
179 void handleBannerPointerDown(sky.PointerEvent event) {
180 initialize();
181 app.setState((){});
182 }
183
184 // User action. The user uncovers the cell which can cause losing the game.
185 void probe(int x, int y) {
186 if (!alive)
187 return;
188 if (uiState[y][x] == flaggedCell)
189 return;
190 // Allowed to probe.
191 if (cells[y][x]) {
192 // Probed on a mine --> dead!!
193 uiState[y][x] = explodedCell;
194 alive = false;
195 } else {
196 // No mine, uncover nearby if possible.
197 cull(x, y);
198 }
199 app.setState((){});
200 }
201
202 // User action. The user is sure a mine is at this location.
203 void flag(int x, int y) {
204 if (uiState[y][x] == flaggedCell) {
205 uiState[y][x] = coveredCell;
206 --detectedCount;
207 } else {
208 uiState[y][x] = flaggedCell;
209 ++detectedCount;
210 }
211 app.setState((){});
212 }
213
214 // Recursively uncovers cells whose totalMineCount is zero.
215 void cull(int x, int y) {
216 if ((x < 0) || (x > rows - 1))
217 return;
218 if ((y < 0) || (y > cols - 1))
219 return;
220
221 if (uiState[y][x] == clearedCell)
222 return;
223 uiState[y][x] = clearedCell;
224
225 if (mineCount(x, y) > 0)
226 return;
227
228 cull(x - 1, y);
229 cull(x + 1, y);
230 cull(x, y - 1);
231 cull(x, y + 1 );
232 cull(x - 1, y - 1);
233 cull(x + 1, y + 1);
234 cull(x + 1, y - 1);
235 cull(x - 1, y + 1);
236 }
237
238 int mineCount(int x, int y) {
239 int count = 0;
240 int my = cols - 1;
241 int mx = rows - 1;
242
243 count += x > 0 ? bombs(x - 1, y) : 0;
244 count += x < mx ? bombs(x + 1, y) : 0;
245 count += y > 0 ? bombs(x, y - 1) : 0;
246 count += y < my ? bombs(x, y + 1 ) : 0;
247
248 count += (x > 0) && (y > 0) ? bombs(x - 1, y - 1) : 0;
249 count += (x < mx) && (y < my) ? bombs(x + 1, y + 1) : 0;
250 count += (x < mx) && (y > 0) ? bombs(x + 1, y - 1) : 0;
251 count += (x > 0) && (y < my) ? bombs(x - 1, y + 1) : 0;
252
253 return count;
254 }
255
256 int bombs(int x, int y) {
257 return cells[y][x] ? 1 : 0;
258 }
259 }
260
261 Widget makeCell(Widget widget) {
262 return new Container(
263 padding: new EdgeDims.all(1.0),
264 height: 27.0, width: 27.0,
265 decoration: new BoxDecoration(backgroundColor: const Color(0xFFC0C0C0)),
266 margin: new EdgeDims.all(2.0),
267 child: widget);
268 }
269
270 Widget makeInnerCell(Widget widget) {
271 return new Container(
272 padding: new EdgeDims.all(1.0),
273 margin: new EdgeDims.all(3.0),
274 height: 17.0, width: 17.0,
275 child: widget);
276 }
277
278 class CoveredMineNode extends Component {
279 final Game game;
280 final bool flagged;
281 final int posX;
282 final int posY;
283 Stopwatch stopwatch;
284
285 CoveredMineNode(this.game, {this.flagged, this.posX, this.posY});
286
287 void _handlePointerDown(sky.PointerEvent event) {
288 if (event.buttons == 1) {
289 game.probe(posX, posY);
290 } else if (event.buttons == 2) {
291 game.flag(posX, posY);
292 } else {
293 // Touch event.
294 stopwatch = new Stopwatch()..start();
295 }
296 }
297
298 void _handlePointerUp(sky.PointerEvent event) {
299 if (stopwatch == null)
300 return;
301 // Pointer down was a touch event.
302 var ms = stopwatch.elapsedMilliseconds;
303 if (stopwatch.elapsedMilliseconds < 250) {
304 game.probe(posX, posY);
305 } else {
306 // Long press flags.
307 game.flag(posX, posY);
308 }
309 stopwatch = null;
310 }
311
312 Widget build() {
313 Widget text = flagged ?
314 makeInnerCell(new StyledText(elements : [Game.textStyles[5], '\u2691'])) :
315 null;
316
317 Container inner = new Container(
318 margin: new EdgeDims.all(2.0),
319 height: 17.0, width: 17.0,
320 decoration: new BoxDecoration(backgroundColor: const Color(0xFFD9D9D9)),
321 child: text);
322
323 return makeCell(new Listener(
324 child: inner,
325 onPointerDown: _handlePointerDown,
326 onPointerUp: _handlePointerUp));
327 }
328 }
329
330 class ExposedMineNode extends Component {
331 final int state;
332 final int count;
333
334 ExposedMineNode({this.state, this.count});
335
336 Widget build() {
337 StyledText text;
338 if (state == Game.clearedCell) {
339 // Uncovered cell with nearby mine count.
340 if (count != 0)
341 text = new StyledText(elements : [Game.textStyles[count], '$count']);
342 } else {
343 // Exploded mine or shown mine for 'game over'.
344 int color = state == Game.explodedCell ? 3 : 0;
345 text = new StyledText(elements : [Game.textStyles[color], '\u2600']);
346 }
347
348 return makeCell(makeInnerCell(text));
349 }
350 }
351
352 class MineDiggerApp extends App {
353 Game game;
354
355 MineDiggerApp() {
356 game = new Game(this);
357 }
358
359 Widget build() {
360 return game.generateUI();
361 }
362 }
363
364 void main() {
365 runApp(new MineDiggerApp());
366 }
OLDNEW
« 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