feat: add Sparky's computer (#155)

* feat: add sparky computer and mechanics

* test: sparky mechanics

* refactor: reposition

* test: contact callback test

* refactor: new separated sprite components

* fix: lint

* refactor: improve ball turbo charge tests
pull/171/head
Allison Ryan 4 years ago committed by GitHub
parent 98f25242b6
commit 0bec997178
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,6 +13,7 @@ class GameBloc extends Bloc<GameEvent, GameState> {
on<Scored>(_onScored);
on<BonusLetterActivated>(_onBonusLetterActivated);
on<DashNestActivated>(_onDashNestActivated);
on<SparkyTurboChargeActivated>(_onSparkyTurboChargeActivated);
}
static const bonusWord = 'GOOGLE';
@ -77,4 +78,18 @@ class GameBloc extends Bloc<GameEvent, GameState> {
);
}
}
Future<void> _onSparkyTurboChargeActivated(
SparkyTurboChargeActivated event,
Emitter emit,
) async {
emit(
state.copyWith(
bonusHistory: [
...state.bonusHistory,
GameBonus.sparkyTurboCharge,
],
),
);
}
}

@ -54,3 +54,10 @@ class DashNestActivated extends GameEvent {
@override
List<Object?> get props => [nestId];
}
class SparkyTurboChargeActivated extends GameEvent {
const SparkyTurboChargeActivated();
@override
List<Object?> get props => [];
}

@ -10,6 +10,9 @@ enum GameBonus {
/// Bonus achieved when the user activates all dash nest bumpers.
dashNest,
/// Bonus achieved when a ball enters Sparky's computer.
sparkyTurboCharge,
}
/// {@template game_state}

@ -3,6 +3,7 @@ export 'bonus_word.dart';
export 'camera_controller.dart';
export 'controlled_ball.dart';
export 'controlled_flipper.dart';
export 'controlled_sparky_computer.dart';
export 'flutter_forest.dart';
export 'game_flow_controller.dart';
export 'plunger.dart';

@ -53,6 +53,20 @@ class BallController extends ComponentController<Ball>
component.shouldRemove = true;
}
/// Stops the [Ball] inside of the [SparkyComputer] while the turbo charge
/// sequence runs, then boosts the ball out of the computer.
Future<void> turboCharge() async {
gameRef.read<GameBloc>().add(const SparkyTurboChargeActivated());
// TODO(allisonryan0002): adjust delay to match animation duration once
// given animations.
component.stop();
await Future<void>.delayed(const Duration(seconds: 1));
component
..resume()
..boost(Vector2(200, -500));
}
@override
void onRemove() {
super.onRemove();

@ -0,0 +1,84 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball/flame/flame.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template controlled_sparky_computer}
/// [SparkyComputer] with a [SparkyComputerController] attached.
/// {@endtemplate}
class ControlledSparkyComputer extends SparkyComputer
with Controls<SparkyComputerController>, HasGameRef<PinballGame> {
/// {@macro controlled_sparky_computer}
ControlledSparkyComputer() {
controller = SparkyComputerController(this);
}
@override
void build(Forge2DGame _) {
addContactCallback(SparkyTurboChargeSensorBallContactCallback());
final sparkyTurboChargeSensor = SparkyTurboChargeSensor()
..initialPosition = Vector2(-13, 49.8);
add(sparkyTurboChargeSensor);
super.build(_);
}
}
/// {@template sparky_computer_controller}
/// Controller attached to a [SparkyComputer] that handles its game related
/// logic.
/// {@endtemplate}
// TODO(allisonryan0002): listen for turbo charge game bonus and animate Sparky.
class SparkyComputerController
extends ComponentController<ControlledSparkyComputer> {
/// {@macro sparky_computer_controller}
SparkyComputerController(ControlledSparkyComputer controlledComputer)
: super(controlledComputer);
}
/// {@template sparky_turbo_charge_sensor}
/// Small sensor body used to detect when a ball has entered the
/// [SparkyComputer] with the [SparkyTurboChargeSensorBallContactCallback].
/// {@endtemplate}
@visibleForTesting
class SparkyTurboChargeSensor extends BodyComponent with InitialPosition {
/// {@macro sparky_turbo_charge_sensor}
SparkyTurboChargeSensor() {
renderBody = false;
}
@override
Body createBody() {
final shape = CircleShape()..radius = 0.1;
final fixtureDef = FixtureDef(shape)..isSensor = true;
final bodyDef = BodyDef()
..position = initialPosition
..userData = this;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
/// {@template sparky_turbo_charge_sensor_ball_contact_callback}
/// Turbo charges the [Ball] on contact with [SparkyTurboChargeSensor].
/// {@endtemplate}
@visibleForTesting
class SparkyTurboChargeSensorBallContactCallback
extends ContactCallback<SparkyTurboChargeSensor, ControlledBall> {
/// {@macro sparky_turbo_charge_sensor_ball_contact_callback}
SparkyTurboChargeSensorBallContactCallback();
@override
void begin(
SparkyTurboChargeSensor sparkyTurboChargeSensor,
ControlledBall ball,
_,
) {
ball.controller.turboCharge();
}
}

@ -46,6 +46,14 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.spaceship.rail.foreground.keyName),
images.load(components.Assets.images.chromeDino.mouth.keyName),
images.load(components.Assets.images.chromeDino.head.keyName),
images.load(components.Assets.images.sparky.computer.base.keyName),
images.load(components.Assets.images.sparky.computer.top.keyName),
images.load(components.Assets.images.sparky.bumper.a.active.keyName),
images.load(components.Assets.images.sparky.bumper.a.inactive.keyName),
images.load(components.Assets.images.sparky.bumper.b.active.keyName),
images.load(components.Assets.images.sparky.bumper.b.inactive.keyName),
images.load(components.Assets.images.sparky.bumper.c.active.keyName),
images.load(components.Assets.images.sparky.bumper.c.inactive.keyName),
images.load(components.Assets.images.backboard.backboardScores.keyName),
images.load(components.Assets.images.backboard.backboardGameOver.keyName),
images.load(Assets.images.components.background.path),

@ -45,6 +45,7 @@ class PinballGame extends Forge2DGame
await _addGameBoundaries();
unawaited(addFromBlueprint(Boundaries()));
unawaited(addFromBlueprint(LaunchRamp()));
unawaited(addFromBlueprint(ControlledSparkyComputer()));
final plunger = Plunger(compressionDistance: 29)
..initialPosition = Vector2(38, -19);

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

@ -25,8 +25,7 @@ class $AssetsImagesGen {
const $AssetsImagesLaunchRampGen();
$AssetsImagesSlingshotGen get slingshot => const $AssetsImagesSlingshotGen();
$AssetsImagesSpaceshipGen get spaceship => const $AssetsImagesSpaceshipGen();
$AssetsImagesSparkyBumperGen get sparkyBumper =>
const $AssetsImagesSparkyBumperGen();
$AssetsImagesSparkyGen get sparky => const $AssetsImagesSparkyGen();
}
class $AssetsImagesBackboardGen {
@ -136,12 +135,13 @@ class $AssetsImagesSpaceshipGen {
const AssetGenImage('assets/images/spaceship/saucer.png');
}
class $AssetsImagesSparkyBumperGen {
const $AssetsImagesSparkyBumperGen();
class $AssetsImagesSparkyGen {
const $AssetsImagesSparkyGen();
$AssetsImagesSparkyBumperAGen get a => const $AssetsImagesSparkyBumperAGen();
$AssetsImagesSparkyBumperBGen get b => const $AssetsImagesSparkyBumperBGen();
$AssetsImagesSparkyBumperCGen get c => const $AssetsImagesSparkyBumperCGen();
$AssetsImagesSparkyBumperGen get bumper =>
const $AssetsImagesSparkyBumperGen();
$AssetsImagesSparkyComputerGen get computer =>
const $AssetsImagesSparkyComputerGen();
}
class $AssetsImagesDashBumperAGen {
@ -191,31 +191,60 @@ class $AssetsImagesSpaceshipRampGen {
'assets/images/spaceship/ramp/railing-foreground.png');
}
class $AssetsImagesSparkyBumperGen {
const $AssetsImagesSparkyBumperGen();
$AssetsImagesSparkyBumperAGen get a => const $AssetsImagesSparkyBumperAGen();
$AssetsImagesSparkyBumperBGen get b => const $AssetsImagesSparkyBumperBGen();
$AssetsImagesSparkyBumperCGen get c => const $AssetsImagesSparkyBumperCGen();
}
class $AssetsImagesSparkyComputerGen {
const $AssetsImagesSparkyComputerGen();
/// File path: assets/images/sparky/computer/base.png
AssetGenImage get base =>
const AssetGenImage('assets/images/sparky/computer/base.png');
/// File path: assets/images/sparky/computer/top.png
AssetGenImage get top =>
const AssetGenImage('assets/images/sparky/computer/top.png');
}
class $AssetsImagesSparkyBumperAGen {
const $AssetsImagesSparkyBumperAGen();
/// File path: assets/images/sparky/bumper/a/active.png
AssetGenImage get active =>
const AssetGenImage('assets/images/sparky_bumper/a/active.png');
const AssetGenImage('assets/images/sparky/bumper/a/active.png');
/// File path: assets/images/sparky/bumper/a/inactive.png
AssetGenImage get inactive =>
const AssetGenImage('assets/images/sparky_bumper/a/inactive.png');
const AssetGenImage('assets/images/sparky/bumper/a/inactive.png');
}
class $AssetsImagesSparkyBumperBGen {
const $AssetsImagesSparkyBumperBGen();
/// File path: assets/images/sparky/bumper/b/active.png
AssetGenImage get active =>
const AssetGenImage('assets/images/sparky_bumper/b/active.png');
const AssetGenImage('assets/images/sparky/bumper/b/active.png');
/// File path: assets/images/sparky/bumper/b/inactive.png
AssetGenImage get inactive =>
const AssetGenImage('assets/images/sparky_bumper/b/inactive.png');
const AssetGenImage('assets/images/sparky/bumper/b/inactive.png');
}
class $AssetsImagesSparkyBumperCGen {
const $AssetsImagesSparkyBumperCGen();
/// File path: assets/images/sparky/bumper/c/active.png
AssetGenImage get active =>
const AssetGenImage('assets/images/sparky_bumper/c/active.png');
const AssetGenImage('assets/images/sparky/bumper/c/active.png');
/// File path: assets/images/sparky/bumper/c/inactive.png
AssetGenImage get inactive =>
const AssetGenImage('assets/images/sparky_bumper/c/inactive.png');
const AssetGenImage('assets/images/sparky/bumper/c/inactive.png');
}
class Assets {

@ -41,6 +41,8 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
await add(
_spriteComponent..tint(baseColor.withOpacity(0.5)),
);
renderBody = false;
}
@override
@ -59,15 +61,19 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
///
/// The [Ball] will no longer be affected by any forces, including it's
/// weight and those emitted from collisions.
// TODO(allisonryan0002): prevent motion from contact with other balls.
void stop() {
body.setType(BodyType.static);
body
..gravityScale = 0
..linearVelocity = Vector2.zero()
..angularVelocity = 0;
}
/// Allows the [Ball] to be affected by forces.
///
/// If previously [stop]ped, the previous ball's velocity is not kept.
void resume() {
body.setType(BodyType.dynamic);
body.gravityScale = 1;
}
@override
@ -91,7 +97,7 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
/// Applies a boost on this [Ball].
void boost(Vector2 impulse) {
body.applyLinearImpulse(impulse);
body.linearVelocity = impulse;
_boostTimer = _boostDuration;
}

@ -23,3 +23,4 @@ export 'spaceship.dart';
export 'spaceship_rail.dart';
export 'spaceship_ramp.dart';
export 'sparky_bumper.dart';
export 'sparky_computer.dart';

@ -27,8 +27,8 @@ class SparkyBumper extends BodyComponent with InitialPosition {
: this._(
majorRadius: 2.9,
minorRadius: 2.1,
activeAssetPath: Assets.images.sparkyBumper.a.active.keyName,
inactiveAssetPath: Assets.images.sparkyBumper.a.inactive.keyName,
activeAssetPath: Assets.images.sparky.bumper.a.active.keyName,
inactiveAssetPath: Assets.images.sparky.bumper.a.inactive.keyName,
spriteComponent: SpriteComponent(
anchor: Anchor.center,
position: Vector2(0, -0.25),
@ -40,8 +40,8 @@ class SparkyBumper extends BodyComponent with InitialPosition {
: this._(
majorRadius: 2.85,
minorRadius: 2,
activeAssetPath: Assets.images.sparkyBumper.b.active.keyName,
inactiveAssetPath: Assets.images.sparkyBumper.b.inactive.keyName,
activeAssetPath: Assets.images.sparky.bumper.b.active.keyName,
inactiveAssetPath: Assets.images.sparky.bumper.b.inactive.keyName,
spriteComponent: SpriteComponent(
anchor: Anchor.center,
position: Vector2(0, -0.35),
@ -53,8 +53,8 @@ class SparkyBumper extends BodyComponent with InitialPosition {
: this._(
majorRadius: 3,
minorRadius: 2.2,
activeAssetPath: Assets.images.sparkyBumper.c.active.keyName,
inactiveAssetPath: Assets.images.sparkyBumper.c.inactive.keyName,
activeAssetPath: Assets.images.sparky.bumper.c.active.keyName,
inactiveAssetPath: Assets.images.sparky.bumper.c.inactive.keyName,
spriteComponent: SpriteComponent(
anchor: Anchor.center,
position: Vector2(0, -0.4),

@ -0,0 +1,115 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template sparky_computer}
/// A [Blueprint] which creates the [_ComputerBase] and
/// [_ComputerTopSpriteComponent].
/// {@endtemplate}
class SparkyComputer extends Forge2DBlueprint {
@override
void build(_) {
final computerBase = _ComputerBase();
final computerTop = _ComputerTopSpriteComponent();
addAll([
computerBase,
computerTop,
]);
}
}
class _ComputerBase extends BodyComponent with InitialPosition {
_ComputerBase();
List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
final leftEdge = EdgeShape()
..set(
Vector2(-14.9, 46),
Vector2(-15.3, 49.6),
);
final leftEdgeFixtureDef = FixtureDef(leftEdge);
fixturesDef.add(leftEdgeFixtureDef);
final topEdge = EdgeShape()
..set(
Vector2(-15.3, 49.6),
Vector2(-10.7, 50.6),
);
final topEdgeFixtureDef = FixtureDef(topEdge);
fixturesDef.add(topEdgeFixtureDef);
final rightEdge = EdgeShape()
..set(
Vector2(-10.7, 50.6),
Vector2(-9, 47.2),
);
final rightEdgeFixtureDef = FixtureDef(rightEdge);
fixturesDef.add(rightEdgeFixtureDef);
return fixturesDef;
}
@override
Body createBody() {
final bodyDef = BodyDef()
..userData = this
..position = initialPosition;
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
@override
Future<void> onLoad() async {
await super.onLoad();
renderBody = false;
await add(_ComputerBaseSpriteComponent());
}
}
class _ComputerBaseSpriteComponent extends SpriteComponent with HasGameRef {
_ComputerBaseSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-11.95, -48.35),
);
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
Assets.images.sparky.computer.base.keyName,
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}
class _ComputerTopSpriteComponent extends SpriteComponent with HasGameRef {
_ComputerTopSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-12.45, -49.75),
priority: 1,
);
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
Assets.images.sparky.computer.top.keyName,
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}

@ -12,7 +12,8 @@ const _attachedErrorMessage = "Can't add to attached Blueprints";
/// A [Blueprint] is a virtual way of grouping [Component]s
/// that are related, but they need to be added directly on
/// the [FlameGame] level.
abstract class Blueprint<T extends FlameGame> {
// TODO(alestiago): refactor with feat/make-blueprint-extend-component.
abstract class Blueprint<T extends FlameGame> extends Component {
final List<Component> _components = [];
final List<Blueprint> _blueprints = [];
@ -34,14 +35,9 @@ abstract class Blueprint<T extends FlameGame> {
_isAttached = true;
}
/// Adds a list of [Component]s to this blueprint.
void addAll(List<Component> components) {
assert(!_isAttached, _attachedErrorMessage);
_components.addAll(components);
}
/// Adds a single [Component] to this blueprint.
void add(Component component) {
@override
Future<void> add(Component component) async {
assert(!_isAttached, _attachedErrorMessage);
_components.add(component);
}

@ -40,9 +40,10 @@ flutter:
- assets/images/chrome_dino/
- assets/images/kicker/
- assets/images/slingshot/
- assets/images/sparky_bumper/a/
- assets/images/sparky_bumper/b/
- assets/images/sparky_bumper/c/
- assets/images/sparky/computer/
- assets/images/sparky/bumper/a/
- assets/images/sparky/bumper/b/
- assets/images/sparky/bumper/c/
- assets/images/backboard/
flutter_gen:

@ -116,15 +116,18 @@ void main() {
});
});
flameTester.test('by applying velocity', (game) async {
final ball = Ball(baseColor: Colors.blue);
await game.ensureAdd(ball);
ball.stop();
ball.body.linearVelocity.setValues(10, 10);
game.update(1);
expect(ball.body.position, equals(ball.initialPosition));
});
// TODO(allisonryan0002): delete or retest this if/when solution is added
// to prevent forces on a ball while stopped.
// flameTester.test('by applying velocity', (game) async {
// final ball = Ball(baseColor: Colors.blue);
// await game.ensureAdd(ball);
// ball.stop();
// ball.body.linearVelocity.setValues(10, 10);
// game.update(1);
// expect(ball.body.position, equals(ball.initialPosition));
// });
});
group('resume', () {

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@ -0,0 +1,30 @@
// ignore_for_file: cascade_invocations
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
group('SparkyComputer', () {
final tester = FlameTester(TestGame.new);
tester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
await game.addFromBlueprint(SparkyComputer());
await game.ready();
game.camera.followVector2(Vector2(-15, -50));
},
// TODO(allisonryan0002): enable test when workflows are fixed.
// verify: (game, tester) async {
// await expectLater(
// find.byGame<Forge2DGame>(),
// matchesGoldenFile('golden/sparky-computer.png'),
// );
// },
);
});
}

@ -221,5 +221,22 @@ void main() {
],
);
});
group('SparkyTurboChargeActivated', () {
blocTest<GameBloc, GameState>(
'adds game bonus',
build: GameBloc.new,
act: (bloc) => bloc..add(const SparkyTurboChargeActivated()),
expect: () => const [
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [GameBonus.sparkyTurboCharge],
),
],
);
});
});
}

