diff --git a/lib/game/components/board_side.dart b/lib/game/components/board_side.dart index 0d85eae6..f7587f47 100644 --- a/lib/game/components/board_side.dart +++ b/lib/game/components/board_side.dart @@ -19,4 +19,9 @@ extension BoardSideX on BoardSide { /// Whether this side is [BoardSide.right]. bool get isRight => this == BoardSide.right; + + /// Direction of the [BoardSide]. + /// + /// Represents the path which the [BoardSide] moves along. + int get direction => isLeft ? -1 : 1; } diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 2ba7b8d4..d5c479c7 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -6,6 +6,7 @@ export 'flipper.dart'; export 'joint_anchor.dart'; export 'pathway.dart'; export 'plunger.dart'; +export 'round_bumper.dart'; export 'score_points.dart'; export 'sling_shot.dart'; export 'wall.dart'; diff --git a/lib/game/components/round_bumper.dart b/lib/game/components/round_bumper.dart new file mode 100644 index 00000000..753ed15b --- /dev/null +++ b/lib/game/components/round_bumper.dart @@ -0,0 +1,41 @@ +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball/game/game.dart'; + +/// {@template round_bumper} +/// Circular body that repels a [Ball] on contact, increasing the score. +/// {@endtemplate} +class RoundBumper extends BodyComponent with ScorePoints { + /// {@macro round_bumper} + RoundBumper({ + required Vector2 position, + required double radius, + required int points, + }) : _position = position, + _radius = radius, + _points = points; + + /// The position of the [RoundBumper] body. + final Vector2 _position; + + /// The radius of the [RoundBumper]. + final double _radius; + + /// Points awarded from hitting this [RoundBumper]. + final int _points; + + @override + int get points => _points; + + @override + Body createBody() { + final shape = CircleShape()..radius = _radius; + + final fixtureDef = FixtureDef(shape)..restitution = 1; + + final bodyDef = BodyDef() + ..position = _position + ..type = BodyType.static; + + return world.createBody(bodyDef)..createFixture(fixtureDef); + } +} diff --git a/test/game/components/board_side_test.dart b/test/game/components/board_side_test.dart index 3d6d3fa1..ba201065 100644 --- a/test/game/components/board_side_test.dart +++ b/test/game/components/board_side_test.dart @@ -23,5 +23,12 @@ void main() { expect(side.isLeft, isFalse); expect(side.isRight, isTrue); }); + + test('direction is correct', () { + const side = BoardSide.left; + expect(side.direction, equals(-1)); + const side2 = BoardSide.right; + expect(side2.direction, equals(1)); + }); }); } diff --git a/test/game/components/round_bumper_test.dart b/test/game/components/round_bumper_test.dart new file mode 100644 index 00000000..c780dd0b --- /dev/null +++ b/test/game/components/round_bumper_test.dart @@ -0,0 +1,124 @@ +// 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'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('RoundBumper', () { + final flameTester = FlameTester(Forge2DGame.new); + const radius = 1.0; + const points = 1; + + flameTester.test( + 'loads correctly', + (game) async { + await game.ready(); + final roundBumper = RoundBumper( + position: Vector2.zero(), + radius: radius, + points: points, + ); + await game.ensureAdd(roundBumper); + + expect(game.contains(roundBumper), isTrue); + }, + ); + + flameTester.test( + 'has points', + (game) async { + final roundBumper = RoundBumper( + position: Vector2.zero(), + radius: radius, + points: points, + ); + await game.ensureAdd(roundBumper); + + expect(roundBumper.points, equals(points)); + }, + ); + + group('body', () { + flameTester.test( + 'positions correctly', + (game) async { + final position = Vector2.all(10); + final roundBumper = RoundBumper( + position: position, + radius: radius, + points: points, + ); + await game.ensureAdd(roundBumper); + game.contains(roundBumper); + + expect(roundBumper.body.position, equals(position)); + }, + ); + + flameTester.test( + 'is static', + (game) async { + final roundBumper = RoundBumper( + position: Vector2.zero(), + radius: radius, + points: points, + ); + await game.ensureAdd(roundBumper); + + expect(roundBumper.body.bodyType, equals(BodyType.static)); + }, + ); + }); + + group('fixture', () { + flameTester.test( + 'exists', + (game) async { + final roundBumper = RoundBumper( + position: Vector2.zero(), + radius: radius, + points: points, + ); + await game.ensureAdd(roundBumper); + + expect(roundBumper.body.fixtures[0], isA()); + }, + ); + + flameTester.test( + 'has restitution', + (game) async { + final roundBumper = RoundBumper( + position: Vector2.zero(), + radius: radius, + points: points, + ); + await game.ensureAdd(roundBumper); + + final fixture = roundBumper.body.fixtures[0]; + expect(fixture.restitution, greaterThan(0)); + }, + ); + + flameTester.test( + 'shape is circular', + (game) async { + final roundBumper = RoundBumper( + position: Vector2.zero(), + radius: radius, + points: points, + ); + await game.ensureAdd(roundBumper); + + final fixture = roundBumper.body.fixtures[0]; + expect(fixture.shape.shapeType, equals(ShapeType.circle)); + expect(fixture.shape.radius, equals(1)); + }, + ); + }); + }); +}