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>
pull/31/head
Alejandro Santiago 2 years ago committed by GitHub
parent 064ae1b1ed
commit 360b5876cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,22 @@
import 'package:pinball/game/game.dart';
/// Indicates a side of the board.
///
/// Usually used to position or mirror elements of a [PinballGame]; such as a
/// [Flipper].
enum BoardSide {
/// The left side of the board.
left,
/// The right side of the board.
right,
}
/// Utility methods for [BoardSide].
extension BoardSideX on BoardSide {
/// Whether this side is [BoardSide.left].
bool get isLeft => this == BoardSide.left;
/// Whether this side is [BoardSide.right].
bool get isRight => this == BoardSide.right;
}

@ -1,5 +1,7 @@
export 'anchor.dart';
export 'ball.dart';
export 'board_side.dart';
export 'flipper.dart';
export 'plunger.dart';
export 'score_points.dart';
export 'wall.dart';

@ -0,0 +1,241 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart';
/// {@template flipper}
/// A bat, typically found in pairs at the bottom of the board.
///
/// [Flipper] can be controlled by the player in an arc motion.
/// {@endtemplate flipper}
class Flipper extends BodyComponent with KeyboardHandler {
/// {@macro flipper}
Flipper._({
required Vector2 position,
required this.side,
required List<LogicalKeyboardKey> keys,
}) : _position = position,
_keys = keys {
// TODO(alestiago): Use sprite instead of color when provided.
paint = Paint()
..color = const Color(0xFF00FF00)
..style = PaintingStyle.fill;
}
/// A left positioned [Flipper].
Flipper.left({
required Vector2 position,
}) : this._(
position: position,
side: BoardSide.left,
keys: [
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.keyA,
],
);
/// A right positioned [Flipper].
Flipper.right({
required Vector2 position,
}) : this._(
position: position,
side: BoardSide.right,
keys: [
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.keyD,
],
);
/// The width of the [Flipper].
static const width = 12.0;
/// The height of the [Flipper].
static const height = 2.8;
/// The speed required to move the [Flipper] to its highest position.
///
/// The higher the value, the faster the [Flipper] will move.
static const double _speed = 60;
/// Whether the [Flipper] is on the left or right side of the board.
///
/// A [Flipper] with [BoardSide.left] has a counter-clockwise arc motion,
/// whereas a [Flipper] with [BoardSide.right] has a clockwise arc motion.
final BoardSide side;
/// The initial position of the [Flipper] body.
final Vector2 _position;
/// The [LogicalKeyboardKey]s that will control the [Flipper].
///
/// [onKeyEvent] method listens to when one of these keys is pressed.
final List<LogicalKeyboardKey> _keys;
/// Applies downward linear velocity to the [Flipper], moving it to its
/// resting position.
void _moveDown() {
body.linearVelocity = Vector2(0, -_speed);
}
/// Applies upward linear velocity to the [Flipper], moving it to its highest
/// position.
void _moveUp() {
body.linearVelocity = Vector2(0, _speed);
}
List<FixtureDef> _createFixtureDefs() {
final fixtures = <FixtureDef>[];
final isLeft = side.isLeft;
final bigCircleShape = CircleShape()..radius = height / 2;
bigCircleShape.position.setValues(
isLeft
? -(width / 2) + bigCircleShape.radius
: (width / 2) - bigCircleShape.radius,
0,
);
final bigCircleFixtureDef = FixtureDef(bigCircleShape);
fixtures.add(bigCircleFixtureDef);
final smallCircleShape = CircleShape()..radius = bigCircleShape.radius / 2;
smallCircleShape.position.setValues(
isLeft
? (width / 2) - smallCircleShape.radius
: -(width / 2) + smallCircleShape.radius,
0,
);
final smallCircleFixtureDef = FixtureDef(smallCircleShape);
fixtures.add(smallCircleFixtureDef);
final trapeziumVertices = isLeft
? [
Vector2(bigCircleShape.position.x, bigCircleShape.radius),
Vector2(smallCircleShape.position.x, smallCircleShape.radius),
Vector2(smallCircleShape.position.x, -smallCircleShape.radius),
Vector2(bigCircleShape.position.x, -bigCircleShape.radius),
]
: [
Vector2(smallCircleShape.position.x, smallCircleShape.radius),
Vector2(bigCircleShape.position.x, bigCircleShape.radius),
Vector2(bigCircleShape.position.x, -bigCircleShape.radius),
Vector2(smallCircleShape.position.x, -smallCircleShape.radius),
];
final trapezium = PolygonShape()..set(trapeziumVertices);
final trapeziumFixtureDef = FixtureDef(trapezium)
..density = 50.0 // TODO(alestiago): Use a proper density.
..friction = .1; // TODO(alestiago): Use a proper friction.
fixtures.add(trapeziumFixtureDef);
return fixtures;
}
@override
Body createBody() {
final bodyDef = BodyDef()
..gravityScale = 0
..type = BodyType.dynamic
..position = _position;
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
// TODO(erickzanardo): Remove this once the issue is solved:
// https://github.com/flame-engine/flame/issues/1417
final Completer hasMounted = Completer<void>();
@override
void onMount() {
super.onMount();
hasMounted.complete();
}
@override
bool onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
// TODO(alestiago): Check why false cancels the event for other components.
// Investigate why return is of type [bool] expected instead of a type
// [KeyEventResult].
if (!_keys.contains(event.logicalKey)) return true;
if (event is RawKeyDownEvent) {
_moveUp();
} else if (event is RawKeyUpEvent) {
_moveDown();
}
return true;
}
}
/// {@template flipper_anchor}
/// [Anchor] positioned at the end of a [Flipper].
///
/// The end of a [Flipper] depends on its [Flipper.side].
/// {@endtemplate}
class FlipperAnchor extends Anchor {
/// {@macro flipper_anchor}
FlipperAnchor({
required Flipper flipper,
}) : super(
position: Vector2(
flipper.side.isLeft
? flipper.body.position.x - Flipper.width / 2
: flipper.body.position.x + Flipper.width / 2,
flipper.body.position.y,
),
);
}
/// {@template flipper_anchor_revolute_joint_def}
/// Hinges one end of [Flipper] to a [Anchor] to achieve an arc motion.
/// {@endtemplate}
class FlipperAnchorRevoluteJointDef extends RevoluteJointDef {
/// {@macro flipper_anchor_revolute_joint_def}
FlipperAnchorRevoluteJointDef({
required Flipper flipper,
required Anchor anchor,
}) {
initialize(
flipper.body,
anchor.body,
anchor.body.position,
);
enableLimit = true;
final angle = (flipper.side.isLeft ? _sweepingAngle : -_sweepingAngle) / 2;
lowerAngle = upperAngle = angle;
}
/// The total angle of the arc motion.
static const _sweepingAngle = math.pi / 3.5;
/// Unlocks the [Flipper] from its resting position.
///
/// The [Flipper] is locked when initialized in order to force it to be at
/// its resting position.
// TODO(alestiago): consider refactor once the issue is solved:
// https://github.com/flame-engine/forge2d/issues/36
static void unlock(RevoluteJoint joint, BoardSide side) {
late final double upperLimit, lowerLimit;
switch (side) {
case BoardSide.left:
lowerLimit = -joint.lowerLimit;
upperLimit = joint.upperLimit;
break;
case BoardSide.right:
lowerLimit = joint.lowerLimit;
upperLimit = -joint.upperLimit;
}
joint.setLimits(lowerLimit, upperLimit);
}
}

