OLD | NEW |
| (Empty) |
1 part of pop_pop_win.canvas; | |
2 | |
3 class GameElement extends ParentThing { | |
4 static const _edgeOffset = 32; | |
5 static const _backgroundSize = const Size(2048, 1536); | |
6 static const _backgroundEdgeOffset = 256; | |
7 static const _backgroundHoleSize = 16 * SquareElement._size + 2 * _edgeOffset; | |
8 static const _boardOffset = const Vector(352, 96); | |
9 static const _popExplodeAnimationOffset = const Vector(-88, -88); | |
10 static const _popAnimationHitFrame = 12; | |
11 | |
12 static const _dartAnimationOffset = | |
13 const Vector(-512 + 0.5 * SquareElement._size, | |
14 -388 + 0.5 * SquareElement._size); | |
15 | |
16 final CanvasThing _canvas = new CanvasThing(0, 0); | |
17 final GameBackgroundElement _background = new GameBackgroundElement(); | |
18 final BoardElement _boardElement = new BoardElement(); | |
19 final ScoreElement _scoreElement; | |
20 final NewGameElement _newGameElement = new NewGameElement(); | |
21 final GameTitleElement _titleElement = new GameTitleElement(); | |
22 final TextureAnimationThing _popAnimationLayer, _dartAnimationLayer; | |
23 final TextureData _textureData; | |
24 final EventHandle _targetChanged = new EventHandle(); | |
25 | |
26 int _targetX, _targetY; | |
27 double _scale; | |
28 Vector _scaledBoardOffset; | |
29 Box _scaledInnerBox; | |
30 SquareElement _mouseDownElement, _lastHoldUnfreeze; | |
31 Timer _mouseDownTimer; | |
32 | |
33 Game _game; | |
34 | |
35 GameElement(TextureData textureData) | |
36 : _textureData = textureData, | |
37 _popAnimationLayer = new TextureAnimationThing(0, 0, textureData), | |
38 _dartAnimationLayer = new TextureAnimationThing(0, 0, textureData), | |
39 _scoreElement = new ScoreElement(), | |
40 super(100, 100) { | |
41 _canvas.registerParent(this); | |
42 _canvas.add(_background); | |
43 _canvas.add(_boardElement); | |
44 _canvas.add(_newGameElement); | |
45 _canvas.add(_scoreElement); | |
46 _canvas.add(_popAnimationLayer); | |
47 _canvas.add(_titleElement); | |
48 _canvas.add(_dartAnimationLayer); | |
49 | |
50 _newGameElement.clicked.listen((args) => GameAudio.click()); | |
51 | |
52 MouseManager.setClickable(_titleElement, true); | |
53 MouseManager.getClickStream(_titleElement).listen((args) => | |
54 _titleClickedEventHandle.add(EventArgs.empty)); | |
55 } | |
56 | |
57 Stream<EventArgs> get newGameClick => _newGameElement.clicked; | |
58 | |
59 Game get game => _game; | |
60 | |
61 void set game(Game value) { | |
62 _game = value; | |
63 if (value == null) { | |
64 size = const Size(100, 100); | |
65 } else { | |
66 _updateSize(value.field.width, value.field.height); | |
67 } | |
68 } | |
69 | |
70 bool get canRevealTarget => | |
71 _targetX != null && _game.canReveal(_targetX, _targetY); | |
72 | |
73 bool get canFlagTarget => | |
74 _targetX != null && _game.canToggleFlag(_targetX, _targetY); | |
75 | |
76 void setGameManager(GameManager manager) { | |
77 _scoreElement.setGameManager(manager); | |
78 } | |
79 | |
80 void revealTarget() { | |
81 if (_targetX != null) { | |
82 game.reveal(_targetX, _targetY); | |
83 _target(null, null); | |
84 } | |
85 } | |
86 | |
87 void toggleTargetFlag() { | |
88 if (_targetX != null) { | |
89 final success = _toggleFlag(_targetX, _targetY); | |
90 if (success) { | |
91 _target(null, null); | |
92 } | |
93 } | |
94 } | |
95 | |
96 Stream get targetChanged => _targetChanged.stream; | |
97 | |
98 int get visualChildCount => 1; | |
99 | |
100 Thing getVisualChild(int index) { | |
101 assert(index == 0); | |
102 return _canvas; | |
103 } | |
104 | |
105 void update() { | |
106 super.update(); | |
107 final offset = _scaledBoardOffset + | |
108 const Coordinate(_edgeOffset, _edgeOffset); | |
109 | |
110 _canvas.setTopLeft(_boardElement, offset); | |
111 _canvas.setTopLeft(_popAnimationLayer, offset); | |
112 _canvas.setTopLeft(_dartAnimationLayer, offset); | |
113 | |
114 // score offset | |
115 // end of the board - score width | |
116 final x = _scale * (40 + _backgroundSize.width - _boardOffset.x - _scoreElem
ent.width); | |
117 | |
118 _canvas.setTopLeft(_scoreElement, new Coordinate(x, 0)); | |
119 _canvas.getChildTransform(_scoreElement).scale(_scale, _scale); | |
120 | |
121 final newGameTopLeft = new Coordinate( | |
122 (_boardOffset.x + _newGameElement.width * 0.2) * _scale, 0); | |
123 _canvas.setTopLeft(_newGameElement, newGameTopLeft); | |
124 _canvas.getChildTransform(_newGameElement).scale(_scale, _scale); | |
125 | |
126 final titleMultiplier = 1.7; | |
127 final titleTopLeft = new Coordinate(_scale * 0.5 * (_backgroundSize.width - | |
128 _titleElement.width * titleMultiplier), 0); | |
129 _canvas.setTopLeft(_titleElement, titleTopLeft); | |
130 _canvas.getChildTransform(_titleElement) | |
131 .scale(titleMultiplier * _scale, titleMultiplier * _scale); | |
132 } | |
133 | |
134 void drawOverride(CanvasRenderingContext2D ctx) { | |
135 // draw children via super | |
136 super.drawOverride(ctx); | |
137 | |
138 // draw target element | |
139 _drawTarget(ctx); | |
140 } | |
141 | |
142 void _drawTarget(CanvasRenderingContext2D ctx) { | |
143 assert((_targetX == null) == (_targetY == null)); | |
144 if (_targetX != null) { | |
145 final halfSize = SquareElement._size * 0.5; | |
146 var targetLoc = new Vector(_targetX, _targetY); | |
147 targetLoc = targetLoc.scale(SquareElement._size); | |
148 | |
149 ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; | |
150 CanvasUtil.centeredCircle(ctx, | |
151 targetLoc.x + halfSize, targetLoc.y + halfSize, halfSize); | |
152 ctx.fill(); | |
153 } | |
154 } | |
155 | |
156 void _startPopAnimation(Coordinate start, [Iterable<Coordinate> reveals = null
]) { | |
157 if(reveals == null) { | |
158 assert(game.state == GameState.lost); | |
159 reveals = new NumberEnumerable.fromRange(0, game.field.length) | |
160 .map((i) { | |
161 final t = game.field.getCoordinate(i); | |
162 final c = new Coordinate(t.item1, t.item2); | |
163 return new Tuple(c, game.getSquareState(c.x, c.y)); | |
164 }) | |
165 .where((t2) => t2.item2 == SquareState.bomb || t2.item2 == SquareState
.hidden) | |
166 .map((t2) => t2.item1) | |
167 .toList(); | |
168 } | |
169 | |
170 final values = reveals.map((c) { | |
171 final initialOffset = new Vector(SquareElement._size * c.x, | |
172 SquareElement._size * c.y); | |
173 final squareOffset = _popExplodeAnimationOffset + initialOffset; | |
174 | |
175 var delay = _popAnimationHitFrame + ((c - start).magnitude * 4).toInt(); | |
176 delay += rnd.nextInt(10); | |
177 | |
178 return [c, initialOffset, squareOffset, delay]; | |
179 }).toList(); | |
180 | |
181 values.sort((a, b) { | |
182 final int da = a[3]; | |
183 final int db = b[3]; | |
184 return da.compareTo(db); | |
185 }); | |
186 | |
187 for (final v in values) { | |
188 final Coordinate c = v[0]; | |
189 final Vector initialOffset = v[1]; | |
190 final Vector squareOffset = v[2]; | |
191 final int delay = v[3]; | |
192 | |
193 final ss = game.getSquareState(c.x, c.y); | |
194 | |
195 String texturePrefix; | |
196 int frameCount; | |
197 | |
198 switch (ss) { | |
199 case SquareState.revealed: | |
200 case SquareState.hidden: | |
201 texturePrefix = 'balloon_pop'; | |
202 frameCount = 28; | |
203 break; | |
204 case SquareState.bomb: | |
205 texturePrefix = 'balloon_explode'; | |
206 frameCount = 24; | |
207 break; | |
208 default: | |
209 throw 'not supported'; | |
210 } | |
211 | |
212 final request = new TextureAnimationRequest(texturePrefix, frameCount, squ
areOffset, | |
213 delay: delay, initialFrame: 'balloon.png', initialFrameOffset: initial
Offset); | |
214 | |
215 switch (ss) { | |
216 case SquareState.revealed: | |
217 case SquareState.hidden: | |
218 request.started.listen((args) => GameAudio.pop()); | |
219 break; | |
220 case SquareState.bomb: | |
221 request.started.listen((args) => GameAudio.bomb()); | |
222 break; | |
223 } | |
224 | |
225 _popAnimationLayer.add(request); | |
226 } | |
227 } | |
228 | |
229 void _startDartAnimation(List<Coordinate> points) { | |
230 assert(points.length >= 1); | |
231 GameAudio.throwDart(); | |
232 for(final point in points) { | |
233 final squareOffset = _dartAnimationOffset + | |
234 new Vector(SquareElement._size * point.x, SquareElement._size * point.
y); | |
235 | |
236 _dartAnimationLayer.add(new TextureAnimationRequest('dart_fly_shadow', 54,
squareOffset)); | |
237 _dartAnimationLayer.add(new TextureAnimationRequest('dart_fly', 54, square
Offset)); | |
238 } | |
239 } | |
240 | |
241 void wireSquareMouseEvent(Thing square) { | |
242 MouseManager.getClickStream(square).listen(_squareClicked); | |
243 MouseManager.getMouseDownStream(square).listen(_squareMouseDown); | |
244 MouseManager.getMouseUpStream(square).listen(_squareMouseUp); | |
245 MouseManager.getMouseMoveStream(square).listen(_squareMouseMove); | |
246 } | |
247 | |
248 void _squareClicked(ThingMouseEventArgs args) { | |
249 if (!_game.gameEnded && _lastHoldUnfreeze == null) { | |
250 final SquareElement se = args.thing; | |
251 _click(se.x, se.y, args.shiftKey); | |
252 } | |
253 } | |
254 | |
255 void _squareMouseDown(ThingMouseEventArgs args) { | |
256 _lastHoldUnfreeze = null; | |
257 if (_mouseDownTimer != null) { | |
258 _mouseDownTimer.cancel(); | |
259 } | |
260 final SquareElement se = args.thing; | |
261 _mouseDownElement = se; | |
262 _mouseDownTimer = new Timer(const Duration(seconds: 1), _mouseDownTimeoutHan
dle); | |
263 } | |
264 | |
265 void _squareMouseMove(ThingMouseEventArgs args) { | |
266 final SquareElement se = args.thing; | |
267 if (_mouseDownElement != se) { | |
268 _clearTimeout(); | |
269 } | |
270 } | |
271 | |
272 void _squareMouseUp(ThingMouseEventArgs args) { | |
273 final SquareElement se = args.thing; | |
274 _clearTimeout(); | |
275 } | |
276 | |
277 void _mouseDownTimeoutHandle() { | |
278 assert(_mouseDownTimer != null); | |
279 assert(_mouseDownElement != null); | |
280 _click(_mouseDownElement.x, _mouseDownElement.y, true); | |
281 _lastHoldUnfreeze = _mouseDownElement; | |
282 _clearTimeout(); | |
283 } | |
284 | |
285 void _clearTimeout() { | |
286 if (_mouseDownTimer != null) { | |
287 _mouseDownTimer.cancel(); | |
288 _mouseDownTimer = null; | |
289 } | |
290 _mouseDownElement = null; | |
291 } | |
292 | |
293 void _target(int x, int y) { | |
294 _targetX = x; | |
295 _targetY = y; | |
296 _targetChanged.add(null); | |
297 invalidateDraw(); | |
298 } | |
299 | |
300 bool _toggleFlag(int x, int y) { | |
301 assert(!game.gameEnded); | |
302 final ss = game.getSquareState(x, y); | |
303 if (ss == SquareState.hidden) { | |
304 game.setFlag(x, y, true); | |
305 GameAudio.flag(); | |
306 return true; | |
307 } else if (ss == SquareState.flagged) { | |
308 game.setFlag(x, y, false); | |
309 GameAudio.unflag(); | |
310 return true; | |
311 } | |
312 return false; | |
313 } | |
314 | |
315 void _click(int x, int y, bool alt) { | |
316 assert(!game.gameEnded); | |
317 final ss = game.getSquareState(x, y); | |
318 | |
319 List<Coordinate> reveals = null; | |
320 | |
321 if (alt) { | |
322 if (ss == SquareState.hidden || ss == SquareState.flagged) { | |
323 _toggleFlag(x, y); | |
324 } else if (ss == SquareState.revealed) { | |
325 if (game.canReveal(x, y)) { | |
326 // get adjacent ballons | |
327 final adjHidden = game.field.getAdjacentIndices(x, y) | |
328 .map((i) { | |
329 final t = game.field.getCoordinate(i); | |
330 return new Coordinate(t.item1, t.item2); | |
331 }) | |
332 .where((t) => game.getSquareState(t.x, t.y) == SquareState.hidden) | |
333 .toList(); | |
334 | |
335 assert(adjHidden.length > 0); | |
336 | |
337 _startDartAnimation(adjHidden); | |
338 reveals = game.reveal(x, y); | |
339 } | |
340 } | |
341 } else { | |
342 if (ss == SquareState.hidden) { | |
343 _startDartAnimation([new Coordinate(x, y)]); | |
344 reveals = game.reveal(x, y); | |
345 } | |
346 } | |
347 | |
348 if (reveals != null && reveals.length > 0) { | |
349 assert(game.state != GameState.lost); | |
350 if (!alt) { | |
351 // if it was a normal click, the first item should be the clicked item | |
352 var first = reveals[0]; | |
353 assert(first.x == x); | |
354 assert(first.y == y); | |
355 } | |
356 _startPopAnimation(new Coordinate(x, y), reveals); | |
357 } else if (game.state == GameState.lost) { | |
358 _startPopAnimation(new Coordinate(x, y)); | |
359 } | |
360 } | |
361 | |
362 void _updateSize(int w, int h) { | |
363 final sizeX = _getScale(w, _backgroundSize.width, _backgroundHoleSize); | |
364 final sizeY = _getScale(h, _backgroundSize.height, _backgroundHoleSize); | |
365 | |
366 _canvas.size = size = new Size(sizeX, sizeY); | |
367 | |
368 // NOTE: width wins here. Need to do work to make left and right sides | |
369 // scale nicely when not a square | |
370 _scale = sizeX / _backgroundSize.width; | |
371 _scaledBoardOffset = _boardOffset.scale(_scale); | |
372 | |
373 _scaledInnerBox = new Box(_backgroundEdgeOffset * _scale, 0, | |
374 sizeX - 2 * _backgroundEdgeOffset * _scale, sizeY); | |
375 } | |
376 | |
377 static num _getScale(int count, num fullSize, num holeSize) { | |
378 final k = count * SquareElement._size + 2 * _edgeOffset; | |
379 | |
380 return k * fullSize / holeSize; | |
381 } | |
382 } | |
OLD | NEW |