| 
									
										
										
										
											2020-01-15 19:04:40 +01:00
										 |  |  | import 'dart:math'; | 
					
						
							|  |  |  | import 'dart:ui'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import 'package:flame/components/mixins/tapable.dart'; | 
					
						
							|  |  |  | import 'package:flame/game.dart'; | 
					
						
							|  |  |  | import 'package:flame/sprite.dart'; | 
					
						
							|  |  |  | import 'package:flame/time.dart'; | 
					
						
							|  |  |  | import 'package:flutter/widgets.dart'; | 
					
						
							|  |  |  | import 'package:vector_math/vector_math_64.dart'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import 'components/cat.dart'; | 
					
						
							| 
									
										
										
										
											2020-01-19 14:12:11 +01:00
										 |  |  | import 'components/coin.dart'; | 
					
						
							| 
									
										
										
										
											2020-01-15 19:04:40 +01:00
										 |  |  | import 'components/tree.dart'; | 
					
						
							|  |  |  | import 'localizations.dart'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | final random = Random(DateTime.now().millisecondsSinceEpoch); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | extension RandomPicker<T> on List<T> { | 
					
						
							|  |  |  |   T pick() { | 
					
						
							|  |  |  |     final index = random.nextInt(length); | 
					
						
							|  |  |  |     return this.removeAt(index); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Cell { | 
					
						
							|  |  |  |   final int x; | 
					
						
							|  |  |  |   final int y; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // A number for doing random things in a deterministic way
 | 
					
						
							|  |  |  |   final r = random.nextDouble(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Tree tree; | 
					
						
							|  |  |  |   Cat cat; | 
					
						
							| 
									
										
										
										
											2020-01-19 14:12:11 +01:00
										 |  |  |   Coin coin; | 
					
						
							| 
									
										
										
										
											2020-01-15 19:04:40 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   Cell(this.x, this.y); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ChristmasCats extends BaseGame with Tapable { | 
					
						
							|  |  |  |   static const marginTop = 64.0; | 
					
						
							|  |  |  |   static const marginLRB = 8.0; | 
					
						
							|  |  |  |   static const nColumns = 5; | 
					
						
							|  |  |  |   static const nRows = 6; | 
					
						
							|  |  |  |   static const nCells = nColumns * nRows; | 
					
						
							|  |  |  |   static const nTrees = nCells - 14; | 
					
						
							|  |  |  |   static const treeProbability = 0.6; | 
					
						
							|  |  |  |   static const minCatWait = 500; | 
					
						
							|  |  |  |   static const maxCatWait = 2000; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   final ChristmasCatsLocalizations localizations; | 
					
						
							|  |  |  |   final Size size; | 
					
						
							|  |  |  |   final bgSprite = Sprite('background.png'); | 
					
						
							|  |  |  |   final void Function(int score) onScoreChanged; | 
					
						
							|  |  |  |   final void Function(int paws) onPawsChanged; | 
					
						
							|  |  |  |   final void Function() onGameOver; | 
					
						
							|  |  |  |   final List<Timer> timers = []; | 
					
						
							|  |  |  |   final List<Cat> cats = []; | 
					
						
							|  |  |  |   final List<Tree> trees = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   double get cellWidth => (size.width - 2 * marginLRB) / nColumns; | 
					
						
							|  |  |  |   double get cellHeight => (size.height - (marginTop + marginLRB)) / nRows; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   bool paused = true; | 
					
						
							|  |  |  |   int nCats = 0; | 
					
						
							|  |  |  |   int paws = 3; | 
					
						
							|  |  |  |   int score = 0; | 
					
						
							|  |  |  |   bool gameOver = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Timer nextCatTimer; | 
					
						
							| 
									
										
										
										
											2020-01-19 14:12:11 +01:00
										 |  |  |   Timer coinTimer; | 
					
						
							| 
									
										
										
										
											2020-01-15 19:04:40 +01:00
										 |  |  |   Timer scoreTimer; | 
					
						
							|  |  |  |   List<Cell> cells; | 
					
						
							|  |  |  |   List<int> treeCells; | 
					
						
							|  |  |  |   List<int> emptyCells; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ChristmasCats({ | 
					
						
							|  |  |  |     this.localizations, | 
					
						
							|  |  |  |     this.size, | 
					
						
							|  |  |  |     this.onScoreChanged, | 
					
						
							|  |  |  |     this.onPawsChanged, | 
					
						
							|  |  |  |     this.onGameOver, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Vector2 getTreePosition(Cell cell) { | 
					
						
							|  |  |  |     final x = marginLRB + (cell.x + 0.5) * cellWidth; | 
					
						
							|  |  |  |     final y = marginTop + (cell.y + 1) * cellHeight; | 
					
						
							|  |  |  |     final offsetX = cell.r * 20.0 - 10.0; | 
					
						
							|  |  |  |     final offsetY = cell.r * -20.0; | 
					
						
							|  |  |  |     return Vector2(x + offsetX, y + offsetY); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Vector2 getCatPosition(Cell cell) { | 
					
						
							|  |  |  |     if (cell.tree != null) { | 
					
						
							|  |  |  |       return getTreePosition(cell) + Vector2(0.0, -10.0); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       final x = | 
					
						
							|  |  |  |           marginLRB + (cell.x + 0.2 + 0.6 * random.nextDouble()) * cellWidth; | 
					
						
							|  |  |  |       final y = | 
					
						
							|  |  |  |           marginTop + (cell.y + 0.2 + 0.6 * random.nextDouble()) * cellHeight; | 
					
						
							|  |  |  |       return Vector2(x, y); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-19 14:12:11 +01:00
										 |  |  |   Vector2 getCoinPosition(Cell cell) { | 
					
						
							|  |  |  |     final x = marginLRB + (cell.x + 0.5) * cellWidth; | 
					
						
							|  |  |  |     final y = marginTop + (cell.y + 0.5) * cellHeight; | 
					
						
							|  |  |  |     return Vector2(x, y); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-15 19:04:40 +01:00
										 |  |  |   void updateCat(int oldCellIndex, Cat cat) { | 
					
						
							|  |  |  |     int newCellIndex; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (oldCellIndex != null) { | 
					
						
							|  |  |  |       if (treeCells.length > 0 && random.nextDouble() < treeProbability) { | 
					
						
							|  |  |  |         newCellIndex = treeCells.pick(); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         newCellIndex = emptyCells.pick(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       final oldCell = cells[oldCellIndex]; | 
					
						
							|  |  |  |       oldCell.cat = null; | 
					
						
							|  |  |  |       if (oldCell.tree != null) { | 
					
						
							|  |  |  |         treeCells.add(oldCellIndex); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         emptyCells.add(oldCellIndex); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       newCellIndex = emptyCells.pick(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final newCell = cells[newCellIndex]; | 
					
						
							|  |  |  |     newCell.cat = cat; | 
					
						
							|  |  |  |     cat.onBored = () { | 
					
						
							|  |  |  |       if (newCell.tree != null) { | 
					
						
							|  |  |  |         newCell.tree.shake(); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         final ms = random.nextInt(maxCatWait - minCatWait) + minCatWait; | 
					
						
							|  |  |  |         Timer timer; | 
					
						
							|  |  |  |         timer = Timer(ms / 1000, callback: () { | 
					
						
							|  |  |  |           updateCat(newCellIndex, cat); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         timer.start(); | 
					
						
							|  |  |  |         timers.add(timer); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cat.runTo(getCatPosition(newCell)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void createCat() { | 
					
						
							|  |  |  |     final cat = Cat(); | 
					
						
							|  |  |  |     add(cat); | 
					
						
							|  |  |  |     cats.add(cat); | 
					
						
							|  |  |  |     updateCat(null, cat); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     nextCatTimer = Timer(20.0 + nCats * 10.0, callback: createCat); | 
					
						
							|  |  |  |     nextCatTimer.start(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     nCats++; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-19 14:12:11 +01:00
										 |  |  |   void createCoin() { | 
					
						
							|  |  |  |     coinTimer = Timer(60.0 + random.nextInt(60), callback: () { | 
					
						
							|  |  |  |       final cellIndex = emptyCells.pick(); | 
					
						
							|  |  |  |       final cell = cells[cellIndex]; | 
					
						
							|  |  |  |       final coin = Coin( | 
					
						
							|  |  |  |         getCoinPosition(cell), | 
					
						
							|  |  |  |         Vector2(size.width / 2, -cellHeight), | 
					
						
							|  |  |  |         () { | 
					
						
							|  |  |  |           cell.coin = null; | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       add(coin); | 
					
						
							|  |  |  |       cell.coin = coin; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       createCoin(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     coinTimer.start(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-15 19:04:40 +01:00
										 |  |  |   void reset() { | 
					
						
							|  |  |  |     paused = true; | 
					
						
							|  |  |  |     nCats = 0; | 
					
						
							|  |  |  |     paws = 3; | 
					
						
							|  |  |  |     score = 0; | 
					
						
							|  |  |  |     gameOver = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cells = []; | 
					
						
							|  |  |  |     treeCells = []; | 
					
						
							|  |  |  |     emptyCells = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (final timer in timers) { | 
					
						
							|  |  |  |       timer.stop(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     timers.clear(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (final cat in cats) { | 
					
						
							|  |  |  |       cat.shouldDestroy = true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cats.clear(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (final tree in trees) { | 
					
						
							|  |  |  |       tree.shouldDestroy = true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     trees.clear(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (var i = 0; i < nCells; i++) { | 
					
						
							|  |  |  |       cells.add(Cell((i / nRows).floor(), i % nRows)); | 
					
						
							|  |  |  |       emptyCells.add(i); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (var i = 0; i < nTrees; i++) { | 
					
						
							|  |  |  |       final cellIndex = emptyCells.pick(); | 
					
						
							|  |  |  |       final cell = cells[cellIndex]; | 
					
						
							|  |  |  |       final tree = Tree(getTreePosition(cell), () { | 
					
						
							|  |  |  |         cell.tree.kill(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         paws -= 1; | 
					
						
							|  |  |  |         onPawsChanged(paws); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (paws <= 0 && !gameOver) { | 
					
						
							|  |  |  |           scoreTimer.stop(); | 
					
						
							|  |  |  |           gameOver = true; | 
					
						
							|  |  |  |           onGameOver(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         cell.tree = null; | 
					
						
							|  |  |  |         updateCat(cellIndex, cell.cat); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       cell.tree = tree; | 
					
						
							|  |  |  |       treeCells.add(cellIndex); | 
					
						
							|  |  |  |       trees.add(tree); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     createCat(); | 
					
						
							| 
									
										
										
										
											2020-01-19 14:12:11 +01:00
										 |  |  |     createCoin(); | 
					
						
							| 
									
										
										
										
											2020-01-15 19:04:40 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for (final tree in trees) { | 
					
						
							|  |  |  |       add(tree); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     scoreTimer = Timer(0.5, repeat: true, callback: () { | 
					
						
							|  |  |  |       score++; | 
					
						
							|  |  |  |       onScoreChanged(score); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     scoreTimer.start(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void play() { | 
					
						
							|  |  |  |     paused = false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void pause() { | 
					
						
							|  |  |  |     paused = true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void render(Canvas canvas) { | 
					
						
							|  |  |  |     bgSprite.render(canvas, width: size.width, height: size.height); | 
					
						
							|  |  |  |     super.render(canvas); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void update(double t) { | 
					
						
							|  |  |  |     if (!paused) { | 
					
						
							|  |  |  |       nextCatTimer?.update(t); | 
					
						
							| 
									
										
										
										
											2020-01-19 14:12:11 +01:00
										 |  |  |       coinTimer?.update(t); | 
					
						
							| 
									
										
										
										
											2020-01-15 19:04:40 +01:00
										 |  |  |       scoreTimer?.update(t); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       final List<Timer> oldTimers = []; | 
					
						
							|  |  |  |       for (final timer in timers) { | 
					
						
							|  |  |  |         timer.update(t); | 
					
						
							|  |  |  |         if (timer.isFinished()) { | 
					
						
							|  |  |  |           oldTimers.add(timer); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       for (final timer in oldTimers) { | 
					
						
							|  |  |  |         timers.remove(timer); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       super.update(t); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Rect toRect() => Rect.fromLTWH(0.0, 0.0, size.width, size.height); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void onTapDown(TapDownDetails details) { | 
					
						
							|  |  |  |     super.onTapDown(details); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!paused) { | 
					
						
							|  |  |  |       final x = details.localPosition.dx; | 
					
						
							|  |  |  |       final y = details.localPosition.dy; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       final cellX = ((x - marginLRB) / cellWidth).floor(); | 
					
						
							|  |  |  |       final cellY = ((y - marginTop) / cellHeight).floor(); | 
					
						
							|  |  |  |       final cellIndex = cellX * nRows + cellY; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (cellIndex >= 0 && cellIndex < cells.length) { | 
					
						
							|  |  |  |         final cell = cells[cellIndex]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (cell?.tree != null) { | 
					
						
							|  |  |  |           cell.tree.stopShaking(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (cell.cat != null) { | 
					
						
							|  |  |  |             updateCat(cellIndex, cell.cat); | 
					
						
							|  |  |  |             score += 25; | 
					
						
							|  |  |  |           } else { | 
					
						
							|  |  |  |             score -= 25; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           onScoreChanged(score); | 
					
						
							| 
									
										
										
										
											2020-01-19 14:12:11 +01:00
										 |  |  |         } else if (cell?.coin != null) { | 
					
						
							|  |  |  |           cell.coin.tap(); | 
					
						
							|  |  |  |           if (cell.cat == null) { | 
					
						
							|  |  |  |             emptyCells.add(cellIndex); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2020-01-15 19:04:40 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |