Merge branch 'main' into feat/baseboard-assets

pull/106/head
Allison Ryan 4 years ago
commit 19cc3445b9

@ -55,9 +55,6 @@ class GameState extends Equatable {
/// Determines when the game is over. /// Determines when the game is over.
bool get isGameOver => balls == 0; bool get isGameOver => balls == 0;
/// Determines when the player has only one ball left.
bool get isLastBall => balls == 1;
/// Shortcut method to check if the given [i] /// Shortcut method to check if the given [i]
/// is activated. /// is activated.
bool isLetterActivated(int i) => activatedBonusLetters.contains(i); bool isLetterActivated(int i) => activatedBonusLetters.contains(i);

@ -19,8 +19,8 @@ class Board extends Component {
// TODO(alestiago): adjust positioning to real design. // TODO(alestiago): adjust positioning to real design.
final dino = ChromeDino() final dino = ChromeDino()
..initialPosition = Vector2( ..initialPosition = Vector2(
PinballGame.boardBounds.center.dx + 25, BoardDimensions.bounds.center.dx + 25,
PinballGame.boardBounds.center.dy + 10, BoardDimensions.bounds.center.dy + 10,
); );
await addAll([ await addAll([

@ -31,7 +31,7 @@ class ChromeDino extends BodyComponent with InitialPosition {
anchor: anchor, anchor: anchor,
); );
final joint = _ChromeDinoJoint(jointDef); final joint = _ChromeDinoJoint(jointDef);
world.createJoint2(joint); world.createJoint(joint);
return joint; return joint;
} }
@ -154,15 +154,3 @@ class _ChromeDinoJoint extends RevoluteJoint {
setMotorSpeed(-motorSpeed); setMotorSpeed(-motorSpeed);
} }
} }
extension on World {
// TODO(alestiago): Remove once Forge2D supports custom joints.
void createJoint2(Joint joint) {
assert(!isLocked, '');
joints.add(joint);
joint.bodyA.joints.add(joint);
joint.bodyB.joints.add(joint);
}
}

