fix: fixed merge conflicts and use model from bloc

pull/51/head
RuiAlonso 4 years ago
commit 0bf0ad2968

@ -0,0 +1,19 @@
name: pinball_components
on:
push:
paths:
- "packages/pinball_components/**"
- ".github/workflows/pinball_components.yaml"
pull_request:
paths:
- "packages/pinball_components/**"
- ".github/workflows/pinball_components.yaml"
jobs:
build:
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
with:
working_directory: packages/pinball_components
coverage_excludes: "lib/src/generated/*.dart"

@ -12,6 +12,7 @@ class GameBloc extends Bloc<GameEvent, GameState> {
on<BallLost>(_onBallLost);
on<Scored>(_onScored);
on<BonusLetterActivated>(_onBonusLetterActivated);
on<DashNestActivated>(_onDashNestActivated);
}
static const bonusWord = 'GOOGLE';
@ -52,4 +53,28 @@ class GameBloc extends Bloc<GameEvent, GameState> {
);
}
}
void _onDashNestActivated(DashNestActivated event, Emitter emit) {
const nestsRequiredForBonus = 3;
final newNests = {
...state.activatedDashNests,
event.nestId,
};
if (newNests.length == nestsRequiredForBonus) {
emit(
state.copyWith(
activatedDashNests: {},
bonusHistory: [
...state.bonusHistory,
GameBonus.dashNest,
],
),
);
} else {
emit(
state.copyWith(activatedDashNests: newNests),
);
}
}
}

@ -45,3 +45,12 @@ class BonusLetterActivated extends GameEvent {
@override
List<Object?> get props => [letterIndex];
}
class DashNestActivated extends GameEvent {
const DashNestActivated(this.nestId);
final String nestId;
@override
List<Object?> get props => [nestId];
}

