Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
b215e4d6
Commit
b215e4d6
authored
Jul 22, 2015
by
Ian Hickson
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #205 from Hixie/minedigger
Many code improvements to Mine Digger.
parents
136b2709
4d2902f2
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
158 additions
and
165 deletions
+158
-165
main.dart
packages/flutter/example/mine_digger/lib/main.dart
+158
-165
No files found.
packages/flutter/example/mine_digger/lib/main.dart
View file @
b215e4d6
...
@@ -9,125 +9,152 @@ import 'package:sky/painting/text_style.dart';
...
@@ -9,125 +9,152 @@ import 'package:sky/painting/text_style.dart';
import
'package:sky/rendering/flex.dart'
;
import
'package:sky/rendering/flex.dart'
;
import
'package:sky/theme/colors.dart'
as
colors
;
import
'package:sky/theme/colors.dart'
as
colors
;
import
'package:sky/widgets/basic.dart'
;
import
'package:sky/widgets/basic.dart'
;
import
'package:sky/widgets/widget.dart'
;
import
'package:sky/widgets/scaffold.dart'
;
import
'package:sky/widgets/scaffold.dart'
;
import
'package:sky/widgets/task_description.dart'
;
import
'package:sky/widgets/task_description.dart'
;
import
'package:sky/widgets/theme.dart'
;
import
'package:sky/widgets/theme.dart'
;
import
'package:sky/widgets/tool_bar.dart'
;
import
'package:sky/widgets/tool_bar.dart'
;
// Classic minesweeper-inspired game. The mouse controls are standard
except
// Classic minesweeper-inspired game. The mouse controls are standard
//
for left + right combo which is not implemented. For touch, the duration of
//
except for left + right combo which is not implemented. For touch,
// the pointer determines probing versus flagging.
// the
duration of the
pointer determines probing versus flagging.
//
//
// There are only 3 classes to understand. Game, which is contains all the
// There are only 3 classes to understand. MineDiggerApp, which is
// logic and two UI classes: CoveredMineNode and ExposedMineNode, none of them
// contains all the logic and two classes that describe the mines:
// holding state.
// CoveredMineNode and ExposedMineNode, none of them holding state.
// Colors for each mine count (0-8):
const
List
<
TextStyle
>
textStyles
=
const
<
TextStyle
>[
const
TextStyle
(
color:
const
Color
(
0xFF555555
),
fontWeight:
bold
),
const
TextStyle
(
color:
const
Color
(
0xFF0094FF
),
fontWeight:
bold
),
// blue
const
TextStyle
(
color:
const
Color
(
0xFF13A023
),
fontWeight:
bold
),
// green
const
TextStyle
(
color:
const
Color
(
0xFFDA1414
),
fontWeight:
bold
),
// red
const
TextStyle
(
color:
const
Color
(
0xFF1E2347
),
fontWeight:
bold
),
// black
const
TextStyle
(
color:
const
Color
(
0xFF7F0037
),
fontWeight:
bold
),
// dark red
const
TextStyle
(
color:
const
Color
(
0xFF000000
),
fontWeight:
bold
),
const
TextStyle
(
color:
const
Color
(
0xFF000000
),
fontWeight:
bold
),
const
TextStyle
(
color:
const
Color
(
0xFF000000
),
fontWeight:
bold
),
];
enum
CellState
{
covered
,
exploded
,
cleared
,
flagged
,
shown
}
class
MineDiggerApp
extends
App
{
void
initState
()
{
resetGame
();
}
class
Game
{
static
const
int
rows
=
9
;
static
const
int
rows
=
9
;
static
const
int
cols
=
9
;
static
const
int
cols
=
9
;
static
const
int
totalMineCount
=
11
;
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
alive
;
bool
hasWon
;
bool
hasWon
;
int
detectedCount
;
int
detectedCount
;
int
randomSeed
;
// |cells| keeps track of the positions of the mines.
// |cells| keeps track of the positions of the mines.
List
<
List
<
bool
>>
cells
;
List
<
List
<
bool
>>
cells
;
// |uiState| keeps track of the visible player progess.
// |uiState| keeps track of the visible player progess.
List
<
List
<
int
>>
uiState
;
List
<
List
<
CellState
>>
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
initializ
e
()
{
void
resetGam
e
()
{
alive
=
true
;
alive
=
true
;
hasWon
=
false
;
hasWon
=
false
;
detectedCount
=
0
;
detectedCount
=
0
;
// Build the arrays.
// Build the arrays.
cells
=
new
List
<
List
<
bool
>>();
cells
=
new
List
<
List
<
bool
>>();
uiState
=
new
List
<
List
<
int
>>();
uiState
=
new
List
<
List
<
CellState
>>();
for
(
int
iy
=
0
;
iy
!=
rows
;
iy
++)
{
for
(
int
iy
=
0
;
iy
!=
rows
;
iy
++)
{
cells
.
add
(
new
List
<
bool
>());
cells
.
add
(
new
List
<
bool
>());
uiState
.
add
(
new
List
<
int
>());
uiState
.
add
(
new
List
<
CellState
>());
for
(
int
ix
=
0
;
ix
!=
cols
;
ix
++)
{
for
(
int
ix
=
0
;
ix
!=
cols
;
ix
++)
{
cells
[
iy
].
add
(
false
);
cells
[
iy
].
add
(
false
);
uiState
[
iy
].
add
(
coveredCell
);
uiState
[
iy
].
add
(
CellState
.
covered
);
}
}
}
}
// Place the mines.
// Place the mines.
Random
random
=
new
Random
(++
randomSeed
);
Random
random
=
new
Random
();
for
(
int
mc
=
0
;
mc
!=
totalMineCount
;
mc
++)
{
int
cellsRemaining
=
rows
*
cols
;
int
rx
=
random
.
nextInt
(
rows
);
int
minesRemaining
=
totalMineCount
;
int
ry
=
random
.
nextInt
(
cols
);
for
(
int
x
=
0
;
x
<
cols
;
x
+=
1
)
{
if
(
cells
[
ry
][
rx
])
{
for
(
int
y
=
0
;
y
<
rows
;
y
+=
1
)
{
// Mine already there. Try again.
if
(
random
.
nextInt
(
cellsRemaining
)
<
minesRemaining
)
{
--
mc
;
cells
[
y
][
x
]
=
true
;
}
else
{
minesRemaining
-=
1
;
cells
[
ry
][
rx
]
=
true
;
if
(
minesRemaining
<=
0
)
return
;
}
cellsRemaining
-=
1
;
}
}
}
}
assert
(
false
);
}
Stopwatch
longPressStopwatch
;
PointerEventListener
_pointerDownHandlerFor
(
int
posX
,
int
posY
)
{
return
(
sky
.
PointerEvent
event
)
{
if
(
event
.
buttons
==
1
)
{
probe
(
posX
,
posY
);
}
else
if
(
event
.
buttons
==
2
)
{
flag
(
posX
,
posY
);
}
else
{
// Touch event.
longPressStopwatch
=
new
Stopwatch
()..
start
();
}
};
}
PointerEventListener
_pointerUpHandlerFor
(
int
posX
,
int
posY
)
{
return
(
sky
.
PointerEvent
event
)
{
if
(
longPressStopwatch
==
null
)
return
;
// Pointer down was a touch event.
if
(
longPressStopwatch
.
elapsedMilliseconds
<
250
)
{
probe
(
posX
,
posY
);
}
else
{
// Long press flags.
flag
(
posX
,
posY
);
}
longPressStopwatch
=
null
;
};
}
}
Widget
buildBoard
()
{
Widget
buildBoard
()
{
bool
hasCoveredCell
=
false
;
bool
hasCoveredCell
=
false
;
List
<
Flex
>
flexRows
=
new
List
<
Flex
>()
;
List
<
Flex
>
flexRows
=
<
Flex
>[]
;
for
(
int
iy
=
0
;
iy
!=
9
;
iy
++)
{
for
(
int
iy
=
0
;
iy
!=
9
;
iy
++)
{
List
<
Component
>
row
=
new
List
<
Component
>()
;
List
<
Widget
>
row
=
<
Widget
>[]
;
for
(
int
ix
=
0
;
ix
!=
9
;
ix
++)
{
for
(
int
ix
=
0
;
ix
!=
9
;
ix
++)
{
int
state
=
uiState
[
iy
][
ix
];
CellState
state
=
uiState
[
iy
][
ix
];
int
count
=
mineCount
(
ix
,
iy
);
int
count
=
mineCount
(
ix
,
iy
);
if
(!
alive
)
{
if
(!
alive
)
{
if
(
state
!=
explodedCell
)
if
(
state
!=
CellState
.
exploded
)
state
=
cells
[
iy
][
ix
]
?
shownCell
:
state
;
state
=
cells
[
iy
][
ix
]
?
CellState
.
shown
:
state
;
}
}
if
(
state
==
CellState
.
covered
)
{
if
(
state
==
coveredCell
)
{
row
.
add
(
new
Listener
(
row
.
add
(
new
CoveredMineNode
(
onPointerDown:
_pointerDownHandlerFor
(
ix
,
iy
),
this
,
onPointerUp:
_pointerUpHandlerFor
(
ix
,
iy
),
flagged:
false
,
child:
new
CoveredMineNode
(
posX:
ix
,
posY:
iy
));
flagged:
false
,
// Mutating |hasCoveredCell| here is hacky, but convenient, same
posX:
ix
,
// goes for mutating |hasWon| below.
posY:
iy
hasCoveredCell
=
true
;
)
}
else
if
(
state
==
flaggedCell
)
{
));
// Mutating |hasCoveredCell| here is hacky, but convenient, same
// goes for mutating |hasWon| below.
hasCoveredCell
=
true
;
}
else
if
(
state
==
CellState
.
flagged
)
{
row
.
add
(
new
CoveredMineNode
(
row
.
add
(
new
CoveredMineNode
(
this
,
flagged:
true
,
flagged:
true
,
posX:
ix
,
posY:
iy
));
posX:
ix
,
posY:
iy
)
);
}
else
{
}
else
{
row
.
add
(
new
ExposedMineNode
(
row
.
add
(
new
ExposedMineNode
(
state:
state
,
state:
state
,
count:
count
));
count:
count
)
);
}
}
}
}
flexRows
.
add
(
flexRows
.
add
(
...
@@ -147,33 +174,32 @@ class Game {
...
@@ -147,33 +174,32 @@ class Game {
}
}
return
new
Container
(
return
new
Container
(
key:
'minefield'
,
padding:
new
EdgeDims
.
all
(
10.0
),
padding:
new
EdgeDims
.
all
(
10.0
),
margin:
new
EdgeDims
.
all
(
10.0
),
margin:
new
EdgeDims
.
all
(
10.0
),
decoration:
new
BoxDecoration
(
backgroundColor:
const
Color
(
0xFF6B6B6B
)),
decoration:
new
BoxDecoration
(
backgroundColor:
const
Color
(
0xFF6B6B6B
)),
child:
new
Flex
(
child:
new
Flex
(
flexRows
,
flexRows
,
direction:
FlexDirection
.
vertical
,
direction:
FlexDirection
.
vertical
key:
'flxv'
));
)
);
}
}
Widget
buildToolBar
()
{
Widget
buildToolBar
()
{
String
banner
=
hasWon
?
String
toolbarCaption
=
hasWon
?
'Awesome!!'
:
alive
?
'Awesome!!'
:
alive
?
'Mine Digger [
$detectedCount
-
$totalMineCount
]'
:
'Kaboom! [press here]'
;
'Mine Digger [
$detectedCount
-
$totalMineCount
]'
:
'Kaboom! [press here]'
;
return
new
ToolBar
(
return
new
ToolBar
(
// FIXME: Strange to have the toolbar be tapable.
// FIXME: Strange to have the toolbar be tapable.
center:
new
Listener
(
center:
new
Listener
(
onPointerDown:
handle
Banne
rPointerDown
,
onPointerDown:
handle
Toolba
rPointerDown
,
child:
new
Text
(
banner
,
style:
Theme
.
of
(
this
.
app
).
text
.
title
)
child:
new
Text
(
toolbarCaption
,
style:
Theme
.
of
(
this
).
text
.
title
)
)
)
);
);
}
}
Widget
buildUI
()
{
Widget
build
()
{
// FIXME: We need to build the board before we build the toolbar because
// We build the board before we build the toolbar because we compute the win state during build step.
// we compute the win state during build step.
Widget
board
=
buildBoard
();
Widget
board
=
buildBoard
();
return
new
TaskDescription
(
return
new
TaskDescription
(
label:
'Mine Digger'
,
label:
'Mine Digger'
,
...
@@ -187,39 +213,42 @@ class Game {
...
@@ -187,39 +213,42 @@ class Game {
);
);
}
}
void
handleBannerPointerDown
(
sky
.
PointerEvent
event
)
{
void
handleToolbarPointerDown
(
sky
.
PointerEvent
event
)
{
initialize
();
setState
(()
{
app
.
scheduleBuild
();
resetGame
();
});
}
}
// User action. The user uncovers the cell which can cause losing the game.
// User action. The user uncovers the cell which can cause losing the game.
void
probe
(
int
x
,
int
y
)
{
void
probe
(
int
x
,
int
y
)
{
if
(!
alive
)
if
(!
alive
)
return
;
return
;
if
(
uiState
[
y
][
x
]
==
flaggedCell
)
if
(
uiState
[
y
][
x
]
==
CellState
.
flagged
)
return
;
return
;
// Allowed to probe.
setState
(()
{
if
(
cells
[
y
][
x
])
{
// Allowed to probe.
// Probed on a mine --> dead!!
if
(
cells
[
y
][
x
])
{
uiState
[
y
][
x
]
=
explodedCell
;
// Probed on a mine --> dead!!
alive
=
false
;
uiState
[
y
][
x
]
=
CellState
.
exploded
;
}
else
{
alive
=
false
;
// No mine, uncover nearby if possible.
}
else
{
cull
(
x
,
y
);
// No mine, uncover nearby if possible.
}
cull
(
x
,
y
);
app
.
scheduleBuild
();
}
});
}
}
// User action. The user is sure a mine is at this location.
// User action. The user is sure a mine is at this location.
void
flag
(
int
x
,
int
y
)
{
void
flag
(
int
x
,
int
y
)
{
if
(
uiState
[
y
][
x
]
==
flaggedCell
)
{
setState
(()
{
uiState
[
y
][
x
]
=
coveredCell
;
if
(
uiState
[
y
][
x
]
==
CellState
.
flagged
)
{
--
detectedCount
;
uiState
[
y
][
x
]
=
CellState
.
covered
;
}
else
{
--
detectedCount
;
uiState
[
y
][
x
]
=
flaggedCell
;
}
else
{
++
detectedCount
;
uiState
[
y
][
x
]
=
CellState
.
flagged
;
}
++
detectedCount
;
app
.
scheduleBuild
();
}
});
}
}
// Recursively uncovers cells whose totalMineCount is zero.
// Recursively uncovers cells whose totalMineCount is zero.
...
@@ -229,9 +258,9 @@ class Game {
...
@@ -229,9 +258,9 @@ class Game {
if
((
y
<
0
)
||
(
y
>
cols
-
1
))
if
((
y
<
0
)
||
(
y
>
cols
-
1
))
return
;
return
;
if
(
uiState
[
y
][
x
]
==
clearedCell
)
if
(
uiState
[
y
][
x
]
==
CellState
.
cleared
)
return
;
return
;
uiState
[
y
][
x
]
=
clearedCell
;
uiState
[
y
][
x
]
=
CellState
.
cleared
;
if
(
mineCount
(
x
,
y
)
>
0
)
if
(
mineCount
(
x
,
y
)
>
0
)
return
;
return
;
...
@@ -269,106 +298,70 @@ class Game {
...
@@ -269,106 +298,70 @@ class Game {
}
}
}
}
Widget
makeCell
(
Widget
widget
)
{
Widget
buildCell
(
Widget
child
)
{
return
new
Container
(
return
new
Container
(
padding:
new
EdgeDims
.
all
(
1.0
),
padding:
new
EdgeDims
.
all
(
1.0
),
height:
27.0
,
width:
27.0
,
height:
27.0
,
width:
27.0
,
decoration:
new
BoxDecoration
(
backgroundColor:
const
Color
(
0xFFC0C0C0
)),
decoration:
new
BoxDecoration
(
backgroundColor:
const
Color
(
0xFFC0C0C0
)),
margin:
new
EdgeDims
.
all
(
2.0
),
margin:
new
EdgeDims
.
all
(
2.0
),
child:
widget
);
child:
child
);
}
}
Widget
makeInnerCell
(
Widget
widget
)
{
Widget
buildInnerCell
(
Widget
child
)
{
return
new
Container
(
return
new
Container
(
padding:
new
EdgeDims
.
all
(
1.0
),
padding:
new
EdgeDims
.
all
(
1.0
),
margin:
new
EdgeDims
.
all
(
3.0
),
margin:
new
EdgeDims
.
all
(
3.0
),
height:
17.0
,
width:
17.0
,
height:
17.0
,
width:
17.0
,
child:
widget
);
child:
child
);
}
}
class
CoveredMineNode
extends
Component
{
class
CoveredMineNode
extends
Component
{
final
Game
game
;
CoveredMineNode
({
this
.
flagged
,
this
.
posX
,
this
.
posY
});
final
bool
flagged
;
final
bool
flagged
;
final
int
posX
;
final
int
posX
;
final
int
posY
;
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.
if
(
stopwatch
.
elapsedMilliseconds
<
250
)
{
game
.
probe
(
posX
,
posY
);
}
else
{
// Long press flags.
game
.
flag
(
posX
,
posY
);
}
stopwatch
=
null
;
}
Widget
build
()
{
Widget
build
()
{
Widget
text
=
flagged
?
Widget
text
;
makeInnerCell
(
new
StyledText
(
elements
:
[
Game
.
textStyles
[
5
],
'
\
u2691'
]))
:
if
(
flagged
)
null
;
text
=
buildInnerCell
(
new
StyledText
(
elements
:
[
textStyles
[
5
],
'
\
u2691'
]))
;
Container
inner
=
new
Container
(
Container
inner
=
new
Container
(
margin:
new
EdgeDims
.
all
(
2.0
),
margin:
new
EdgeDims
.
all
(
2.0
),
height:
17.0
,
width:
17.0
,
height:
17.0
,
width:
17.0
,
decoration:
new
BoxDecoration
(
backgroundColor:
const
Color
(
0xFFD9D9D9
)),
decoration:
new
BoxDecoration
(
backgroundColor:
const
Color
(
0xFFD9D9D9
)),
child:
text
);
child:
text
);
return
makeCell
(
new
Listener
(
return
buildCell
(
inner
);
child:
inner
,
onPointerDown:
_handlePointerDown
,
onPointerUp:
_handlePointerUp
));
}
}
}
}
class
ExposedMineNode
extends
Component
{
class
ExposedMineNode
extends
Component
{
final
int
state
;
final
int
count
;
ExposedMineNode
({
this
.
state
,
this
.
count
});
ExposedMineNode
({
this
.
state
,
this
.
count
});
final
CellState
state
;
final
int
count
;
Widget
build
()
{
Widget
build
()
{
StyledText
text
;
StyledText
text
;
if
(
state
==
Game
.
clearedCell
)
{
if
(
state
==
CellState
.
cleared
)
{
// Uncovered cell with nearby mine count.
// Uncovered cell with nearby mine count.
if
(
count
!=
0
)
if
(
count
!=
0
)
text
=
new
StyledText
(
elements
:
[
Game
.
textStyles
[
count
],
'
$count
'
]);
text
=
new
StyledText
(
elements
:
[
textStyles
[
count
],
'
$count
'
]);
}
else
{
}
else
{
// Exploded mine or shown mine for 'game over'.
// Exploded mine or shown mine for 'game over'.
int
color
=
state
==
Game
.
explodedCell
?
3
:
0
;
int
color
=
state
==
CellState
.
exploded
?
3
:
0
;
text
=
new
StyledText
(
elements
:
[
Game
.
textStyles
[
color
],
'
\
u2600'
]);
text
=
new
StyledText
(
elements
:
[
textStyles
[
color
],
'
\
u2600'
]);
}
}
return
buildCell
(
buildInnerCell
(
text
));
return
makeCell
(
makeInnerCell
(
text
));
}
}
}
class
MineDiggerApp
extends
App
{
Game
game
;
MineDiggerApp
()
{
game
=
new
Game
(
this
);
}
Widget
build
()
{
return
game
.
buildUI
();
}
}
}
void
main
(
)
{
void
main
(
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment