fix: fixed merge conflicts on barrel components

pull/40/head
RuiAlonso 4 years ago
commit 9e01799203

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

@ -0,0 +1,91 @@
import 'dart:math' as math;
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
/// {@template baseboard}
/// Straight, angled board piece to corral the [Ball] towards the [Flipper]s.
/// {@endtemplate}
class Baseboard extends BodyComponent {
/// {@macro baseboard}
Baseboard._({
required Vector2 position,
required BoardSide side,
}) : _position = position,
_side = side;
/// A left positioned [Baseboard].
Baseboard.left({
required Vector2 position,
}) : this._(
position: position,
side: BoardSide.left,
);
/// A right positioned [Baseboard].
Baseboard.right({
required Vector2 position,
}) : this._(
position: position,
side: BoardSide.right,
);
/// The width of the [Baseboard].
static const width = 10.0;
/// The height of the [Baseboard].
static const height = 2.0;
/// The position of the [Baseboard] body.
final Vector2 _position;
/// Whether the [Baseboard] is on the left or right side of the board.
final BoardSide _side;
List<FixtureDef> _createFixtureDefs() {
final fixtures = <FixtureDef>[];
final circleShape1 = CircleShape()..radius = Baseboard.height / 2;
circleShape1.position.setValues(
-(Baseboard.width / 2) + circleShape1.radius,
0,
);
final circle1FixtureDef = FixtureDef(circleShape1);
fixtures.add(circle1FixtureDef);
final circleShape2 = CircleShape()..radius = Baseboard.height / 2;
circleShape2.position.setValues(
(Baseboard.width / 2) - circleShape2.radius,
0,
);
final circle2FixtureDef = FixtureDef(circleShape2);
fixtures.add(circle2FixtureDef);
final rectangle = PolygonShape()
..setAsBoxXY(
(Baseboard.width - Baseboard.height) / 2,
Baseboard.height / 2,
);
final rectangleFixtureDef = FixtureDef(rectangle);
fixtures.add(rectangleFixtureDef);
return fixtures;
}
@override
Body createBody() {
// TODO(allisonryan0002): share sweeping angle with flipper when components
// are grouped.
const angle = math.pi / 7;
final bodyDef = BodyDef()
..position = _position
..type = BodyType.static
..angle = _side.isLeft ? -angle : angle;
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
}

@ -0,0 +1,134 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
/// {@template bonus_word}
/// Loads all [BonusLetter]s to compose a [BonusWord].
/// {@endtemplate}
class BonusWord extends Component {
/// {@macro bonus_word}
BonusWord({required Vector2 position}) : _position = position;
final Vector2 _position;
@override
Future<void> onLoad() async {
await super.onLoad();
final letters = GameBloc.bonusWord.split('');
for (var i = 0; i < letters.length; i++) {
unawaited(
add(
BonusLetter(
position: _position - Vector2(16 - (i * 6), -30),
letter: letters[i],
index: i,
),
),
);
}
}
}
/// {@template bonus_letter}
/// [BodyType.static] sensor component, part of a word bonus,
/// which will activate its letter after contact with a [Ball].
/// {@endtemplate}
class BonusLetter extends BodyComponent<PinballGame>
with BlocComponent<GameBloc, GameState> {
/// {@macro bonus_letter}
BonusLetter({
required Vector2 position,
required String letter,
required int index,
}) : _position = position,
_letter = letter,
_index = index {
paint = Paint()..color = _disableColor;
}
/// The area size of this [BonusLetter].
static final areaSize = Vector2.all(4);
static const _activeColor = Colors.green;
static const _disableColor = Colors.red;
final Vector2 _position;
final String _letter;
final int _index;
@override
Future<void> onLoad() async {
await super.onLoad();
await add(
TextComponent(
position: Vector2(-1, -1),
text: _letter,
textRenderer: TextPaint(
style: const TextStyle(fontSize: 2, color: Colors.white),
),
),
);
}
@override
Body createBody() {
final shape = CircleShape()..radius = areaSize.x / 2;
final fixtureDef = FixtureDef(shape)..isSensor = true;
final bodyDef = BodyDef()
..userData = this
..position = _position
..type = BodyType.static;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
@override
bool listenWhen(GameState? previousState, GameState newState) {
final wasActive = previousState?.isLetterActivated(_index) ?? false;
final isActive = newState.isLetterActivated(_index);
return wasActive != isActive;
}
@override
void onNewState(GameState state) {
final isActive = state.isLetterActivated(_index);
add(
ColorEffect(
isActive ? _activeColor : _disableColor,
const Offset(0, 1),
EffectController(duration: 0.25),
),
);
}
/// Activates this [BonusLetter], if it's not already activated.
void activate() {
final isActive = state?.isLetterActivated(_index) ?? false;
if (!isActive) {
gameRef.read<GameBloc>().add(BonusLetterActivated(_index));
}
}
}
/// Triggers [BonusLetter.activate] method when a [BonusLetter] and a [Ball]
/// come in contact.
class BonusLetterBallContactCallback
extends ContactCallback<Ball, BonusLetter> {
@override
void begin(Ball ball, BonusLetter bonusLetter, Contact contact) {
bonusLetter.activate();
}
}

@ -1,6 +1,8 @@
export 'anchor.dart';
export 'ball.dart';
export 'baseboard.dart';
export 'board_side.dart';
export 'bonus_word.dart';
export 'crossing_ramp.dart';
export 'flipper.dart';
export 'jetpack_ramp.dart';

@ -1,12 +1,45 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flame/components.dart' show SpriteComponent;
import 'package:flame/components.dart' show PositionComponent, SpriteComponent;
import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart';
/// {@template flipper_group}
/// Loads a [Flipper.right] and a [Flipper.left].
/// {@endtemplate}
class FlipperGroup extends PositionComponent {
/// {@macro flipper_group}
FlipperGroup({
required Vector2 position,
required this.spacing,
}) : super(position: position);
/// The amount of space between the [Flipper.right] and [Flipper.left].
final double spacing;
@override
Future<void> onLoad() async {
final leftFlipper = Flipper.left(
position: Vector2(
position.x - (Flipper.width / 2) - (spacing / 2),
position.y,
),
);
await add(leftFlipper);
final rightFlipper = Flipper.right(
position: Vector2(
position.x + (Flipper.width / 2) + (spacing / 2),
position.y,
),
);
await add(rightFlipper);
}
}
/// {@template flipper}
/// A bat, typically found in pairs at the bottom of the board.
///
@ -76,20 +109,6 @@ class Flipper extends PositionBodyComponent with KeyboardHandler {
/// [onKeyEvent] method listens to when one of these keys is pressed.
final List<LogicalKeyboardKey> _keys;
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(spritePath);
positionComponent = SpriteComponent(
sprite: sprite,
size: size,
);
if (side == BoardSide.right) {
positionComponent?.flipHorizontally();
}
}
/// Applies downward linear velocity to the [Flipper], moving it to its
/// resting position.
void _moveDown() {
@ -102,15 +121,50 @@ class Flipper extends PositionBodyComponent with KeyboardHandler {
body.linearVelocity = Vector2(0, _speed);
}
/// Loads the sprite that renders with the [Flipper].
Future<void> _loadSprite() async {
final sprite = await gameRef.loadSprite(spritePath);
positionComponent = SpriteComponent(
sprite: sprite,
size: size,
);
if (side.isRight) {
positionComponent!.flipHorizontally();
}
}
/// Anchors the [Flipper] to the [RevoluteJoint] that controls its arc motion.
Future<void> _anchorToJoint() async {
final anchor = FlipperAnchor(flipper: this);
await add(anchor);
final jointDef = FlipperAnchorRevoluteJointDef(
flipper: this,
anchor: anchor,
);
// TODO(alestiago): Remove casting once the following is closed:
// https://github.com/flame-engine/forge2d/issues/36
final joint = world.createJoint(jointDef) as RevoluteJoint;
// FIXME(erickzanardo): when mounted the initial position is not fully
// reached.
unawaited(
mounted.whenComplete(
() => FlipperAnchorRevoluteJointDef.unlock(joint, side),
),
);
}
List<FixtureDef> _createFixtureDefs() {
final fixtures = <FixtureDef>[];
final isLeft = side.isLeft;
final bigCircleShape = CircleShape()..radius = height / 2;
final bigCircleShape = CircleShape()..radius = size.y / 2;
bigCircleShape.position.setValues(
isLeft
? -(width / 2) + bigCircleShape.radius
: (width / 2) - bigCircleShape.radius,
? -(size.x / 2) + bigCircleShape.radius
: (size.x / 2) - bigCircleShape.radius,
0,
);
final bigCircleFixtureDef = FixtureDef(bigCircleShape);
@ -119,8 +173,8 @@ class Flipper extends PositionBodyComponent with KeyboardHandler {
final smallCircleShape = CircleShape()..radius = bigCircleShape.radius / 2;
smallCircleShape.position.setValues(
isLeft
? (width / 2) - smallCircleShape.radius
: -(width / 2) + smallCircleShape.radius,
? (size.x / 2) - smallCircleShape.radius
: -(size.x / 2) + smallCircleShape.radius,
0,
);
final smallCircleFixtureDef = FixtureDef(smallCircleShape);
@ -148,6 +202,15 @@ class Flipper extends PositionBodyComponent with KeyboardHandler {
return fixtures;
}
@override
Future<void> onLoad() async {
await super.onLoad();
await Future.wait([
_loadSprite(),
_anchorToJoint(),
]);
}
@override
Body createBody() {
final bodyDef = BodyDef()
@ -161,17 +224,6 @@ class Flipper extends PositionBodyComponent with KeyboardHandler {
return body;
}
// TODO(erickzanardo): Remove this once the issue is solved:
// https://github.com/flame-engine/flame/issues/1417
// ignore: public_member_api_docs
final Completer hasMounted = Completer<void>();
@override
void onMount() {
super.onMount();
hasMounted.complete();
}
@override
bool onKeyEvent(
RawKeyEvent event,
@ -204,8 +256,8 @@ class FlipperAnchor extends Anchor {
}) : super(
position: Vector2(
flipper.side.isLeft
? flipper.body.position.x - Flipper.width / 2
: flipper.body.position.x + Flipper.width / 2,
? flipper.body.position.x - flipper.size.x / 2
: flipper.body.position.x + flipper.size.x / 2,
flipper.body.position.y,
),
);

@ -50,7 +50,6 @@ class PinballGame extends Forge2DGame
_addContactCallbacks();
await _addGameBoundaries();
unawaited(_addFlippers());
unawaited(_addPlunger());
unawaited(_addPaths());
@ -73,20 +72,23 @@ class PinballGame extends Forge2DGame
),
),
);
}
void spawnBall() {
add(Ball(position: plunger.body.position));
}
unawaited(_addFlippers());
void _addContactCallbacks() {
addContactCallback(BallScorePointsCallback());
addContactCallback(BottomWallBallContactCallback());
unawaited(_addBonusWord());
}
Future<void> _addGameBoundaries() async {
await add(BottomWall(this));
createBoundaries(this).forEach(add);
Future<void> _addBonusWord() async {
await add(
BonusWord(
position: screenToWorld(
Vector2(
camera.viewport.effectiveSize.x / 2,
camera.viewport.effectiveSize.y - 50,
),
),
),
);
}
Future<void> _addFlippers() async {
@ -96,63 +98,31 @@ class PinballGame extends Forge2DGame
camera.viewport.effectiveSize.y / 1.1,
),
);
const spaceBetweenFlippers = 2;
final leftFlipper = Flipper.left(
position: Vector2(
flippersPosition.x - (Flipper.width / 2) - (spaceBetweenFlippers / 2),
flippersPosition.y,
),
);
await add(leftFlipper);
final leftFlipperAnchor = FlipperAnchor(flipper: leftFlipper);
await add(leftFlipperAnchor);
final leftFlipperRevoluteJointDef = FlipperAnchorRevoluteJointDef(
flipper: leftFlipper,
anchor: leftFlipperAnchor,
);
// TODO(alestiago): Remove casting once the following is closed:
// https://github.com/flame-engine/forge2d/issues/36
final leftFlipperRevoluteJoint =
world.createJoint(leftFlipperRevoluteJointDef) as RevoluteJoint;
final rightFlipper = Flipper.right(
position: Vector2(
flippersPosition.x + (Flipper.width / 2) + (spaceBetweenFlippers / 2),
flippersPosition.y,
),
);
await add(rightFlipper);
final rightFlipperAnchor = FlipperAnchor(flipper: rightFlipper);
await add(rightFlipperAnchor);
final rightFlipperRevoluteJointDef = FlipperAnchorRevoluteJointDef(
flipper: rightFlipper,
anchor: rightFlipperAnchor,
);
// TODO(alestiago): Remove casting once the following is closed:
// https://github.com/flame-engine/forge2d/issues/36
final rightFlipperRevoluteJoint =
world.createJoint(rightFlipperRevoluteJointDef) as RevoluteJoint;
// TODO(erickzanardo): Clean this once the issue is solved:
// https://github.com/flame-engine/flame/issues/1417
// FIXME(erickzanardo): when mounted the initial position is not fully
// reached.
unawaited(
leftFlipper.hasMounted.future.whenComplete(
() => FlipperAnchorRevoluteJointDef.unlock(
leftFlipperRevoluteJoint,
leftFlipper.side,
),
),
);
unawaited(
rightFlipper.hasMounted.future.whenComplete(
() => FlipperAnchorRevoluteJointDef.unlock(
rightFlipperRevoluteJoint,
rightFlipper.side,
add(
FlipperGroup(
position: flippersPosition,
spacing: 2,
),
),
);
unawaited(_addBaseboards());
}
void spawnBall() {
add(Ball(position: plunger.body.position));
}
void _addContactCallbacks() {
addContactCallback(BallScorePointsCallback());
addContactCallback(BottomWallBallContactCallback());
addContactCallback(BonusLetterBallContactCallback());
}
Future<void> _addGameBoundaries() async {
await add(BottomWall(this));
createBoundaries(this).forEach(add);
}
Future<void> _addPaths() async {
@ -204,6 +174,31 @@ class PinballGame extends Forge2DGame
),
);
}
Future<void> _addBaseboards() async {
final spaceBetweenBaseboards = camera.viewport.effectiveSize.x / 2;
final baseboardY = camera.viewport.effectiveSize.y / 1.12;
final leftBaseboard = Baseboard.left(
position: screenToWorld(
Vector2(
camera.viewport.effectiveSize.x / 2 - (spaceBetweenBaseboards / 2),
baseboardY,
),
),
);
await add(leftBaseboard);
final rightBaseboard = Baseboard.right(
position: screenToWorld(
Vector2(
camera.viewport.effectiveSize.x / 2 + (spaceBetweenBaseboards / 2),
baseboardY,
),
),
);
await add(rightBaseboard);
}
}
class DebugPinballGame extends PinballGame with TapDetector {

@ -63,6 +63,8 @@ class _PinballGameViewState extends State<PinballGameView> {
@override
Widget build(BuildContext context) {
return BlocListener<GameBloc, GameState>(
listenWhen: (previous, current) =>
previous.isGameOver != current.isGameOver,
listener: (context, state) {
if (state.isGameOver) {
showDialog<void>(

@ -9,15 +9,15 @@ environment:
dependencies:
bloc: ^8.0.2
equatable: ^2.0.3
flame: ^1.1.0-releasecandidate.2
flame_bloc: ^1.2.0-releasecandidate.2
flame_forge2d: ^0.9.0-releasecandidate.2
flame: ^1.1.0-releasecandidate.4
flame_bloc: ^1.2.0-releasecandidate.4
flame_forge2d: ^0.9.0-releasecandidate.4
flutter:
sdk: flutter
flutter_bloc: ^8.0.1
flutter_localizations:
sdk: flutter
geometry:
geometry:
path: packages/geometry
intl: ^0.17.0
pinball_theme:

@ -126,6 +126,34 @@ void main() {
);
});
group('isLetterActivated', () {
test(
'is true when the letter is activated',
() {
const gameState = GameState(
balls: 3,
score: 0,
activatedBonusLetters: [1],
bonusHistory: [],
);
expect(gameState.isLetterActivated(1), isTrue);
},
);
test(
'is false when the letter is not activated',
() {
const gameState = GameState(
balls: 3,
score: 0,
activatedBonusLetters: [1],
bonusHistory: [],
);
expect(gameState.isLetterActivated(0), isFalse);
},
);
});
group('copyWith', () {
test(
'throws AssertionError '

@ -100,9 +100,10 @@ void main() {
});
group('resetting a ball', () {
final gameBloc = MockGameBloc();
late GameBloc gameBloc;
setUp(() {
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
@ -110,7 +111,7 @@ void main() {
);
});
final tester = flameBlocTester(gameBloc: gameBloc);
final tester = flameBlocTester(gameBloc: () => gameBloc);
tester.widgetTest(
'adds BallLost to GameBloc',
@ -129,7 +130,7 @@ void main() {
(game, tester) async {
await game.ready();
game.children.whereType<Ball>().first.removeFromParent();
game.children.whereType<Ball>().first.lost();
await game.ready(); // Making sure that all additions are done
expect(

@ -0,0 +1,76 @@
// 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/game/game.dart';
import '../../helpers/helpers.dart';
void main() {
group('Baseboard', () {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameTest.create);
flameTester.test(
'loads correctly',
(game) async {
await game.ready();
final leftBaseboard = Baseboard.left(position: Vector2.zero());
final rightBaseboard = Baseboard.right(position: Vector2.zero());
await game.ensureAddAll([leftBaseboard, rightBaseboard]);
expect(game.contains(leftBaseboard), isTrue);
expect(game.contains(rightBaseboard), isTrue);
},
);
group('body', () {
flameTester.test(
'positions correctly',
(game) async {
final position = Vector2.all(10);
final baseboard = Baseboard.left(position: position);
await game.ensureAdd(baseboard);
game.contains(baseboard);
expect(baseboard.body.position, position);
},
);
flameTester.test(
'is static',
(game) async {
final baseboard = Baseboard.left(position: Vector2.zero());
await game.ensureAdd(baseboard);
expect(baseboard.body.bodyType, equals(BodyType.static));
},
);
flameTester.test(
'is at an angle',
(game) async {
final leftBaseboard = Baseboard.left(position: Vector2.zero());
final rightBaseboard = Baseboard.right(position: Vector2.zero());
await game.ensureAddAll([leftBaseboard, rightBaseboard]);
expect(leftBaseboard.body.angle, isNegative);
expect(rightBaseboard.body.angle, isPositive);
},
);
});
group('fixtures', () {
flameTester.test(
'has three',
(game) async {
final baseboard = Baseboard.left(position: Vector2.zero());
await game.ensureAdd(baseboard);
expect(baseboard.body.fixtures.length, equals(3));
},
);
});
});
}

@ -0,0 +1,244 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/effects.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
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() {
TestWidgetsFlutterBinding.ensureInitialized();
group('BonusWord', () {
final flameTester = FlameTester(PinballGameTest.create);
flameTester.test(
'loads the letters correctly',
(game) async {
await game.ready();
final bonusWord = game.children.whereType<BonusWord>().first;
final letters = bonusWord.children.whereType<BonusLetter>();
expect(letters.length, equals(GameBloc.bonusWord.length));
},
);
});
group('BonusLetter', () {
final flameTester = FlameTester(PinballGameTest.create);
flameTester.test(
'loads correctly',
(game) async {
final bonusLetter = BonusLetter(
position: Vector2.zero(),
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
await game.ready();
expect(game.contains(bonusLetter), isTrue);
},
);
group('body', () {
flameTester.test(
'positions correctly',
(game) async {
final position = Vector2.all(10);
final bonusLetter = BonusLetter(
position: position,
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
game.contains(bonusLetter);
expect(bonusLetter.body.position, position);
},
);
flameTester.test(
'is static',
(game) async {
final bonusLetter = BonusLetter(
position: Vector2.zero(),
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
expect(bonusLetter.body.bodyType, equals(BodyType.static));
},
);
});
group('fixture', () {
flameTester.test(
'exists',
(game) async {
final bonusLetter = BonusLetter(
position: Vector2.zero(),
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
expect(bonusLetter.body.fixtures[0], isA<Fixture>());
},
);
flameTester.test(
'is sensor',
(game) async {
final bonusLetter = BonusLetter(
position: Vector2.zero(),
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
final fixture = bonusLetter.body.fixtures[0];
expect(fixture.isSensor, isTrue);
},
);
flameTester.test(
'shape is circular',
(game) async {
final bonusLetter = BonusLetter(
position: Vector2.zero(),
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
final fixture = bonusLetter.body.fixtures[0];
expect(fixture.shape.shapeType, equals(ShapeType.circle));
expect(fixture.shape.radius, equals(2));
},
);
});
group('bonus letter activation', () {
final gameBloc = MockGameBloc();
BonusLetter _getBonusLetter(PinballGame game) {
return game.children
.whereType<BonusWord>()
.first
.children
.whereType<BonusLetter>()
.first;
}
setUp(() {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final tester = flameBlocTester(gameBloc: () => gameBloc);
tester.widgetTest(
'adds BonusLetterActivated to GameBloc when not activated',
(game, tester) async {
await game.ready();
_getBonusLetter(game).activate();
await tester.pump();
verify(() => gameBloc.add(const BonusLetterActivated(0))).called(1);
},
);
tester.widgetTest(
"doesn't add BonusLetterActivated to GameBloc when already activated",
(game, tester) async {
const state = GameState(
score: 0,
balls: 2,
activatedBonusLetters: [0],
bonusHistory: [],
);
whenListen(
gameBloc,
Stream.value(state),
initialState: state,
);
await game.ready();
_getBonusLetter(game).activate();
await game.ready(); // Making sure that all additions are done
verifyNever(() => gameBloc.add(const BonusLetterActivated(0)));
},
);
tester.widgetTest(
'adds a ColorEffect',
(game, tester) async {
await game.ready();
const state = GameState(
score: 0,
balls: 2,
activatedBonusLetters: [0],
bonusHistory: [],
);
final bonusLetter = _getBonusLetter(game);
bonusLetter.onNewState(state);
await tester.pump();
expect(
bonusLetter.children.whereType<ColorEffect>().length,
equals(1),
);
},
);
tester.widgetTest(
'only listens when there is a change on the letter status',
(game, tester) async {
await game.ready();
const state = GameState(
score: 0,
balls: 2,
activatedBonusLetters: [0],
bonusHistory: [],
);
final bonusLetter = _getBonusLetter(game);
expect(
bonusLetter.listenWhen(const GameState.initial(), state),
isTrue,
);
},
);
});
group('BonusLetterBallContactCallback', () {
test('calls ball.activate', () {
final ball = MockBall();
final bonusLetter = MockBonusLetter();
final contactCallback = BonusLetterBallContactCallback();
contactCallback.begin(ball, bonusLetter, MockContact());
verify(bonusLetter.activate).called(1);
});
});
});
}

@ -2,6 +2,7 @@
import 'dart:collection';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/services.dart';
@ -13,6 +14,105 @@ import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameTest.create);
group('FlipperGroup', () {
flameTester.test(
'loads correctly',
(game) async {
final flipperGroup = FlipperGroup(
position: Vector2.zero(),
spacing: 0,
);
await game.ensureAdd(flipperGroup);
expect(game.contains(flipperGroup), isTrue);
},
);
group('constructor', () {
flameTester.test(
'positions correctly',
(game) async {
final position = Vector2.all(10);
final flipperGroup = FlipperGroup(
position: position,
spacing: 0,
);
await game.ensureAdd(flipperGroup);
expect(flipperGroup.position, equals(position));
},
);
});
group('children', () {
bool Function(Component) flipperSelector(BoardSide side) =>
(component) => component is Flipper && component.side == side;
flameTester.test(
'has only one left Flipper',
(game) async {
final flipperGroup = FlipperGroup(
position: Vector2.zero(),
spacing: 0,
);
await game.ensureAdd(flipperGroup);
expect(
() => flipperGroup.children.singleWhere(
flipperSelector(BoardSide.left),
),
returnsNormally,
);
},
);
flameTester.test(
'has only one right Flipper',
(game) async {
final flipperGroup = FlipperGroup(
position: Vector2.zero(),
spacing: 0,
);
await game.ensureAdd(flipperGroup);
expect(
() => flipperGroup.children.singleWhere(
flipperSelector(BoardSide.right),
),
returnsNormally,
);
},
);
flameTester.test(
'spaced correctly',
(game) async {
final flipperGroup = FlipperGroup(
position: Vector2.zero(),
spacing: 2,
);
await game.ready();
await game.ensureAdd(flipperGroup);
final leftFlipper = flipperGroup.children.singleWhere(
flipperSelector(BoardSide.left),
) as Flipper;
final rightFlipper = flipperGroup.children.singleWhere(
flipperSelector(BoardSide.right),
) as Flipper;
expect(
leftFlipper.body.position.x +
leftFlipper.size.x +
flipperGroup.spacing,
equals(rightFlipper.body.position.x),
);
},
);
});
});
group(
'Flipper',
() {
@ -21,9 +121,11 @@ void main() {
(game) async {
final leftFlipper = Flipper.left(position: Vector2.zero());
final rightFlipper = Flipper.right(position: Vector2.zero());
await game.ready();
await game.ensureAddAll([leftFlipper, rightFlipper]);
expect(game.contains(leftFlipper), isTrue);
expect(game.contains(rightFlipper), isTrue);
},
);

@ -227,7 +227,7 @@ void main() {
);
});
final flameTester = flameBlocTester(gameBloc: gameBloc);
final flameTester = flameBlocTester(gameBloc: () => gameBloc);
group('initializes with', () {
flameTester.test(

@ -18,72 +18,40 @@ void main() {
// [BallScorePointsCallback] once the following issue is resolved:
// https://github.com/flame-engine/flame/issues/1416
group('components', () {
group('Walls', () {
flameTester.test(
'has three Walls',
(game) async {
await game.ready();
final walls = game.children
.where(
(component) => component is Wall && component is! BottomWall,
)
.toList();
// TODO(allisonryan0002): expect 3 when launch track is added and
// temporary wall is removed.
expect(walls.length, 4);
},
);
bool Function(Component) componentSelector<T>() =>
(component) => component is T;
flameTester.test(
'has only one BottomWall',
(game) async {
await game.ready();
expect(
() => game.children.singleWhere(
(component) => component is BottomWall,
),
returnsNormally,
);
},
);
});
flameTester.test(
'has three Walls',
(game) async {
await game.ready();
final walls = game.children
.where(
(component) => component is Wall && component is! BottomWall,
)
.toList();
// TODO(allisonryan0002): expect 3 when launch track is added and
// temporary wall is removed.
expect(walls.length, 4);
},
);
group('Flippers', () {
bool Function(Component) flipperSelector(BoardSide side) =>
(component) => component is Flipper && component.side == side;
flameTester.test(
'has only one left Flipper',
(game) async {
await game.ready();
expect(
() => game.children.singleWhere(
flipperSelector(BoardSide.left),
),
returnsNormally,
);
},
);
flameTester.test(
'has only one BottomWall',
(game) async {
await game.ready();
flameTester.test(
'has only one right Flipper',
(game) async {
await game.ready();
expect(
() => game.children.singleWhere(
flipperSelector(BoardSide.right),
),
returnsNormally,
);
},
);
});
expect(
() => game.children.singleWhere(
componentSelector<BottomWall>(),
),
returnsNormally,
);
},
);
flameTester.test(
'Plunger has only one Plunger',
'has only one Plunger',
(game) async {
await game.ready();
@ -95,6 +63,26 @@ void main() {
);
},
);
flameTester.test('has only one FlipperGroup', (game) async {
await game.ready();
expect(
() => game.children.singleWhere(
(component) => component is FlipperGroup,
),
returnsNormally,
);
});
flameTester.test(
'has two Baseboards',
(game) async {
await game.ready();
final baseboards = game.children.whereType<Baseboard>().toList();
expect(baseboards.length, 2);
},
);
});
group('Paths', () {

@ -94,7 +94,7 @@ void main() {
whenListen(
gameBloc,
Stream.value(state),
initialState: state,
initialState: GameState.initial(),
);
await tester.pumpApp(

@ -5,14 +5,14 @@ import 'package:pinball/game/game.dart';
import 'helpers.dart';
FlameTester<PinballGame> flameBlocTester({
required GameBloc gameBloc,
required GameBloc Function() gameBloc,
}) {
return FlameTester<PinballGame>(
PinballGameTest.create,
pumpWidget: (gameWidget, tester) async {
await tester.pumpWidget(
BlocProvider.value(
value: gameBloc,
value: gameBloc(),
child: gameWidget,
),
);

@ -37,3 +37,5 @@ class MockRawKeyUpEvent extends Mock implements RawKeyUpEvent {
class MockTapUpInfo extends Mock implements TapUpInfo {}
class MockEventPosition extends Mock implements EventPosition {}
class MockBonusLetter extends Mock implements BonusLetter {}

Loading…
Cancel
Save