@ -1,4 +1,5 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/forge2d_game.dart'; import 'package:flame_forge2d/forge2d_game.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/flame/flame.dart'; import 'package:pinball/flame/flame.dart';
@ -28,19 +29,19 @@ class ControlledBall extends Ball with Controls<BallController> {
ControlledBall.bonus({ ControlledBall.bonus({
required PinballTheme theme, required PinballTheme theme,
}) : super(baseColor: theme.characterTheme.ballColor) { }) : super(baseColor: theme.characterTheme.ballColor) {
controller = BallController(this); controller = BonusBallController(this);
} }
/// [Ball] used in [DebugPinballGame]. /// [Ball] used in [DebugPinballGame].
ControlledBall.debug() : super(baseColor: const Color(0xFFFF0000)) { ControlledBall.debug() : super(baseColor: const Color(0xFFFF0000)) {
controller = BallController(this); controller = BonusBallController(this);
} }
} }
/// {@template ball_controller} /// {@template ball_controller}
/// Controller attached to a [Ball] that handles its game related logic. /// Controller attached to a [Ball] that handles its game related logic.
/// {@endtemplate} /// {@endtemplate}
class BallController extends ComponentController<Ball> { abstract class BallController extends ComponentController<Ball> {
/// {@macro ball_controller} /// {@macro ball_controller}
BallController(Ball ball) : super(ball); BallController(Ball ball) : super(ball);
@ -50,30 +51,52 @@ class BallController extends ComponentController<Ball> {
/// Triggered by [BottomWallBallContactCallback] when the [Ball] falls into /// Triggered by [BottomWallBallContactCallback] when the [Ball] falls into
/// a [BottomWall]. /// a [BottomWall].
/// {@endtemplate} /// {@endtemplate}
@mustCallSuper void lost();
}
/// {@template bonus_ball_controller}
/// {@macro ball_controller}
///
/// A [BonusBallController] doesn't change the [GameState.balls] count.
/// {@endtemplate}
class BonusBallController extends BallController {
/// {@macro bonus_ball_controller}
BonusBallController(Ball<Forge2DGame> component) : super(component);
@override
void lost() { void lost() {
component.shouldRemove = true; component.shouldRemove = true;
} }
} }
/// {@template launched_ball_controller}
/// {@macro ball_controller} /// {@macro ball_controller}
///
/// A [LaunchedBallController] changes the [GameState.balls] count.
/// {@endtemplate}
class LaunchedBallController extends BallController class LaunchedBallController extends BallController
with HasGameRef<PinballGame> { with HasGameRef<PinballGame>, BlocComponent<GameBloc, GameState> {
/// {@macro ball_controller} /// {@macro launched_ball_controller}
LaunchedBallController(Ball<Forge2DGame> ball) : super(ball); LaunchedBallController(Ball<Forge2DGame> ball) : super(ball);
@override
bool listenWhen(GameState? previousState, GameState newState) {
return (previousState?.balls ?? 0) > newState.balls;
}
@override
void onNewState(GameState state) {
super.onNewState(state);
component.shouldRemove = true;
if (state.balls > 1) gameRef.spawnBall();
}
/// Removes the [Ball] from a [PinballGame]; spawning a new [Ball] if /// Removes the [Ball] from a [PinballGame]; spawning a new [Ball] if
/// any are left. /// any are left.
/// ///
/// {@macro ball_controller_lost} /// {@macro ball_controller_lost}
@override @override
void lost() { void lost() {
super.lost(); gameRef.read<GameBloc>().add(const BallLost());
final bloc = gameRef.read<GameBloc>()..add(const BallLost());
// TODO(alestiago): Consider the use of onNewState instead.
final shouldBallRespwan = !bloc.state.isLastBall && !bloc.state.isGameOver;
if (shouldBallRespwan) gameRef.spawnBall();
} }
} }

@ -13,8 +13,8 @@ class Jetpack extends Forge2DBlueprint {
@override @override
void build(_) { void build(_) {
final position = Vector2( final position = Vector2(
PinballGame.boardBounds.left + 40.5, BoardDimensions.bounds.left + 40.5,
PinballGame.boardBounds.top - 31.5, BoardDimensions.bounds.top - 31.5,
); );
addAllContactCallback([ addAllContactCallback([

@ -13,8 +13,8 @@ class Launcher extends Forge2DBlueprint {
@override @override
void build(_) { void build(_) {
final position = Vector2( final position = Vector2(
PinballGame.boardBounds.right - 31.3, BoardDimensions.bounds.right - 31.3,
PinballGame.boardBounds.bottom + 33, BoardDimensions.bounds.bottom + 33,
); );
addAllContactCallback([ addAllContactCallback([
@ -67,8 +67,8 @@ class LauncherRamp extends BodyComponent with InitialPosition, Layered {
final rightStraightShape = EdgeShape() final rightStraightShape = EdgeShape()
..set( ..set(
startPosition..rotate(PinballGame.boardPerspectiveAngle), startPosition..rotate(BoardDimensions.perspectiveAngle),
endPosition..rotate(PinballGame.boardPerspectiveAngle), endPosition..rotate(BoardDimensions.perspectiveAngle),
); );
final rightStraightFixtureDef = FixtureDef(rightStraightShape); final rightStraightFixtureDef = FixtureDef(rightStraightShape);
fixturesDef.add(rightStraightFixtureDef); fixturesDef.add(rightStraightFixtureDef);

@ -1,7 +1,6 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template plunger} /// {@template plunger}
@ -26,10 +25,10 @@ class Plunger extends BodyComponent with KeyboardHandler, InitialPosition {
1.35, 1.35,
0.5, 0.5,
Vector2.zero(), Vector2.zero(),
PinballGame.boardPerspectiveAngle, BoardDimensions.perspectiveAngle,
); );
final fixtureDef = FixtureDef(shape)..density = 20; final fixtureDef = FixtureDef(shape)..density = 80;
final bodyDef = BodyDef() final bodyDef = BodyDef()
..position = initialPosition ..position = initialPosition
@ -50,7 +49,7 @@ class Plunger extends BodyComponent with KeyboardHandler, InitialPosition {
/// The velocity's magnitude depends on how far the [Plunger] has been pulled /// The velocity's magnitude depends on how far the [Plunger] has been pulled
/// from its original [initialPosition]. /// from its original [initialPosition].
void _release() { void _release() {
final velocity = (initialPosition.y - body.position.y) * 4; final velocity = (initialPosition.y - body.position.y) * 5;
body.linearVelocity = Vector2(0, velocity); body.linearVelocity = Vector2(0, velocity);
} }
@ -127,12 +126,12 @@ class PlungerAnchorPrismaticJointDef extends PrismaticJointDef {
plunger.body, plunger.body,
anchor.body, anchor.body,
anchor.body.position, anchor.body.position,
Vector2(18.6, PinballGame.boardBounds.height), Vector2(18.6, BoardDimensions.bounds.height),
); );
enableLimit = true; enableLimit = true;
lowerTranslation = double.negativeInfinity; lowerTranslation = double.negativeInfinity;
enableMotor = true; enableMotor = true;
motorSpeed = 80; motorSpeed = 1000;
maxMotorForce = motorSpeed; maxMotorForce = motorSpeed;
collideConnected = true; collideConnected = true;
} }

@ -3,7 +3,6 @@
import 'package:flame/extensions.dart'; import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/components/components.dart'; import 'package:pinball/game/components/components.dart';
import 'package:pinball/game/pinball_game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template wall} /// {@template wall}
@ -42,13 +41,12 @@ class Wall extends BodyComponent {
/// Create top, left, and right [Wall]s for the game board. /// Create top, left, and right [Wall]s for the game board.
List<Wall> createBoundaries(Forge2DGame game) { List<Wall> createBoundaries(Forge2DGame game) {
final topLeft = final topLeft = BoardDimensions.bounds.topLeft.toVector2() + Vector2(18.6, 0);
PinballGame.boardBounds.topLeft.toVector2() + Vector2(18.6, 0); final bottomRight = BoardDimensions.bounds.bottomRight.toVector2();
final bottomRight = PinballGame.boardBounds.bottomRight.toVector2();
final topRight = final topRight =
PinballGame.boardBounds.topRight.toVector2() - Vector2(18.6, 0); BoardDimensions.bounds.topRight.toVector2() - Vector2(18.6, 0);
final bottomLeft = PinballGame.boardBounds.bottomLeft.toVector2(); final bottomLeft = BoardDimensions.bounds.bottomLeft.toVector2();
return [ return [
Wall(start: topLeft, end: topRight), Wall(start: topLeft, end: topRight),
@ -67,8 +65,8 @@ class BottomWall extends Wall {
/// {@macro bottom_wall} /// {@macro bottom_wall}
BottomWall() BottomWall()
: super( : super(
start: PinballGame.boardBounds.bottomLeft.toVector2(), start: BoardDimensions.bounds.bottomLeft.toVector2(),
end: PinballGame.boardBounds.bottomRight.toVector2(), end: BoardDimensions.bounds.bottomRight.toVector2(),
); );
} }

@ -1,6 +1,5 @@
// ignore_for_file: public_member_api_docs // ignore_for_file: public_member_api_docs
import 'dart:async'; import 'dart:async';
import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/extensions.dart'; import 'package:flame/extensions.dart';
@ -22,15 +21,6 @@ class PinballGame extends Forge2DGame
late final Plunger plunger; late final Plunger plunger;
static final boardSize = Vector2(101.6, 143.8);
static final boardBounds = Rect.fromCenter(
center: Offset.zero,
width: boardSize.x,
height: -boardSize.y,
);
static final boardPerspectiveAngle =
-math.atan(18.6 / PinballGame.boardBounds.height);
@override @override
void onAttach() { void onAttach() {
super.onAttach(); super.onAttach();
@ -80,7 +70,8 @@ class PinballGame extends Forge2DGame
Future<void> _addPlunger() async { Future<void> _addPlunger() async {
plunger = Plunger(compressionDistance: 29) plunger = Plunger(compressionDistance: 29)
..initialPosition = boardBounds.center.toVector2() + Vector2(41.5, -49); ..initialPosition =
BoardDimensions.bounds.center.toVector2() + Vector2(41.5, -49);
await add(plunger); await add(plunger);
} }
@ -88,8 +79,8 @@ class PinballGame extends Forge2DGame
await add( await add(
BonusWord( BonusWord(
position: Vector2( position: Vector2(
boardBounds.center.dx - 3.07, BoardDimensions.bounds.center.dx - 3.07,
boardBounds.center.dy - 2.4, BoardDimensions.bounds.center.dy - 2.4,
), ),
), ),
); );

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -23,13 +23,14 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
} }
/// The size of the [Ball] /// The size of the [Ball]
static final Vector2 size = Vector2.all(3); static final Vector2 size = Vector2.all(4.5);
/// The base [Color] used to tint this [Ball] /// The base [Color] used to tint this [Ball]
final Color baseColor; final Color baseColor;
double _boostTimer = 0; double _boostTimer = 0;
static const _boostDuration = 2.0; static const _boostDuration = 2.0;
late SpriteComponent _spriteComponent;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
@ -37,9 +38,9 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
final sprite = await gameRef.loadSprite(Assets.images.ball.keyName); final sprite = await gameRef.loadSprite(Assets.images.ball.keyName);
final tint = baseColor.withOpacity(0.5); final tint = baseColor.withOpacity(0.5);
await add( await add(
SpriteComponent( _spriteComponent = SpriteComponent(
sprite: sprite, sprite: sprite,
size: size, size: size * 1.15,
anchor: Anchor.center, anchor: Anchor.center,
)..tint(tint), )..tint(tint),
); );
@ -88,6 +89,8 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
unawaited(gameRef.add(effect)); unawaited(gameRef.add(effect));
} }
_rescale();
} }
/// Applies a boost on this [Ball]. /// Applies a boost on this [Ball].
@ -95,4 +98,18 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
body.applyLinearImpulse(impulse); body.applyLinearImpulse(impulse);
_boostTimer = _boostDuration; _boostTimer = _boostDuration;
} }
void _rescale() {
final boardHeight = BoardDimensions.size.y;
const maxShrinkAmount = BoardDimensions.perspectiveShrinkFactor;
final adjustedYPosition = body.position.y + (boardHeight / 2);
final scaleFactor = ((boardHeight - adjustedYPosition) /
BoardDimensions.shrinkAdjustedHeight) +
maxShrinkAmount;
body.fixtures.first.shape.radius = (size.x / 2) * scaleFactor;
_spriteComponent.scale = Vector2.all(scaleFactor);
}
} }

@ -0,0 +1,29 @@
import 'dart:math' as math;
import 'package:flame/extensions.dart';
/// {@template board_dimensions}
/// Contains various board properties and dimensions for global use.
/// {@endtemplate}
// TODO(allisonryan0002): consider alternatives for global dimensions.
class BoardDimensions {
/// Width and height of the board.
static final size = Vector2(101.6, 143.8);
/// [Rect] for easier access to board boundaries.
static final bounds = Rect.fromCenter(
center: Offset.zero,
width: size.x,
height: -size.y,
);
/// 3D perspective angle of the board in radians.
static final perspectiveAngle = -math.atan(18.6 / bounds.height);
/// Factor the board shrinks by from the closest point to the farthest.
static const perspectiveShrinkFactor = 0.63;
/// Board height based on the [perspectiveShrinkFactor].
static final shrinkAdjustedHeight =
(1 / (1 - perspectiveShrinkFactor)) * size.y;
}

@ -1,5 +1,6 @@
export 'ball.dart'; export 'ball.dart';
export 'baseboard.dart'; export 'baseboard.dart';
export 'board_dimensions.dart';
export 'board_side.dart'; export 'board_side.dart';
export 'fire_effect.dart'; export 'fire_effect.dart';
export 'flipper.dart'; export 'flipper.dart';

@ -68,7 +68,7 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
anchor: anchor, anchor: anchor,
); );
final joint = _FlipperJoint(jointDef); final joint = _FlipperJoint(jointDef);
world.createJoint2(joint); world.createJoint(joint);
unawaited(mounted.whenComplete(joint.unlock)); unawaited(mounted.whenComplete(joint.unlock));
} }
@ -219,15 +219,3 @@ class _FlipperJoint extends RevoluteJoint {
setLimits(-angle, angle); setLimits(-angle, angle);
} }
} }
// TODO(alestiago): Remove once Forge2D supports custom joints.
extension on World {
void createJoint2(Joint joint) {
assert(!isLocked, '');
joints.add(joint);
joint.bodyA.joints.add(joint);
joint.bodyB.joints.add(joint);
}
}

@ -86,7 +86,7 @@ void main() {
final fixture = ball.body.fixtures[0]; final fixture = ball.body.fixtures[0];
expect(fixture.shape.shapeType, equals(ShapeType.circle)); expect(fixture.shape.shapeType, equals(ShapeType.circle));
expect(fixture.shape.radius, equals(1.5)); expect(fixture.shape.radius, equals(2.25));
}, },
); );