@ -1,5 +1,5 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/game/game.dart' show Anchor;
/// {@template plunger}
/// [Plunger] serves as a spring, that shoots the ball on the right side of the

@ -1,16 +1,12 @@
import 'dart:async';
import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
class PinballGame extends Forge2DGame with FlameBloc {
void spawnBall() {
add(
Ball(position: ballStartingPosition),
);
}
class PinballGame extends Forge2DGame
with FlameBloc, HasKeyboardHandlerComponents {
// TODO(erickzanardo): Change to the plumber position
late final ballStartingPosition = screenToWorld(
Vector2(
@ -20,17 +16,86 @@ class PinballGame extends Forge2DGame with FlameBloc {
) -
Vector2(0, -20);
// TODO(alestiago): Change to the design position.
late final flippersPosition = ballStartingPosition - Vector2(0, 5);
@override
void onAttach() {
super.onAttach();
spawnBall();
}
void spawnBall() {
add(Ball(position: ballStartingPosition));
}
@override
Future<void> onLoad() async {
addContactCallback(BallScorePointsCallback());
await add(BottomWall(this));
addContactCallback(BottomWallBallContactCallback());
unawaited(_addFlippers());
}
@override
void onAttach() {
super.onAttach();
spawnBall();
Future<void> _addFlippers() async {
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,
),
),
);
}
}

@ -0,0 +1,27 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
void main() {
group(
'BoardSide',
() {
test('has two values', () {
expect(BoardSide.values.length, equals(2));
});
},
);
group('BoardSideX', () {
test('isLeft is correct', () {
const side = BoardSide.left;
expect(side.isLeft, isTrue);
expect(side.isRight, isFalse);
});
test('isRight is correct', () {
const side = BoardSide.right;
expect(side.isLeft, isFalse);
expect(side.isRight, isTrue);
});
});
}

@ -0,0 +1,401 @@
// ignore_for_file: cascade_invocations
import 'dart:collection';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGame.new);
group(
'Flipper',
() {
flameTester.test(
'loads correctly',
(game) async {
final leftFlipper = Flipper.left(position: Vector2.zero());
final rightFlipper = Flipper.right(position: Vector2.zero());
await game.ensureAddAll([leftFlipper, rightFlipper]);
expect(game.contains(leftFlipper), isTrue);
},
);
group('constructor', () {
test('sets BoardSide', () {
final leftFlipper = Flipper.left(position: Vector2.zero());
expect(leftFlipper.side, equals(leftFlipper.side));
final rightFlipper = Flipper.right(position: Vector2.zero());
expect(rightFlipper.side, equals(rightFlipper.side));
});
});
group('body', () {
flameTester.test(
'positions correctly',
(game) async {
final position = Vector2.all(10);
final flipper = Flipper.left(position: position);
await game.ensureAdd(flipper);
game.contains(flipper);
expect(flipper.body.position, position);
},
);
flameTester.test(
'is dynamic',
(game) async {
final flipper = Flipper.left(position: Vector2.zero());
await game.ensureAdd(flipper);
expect(flipper.body.bodyType, equals(BodyType.dynamic));
},
);
flameTester.test(
'ignores gravity',
(game) async {
final flipper = Flipper.left(position: Vector2.zero());
await game.ensureAdd(flipper);
expect(flipper.body.gravityScale, isZero);
},
);
flameTester.test(
'has greater mass than Ball',
(game) async {
final flipper = Flipper.left(position: Vector2.zero());
final ball = Ball(position: Vector2.zero());
await game.ensureAddAll([flipper, ball]);
expect(
flipper.body.getMassData().mass,
greaterThan(ball.body.getMassData().mass),
);
},
);
});
group('fixtures', () {
flameTester.test(
'has three',
(game) async {
final flipper = Flipper.left(position: Vector2.zero());
await game.ensureAdd(flipper);
expect(flipper.body.fixtures.length, equals(3));
},
);
flameTester.test(
'has density',
(game) async {
final flipper = Flipper.left(position: Vector2.zero());
await game.ensureAdd(flipper);
final fixtures = flipper.body.fixtures;
final density = fixtures.fold<double>(
0,
(sum, fixture) => sum + fixture.density,
);
expect(density, greaterThan(0));
},
);
});
group('onKeyEvent', () {
final leftKeys = UnmodifiableListView([
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.keyA,
]);
final rightKeys = UnmodifiableListView([
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.keyD,
]);
group('and Flipper is left', () {
late Flipper flipper;
setUp(() {
flipper = Flipper.left(position: Vector2.zero());
});
testRawKeyDownEvents(leftKeys, (event) {
flameTester.test(
'moves upwards '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isPositive);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(leftKeys, (event) {
flameTester.test(
'moves downwards '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isNegative);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(rightKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyDownEvents(rightKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
});
group('and Flipper is right', () {
late Flipper flipper;
setUp(() {
flipper = Flipper.right(position: Vector2.zero());
});
testRawKeyDownEvents(rightKeys, (event) {
flameTester.test(
'moves upwards '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isPositive);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(rightKeys, (event) {
flameTester.test(
'moves downwards '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isNegative);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(leftKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyDownEvents(leftKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
});
});
},
);
group(
'FlipperAnchor',
() {
flameTester.test(
'position is at the left of the left Flipper',
(game) async {
final flipper = Flipper.left(position: Vector2.zero());
await game.ensureAdd(flipper);
final flipperAnchor = FlipperAnchor(flipper: flipper);
await game.ensureAdd(flipperAnchor);
expect(flipperAnchor.body.position.x, equals(-Flipper.width / 2));
},
);
flameTester.test(
'position is at the right of the right Flipper',
(game) async {
final flipper = Flipper.right(position: Vector2.zero());
await game.ensureAdd(flipper);
final flipperAnchor = FlipperAnchor(flipper: flipper);
await game.ensureAdd(flipperAnchor);
expect(flipperAnchor.body.position.x, equals(Flipper.width / 2));
},
);
},
);
group('FlipperAnchorRevoluteJointDef', () {
group('initializes with', () {
flameTester.test(
'limits enabled',
(game) async {
final flipper = Flipper.left(position: Vector2.zero());
await game.ensureAdd(flipper);
final flipperAnchor = FlipperAnchor(flipper: flipper);
await game.ensureAdd(flipperAnchor);
final jointDef = FlipperAnchorRevoluteJointDef(
flipper: flipper,
anchor: flipperAnchor,
);
expect(jointDef.enableLimit, isTrue);
},
);
group('equal upper and lower limits', () {
flameTester.test(
'when Flipper is left',
(game) async {
final flipper = Flipper.left(position: Vector2.zero());
await game.ensureAdd(flipper);
final flipperAnchor = FlipperAnchor(flipper: flipper);
await game.ensureAdd(flipperAnchor);
final jointDef = FlipperAnchorRevoluteJointDef(
flipper: flipper,
anchor: flipperAnchor,
);
expect(jointDef.lowerAngle, equals(jointDef.upperAngle));
},
);
flameTester.test(
'when Flipper is right',
(game) async {
final flipper = Flipper.right(position: Vector2.zero());
await game.ensureAdd(flipper);
final flipperAnchor = FlipperAnchor(flipper: flipper);
await game.ensureAdd(flipperAnchor);
final jointDef = FlipperAnchorRevoluteJointDef(
flipper: flipper,
anchor: flipperAnchor,
);
expect(jointDef.lowerAngle, equals(jointDef.upperAngle));
},
);
});
});
group(
'unlocks',
() {
flameTester.test(
'when Flipper is left',
(game) async {
final flipper = Flipper.left(position: Vector2.zero());
await game.ensureAdd(flipper);
final flipperAnchor = FlipperAnchor(flipper: flipper);
await game.ensureAdd(flipperAnchor);
final jointDef = FlipperAnchorRevoluteJointDef(
flipper: flipper,
anchor: flipperAnchor,
);
final joint = game.world.createJoint(jointDef) as RevoluteJoint;
FlipperAnchorRevoluteJointDef.unlock(joint, flipper.side);
expect(
joint.upperLimit,
isNot(equals(joint.lowerLimit)),
);
},
);
flameTester.test(
'when Flipper is right',
(game) async {
final flipper = Flipper.right(position: Vector2.zero());
await game.ensureAdd(flipper);
final flipperAnchor = FlipperAnchor(flipper: flipper);
await game.ensureAdd(flipperAnchor);
final jointDef = FlipperAnchorRevoluteJointDef(
flipper: flipper,
anchor: flipperAnchor,
);
final joint = game.world.createJoint(jointDef) as RevoluteJoint;
FlipperAnchorRevoluteJointDef.unlock(joint, flipper.side);
expect(
joint.upperLimit,
isNot(equals(joint.lowerLimit)),
);
},
);
},
);
});
}

@ -1,9 +1,54 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
void main() {
group('PinballGame', () {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGame.new);
// TODO(alestiago): test if [PinballGame] registers
// [BallScorePointsCallback] once the following issue is resolved:
// https://github.com/flame-engine/flame/issues/1416
group(
'components',
() {
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 right Flipper',
(game) async {
await game.ready();
expect(
() => game.children.singleWhere(
flipperSelector(BoardSide.right),
),
returnsNormally,
);
},
);
});
},
);
});
}

@ -6,5 +6,6 @@
// https://opensource.org/licenses/MIT.
export 'builders.dart';
export 'key_testers.dart';
export 'mocks.dart';
export 'pump_app.dart';

@ -0,0 +1,37 @@
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
import 'package:mocktail/mocktail.dart';
import 'helpers.dart';
@isTest
void testRawKeyUpEvents(
List<LogicalKeyboardKey> keys,
Function(RawKeyUpEvent) test,
) {
for (final key in keys) {
test(_mockKeyUpEvent(key));
}
}
RawKeyUpEvent _mockKeyUpEvent(LogicalKeyboardKey key) {
final event = MockRawKeyUpEvent();
when(() => event.logicalKey).thenReturn(key);
return event;
}
@isTest
void testRawKeyDownEvents(
List<LogicalKeyboardKey> keys,
Function(RawKeyDownEvent) test,
) {
for (final key in keys) {
test(_mockKeyDownEvent(key));
}
}
RawKeyDownEvent _mockKeyDownEvent(LogicalKeyboardKey key) {
final event = MockRawKeyDownEvent();
when(() => event.logicalKey).thenReturn(key);
return event;
}

@ -1,4 +1,6 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
@ -13,3 +15,17 @@ class MockBall extends Mock implements Ball {}
class MockContact extends Mock implements Contact {}
class MockGameBloc extends Mock implements GameBloc {}
class MockRawKeyDownEvent extends Mock implements RawKeyDownEvent {
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return super.toString();
}
}
class MockRawKeyUpEvent extends Mock implements RawKeyUpEvent {
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return super.toString();
}
}

Loading…
Cancel
Save