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

Side by Side Diff: sky/examples/mine_digger/mine_digger.dart

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

Powered by Google App Engine
This is Rietveld 408576698