@ -0,0 +1,27 @@
import 'package:flame/extensions.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
void main() {
group('BoardDimensions', () {
test('has size', () {
expect(BoardDimensions.size, equals(Vector2(101.6, 143.8)));
});
test('has bounds', () {
expect(BoardDimensions.bounds, isNotNull);
});
test('has perspectiveAngle', () {
expect(BoardDimensions.perspectiveAngle, isNotNull);
});
test('has perspectiveShrinkFactor', () {
expect(BoardDimensions.perspectiveShrinkFactor, equals(0.63));
});
test('has shrinkAdjustedHeight', () {
expect(BoardDimensions.shrinkAdjustedHeight, isNotNull);
});
});
}

@ -103,38 +103,6 @@ void main() {
}); });
}); });
group('isLastBall', () {
test(
'is true '
'when there is only one ball left',
() {
const gameState = GameState(
balls: 1,
score: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
);
expect(gameState.isLastBall, isTrue);
},
);
test(
'is false '
'when there are more balls left',
() {
const gameState = GameState(
balls: 2,
score: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
);
expect(gameState.isLastBall, isFalse);
},
);
});
group('isLetterActivated', () { group('isLetterActivated', () {
test( test(
'is true when the letter is activated', 'is true when the letter is activated',

@ -196,7 +196,7 @@ void main() {
group('bonus letter activation', () { group('bonus letter activation', () {
late GameBloc gameBloc; late GameBloc gameBloc;
final tester = flameBlocTester( final tester = flameBlocTester<PinballGame>(
// TODO(alestiago): Use TestGame once BonusLetter has controller. // TODO(alestiago): Use TestGame once BonusLetter has controller.
game: PinballGameTest.create, game: PinballGameTest.create,
gameBloc: () => gameBloc, gameBloc: () => gameBloc,
@ -217,13 +217,8 @@ void main() {
await game.ready(); await game.ready();
final bonusLetter = game.descendants().whereType<BonusLetter>().first; final bonusLetter = game.descendants().whereType<BonusLetter>().first;
await game.add(bonusLetter);
await game.ready();
bonusLetter.activate(); bonusLetter.activate();
await game.ready(); await game.ready();
await tester.pump();
}, },
verify: (game, tester) async { verify: (game, tester) async {
verify(() => gameBloc.add(const BonusLetterActivated(0))).called(1); verify(() => gameBloc.add(const BonusLetterActivated(0))).called(1);

@ -15,20 +15,26 @@ void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameTest.create); final flameTester = FlameTester(PinballGameTest.create);
group('BallController', () { group('BonusBallController', () {
late Ball ball; late Ball ball;
setUp(() { setUp(() {
ball = Ball(baseColor: const Color(0xFF00FFFF)); ball = Ball(baseColor: const Color(0xFF00FFFF));
}); });
test('can be instantiated', () {
expect(
BonusBallController(ball),
isA<BonusBallController>(),
);
});
flameTester.test( flameTester.test(
'lost removes ball', 'lost removes ball',
(game) async { (game) async {
await game.add(ball); await game.add(ball);
final controller = BallController(ball); final controller = BonusBallController(ball);
await ball.add(controller); await ball.ensureAdd(controller);
await game.ready();
controller.lost(); controller.lost();
await game.ready(); await game.ready();
@ -39,13 +45,20 @@ void main() {
}); });
group('LaunchedBallController', () { group('LaunchedBallController', () {
group('lost', () { test('can be instantiated', () {
late GameBloc gameBloc; expect(
LaunchedBallController(MockBall()),
isA<LaunchedBallController>(),
);
});
group('description', () {
late Ball ball; late Ball ball;
late GameBloc gameBloc;
setUp(() { setUp(() {
gameBloc = MockGameBloc();
ball = Ball(baseColor: const Color(0xFF00FFFF)); ball = Ball(baseColor: const Color(0xFF00FFFF));
gameBloc = MockGameBloc();
whenListen( whenListen(
gameBloc, gameBloc,
const Stream<GameState>.empty(), const Stream<GameState>.empty(),
@ -59,81 +72,126 @@ void main() {
); );
tester.testGameWidget( tester.testGameWidget(
'removes ball', 'lost adds BallLost to GameBloc',
verify: (game, tester) async { setUp: (game, tester) async {
await game.add(ball);
final controller = LaunchedBallController(ball); final controller = LaunchedBallController(ball);
await ball.add(controller); await ball.add(controller);
await game.ready(); await game.ensureAdd(ball);
controller.lost(); controller.lost();
await game.ready();
expect(game.contains(ball), isFalse);
}, },
);
tester.testGameWidget(
'adds BallLost to GameBloc',
verify: (game, tester) async { verify: (game, tester) async {
final controller = LaunchedBallController(ball);
await ball.add(controller);
await game.add(ball);
await game.ready();
controller.lost();
verify(() => gameBloc.add(const BallLost())).called(1); verify(() => gameBloc.add(const BallLost())).called(1);
}, },
); );
tester.testGameWidget( group('listenWhen', () {
'adds a new ball if the game is not over', tester.testGameWidget(
verify: (game, tester) async { 'listens when a ball has been lost',
final controller = LaunchedBallController(ball); setUp: (game, tester) async {
await ball.add(controller); final controller = LaunchedBallController(ball);
await game.add(ball);
await game.ready(); await ball.add(controller);
await game.ensureAdd(ball);
},
verify: (game, tester) async {
final controller =
game.descendants().whereType<LaunchedBallController>().first;
final previousState = MockGameState();
final newState = MockGameState();
when(() => previousState.balls).thenReturn(3);
when(() => newState.balls).thenReturn(2);
expect(controller.listenWhen(previousState, newState), isTrue);
},
);
final previousBalls = game.descendants().whereType<Ball>().length; tester.testGameWidget(
controller.lost(); 'does not listen when a ball has not been lost',
await game.ready(); setUp: (game, tester) async {
final currentBalls = game.descendants().whereType<Ball>().length; final controller = LaunchedBallController(ball);
await ball.add(controller);
await game.ensureAdd(ball);
},
verify: (game, tester) async {
final controller =
game.descendants().whereType<LaunchedBallController>().first;
final previousState = MockGameState();
final newState = MockGameState();
when(() => previousState.balls).thenReturn(3);
when(() => newState.balls).thenReturn(3);
expect(controller.listenWhen(previousState, newState), isFalse);
},
);
});
expect(previousBalls, equals(currentBalls)); group('onNewState', () {
}, tester.testGameWidget(
); 'removes ball',
setUp: (game, tester) async {
final controller = LaunchedBallController(ball);
await ball.add(controller);
await game.ensureAdd(ball);
final state = MockGameState();
when(() => state.balls).thenReturn(1);
controller.onNewState(state);
await game.ready();
},
verify: (game, tester) async {
expect(game.contains(ball), isFalse);
},
);
tester.testGameWidget( tester.testGameWidget(
'no ball is added on game over', 'spawns a new ball when the ball is not the last one',
verify: (game, tester) async { setUp: (game, tester) async {
whenListen( final controller = LaunchedBallController(ball);
gameBloc, await ball.add(controller);
const Stream<GameState>.empty(), await game.ensureAdd(ball);
initialState: const GameState(
score: 10,
balls: 1,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
);
final controller = BallController(ball);
await ball.add(controller);
await game.add(ball);
await game.ready();
final previousBalls = game.descendants().whereType<Ball>().toList(); final state = MockGameState();
controller.lost(); when(() => state.balls).thenReturn(2);
await game.ready();
final currentBalls = game.descendants().whereType<Ball>().length;
expect( final previousBalls = game.descendants().whereType<Ball>().toList();
currentBalls, controller.onNewState(state);
equals((previousBalls..remove(ball)).length), await game.ready();
);
}, final currentBalls = game.descendants().whereType<Ball>();
);
expect(currentBalls.contains(ball), isFalse);
expect(currentBalls.length, equals(previousBalls.length));
},
);
tester.testGameWidget(
'does not spawn a new ball is the last one',
setUp: (game, tester) async {
final controller = LaunchedBallController(ball);
await ball.add(controller);
await game.ensureAdd(ball);
final state = MockGameState();
when(() => state.balls).thenReturn(1);
final previousBalls = game.descendants().whereType<Ball>().toList();
controller.onNewState(state);
await game.ready();
final currentBalls = game.descendants().whereType<Ball>();
expect(currentBalls.contains(ball), isFalse);
expect(
currentBalls.length,
equals((previousBalls..remove(ball)).length),
);
},
);
});
}); });
}); });
} }

@ -1,6 +1,7 @@
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
class TestGame extends Forge2DGame { class TestGame extends Forge2DGame with FlameBloc {
TestGame() { TestGame() {
images.prefix = ''; images.prefix = '';
} }

Loading…
Cancel
Save