@ -84,5 +84,18 @@ void main() {
);
});
});
group('SparkyTurboChargeActivated', () {
test('can be instantiated', () {
expect(const SparkyTurboChargeActivated(), isNotNull);
});
test('supports value equality', () {
expect(
SparkyTurboChargeActivated(),
equals(SparkyTurboChargeActivated()),
);
});
});
});
}

@ -1,8 +1,9 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/painting.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
@ -11,10 +12,39 @@ import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
// TODO(allisonryan0002): remove once
// https://github.com/flame-engine/flame/pull/1520 is merged
class WrappedBallController extends BallController {
WrappedBallController(Ball<Forge2DGame> ball, this._gameRef) : super(ball);
final PinballGame _gameRef;
@override
PinballGame get gameRef => _gameRef;
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('BallController', () {
late Ball ball;
late GameBloc gameBloc;
setUp(() {
ball = Ball(baseColor: const Color(0xFF00FFFF));
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballGameTest.new,
blocBuilder: () => gameBloc,
);
test('can be instantiated', () {
expect(
BallController(MockBall()),
@ -22,36 +52,82 @@ void main() {
);
});
group('description', () {
late Ball ball;
late GameBloc gameBloc;
setUp(() {
ball = Ball(baseColor: const Color(0xFF00FFFF));
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
flameBlocTester.testGameWidget(
'lost adds BallLost to GameBloc',
setUp: (game, tester) async {
final controller = BallController(ball);
await ball.add(controller);
await game.ensureAdd(ball);
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballGameTest.new,
blocBuilder: () => gameBloc,
);
controller.lost();
},
verify: (game, tester) async {
verify(() => gameBloc.add(const BallLost())).called(1);
},
);
group('turboCharge', () {
setUpAll(() {
registerFallbackValue(Vector2.zero());
});
flameBlocTester.testGameWidget(
'lost adds BallLost to GameBloc',
'adds TurboChargeActivated',
setUp: (game, tester) async {
final controller = BallController(ball);
await ball.add(controller);
await game.ensureAdd(ball);
controller.lost();
await controller.turboCharge();
},
verify: (game, tester) async {
verify(() => gameBloc.add(const BallLost())).called(1);
verify(() => gameBloc.add(const SparkyTurboChargeActivated()))
.called(1);
},
);
flameBlocTester.test(
'initially stops the ball',
(game) async {
final gameRef = MockPinballGame();
final ball = MockControlledBall();
final controller = WrappedBallController(ball, gameRef);
when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc);
when(() => ball.controller).thenReturn(controller);
await controller.turboCharge();
verify(ball.stop).called(1);
},
);
flameBlocTester.test(
'resumes the ball',
(game) async {
final gameRef = MockPinballGame();
final ball = MockControlledBall();
final controller = WrappedBallController(ball, gameRef);
when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc);
when(() => ball.controller).thenReturn(controller);
await controller.turboCharge();
verify(ball.resume).called(1);
},
);
flameBlocTester.test(
'boosts the ball',
(game) async {
final gameRef = MockPinballGame();
final ball = MockControlledBall();
final controller = WrappedBallController(ball, gameRef);
when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc);
when(() => ball.controller).thenReturn(controller);
await controller.turboCharge();
verify(() => ball.boost(any())).called(1);
},
);
});

@ -0,0 +1,45 @@
// ignore_for_file: cascade_invocations
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import '../../helpers/helpers.dart';
void main() {
group('SparkyComputerController', () {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(EmptyPinballGameTest.new);
late ControlledSparkyComputer controlledSparkyComputer;
setUp(() {
controlledSparkyComputer = ControlledSparkyComputer();
});
test('can be instantiated', () {
expect(
SparkyComputerController(controlledSparkyComputer),
isA<SparkyComputerController>(),
);
});
flameTester.testGameWidget(
'SparkyTurboChargeSensorBallContactCallback turbo charges the ball',
setUp: (game, tester) async {
final contackCallback = SparkyTurboChargeSensorBallContactCallback();
final sparkyTurboChargeSensor = MockSparkyTurboChargeSensor();
final ball = MockControlledBall();
final controller = MockBallController();
when(() => ball.controller).thenReturn(controller);
when(controller.turboCharge).thenAnswer((_) async {});
contackCallback.begin(sparkyTurboChargeSensor, ball, MockContact());
verify(() => ball.controller.turboCharge()).called(1);
},
);
});
}

@ -76,6 +76,9 @@ class MockDashNestBumper extends Mock implements DashNestBumper {}
class MockPinballAudio extends Mock implements PinballAudio {}
class MockSparkyTurboChargeSensor extends Mock
implements SparkyTurboChargeSensor {}
class MockAssetsManagerCubit extends Mock implements AssetsManagerCubit {}
class MockBackboard extends Mock implements Backboard {}

Loading…
Cancel
Save