mirror of
https://github.com/johrpan/christmas_cats.git
synced 2025-10-28 03:07:25 +01:00
This adds a new asset, a new component and some new game mechanics. Coins will appear every one to two minutes and can be collected by tapping them.
328 lines
7.3 KiB
Dart
328 lines
7.3 KiB
Dart
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';
|
|
import 'components/coin.dart';
|
|
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;
|
|
Coin coin;
|
|
|
|
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;
|
|
Timer coinTimer;
|
|
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);
|
|
}
|
|
}
|
|
|
|
Vector2 getCoinPosition(Cell cell) {
|
|
final x = marginLRB + (cell.x + 0.5) * cellWidth;
|
|
final y = marginTop + (cell.y + 0.5) * cellHeight;
|
|
return Vector2(x, y);
|
|
}
|
|
|
|
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++;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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();
|
|
createCoin();
|
|
|
|
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);
|
|
coinTimer?.update(t);
|
|
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);
|
|
} else if (cell?.coin != null) {
|
|
cell.coin.tap();
|
|
if (cell.cat == null) {
|
|
emptyCells.add(cellIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|