| 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 |