You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pinball/lib/game/pinball_game.dart

292 lines
7.7 KiB

// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
feat: Flipper (#15) * feat: explicitely imported Anchor * feat: implemented Flipper * feat: implemented Flipper in PinballGame * feat: implemented calculateRequiredSpeed * feat: included right and left constructors * feat: used right and left constructors * refactor: cleaned calcualteSpeed method * refactor: used width and height instead of size * feat: implemented FlipperAnchor * feat: implemented BoardSide enum * docs: used prose in doc comment * feat: implemented BoardSideX * refactor: used isLeft instead of isRight * refactor: implemented unlock method * refactor: same line assignment Co-authored-by: Erick <erickzanardoo@gmail.com> * feat: add themes * feat: add theme cubit * test: character themes * test: remove grouping * refactor: move themes to package * chore: add workflow * fix: workflow * docs: character themes update * refactor: one theme for entire game * chore: add to props * fix: changing ball spawning point to avoid context errors * refactor: modified unlock method due to invalid cast * feat: included test for BoardSide * refactor: removed sweepingAnimationDuration * feat: tested flipper.dart * refactor: included flippersPosition * refactor: implemented _addFlippers method * feat: centered vertices * feat: modified test to match new center * refactor: removed unecessary parenthesis * refactor: removed unecessary calculation * fix: changing ball spawning point to avoid context errors * chore: rebasing * docs: included FIXME comment * feat: moved key listening to Flipper * docs: include TOOD comment * feat: including test and refactor * docs: fixed doc comment typo Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * docs: fixed do comment template name Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * refactor: removed unnecessary verbose multiplication Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * refactor: removed unnecessary verbose multiplication * refactor: used ensureAddAll instead of ensureAdd Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * docs: fixed doc comment typo * refactor: used bigCircleShape.radius * refactor: reorganized methods * docs: improved doc comment * refactor: removed unecessary class variables * docs: fix doc comment typo * refactor: removed unused helper * fix: simplified keyEvents * fix: corrected erroneous key tests * refactor: modified component tests * refactor: capitalized Flipper test description * refactor: changed angle calculations * fix: tests * refactor: removed exta line Co-authored-by: Erick <erickzanardoo@gmail.com> Co-authored-by: Allison Ryan <allisonryan0002@gmail.com> Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
3 years ago
import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
class PinballGame extends PinballForge2DGame
with
FlameBloc,
HasKeyboardHandlerComponents,
Controls<_GameBallsController>,
MultiTouchTapDetector {
PinballGame({
required this.characterTheme,
required this.audio,
required this.l10n,
}) : super(gravity: Vector2(0, 30)) {
images.prefix = '';
controller = _GameBallsController(this);
}
/// Identifier of the play button overlay
static const playButtonOverlay = 'play_button';
@override
Color backgroundColor() => Colors.transparent;
final CharacterTheme characterTheme;
final PinballAudio audio;
final AppLocalizations l10n;
late final GameFlowController gameFlowController;
@override
Future<void> onLoad() async {
await add(gameFlowController = GameFlowController(this));
await add(CameraController(this));
final machine = [
BoardBackgroundSpriteComponent(),
Boundaries(),
Backbox(),
];
final decals = [
GoogleWord(position: Vector2(-4.25, 1.8)),
Multipliers(),
feat: multiball asset (#235) * feat: multiball assets * feat: added multiball to components * feat: added controller for multiball * feat: positioned multiball and changed animation * feat: added sandbox for multiball * chore: unused import * refactor: add rotation to multiball constructor * test: coverage for multiball * chore: todos for refactor multiball childrens * test: removed unused mock * chore: removed unused imports * test: removed golden tests * refactor: changed assets and refactored multiball * refactor: changed assets and refactored multiball * test: tests for multiball * refactor: multiballs group refactored * chore: names and doc * refactor: removed duplicated images for multiball * refactor: changed multiball cubit and state * refactor: changed multiball and group * chore: positions of lights * refactor: changing blink behavior * test: blink behavior * refactor: blinking multiball lights * test: tests for blink behavior * chore: analysis errors * test: coverage for blinking * test: coverage * test: trying to fix tests * fix: fixed bloc error on behavior with tests * refactor: multiball blink * refactor: blinking behavior to TimerComponent and test coverage * refactor: modified blinking behavior * chore: error on merge tests * test: coverage multiballs * refactor: cleaned blink behavior * chore: unused import * Update packages/pinball_components/test/src/components/multiball/behaviors/multiball_blinking_behavior_test.dart Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * Update packages/pinball_components/test/src/components/multiball/behaviors/multiball_blinking_behavior_test.dart Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * Update test/game/components/multiballs/behaviors/multiballs_behavior_test.dart Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * Update packages/pinball_components/test/src/components/multiball/multiball_test.dart Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * refactor: changed multiball states enum values * test: multiball descendant test at pinball Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
2 years ago
Multiballs(),
SkillShot(
children: [
ScoringContactBehavior(points: Points.oneMillion),
],
),
];
final characterAreas = [
AndroidAcres(),
DinoDesert(),
FlutterForest(),
SparkyScorch(),
];
await add(
ZCanvasComponent(
children: [
...machine,
...decals,
...characterAreas,
Drain(),
BottomGroup(),
Launcher(),
],
),
);
feat: migration to new `ContactCallbacks` (#234) * feat: migrated to new ContactCallbacks * refactor: moved renderBody to super call * feat: defined AlienBumper behaviours * feat: included ParentIsA mixin * refactor: modified ContactCallbacksGroup * feat: resolved missing ContactCallbacks with Behaviors * refactor: removed unused ParentIsA * refactor: moved tests * fix: invalid export * refactor: renamed behaviours * test: tested pinball_components * refactor: removed "Behaviour" typo * docs: included TODO comment for generics * docs: included flame_bloc TODO comments * refactor: renamed ContacCallbacksGroup variable * docs: included doc comments where possible * docs: rephrased ContactBehaviour doc comment * test: included ContactBehavior tests * feat: implemented FlutterForestBonusBehavior * refactor: fixed analyser warnings * test: tested DashNestBumper * refactor: moved children to last arguement * test: included closing test * refactor: used barrel files as imports * docs: included flutter_bloc TODO * test: correctly tested GoogleWordBonusBehavior * refactor: moved flutter_forest_test.dart * test: fixed AlienZone typo * feat: removed FlutterForestCubit * test: closed streams * refactor: removed optional bloc parameter * refactor: added flame_bloc TODO comment * docs: included .test constructor docs * feat: included GoogleLetter.test * test: made blink test pass * fix: renamed theme to CharacterTheme * refactor: moved timer.stop(); * refactor: renamed hasBonus to achievedBonus * refactor: ignore public_member_api_docs for cubits * test: removed beginContact group * refactor: typos correction * docs: used correct AlienBumper reference * docs: removed TODO comment from ContactBehavior subclasses * docs: includes ScoringBehavior doc * feat: adjusted FlutterForest priorities Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
2 years ago
await super.onLoad();
}
final focusedBoardSide = <int, BoardSide>{};
@override
void onTapDown(int pointerId, TapDownInfo info) {
if (info.raw.kind == PointerDeviceKind.touch) {
final rocket = descendants().whereType<RocketSpriteComponent>().first;
final bounds = rocket.topLeftPosition & rocket.size;
// NOTE(wolfen): As long as Flame does not have https://github.com/flame-engine/flame/issues/1586 we need to check it at the highest level manually.
if (bounds.contains(info.eventPosition.game.toOffset())) {
descendants().whereType<Plunger>().single.pullFor(2);
} else {
final leftSide = info.eventPosition.widget.x < canvasSize.x / 2;
focusedBoardSide[pointerId] =
leftSide ? BoardSide.left : BoardSide.right;
final flippers = descendants().whereType<Flipper>().where((flipper) {
return flipper.side == focusedBoardSide[pointerId];
});
flippers.first.moveUp();
}
}
super.onTapDown(pointerId, info);
}
@override
void onTapUp(int pointerId, TapUpInfo info) {
_moveFlippersDown(pointerId);
super.onTapUp(pointerId, info);
}
@override
void onTapCancel(int pointerId) {
_moveFlippersDown(pointerId);
super.onTapCancel(pointerId);
}
void _moveFlippersDown(int pointerId) {
if (focusedBoardSide[pointerId] != null) {
final flippers = descendants().whereType<Flipper>().where((flipper) {
return flipper.side == focusedBoardSide[pointerId];
});
flippers.first.moveDown();
focusedBoardSide.remove(pointerId);
}
}
}
class _GameBallsController extends ComponentController<PinballGame>
feat: game bloc multiplier (#213) * feat: added events for multiplier * feat: added events for increment, apply and reset multiplier * feat: added multiplier to game bloc state * test: test for multiplier at game bloc * test: added multiplier to game state * refactor: multiplier always increased by 1 * refactor: add multiplier state on BallLost * refactor: added round to game state and changed gameover and ball lost logic * test: fixed tests for game bloc * refactor: multiplier max value 6 at game bloc * test: fixed tests with new game over logic * chore: properties renamed and removed unused * Update lib/game/bloc/game_event.dart Co-authored-by: Alejandro Santiago <dev@alestiago.com> * fix: pubspec from main * refactor: pubspec from main * chore: removed unused import * feat: ball added event to game bloc * test: fixed test for ball added * feat: added BallAdded event on ball mounted * test: fixing tests for BallAdded * test: flamebloctester for ball added * test: refactored tests for pinballgame * refactor: BallAdded event on ball mounted * chore: removed unnecessary imports * test: refactor tests for pinball_game * refactor: use rounds instead of balls * refactor: changed BallLost event with RoundLost, and moved part of the logic to controlled_ball * test: tests for RoundLost * fix: fixed wrong tests for pinball_game * test: remove deleted balls property from GameState * chore: doc Co-authored-by: Alejandro Santiago <dev@alestiago.com> Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
2 years ago
with BlocComponent<GameBloc, GameState> {
_GameBallsController(PinballGame game) : super(game);
@override
bool listenWhen(GameState? previousState, GameState newState) {
final noBallsLeft = component.descendants().whereType<Ball>().isEmpty;
feat: game bloc multiplier (#213) * feat: added events for multiplier * feat: added events for increment, apply and reset multiplier * feat: added multiplier to game bloc state * test: test for multiplier at game bloc * test: added multiplier to game state * refactor: multiplier always increased by 1 * refactor: add multiplier state on BallLost * refactor: added round to game state and changed gameover and ball lost logic * test: fixed tests for game bloc * refactor: multiplier max value 6 at game bloc * test: fixed tests with new game over logic * chore: properties renamed and removed unused * Update lib/game/bloc/game_event.dart Co-authored-by: Alejandro Santiago <dev@alestiago.com> * fix: pubspec from main * refactor: pubspec from main * chore: removed unused import * feat: ball added event to game bloc * test: fixed test for ball added * feat: added BallAdded event on ball mounted * test: fixing tests for BallAdded * test: flamebloctester for ball added * test: refactored tests for pinballgame * refactor: BallAdded event on ball mounted * chore: removed unnecessary imports * test: refactor tests for pinball_game * refactor: use rounds instead of balls * refactor: changed BallLost event with RoundLost, and moved part of the logic to controlled_ball * test: tests for RoundLost * fix: fixed wrong tests for pinball_game * test: remove deleted balls property from GameState * chore: doc Co-authored-by: Alejandro Santiago <dev@alestiago.com> Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
2 years ago
final notGameOver = !newState.isGameOver;
feat: game bloc multiplier (#213) * feat: added events for multiplier * feat: added events for increment, apply and reset multiplier * feat: added multiplier to game bloc state * test: test for multiplier at game bloc * test: added multiplier to game state * refactor: multiplier always increased by 1 * refactor: add multiplier state on BallLost * refactor: added round to game state and changed gameover and ball lost logic * test: fixed tests for game bloc * refactor: multiplier max value 6 at game bloc * test: fixed tests with new game over logic * chore: properties renamed and removed unused * Update lib/game/bloc/game_event.dart Co-authored-by: Alejandro Santiago <dev@alestiago.com> * fix: pubspec from main * refactor: pubspec from main * chore: removed unused import * feat: ball added event to game bloc * test: fixed test for ball added * feat: added BallAdded event on ball mounted * test: fixing tests for BallAdded * test: flamebloctester for ball added * test: refactored tests for pinballgame * refactor: BallAdded event on ball mounted * chore: removed unnecessary imports * test: refactor tests for pinball_game * refactor: use rounds instead of balls * refactor: changed BallLost event with RoundLost, and moved part of the logic to controlled_ball * test: tests for RoundLost * fix: fixed wrong tests for pinball_game * test: remove deleted balls property from GameState * chore: doc Co-authored-by: Alejandro Santiago <dev@alestiago.com> Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
2 years ago
return noBallsLeft && notGameOver;
}
@override
void onNewState(GameState state) {
super.onNewState(state);
spawnBall();
}
@override
Future<void> onLoad() async {
await super.onLoad();
spawnBall();
}
void spawnBall() {
// TODO(alestiago): Refactor with behavioural pattern.
component.ready().whenComplete(() {
final plunger = parent!.descendants().whereType<Plunger>().single;
final ball = ControlledBall.launch(
characterTheme: component.characterTheme,
)..initialPosition = Vector2(
plunger.body.position.x,
plunger.body.position.y - Ball.size.y,
);
component.firstChild<ZCanvasComponent>()?.add(ball);
});
}
}
class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
DebugPinballGame({
required CharacterTheme characterTheme,
required PinballAudio audio,
required AppLocalizations l10n,
}) : super(
characterTheme: characterTheme,
audio: audio,
l10n: l10n,
) {
controller = _GameBallsController(this);
}
Vector2? lineStart;
Vector2? lineEnd;
@override
Future<void> onLoad() async {
await super.onLoad();
await add(PreviewLine());
await add(_DebugInformation());
}
@override
void onTapUp(int pointerId, TapUpInfo info) {
super.onTapUp(pointerId, info);
if (info.raw.kind == PointerDeviceKind.mouse) {
final ball = ControlledBall.debug()
..initialPosition = info.eventPosition.game;
firstChild<ZCanvasComponent>()?.add(ball);
}
}
@override
void onPanStart(DragStartInfo info) {
lineStart = info.eventPosition.game;
}
@override
void onPanUpdate(DragUpdateInfo info) {
lineEnd = info.eventPosition.game;
}
@override
void onPanEnd(DragEndInfo info) {
if (lineEnd != null) {
final line = lineEnd! - lineStart!;
_turboChargeBall(line);
lineEnd = null;
lineStart = null;
}
}
void _turboChargeBall(Vector2 line) {
final ball = ControlledBall.debug()..initialPosition = lineStart!;
final impulse = line * -1 * 10;
ball.add(BallTurboChargingBehavior(impulse: impulse));
firstChild<ZCanvasComponent>()?.add(ball);
}
}
// coverage:ignore-start
class PreviewLine extends PositionComponent with HasGameRef<DebugPinballGame> {
static final _previewLinePaint = Paint()
..color = Colors.pink
..strokeWidth = 0.4
..style = PaintingStyle.stroke;
@override
void render(Canvas canvas) {
super.render(canvas);
if (gameRef.lineEnd != null) {
canvas.drawLine(
gameRef.lineStart!.toOffset(),
gameRef.lineEnd!.toOffset(),
_previewLinePaint,
);
}
}
}
// TODO(wolfenrain): investigate this CI failure.
class _DebugInformation extends Component with HasGameRef<DebugPinballGame> {
@override
PositionType get positionType => PositionType.widget;
final _debugTextPaint = TextPaint(
style: const TextStyle(
color: Colors.green,
fontSize: 10,
),
);
final _debugBackgroundPaint = Paint()..color = Colors.white;
@override
void render(Canvas canvas) {
final debugText = [
'FPS: ${gameRef.fps().toStringAsFixed(1)}',
'BALLS: ${gameRef.descendants().whereType<ControlledBall>().length}',
].join(' | ');
final height = _debugTextPaint.measureTextHeight(debugText);
final position = Vector2(0, gameRef.camera.canvasSize.y - height);
canvas.drawRect(
position & Vector2(gameRef.camera.canvasSize.x, height),
_debugBackgroundPaint,
);
_debugTextPaint.render(canvas, debugText, position);
}
}
// coverage:ignore-end