@ -7,6 +7,10 @@ enum GameBonus {
/// Bonus achieved when the user activate all of the bonus
/// letters on the board, forming the bonus word
word,
/// Bonus achieved when the user activates all of the Dash
/// nests on the board, adding a new ball to the board.
dashNest,
}
/// {@template game_state}
@ -19,6 +23,7 @@ class GameState extends Equatable {
required this.balls,
required this.activatedBonusLetters,
required this.bonusHistory,
required this.activatedDashNests,
}) : assert(score >= 0, "Score can't be negative"),
assert(balls >= 0, "Number of balls can't be negative");
@ -26,6 +31,7 @@ class GameState extends Equatable {
: score = 0,
balls = 3,
activatedBonusLetters = const [],
activatedDashNests = const {},
bonusHistory = const [];
/// The current score of the game.
@ -39,6 +45,9 @@ class GameState extends Equatable {
/// Active bonus letters.
final List<int> activatedBonusLetters;
/// Active dash nests.
final Set<String> activatedDashNests;
/// Holds the history of all the [GameBonus]es earned by the player during a
/// PinballGame.
final List<GameBonus> bonusHistory;
@ -57,6 +66,7 @@ class GameState extends Equatable {
int? score,
int? balls,
List<int>? activatedBonusLetters,
Set<String>? activatedDashNests,
List<GameBonus>? bonusHistory,
}) {
assert(
@ -69,6 +79,7 @@ class GameState extends Equatable {
balls: balls ?? this.balls,
activatedBonusLetters:
activatedBonusLetters ?? this.activatedBonusLetters,
activatedDashNests: activatedDashNests ?? this.activatedDashNests,
bonusHistory: bonusHistory ?? this.bonusHistory,
);
}
@ -78,6 +89,7 @@ class GameState extends Equatable {
score,
balls,
activatedBonusLetters,
activatedDashNests,
bonusHistory,
];
}

@ -127,7 +127,7 @@ class _BottomGroupSide extends Component {
Future<void> onLoad() async {
final direction = _side.direction;
final flipper = Flipper.fromSide(
final flipper = Flipper(
side: _side,
)..initialPosition = _position;
final baseboard = Baseboard(side: _side)

@ -3,11 +3,20 @@ import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart';
const _leftFlipperKeys = [
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.keyA,
];
const _rightFlipperKeys = [
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.keyD,
];
/// {@template flipper}
/// A bat, typically found in pairs at the bottom of the board.
///
@ -15,43 +24,9 @@ import 'package:pinball/gen/assets.gen.dart';
/// {@endtemplate flipper}
class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
/// {@macro flipper}
Flipper._({
Flipper({
required this.side,
required List<LogicalKeyboardKey> keys,
}) : _keys = keys;
Flipper._left()
: this._(
side: BoardSide.left,
keys: [
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.keyA,
],
);
Flipper._right()
: this._(
side: BoardSide.right,
keys: [
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.keyD,
],
);
/// Constructs a [Flipper] from a [BoardSide].
///
/// A [Flipper._right] and [Flipper._left] besides being mirrored
/// horizontally, also have different [LogicalKeyboardKey]s that control them.
factory Flipper.fromSide({
required BoardSide side,
}) {
switch (side) {
case BoardSide.left:
return Flipper._left();
case BoardSide.right:
return Flipper._right();
}
}
}) : _keys = side.isLeft ? _leftFlipperKeys : _rightFlipperKeys;
/// The size of the [Flipper].
static final size = Vector2(12, 2.8);
@ -104,35 +79,29 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
/// Anchors the [Flipper] to the [RevoluteJoint] that controls its arc motion.
Future<void> _anchorToJoint() async {
final anchor = FlipperAnchor(flipper: this);
final anchor = _FlipperAnchor(flipper: this);
await add(anchor);
final jointDef = FlipperAnchorRevoluteJointDef(
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;
final joint = _FlipperJoint(jointDef)..create(world);
// FIXME(erickzanardo): when mounted the initial position is not fully
// reached.
unawaited(
mounted.whenComplete(
() => FlipperAnchorRevoluteJointDef.unlock(joint, side),
),
mounted.whenComplete(joint.unlock),
);
}
List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
final isLeft = side.isLeft;
final direction = side.direction;
final bigCircleShape = CircleShape()..radius = 1.75;
bigCircleShape.position.setValues(
isLeft
? -(size.x / 2) + bigCircleShape.radius
: (size.x / 2) - bigCircleShape.radius,
((size.x / 2) * direction) + (bigCircleShape.radius * -direction),
0,
);
final bigCircleFixtureDef = FixtureDef(bigCircleShape);
@ -140,15 +109,13 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
final smallCircleShape = CircleShape()..radius = 0.9;
smallCircleShape.position.setValues(
isLeft
? (size.x / 2) - smallCircleShape.radius
: -(size.x / 2) + smallCircleShape.radius,
((size.x / 2) * -direction) + (smallCircleShape.radius * direction),
0,
);
final smallCircleFixtureDef = FixtureDef(smallCircleShape);
fixturesDef.add(smallCircleFixtureDef);
final trapeziumVertices = isLeft
final trapeziumVertices = side.isLeft
? [
Vector2(bigCircleShape.position.x, bigCircleShape.radius),
Vector2(smallCircleShape.position.x, smallCircleShape.radius),
@ -173,7 +140,8 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
@override
Future<void> onLoad() async {
await super.onLoad();
paint = Paint()..color = Colors.transparent;
renderBody = false;
await Future.wait([
_loadSprite(),
_anchorToJoint(),
@ -214,61 +182,66 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
///
/// The end of a [Flipper] depends on its [Flipper.side].
/// {@endtemplate}
class FlipperAnchor extends JointAnchor {
class _FlipperAnchor extends JointAnchor {
/// {@macro flipper_anchor}
FlipperAnchor({
_FlipperAnchor({
required Flipper flipper,
}) {
initialPosition = Vector2(
flipper.side.isLeft
? flipper.body.position.x - Flipper.size.x / 2
: flipper.body.position.x + Flipper.size.x / 2,
flipper.body.position.x + ((Flipper.size.x * flipper.side.direction) / 2),
flipper.body.position.y,
);
}
}
/// {@template flipper_anchor_revolute_joint_def}
/// Hinges one end of [Flipper] to a [FlipperAnchor] to achieve an arc motion.
/// Hinges one end of [Flipper] to a [_FlipperAnchor] to achieve an arc motion.
/// {@endtemplate}
class FlipperAnchorRevoluteJointDef extends RevoluteJointDef {
class _FlipperAnchorRevoluteJointDef extends RevoluteJointDef {
/// {@macro flipper_anchor_revolute_joint_def}
FlipperAnchorRevoluteJointDef({
_FlipperAnchorRevoluteJointDef({
required Flipper flipper,
required FlipperAnchor anchor,
}) {
required _FlipperAnchor anchor,
}) : side = flipper.side {
initialize(
flipper.body,
anchor.body,
anchor.body.position,
);
enableLimit = true;
final angle = (flipper.side.isLeft ? _sweepingAngle : -_sweepingAngle) / 2;
enableLimit = true;
final angle = (_sweepingAngle * -side.direction) / 2;
lowerAngle = upperAngle = angle;
}
/// The total angle of the arc motion.
static const _sweepingAngle = math.pi / 3.5;
final BoardSide side;
}
class _FlipperJoint extends RevoluteJoint {
_FlipperJoint(_FlipperAnchorRevoluteJointDef def)
: side = def.side,
super(def);
final BoardSide side;
// TODO(alestiago): Remove once Forge2D supports custom joints.
void create(World world) {
world.joints.add(this);
bodyA.joints.add(this);
bodyB.joints.add(this);
}
/// 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);
void unlock() {
setLimits(
lowerLimit * side.direction,
-upperLimit * side.direction,
);
}
}

@ -144,9 +144,50 @@ class Pathway extends BodyComponent with InitialPosition, Layered {
);
}
/// Creates an ellipse [Pathway].
///
/// Does so with two [ChainShape]s separated by a [width]. Can
/// be rotated by a given [rotation] in radians.
///
/// If [singleWall] is true, just one [ChainShape] is created.
factory Pathway.ellipse({
Color? color,
required Vector2 center,
required double width,
required double majorRadius,
required double minorRadius,
double rotation = 0,
bool singleWall = false,
}) {
final paths = <List<Vector2>>[];
// TODO(ruialonso): Refactor repetitive logic
final outerWall = calculateEllipse(
center: center,
majorRadius: majorRadius,
minorRadius: minorRadius,
).map((vector) => vector..rotate(rotation)).toList();
paths.add(outerWall);
if (!singleWall) {
final innerWall = calculateEllipse(
center: center,
majorRadius: majorRadius - width,
minorRadius: minorRadius - width,
).map((vector) => vector..rotate(rotation)).toList();
paths.add(innerWall);
}
return Pathway._(
color: color,
paths: paths,
);
}
final List<List<Vector2>> _paths;
List<FixtureDef> _createFixtureDefs() {
/// Constructs different [ChainShape]s to form the [Pathway] shape.
List<FixtureDef> createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
for (final path in _paths) {
@ -161,7 +202,7 @@ class Pathway extends BodyComponent with InitialPosition, Layered {
Body createBody() {
final bodyDef = BodyDef()..position = initialPosition;
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
createFixtureDefs().forEach(body.createFixture);
return body;
}

@ -9,6 +9,12 @@ import 'package:pinball/game/game.dart';
mixin ScorePoints on BodyComponent {
/// {@macro score_points}
int get points;
@override
Future<void> onLoad() async {
await super.onLoad();
body.userData = this;
}
}
/// Adds points to the score when a [Ball] collides with a [BodyComponent] that

@ -1,5 +1,4 @@
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'package:flame/extensions.dart';
import 'package:flame/input.dart';

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
part 'leaderboard_event.dart';
part 'leaderboard_state.dart';
@ -30,10 +31,16 @@ class LeaderboardBloc extends Bloc<LeaderboardEvent, LeaderboardState> {
try {
final top10Leaderboard =
await _leaderboardRepository.fetchTop10Leaderboard();
final leaderboardEntries = <LeaderboardEntry>[];
top10Leaderboard.asMap().forEach(
(index, value) => leaderboardEntries.add(value.toEntry(index + 1)),
);
emit(
state.copyWith(
status: LeaderboardStatus.success,
leaderboard: top10Leaderboard,
leaderboard: leaderboardEntries,
),
);
} catch (error) {

@ -9,7 +9,7 @@ abstract class LeaderboardEvent extends Equatable {
}
/// {@template top_10_fetched}
/// Request the top 10 [LeaderboardEntry]s.
/// Request the top 10 [LeaderboardEntryData]s.
/// {endtemplate}
class Top10Fetched extends LeaderboardEvent {
/// {@macro top_10_fetched}
@ -20,7 +20,7 @@ class Top10Fetched extends LeaderboardEvent {
}
/// {@template leaderboard_entry_added}
/// Writes a new [LeaderboardEntry].
/// Writes a new [LeaderboardEntryData].
///
/// Should be added when a player finishes a game.
/// {endtemplate}
@ -28,8 +28,8 @@ class LeaderboardEntryAdded extends LeaderboardEvent {
/// {@macro leaderboard_entry_added}
const LeaderboardEntryAdded({required this.entry});
/// [LeaderboardEntry] to be written to the remote storage.
final LeaderboardEntry entry;
/// [LeaderboardEntryData] to be written to the remote storage.
final LeaderboardEntryData entry;
@override
List<Object?> get props => [entry];

@ -1,2 +1,3 @@
export 'bloc/leaderboard_bloc.dart';
export 'models/leader_board_entry.dart';
export 'view/leaderboard_page.dart';

@ -0,0 +1,80 @@
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// {@template leaderboard_entry}
/// A model representing a leaderboard entry containing the ranking position,
/// player's initials, score, and chosen character.
///
/// {@endtemplate}
class LeaderboardEntry {
/// {@macro leaderboard_entry}
LeaderboardEntry({
required this.rank,
required this.playerInitials,
required this.score,
required this.character,
});
/// Ranking position for [LeaderboardEntry].
final String rank;
/// Player's chosen initials for [LeaderboardEntry].
final String playerInitials;
/// Score for [LeaderboardEntry].
final int score;
/// [CharacterTheme] for [LeaderboardEntry].
final AssetGenImage character;
}
/// Converts [LeaderboardEntryData] from repository to [LeaderboardEntry].
extension LeaderboardEntryDataX on LeaderboardEntryData {
/// Conversion method to [LeaderboardEntry]
LeaderboardEntry toEntry(int position) {
return LeaderboardEntry(
rank: position.toString(),
playerInitials: playerInitials,
score: score,
character: character.toTheme.characterAsset,
);
}
}
/// Converts [CharacterType] to [CharacterTheme] to show on UI character theme
/// from repository.
extension CharacterTypeX on CharacterType {
/// Conversion method to [CharacterTheme]
CharacterTheme get toTheme {
switch (this) {
case CharacterType.dash:
return const DashTheme();
case CharacterType.sparky:
return const SparkyTheme();
case CharacterType.android:
return const AndroidTheme();
case CharacterType.dino:
return const DinoTheme();
}
}
}
/// Converts [CharacterTheme] to [CharacterType] to persist at repository the
/// character theme from UI.
extension CharacterThemeX on CharacterTheme {
/// Conversion method to [CharacterType]
CharacterType get toType {
switch (runtimeType) {
case DashTheme:
return CharacterType.dash;
case SparkyTheme:
return CharacterType.sparky;
case AndroidTheme:
return CharacterType.android;
case DinoTheme:
return CharacterType.dino;
default:
return CharacterType.dash;
}
}
}

@ -202,14 +202,7 @@ class _LeaderboardList extends StatelessWidget {
return ListView.builder(
shrinkWrap: true,
itemBuilder: (_, index) => _LeaderBoardCompetitor(
competitor: Competitor(
rank: (index + 1).toString(),
entry: LeaderboardEntry(
character: CharacterType.android,
playerInitials: 'user$index',
score: 0,
),
),
entry: ranking[index],
theme: theme,
),
itemCount: ranking.length,
@ -220,13 +213,13 @@ class _LeaderboardList extends StatelessWidget {
class _LeaderBoardCompetitor extends StatelessWidget {
const _LeaderBoardCompetitor({
Key? key,
required this.competitor,
required this.entry,
required this.theme,
}) : super(key: key);
final CharacterTheme theme;
final Competitor competitor;
final LeaderboardEntry entry;
@override
Widget build(BuildContext context) {
@ -234,19 +227,19 @@ class _LeaderBoardCompetitor extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
_LeaderboardCompetitorField(
text: competitor.rank,
text: entry.rank,
theme: theme,
),
_LeaderboardCompetitorCharacter(
characterTheme: competitor.entry.character.theme,
characterAsset: entry.character,
theme: theme,
),
_LeaderboardCompetitorField(
text: competitor.entry.playerInitials,
text: entry.playerInitials,
theme: theme,
),
_LeaderboardCompetitorField(
text: competitor.entry.score.toString(),
text: entry.score.toString(),
theme: theme,
),
],
@ -286,12 +279,12 @@ class _LeaderboardCompetitorField extends StatelessWidget {
class _LeaderboardCompetitorCharacter extends StatelessWidget {
const _LeaderboardCompetitorCharacter({
Key? key,
required this.characterTheme,
required this.characterAsset,
required this.theme,
}) : super(key: key);
final CharacterTheme theme;
final CharacterTheme characterTheme;
final AssetGenImage characterAsset;
@override
Widget build(BuildContext context) {
@ -305,49 +298,9 @@ class _LeaderboardCompetitorCharacter extends StatelessWidget {
),
child: SizedBox(
height: 30,
child: characterTheme.characterAsset.image(),
child: characterAsset.image(),
),
),
);
}
}
// TODO(ruimiguel): move below model and extensions to LeaderboardState
class Competitor {
Competitor({required this.rank, required this.entry});
final String rank;
final LeaderboardEntry entry;
}
extension CharacterTypeX on CharacterType {
CharacterTheme get theme {
switch (this) {
case CharacterType.dash:
return const DashTheme();
case CharacterType.sparky:
return const SparkyTheme();
case CharacterType.android:
return const AndroidTheme();
case CharacterType.dino:
return const DinoTheme();
}
}
}
extension CharacterThemeX on CharacterTheme {
CharacterType get toType {
switch (runtimeType) {
case DashTheme:
return CharacterType.dash;
case SparkyTheme:
return CharacterType.sparky;
case AndroidTheme:
return CharacterType.android;
case DinoTheme:
return CharacterType.dino;
default:
return CharacterType.dash;
}
}
}

@ -23,10 +23,45 @@ List<Vector2> calculateArc({
final points = <Vector2>[];
for (var i = 0; i < precision; i++) {
final xCoord = center.x + radius * math.cos((stepAngle * i) + offsetAngle);
final yCoord = center.y - radius * math.sin((stepAngle * i) + offsetAngle);
final x = center.x + radius * math.cos((stepAngle * i) + offsetAngle);
final y = center.y - radius * math.sin((stepAngle * i) + offsetAngle);
final point = Vector2(xCoord, yCoord);
final point = Vector2(x, y);
points.add(point);
}
return points;
}
/// Calculates all [Vector2]s of an ellipse.
///
/// An ellipse can be achieved by specifying a [center], a [majorRadius] and a
/// [minorRadius].
///
/// The higher the [precision], the more [Vector2]s will be calculated;
/// achieving a more rounded ellipse.
///
/// For more information read: https://en.wikipedia.org/wiki/Ellipse.
List<Vector2> calculateEllipse({
required Vector2 center,
required double majorRadius,
required double minorRadius,
int precision = 100,
}) {
assert(
0 < minorRadius && minorRadius <= majorRadius,
'smallRadius ($minorRadius) and bigRadius ($majorRadius) must be in '
'range 0 < smallRadius <= bigRadius',
);
final stepAngle = 2 * math.pi / (precision - 1);
final points = <Vector2>[];
for (var i = 0; i < precision; i++) {
final x = center.x + minorRadius * math.cos(stepAngle * i);
final y = center.y - majorRadius * math.sin(stepAngle * i);
final point = Vector2(x, y);
points.add(point);
}
@ -63,17 +98,15 @@ List<Vector2> calculateBezierCurve({
final points = <Vector2>[];
do {
var xCoord = 0.0;
var yCoord = 0.0;
var x = 0.0;
var y = 0.0;
for (var i = 0; i <= n; i++) {
final point = controlPoints[i];
xCoord +=
binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.x;
yCoord +=
binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.y;
x += binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.x;
y += binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.y;
}
points.add(Vector2(xCoord, yCoord));
points.add(Vector2(x, y));
t = t + step;
} while (t <= 1);

@ -33,6 +33,46 @@ void main() {
});
});
group('calculateEllipse', () {
test('returns by default 100 points as indicated by precision', () {
final points = calculateEllipse(
center: Vector2.zero(),
majorRadius: 100,
minorRadius: 50,
);
expect(points.length, 100);
});
test('returns as many points as indicated by precision', () {
final points = calculateEllipse(
center: Vector2.zero(),
majorRadius: 100,
minorRadius: 50,
precision: 50,
);
expect(points.length, 50);
});
test('fails if radius not in range', () {
expect(
() => calculateEllipse(
center: Vector2.zero(),
majorRadius: 100,
minorRadius: 150,
),
throwsA(isA<AssertionError>()),
);
expect(
() => calculateEllipse(
center: Vector2.zero(),
majorRadius: 100,
minorRadius: 0,
),
throwsA(isA<AssertionError>()),
);
});
});
group('calculateBezierCurve', () {
test('fails if step not in range', () {
expect(

@ -83,9 +83,9 @@ class LeaderboardRepository {
final FirebaseFirestore _firebaseFirestore;
/// Acquires top 10 [LeaderboardEntry]s.
Future<List<LeaderboardEntry>> fetchTop10Leaderboard() async {
final leaderboardEntries = <LeaderboardEntry>[];
/// Acquires top 10 [LeaderboardEntryData]s.
Future<List<LeaderboardEntryData>> fetchTop10Leaderboard() async {
final leaderboardEntries = <LeaderboardEntryData>[];
late List<QueryDocumentSnapshot> documents;
try {
@ -103,7 +103,7 @@ class LeaderboardRepository {
final data = document.data() as Map<String, dynamic>?;
if (data != null) {
try {
leaderboardEntries.add(LeaderboardEntry.fromJson(data));
leaderboardEntries.add(LeaderboardEntryData.fromJson(data));
} catch (error, stackTrace) {
throw LeaderboardDeserializationException(error, stackTrace);
}
@ -115,7 +115,9 @@ class LeaderboardRepository {
/// Adds player's score entry to the leaderboard and gets their
/// [LeaderboardRanking].
Future<LeaderboardRanking> addLeaderboardEntry(LeaderboardEntry entry) async {
Future<LeaderboardRanking> addLeaderboardEntry(
LeaderboardEntryData entry,
) async {
late DocumentReference entryReference;
try {
entryReference = await _firebaseFirestore

@ -1,9 +1,9 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'leaderboard_entry.g.dart';
part 'leaderboard_entry_data.g.dart';
/// Google character type associated with a [LeaderboardEntry].
/// Google character type associated with a [LeaderboardEntryData].
enum CharacterType {
/// Dash character.
dash,
@ -18,7 +18,7 @@ enum CharacterType {
dino,
}
/// {@template leaderboard_entry}
/// {@template leaderboard_entry_data}
/// A model representing a leaderboard entry containing the player's initials,
/// score, and chosen character.
///
@ -34,42 +34,42 @@ enum CharacterType {
/// ```
/// {@endtemplate}
@JsonSerializable()
class LeaderboardEntry extends Equatable {
/// {@macro leaderboard_entry}
const LeaderboardEntry({
class LeaderboardEntryData extends Equatable {
/// {@macro leaderboard_entry_data}
const LeaderboardEntryData({
required this.playerInitials,
required this.score,
required this.character,
});
/// Factory which converts a [Map] into a [LeaderboardEntry].
factory LeaderboardEntry.fromJson(Map<String, dynamic> json) {
/// Factory which converts a [Map] into a [LeaderboardEntryData].
factory LeaderboardEntryData.fromJson(Map<String, dynamic> json) {
return _$LeaderboardEntryFromJson(json);
}
/// Converts the [LeaderboardEntry] to [Map].
/// Converts the [LeaderboardEntryData] to [Map].
Map<String, dynamic> toJson() => _$LeaderboardEntryToJson(this);
/// Player's chosen initials for [LeaderboardEntry].
/// Player's chosen initials for [LeaderboardEntryData].
///
/// Example: 'ABC'.
@JsonKey(name: 'playerInitials')
final String playerInitials;
/// Score for [LeaderboardEntry].
/// Score for [LeaderboardEntryData].
///
/// Example: 1500.
@JsonKey(name: 'score')
final int score;
/// [CharacterType] for [LeaderboardEntry].
/// [CharacterType] for [LeaderboardEntryData].
///
/// Example: [CharacterType.dash].
@JsonKey(name: 'character')
final CharacterType character;
/// An empty [LeaderboardEntry] object.
static const empty = LeaderboardEntry(
/// An empty [LeaderboardEntryData] object.
static const empty = LeaderboardEntryData(
playerInitials: '',
score: 0,
character: CharacterType.dash,

@ -1,19 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'leaderboard_entry.dart';
part of 'leaderboard_entry_data.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LeaderboardEntry _$LeaderboardEntryFromJson(Map<String, dynamic> json) =>
LeaderboardEntry(
LeaderboardEntryData _$LeaderboardEntryFromJson(Map<String, dynamic> json) =>
LeaderboardEntryData(
playerInitials: json['playerInitials'] as String,
score: json['score'] as int,
character: $enumDecode(_$CharacterTypeEnumMap, json['character']),
);
Map<String, dynamic> _$LeaderboardEntryToJson(LeaderboardEntry instance) =>
Map<String, dynamic> _$LeaderboardEntryToJson(LeaderboardEntryData instance) =>
<String, dynamic>{
'playerInitials': instance.playerInitials,
'score': instance.score,

@ -2,17 +2,17 @@ import 'package:equatable/equatable.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
/// {@template leaderboard_ranking}
/// Contains [ranking] for a single [LeaderboardEntry] and the number of players
/// the [ranking] is [outOf].
/// Contains [ranking] for a single [LeaderboardEntryData] and the number of
/// players the [ranking] is [outOf].
/// {@endtemplate}
class LeaderboardRanking extends Equatable {
/// {@macro leaderboard_ranking}
const LeaderboardRanking({required this.ranking, required this.outOf});
/// Place ranking by score for a [LeaderboardEntry].
/// Place ranking by score for a [LeaderboardEntryData].
final int ranking;
/// Number of [LeaderboardEntry]s at the time of score entry.
/// Number of [LeaderboardEntryData]s at the time of score entry.
final int outOf;
@override

@ -1,2 +1,2 @@
export 'leaderboard_entry.dart';
export 'leaderboard_entry_data.dart';
export 'leaderboard_ranking.dart';

@ -57,7 +57,7 @@ void main() {
final top10Leaderboard = top10Scores
.map(
(score) => LeaderboardEntry(
(score) => LeaderboardEntryData(
playerInitials: 'user$score',
score: score,
character: CharacterType.dash,
@ -144,7 +144,7 @@ void main() {
entryScore,
1000,
];
final leaderboardEntry = LeaderboardEntry(
final leaderboardEntry = LeaderboardEntryData(
playerInitials: 'ABC',
score: entryScore,
character: CharacterType.dash,

@ -9,21 +9,21 @@ void main() {
'character': 'dash',
};
const leaderboardEntry = LeaderboardEntry(
const leaderboardEntry = LeaderboardEntryData(
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,
);
test('can be instantiated', () {
const leaderboardEntry = LeaderboardEntry.empty;
const leaderboardEntry = LeaderboardEntryData.empty;
expect(leaderboardEntry, isNotNull);
});
test('supports value equality.', () {
const leaderboardEntry = LeaderboardEntry.empty;
const leaderboardEntry2 = LeaderboardEntry.empty;
const leaderboardEntry = LeaderboardEntryData.empty;
const leaderboardEntry2 = LeaderboardEntryData.empty;
expect(leaderboardEntry, equals(leaderboardEntry2));
});
@ -33,7 +33,7 @@ void main() {
});
test('can be obtained from json', () {
final leaderboardEntryFrom = LeaderboardEntry.fromJson(data);
final leaderboardEntryFrom = LeaderboardEntryData.fromJson(data);
expect(leaderboardEntry, equals(leaderboardEntryFrom));
});

@ -0,0 +1,39 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# VSCode related
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json

@ -0,0 +1,11 @@
# pinball_components
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![License: MIT][license_badge]][license_link]
Package with the UI game components for the Pinball Game
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis

@ -0,0 +1 @@
include: package:very_good_analysis/analysis_options.2.4.0.yaml

@ -0,0 +1,3 @@
library pinball_components;
export 'src/pinball_components.dart';

@ -0,0 +1,7 @@
/// {@template pinball_components}
/// Package with the UI game components for the Pinball Game
/// {@endtemplate}
class PinballComponents {
/// {@macro pinball_components}
const PinballComponents();
}

@ -0,0 +1,16 @@
name: pinball_components
description: Package with the UI game components for the Pinball Game
version: 1.0.0+1
publish_to: none
environment:
sdk: ">=2.16.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
very_good_analysis: ^2.4.0

@ -0,0 +1,11 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
void main() {
group('PinballComponents', () {
test('can be instantiated', () {
expect(PinballComponents(), isNotNull);
});
});
}

@ -25,18 +25,21 @@ void main() {
score: 0,
balls: 2,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
const GameState(
score: 0,
balls: 1,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
const GameState(
score: 0,
balls: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
],
@ -56,12 +59,14 @@ void main() {
score: 2,
balls: 3,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
const GameState(
score: 5,
balls: 3,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
],
@ -82,18 +87,21 @@ void main() {
score: 0,
balls: 2,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
const GameState(
score: 0,
balls: 1,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
const GameState(
score: 0,
balls: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
],
@ -113,18 +121,21 @@ void main() {
score: 0,
balls: 3,
activatedBonusLetters: [0],
activatedDashNests: {},
bonusHistory: [],
),
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [0, 1],
activatedDashNests: {},
bonusHistory: [],
),
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [0, 1, 2],
activatedDashNests: {},
bonusHistory: [],
),
],
@ -145,46 +156,87 @@ void main() {
score: 0,
balls: 3,
activatedBonusLetters: [0],
activatedDashNests: {},
bonusHistory: [],
),
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [0, 1],
activatedDashNests: {},
bonusHistory: [],
),
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [0, 1, 2],
activatedDashNests: {},
bonusHistory: [],
),
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [0, 1, 2, 3],
activatedDashNests: {},
bonusHistory: [],
),
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [0, 1, 2, 3, 4],
activatedDashNests: {},
bonusHistory: [],
),
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [GameBonus.word],
),
GameState(
score: GameBloc.bonusWordScore,
balls: 3,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [GameBonus.word],
),
],
);
});
group('DashNestActivated', () {
blocTest<GameBloc, GameState>(
'adds the bonus when all nests are activated',
build: GameBloc.new,
act: (bloc) => bloc
..add(const DashNestActivated('0'))
..add(const DashNestActivated('1'))
..add(const DashNestActivated('2')),
expect: () => const [
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [],
activatedDashNests: {'0'},
bonusHistory: [],
),
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [],
activatedDashNests: {'0', '1'},
bonusHistory: [],
),
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [GameBonus.dashNest],
),
],
);
});
});
}

@ -67,5 +67,22 @@ void main() {
},
);
});
group('DashNestActivated', () {
test('can be instantiated', () {
expect(const DashNestActivated('0'), isNotNull);
});
test('supports value equality', () {
expect(
DashNestActivated('0'),
equals(DashNestActivated('0')),
);
expect(
DashNestActivated('0'),
isNot(equals(DashNestActivated('1'))),
);
});
});
});
}

@ -11,6 +11,7 @@ void main() {
score: 0,
balls: 0,
activatedBonusLetters: const [],
activatedDashNests: const {},
bonusHistory: const [],
),
equals(
@ -18,6 +19,7 @@ void main() {
score: 0,
balls: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
),
@ -31,6 +33,7 @@ void main() {
score: 0,
balls: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
isNotNull,
@ -47,6 +50,7 @@ void main() {
balls: -1,
score: 0,
activatedBonusLetters: const [],
activatedDashNests: const {},
bonusHistory: const [],
),
throwsAssertionError,
@ -63,6 +67,7 @@ void main() {
balls: 0,
score: -1,
activatedBonusLetters: const [],
activatedDashNests: const {},
bonusHistory: const [],
),
throwsAssertionError,
@ -78,6 +83,7 @@ void main() {
balls: 0,
score: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
);
expect(gameState.isGameOver, isTrue);
@ -90,6 +96,7 @@ void main() {
balls: 1,
score: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
);
expect(gameState.isGameOver, isFalse);
@ -105,6 +112,7 @@ void main() {
balls: 1,
score: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
);
expect(gameState.isLastBall, isTrue);
@ -119,6 +127,7 @@ void main() {
balls: 2,
score: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
);
expect(gameState.isLastBall, isFalse);
@ -134,6 +143,7 @@ void main() {
balls: 3,
score: 0,
activatedBonusLetters: [1],
activatedDashNests: {},
bonusHistory: [],
);
expect(gameState.isLetterActivated(1), isTrue);
@ -147,6 +157,7 @@ void main() {
balls: 3,
score: 0,
activatedBonusLetters: [1],
activatedDashNests: {},
bonusHistory: [],
);
expect(gameState.isLetterActivated(0), isFalse);
@ -163,6 +174,7 @@ void main() {
balls: 0,
score: 2,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
);
expect(
@ -180,6 +192,7 @@ void main() {
balls: 0,
score: 2,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
);
expect(
@ -197,12 +210,14 @@ void main() {
score: 2,
balls: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
);
final otherGameState = GameState(
score: gameState.score + 1,
balls: gameState.balls + 1,
activatedBonusLetters: const [0],
activatedDashNests: const {'1'},
bonusHistory: const [GameBonus.word],
);
expect(gameState, isNot(equals(otherGameState)));
@ -212,6 +227,7 @@ void main() {
score: otherGameState.score,
balls: otherGameState.balls,
activatedBonusLetters: otherGameState.activatedBonusLetters,
activatedDashNests: otherGameState.activatedDashNests,
bonusHistory: otherGameState.bonusHistory,
),
equals(otherGameState),

@ -156,6 +156,7 @@ void main() {
score: 10,
balls: 1,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
),
);

@ -30,8 +30,8 @@ void main() {
await game.ready();
await game.ensureAdd(board);
final leftFlippers = board.findNestedChildren<Flipper>(
condition: (flipper) => flipper.side.isLeft,
final leftFlippers = board.descendants().whereType<Flipper>().where(
(flipper) => flipper.side.isLeft,
);
expect(leftFlippers.length, equals(1));
},
@ -43,9 +43,8 @@ void main() {
final board = Board();
await game.ready();
await game.ensureAdd(board);
final rightFlippers = board.findNestedChildren<Flipper>(
condition: (flipper) => flipper.side.isRight,
final rightFlippers = board.descendants().whereType<Flipper>().where(
(flipper) => flipper.side.isRight,
);
expect(rightFlippers.length, equals(1));
},
@ -58,7 +57,7 @@ void main() {
await game.ready();
await game.ensureAdd(board);
final baseboards = board.findNestedChildren<Baseboard>();
final baseboards = board.descendants().whereType<Baseboard>();
expect(baseboards.length, equals(2));
},
);
@ -70,7 +69,7 @@ void main() {
await game.ready();
await game.ensureAdd(board);
final kickers = board.findNestedChildren<Kicker>();
final kickers = board.descendants().whereType<Kicker>();
expect(kickers.length, equals(2));
},
);
@ -83,7 +82,7 @@ void main() {
await game.ready();
await game.ensureAdd(board);
final roundBumpers = board.findNestedChildren<RoundBumper>();
final roundBumpers = board.descendants().whereType<RoundBumper>();
expect(roundBumpers.length, equals(3));
},
);

@ -234,6 +234,7 @@ void main() {
score: 0,
balls: 2,
activatedBonusLetters: [0],
activatedDashNests: {},
bonusHistory: [],
);
whenListen(
@ -259,6 +260,7 @@ void main() {
score: 0,
balls: 2,
activatedBonusLetters: [0],
activatedDashNests: {},
bonusHistory: [],
);
@ -283,6 +285,7 @@ void main() {
score: 0,
balls: 2,
activatedBonusLetters: [0],
activatedDashNests: {},
bonusHistory: [],
);

@ -17,13 +17,14 @@ void main() {
group(
'Flipper',
() {
// TODO(alestiago): Add golden tests.
flameTester.test(
'loads correctly',
(game) async {
final leftFlipper = Flipper.fromSide(
final leftFlipper = Flipper(
side: BoardSide.left,
);
final rightFlipper = Flipper.fromSide(
final rightFlipper = Flipper(
side: BoardSide.right,
);
await game.ready();
@ -36,13 +37,13 @@ void main() {
group('constructor', () {
test('sets BoardSide', () {
final leftFlipper = Flipper.fromSide(
final leftFlipper = Flipper(
side: BoardSide.left,
);
expect(leftFlipper.side, equals(leftFlipper.side));
final rightFlipper = Flipper.fromSide(
final rightFlipper = Flipper(
side: BoardSide.right,
);
expect(rightFlipper.side, equals(rightFlipper.side));
@ -53,7 +54,7 @@ void main() {
flameTester.test(
'is dynamic',
(game) async {
final flipper = Flipper.fromSide(
final flipper = Flipper(
side: BoardSide.left,
);
await game.ensureAdd(flipper);
@ -65,7 +66,7 @@ void main() {
flameTester.test(
'ignores gravity',
(game) async {
final flipper = Flipper.fromSide(
final flipper = Flipper(
side: BoardSide.left,
);
await game.ensureAdd(flipper);
@ -77,7 +78,7 @@ void main() {
flameTester.test(
'has greater mass than Ball',
(game) async {
final flipper = Flipper.fromSide(
final flipper = Flipper(
side: BoardSide.left,
);
final ball = Ball();
@ -97,7 +98,7 @@ void main() {
flameTester.test(
'has three',
(game) async {
final flipper = Flipper.fromSide(
final flipper = Flipper(
side: BoardSide.left,
);
await game.ensureAdd(flipper);
@ -109,7 +110,7 @@ void main() {
flameTester.test(
'has density',
(game) async {
final flipper = Flipper.fromSide(
final flipper = Flipper(
side: BoardSide.left,
);
await game.ensureAdd(flipper);
@ -139,7 +140,7 @@ void main() {
late Flipper flipper;
setUp(() {
flipper = Flipper.fromSide(
flipper = Flipper(
side: BoardSide.left,
);
});
@ -205,7 +206,7 @@ void main() {
late Flipper flipper;
setUp(() {
flipper = Flipper.fromSide(
flipper = Flipper(
side: BoardSide.right,
);
});
@ -269,159 +270,4 @@ void main() {
});
},
);
group('FlipperAnchor', () {
flameTester.test(
'position is at the left of the left Flipper',
(game) async {
final flipper = Flipper.fromSide(
side: BoardSide.left,
);
await game.ensureAdd(flipper);
final flipperAnchor = FlipperAnchor(flipper: flipper);
await game.ensureAdd(flipperAnchor);
expect(flipperAnchor.body.position.x, equals(-Flipper.size.x / 2));
},
);
flameTester.test(
'position is at the right of the right Flipper',
(game) async {
final flipper = Flipper.fromSide(
side: BoardSide.right,
);
await game.ensureAdd(flipper);
final flipperAnchor = FlipperAnchor(flipper: flipper);
await game.ensureAdd(flipperAnchor);
expect(flipperAnchor.body.position.x, equals(Flipper.size.x / 2));
},
);
});
group('FlipperAnchorRevoluteJointDef', () {
group('initializes with', () {
flameTester.test(
'limits enabled',
(game) async {
final flipper = Flipper.fromSide(
side: BoardSide.left,
);
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.fromSide(
side: BoardSide.left,
);
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.fromSide(
side: BoardSide.right,
);
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.fromSide(
side: BoardSide.left,
);
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.fromSide(
side: BoardSide.right,
);
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)),
);
},
);
},
);
});
}

@ -165,6 +165,42 @@ void main() {
});
});
group('ellipse', () {
flameTester.test(
'loads correctly',
(game) async {
final pathway = Pathway.ellipse(
center: Vector2.zero(),
width: width,
majorRadius: 150,
minorRadius: 70,
);
await game.ready();
await game.ensureAdd(pathway);
expect(game.contains(pathway), isTrue);
},
);
group('body', () {
flameTester.test(
'is static',
(game) async {
final pathway = Pathway.ellipse(
center: Vector2.zero(),
width: width,
majorRadius: 150,
minorRadius: 70,
);
await game.ready();
await game.ensureAdd(pathway);
expect(pathway.body.bodyType, equals(BodyType.static));
},
);
});
});
group('bezier curve', () {
final controlPoints = [
Vector2(0, 0),

@ -13,6 +13,7 @@ void main() {
score: 10,
balls: 2,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
);

@ -88,6 +88,7 @@ void main() {
score: 0,
balls: 0,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [],
);

@ -1,4 +1,3 @@
import 'package:flame/components.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_theme/pinball_theme.dart';
@ -21,41 +20,3 @@ extension DebugPinballGameTest on DebugPinballGame {
),
);
}
extension ComponentX on Component {
T findNestedChild<T extends Component>({
bool Function(T)? condition,
}) {
T? nestedChild;
propagateToChildren<T>((child) {
final foundChild = (condition ?? (_) => true)(child);
if (foundChild) {
nestedChild = child;
}
return !foundChild;
});
if (nestedChild == null) {
throw Exception('No child of type $T found.');
} else {
return nestedChild!;
}
}
List<T> findNestedChildren<T extends Component>({
bool Function(T)? condition,
}) {
final nestedChildren = <T>[];
propagateToChildren<T>((child) {
final foundChild = (condition ?? (_) => true)(child);
if (foundChild) {
nestedChildren.add(child);
}
return true;
});
return nestedChildren;
}
}

@ -5,8 +5,9 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
import 'package:pinball_theme/pinball_theme.dart';
class MockLeaderboardRepository extends Mock implements LeaderboardRepository {}
import '../../helpers/helpers.dart';
void main() {
group('LeaderboardBloc', () {
@ -42,7 +43,7 @@ void main() {
final top10Leaderboard = top10Scores
.map(
(score) => LeaderboardEntry(
(score) => LeaderboardEntryData(
playerInitials: 'user$score',
score: score,
character: CharacterType.dash,
@ -101,7 +102,7 @@ void main() {
});
group('LeaderboardEntryAdded', () {
final leaderboardEntry = LeaderboardEntry(
final leaderboardEntry = LeaderboardEntryData(
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,
@ -163,4 +164,40 @@ void main() {
);
});
});
group('CharacterTypeX', () {
test('converts CharacterType.android to AndroidTheme', () {
expect(CharacterType.android.toTheme, equals(AndroidTheme()));
});
test('converts CharacterType.dash to DashTheme', () {
expect(CharacterType.dash.toTheme, equals(DashTheme()));
});
test('converts CharacterType.dino to DinoTheme', () {
expect(CharacterType.dino.toTheme, equals(DinoTheme()));
});
test('converts CharacterType.sparky to SparkyTheme', () {
expect(CharacterType.sparky.toTheme, equals(SparkyTheme()));
});
});
group('CharacterThemeX', () {
test('converts AndroidTheme to CharacterType.android', () {
expect(AndroidTheme().toType, equals(CharacterType.android));
});
test('converts DashTheme to CharacterType.dash', () {
expect(DashTheme().toType, equals(CharacterType.dash));
});
test('converts DinoTheme to CharacterType.dino', () {
expect(DinoTheme().toType, equals(CharacterType.dino));
});
test('converts SparkyTheme to CharacterType.sparky', () {
expect(SparkyTheme().toType, equals(CharacterType.sparky));
});
});
}

@ -20,7 +20,7 @@ void main() {
});
group('LeaderboardEntryAdded', () {
const leaderboardEntry = LeaderboardEntry(
const leaderboardEntry = LeaderboardEntryData(
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,

@ -3,6 +3,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
import 'package:pinball_theme/pinball_theme.dart';
void main() {
group('LeaderboardState', () {
@ -25,10 +26,11 @@ void main() {
});
group('copyWith', () {
const leaderboardEntry = LeaderboardEntry(
final leaderboardEntry = LeaderboardEntry(
rank: '1',
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,
character: DashTheme().characterAsset,
);
test(
@ -51,7 +53,7 @@ void main() {
final otherLeaderboardState = LeaderboardState(
status: LeaderboardStatus.success,
ranking: LeaderboardRanking(ranking: 0, outOf: 0),
leaderboard: const [leaderboardEntry],
leaderboard: [leaderboardEntry],
);
expect(leaderboardState, isNot(equals(otherLeaderboardState)));

Loading…
Cancel
Save