christmas_cats/lib/game.dart

329 lines
7.3 KiB
Dart
Raw Normal View History

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';
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;
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;
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);
}
}
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++;
}
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();
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);
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);
} else if (cell?.coin != null) {
cell.coin.tap();
if (cell.cat == null) {
emptyCells.add(cellIndex);
}
2020-01-15 19:04:40 +01:00
}
}
}
}
}