Merge branch 'main' into refactor/use-children-param

pull/189/head
Alejandro Santiago 3 years ago committed by GitHub
commit 5a7fa25a8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -11,14 +11,11 @@ class GameBloc extends Bloc<GameEvent, GameState> {
GameBloc() : super(const GameState.initial()) { GameBloc() : super(const GameState.initial()) {
on<BallLost>(_onBallLost); on<BallLost>(_onBallLost);
on<Scored>(_onScored); on<Scored>(_onScored);
on<BonusLetterActivated>(_onBonusLetterActivated); on<BonusActivated>(_onBonusActivated);
on<DashNestActivated>(_onDashNestActivated); on<DashNestActivated>(_onDashNestActivated);
on<SparkyTurboChargeActivated>(_onSparkyTurboChargeActivated); on<SparkyTurboChargeActivated>(_onSparkyTurboChargeActivated);
} }
static const bonusWord = 'GOOGLE';
static const bonusWordScore = 10000;
void _onBallLost(BallLost event, Emitter emit) { void _onBallLost(BallLost event, Emitter emit) {
emit(state.copyWith(balls: state.balls - 1)); emit(state.copyWith(balls: state.balls - 1));
} }
@ -29,29 +26,12 @@ class GameBloc extends Bloc<GameEvent, GameState> {
} }
} }
void _onBonusLetterActivated(BonusLetterActivated event, Emitter emit) { void _onBonusActivated(BonusActivated event, Emitter emit) {
final newBonusLetters = [
...state.activatedBonusLetters,
event.letterIndex,
];
final achievedBonus = newBonusLetters.length == bonusWord.length;
if (achievedBonus) {
emit( emit(
state.copyWith( state.copyWith(
activatedBonusLetters: [], bonusHistory: [...state.bonusHistory, event.bonus],
bonusHistory: [
...state.bonusHistory,
GameBonus.word,
],
), ),
); );
add(const Scored(points: bonusWordScore));
} else {
emit(
state.copyWith(activatedBonusLetters: newBonusLetters),
);
}
} }
void _onDashNestActivated(DashNestActivated event, Emitter emit) { void _onDashNestActivated(DashNestActivated event, Emitter emit) {

@ -33,17 +33,13 @@ class Scored extends GameEvent {
List<Object?> get props => [points]; List<Object?> get props => [points];
} }
class BonusLetterActivated extends GameEvent { class BonusActivated extends GameEvent {
const BonusLetterActivated(this.letterIndex) const BonusActivated(this.bonus);
: assert(
letterIndex < GameBloc.bonusWord.length,
'Index must be smaller than the length of the word',
);
final int letterIndex; final GameBonus bonus;
@override @override
List<Object?> get props => [letterIndex]; List<Object?> get props => [bonus];
} }
class DashNestActivated extends GameEvent { class DashNestActivated extends GameEvent {

@ -4,9 +4,8 @@ part of 'game_bloc.dart';
/// Defines bonuses that a player can gain during a PinballGame. /// Defines bonuses that a player can gain during a PinballGame.
enum GameBonus { enum GameBonus {
/// Bonus achieved when the user activate all of the bonus /// Bonus achieved when the ball activates all Google letters.
/// letters on the board, forming the bonus word. googleWord,
word,
/// Bonus achieved when the user activates all dash nest bumpers. /// Bonus achieved when the user activates all dash nest bumpers.
dashNest, dashNest,
@ -23,7 +22,6 @@ class GameState extends Equatable {
const GameState({ const GameState({
required this.score, required this.score,
required this.balls, required this.balls,
required this.activatedBonusLetters,
required this.bonusHistory, required this.bonusHistory,
required this.activatedDashNests, required this.activatedDashNests,
}) : assert(score >= 0, "Score can't be negative"), }) : assert(score >= 0, "Score can't be negative"),
@ -32,7 +30,6 @@ class GameState extends Equatable {
const GameState.initial() const GameState.initial()
: score = 0, : score = 0,
balls = 3, balls = 3,
activatedBonusLetters = const [],
activatedDashNests = const {}, activatedDashNests = const {},
bonusHistory = const []; bonusHistory = const [];
@ -44,9 +41,6 @@ class GameState extends Equatable {
/// When the number of balls is 0, the game is over. /// When the number of balls is 0, the game is over.
final int balls; final int balls;
/// Active bonus letters.
final List<int> activatedBonusLetters;
/// Active dash nests. /// Active dash nests.
final Set<String> activatedDashNests; final Set<String> activatedDashNests;
@ -57,14 +51,9 @@ class GameState extends Equatable {
/// Determines when the game is over. /// Determines when the game is over.
bool get isGameOver => balls == 0; bool get isGameOver => balls == 0;
/// Shortcut method to check if the given [i]
/// is activated.
bool isLetterActivated(int i) => activatedBonusLetters.contains(i);
GameState copyWith({ GameState copyWith({
int? score, int? score,
int? balls, int? balls,
List<int>? activatedBonusLetters,
Set<String>? activatedDashNests, Set<String>? activatedDashNests,
List<GameBonus>? bonusHistory, List<GameBonus>? bonusHistory,
}) { }) {
@ -76,8 +65,6 @@ class GameState extends Equatable {
return GameState( return GameState(
score: score ?? this.score, score: score ?? this.score,
balls: balls ?? this.balls, balls: balls ?? this.balls,
activatedBonusLetters:
activatedBonusLetters ?? this.activatedBonusLetters,
activatedDashNests: activatedDashNests ?? this.activatedDashNests, activatedDashNests: activatedDashNests ?? this.activatedDashNests,
bonusHistory: bonusHistory ?? this.bonusHistory, bonusHistory: bonusHistory ?? this.bonusHistory,
); );
@ -87,7 +74,6 @@ class GameState extends Equatable {
List<Object?> get props => [ List<Object?> get props => [
score, score,
balls, balls,
activatedBonusLetters,
activatedDashNests, activatedDashNests,
bonusHistory, bonusHistory,
]; ];

@ -1,208 +0,0 @@
// 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';
import 'package:pinball_components/pinball_components.dart';
/// {@template bonus_word}
/// Loads all [BonusLetter]s to compose a [BonusWord].
/// {@endtemplate}
class BonusWord extends Component
with BlocComponent<GameBloc, GameState>, HasGameRef<PinballGame> {
/// {@macro bonus_word}
BonusWord({required Vector2 position}) : _position = position;
final Vector2 _position;
@override
bool listenWhen(GameState? previousState, GameState newState) {
return (previousState?.bonusHistory.length ?? 0) <
newState.bonusHistory.length &&
newState.bonusHistory.last == GameBonus.word;
}
@override
void onNewState(GameState state) {
if (state.bonusHistory.last == GameBonus.word) {
gameRef.audio.googleBonus();
final letters = children.whereType<BonusLetter>().toList();
for (var i = 0; i < letters.length; i++) {
final letter = letters[i];
letter
..isEnabled = false
..add(
SequenceEffect(
[
ColorEffect(
i.isOdd
? BonusLetter._activeColor
: BonusLetter._disableColor,
const Offset(0, 1),
EffectController(duration: 0.25),
),
ColorEffect(
i.isOdd
? BonusLetter._disableColor
: BonusLetter._activeColor,
const Offset(0, 1),
EffectController(duration: 0.25),
),
],
repeatCount: 4,
)..onFinishCallback = () {
letter
..isEnabled = true
..add(
ColorEffect(
BonusLetter._disableColor,
const Offset(0, 1),
EffectController(duration: 0.25),
),
);
},
);
}
}
}
@override
Future<void> onLoad() async {
await super.onLoad();
final offsets = [
Vector2(-12.92, 1.82),
Vector2(-8.33, -0.65),
Vector2(-2.88, -1.75),
];
offsets.addAll(
offsets.reversed
.map(
(offset) => Vector2(-offset.x, offset.y),
)
.toList(),
);
assert(offsets.length == GameBloc.bonusWord.length, 'Invalid positions');
final letters = <BonusLetter>[];
for (var i = 0; i < GameBloc.bonusWord.length; i++) {
letters.add(
BonusLetter(
letter: GameBloc.bonusWord[i],
index: i,
)..initialPosition = _position + offsets[i],
);
}
await addAll(letters);
}
}
/// {@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>, InitialPosition {
/// {@macro bonus_letter}
BonusLetter({
required String letter,
required int index,
}) : _letter = letter,
_index = index {
paint = Paint()..color = _disableColor;
}
/// The size of the [BonusLetter].
static final size = Vector2.all(3.7);
static const _activeColor = Colors.green;
static const _disableColor = Colors.red;
final String _letter;
final int _index;
/// Indicates if a [BonusLetter] can be activated on [Ball] contact.
///
/// It is disabled whilst animating and enabled again once the animation
/// completes. The animation is triggered when [GameBonus.word] is
/// awarded.
bool isEnabled = true;
@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 = size.x / 2;
final fixtureDef = FixtureDef(shape)..isSensor = true;
final bodyDef = BodyDef()
..position = initialPosition
..userData = this
..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) {
if (bonusLetter.isEnabled) {
bonusLetter.activate();
}
}
}

@ -1,6 +1,5 @@
export 'alien_zone.dart'; export 'alien_zone.dart';
export 'board.dart'; export 'board.dart';
export 'bonus_word.dart';
export 'camera_controller.dart'; export 'camera_controller.dart';
export 'controlled_ball.dart'; export 'controlled_ball.dart';
export 'controlled_flipper.dart'; export 'controlled_flipper.dart';
@ -8,6 +7,7 @@ export 'controlled_plunger.dart';
export 'controlled_sparky_computer.dart'; export 'controlled_sparky_computer.dart';
export 'flutter_forest.dart'; export 'flutter_forest.dart';
export 'game_flow_controller.dart'; export 'game_flow_controller.dart';
export 'google_word.dart';
export 'launcher.dart'; export 'launcher.dart';
export 'score_effect_controller.dart'; export 'score_effect_controller.dart';
export 'score_points.dart'; export 'score_points.dart';

@ -0,0 +1,83 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/flame/flame.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template google_word}
/// Loads all [GoogleLetter]s to compose a [GoogleWord].
/// {@endtemplate}
class GoogleWord extends Component
with HasGameRef<PinballGame>, Controls<_GoogleWordController> {
/// {@macro google_word}
GoogleWord({
required Vector2 position,
}) : _position = position {
controller = _GoogleWordController(this);
}
final Vector2 _position;
@override
Future<void> onLoad() async {
await super.onLoad();
gameRef.addContactCallback(_GoogleLetterBallContactCallback());
final offsets = [
Vector2(-12.92, 1.82),
Vector2(-8.33, -0.65),
Vector2(-2.88, -1.75),
Vector2(2.88, -1.75),
Vector2(8.33, -0.65),
Vector2(12.92, 1.82),
];
final letters = <GoogleLetter>[];
for (var index = 0; index < offsets.length; index++) {
letters.add(
GoogleLetter(index)..initialPosition = _position + offsets[index],
);
}
await addAll(letters);
}
}
class _GoogleWordController extends ComponentController<GoogleWord>
with HasGameRef<PinballGame> {
_GoogleWordController(GoogleWord googleWord) : super(googleWord);
final _activatedLetters = <GoogleLetter>{};
void activate(GoogleLetter googleLetter) {
if (!_activatedLetters.add(googleLetter)) return;
googleLetter.activate();
final activatedBonus = _activatedLetters.length == 6;
if (activatedBonus) {
gameRef.audio.googleBonus();
gameRef.read<GameBloc>().add(const BonusActivated(GameBonus.googleWord));
component.children.whereType<GoogleLetter>().forEach(
(letter) => letter.deactivate(),
);
_activatedLetters.clear();
}
}
}
/// Activates a [GoogleLetter] when it contacts with a [Ball].
class _GoogleLetterBallContactCallback
extends ContactCallback<GoogleLetter, Ball> {
@override
void begin(GoogleLetter googleLetter, _, __) {
final parent = googleLetter.parent;
if (parent is GoogleWord) {
parent.controller.activate(googleLetter);
}
}
}

@ -58,6 +58,12 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.sparky.bumper.c.inactive.keyName), images.load(components.Assets.images.sparky.bumper.c.inactive.keyName),
images.load(components.Assets.images.backboard.backboardScores.keyName), images.load(components.Assets.images.backboard.backboardScores.keyName),
images.load(components.Assets.images.backboard.backboardGameOver.keyName), images.load(components.Assets.images.backboard.backboardGameOver.keyName),
images.load(components.Assets.images.googleWord.letter1.keyName),
images.load(components.Assets.images.googleWord.letter2.keyName),
images.load(components.Assets.images.googleWord.letter3.keyName),
images.load(components.Assets.images.googleWord.letter4.keyName),
images.load(components.Assets.images.googleWord.letter5.keyName),
images.load(components.Assets.images.googleWord.letter6.keyName),
images.load(components.Assets.images.backboard.display.keyName), images.load(components.Assets.images.backboard.display.keyName),
images.load(Assets.images.components.background.path), images.load(Assets.images.components.background.path),
]; ];

@ -72,7 +72,6 @@ class PinballGame extends Forge2DGame
void _addContactCallbacks() { void _addContactCallbacks() {
addContactCallback(BallScorePointsCallback(this)); addContactCallback(BallScorePointsCallback(this));
addContactCallback(BottomWallBallContactCallback()); addContactCallback(BottomWallBallContactCallback());
addContactCallback(BonusLetterBallContactCallback());
} }
Future<void> _addGameBoundaries() async { Future<void> _addGameBoundaries() async {
@ -82,7 +81,7 @@ class PinballGame extends Forge2DGame
Future<void> _addBonusWord() async { Future<void> _addBonusWord() async {
await add( await add(
BonusWord( GoogleWord(
position: Vector2( position: Vector2(
BoardDimensions.bounds.center.dx - 4.1, BoardDimensions.bounds.center.dx - 4.1,
BoardDimensions.bounds.center.dy + 1.8, BoardDimensions.bounds.center.dy + 1.8,

@ -72,13 +72,14 @@ class AlienBumper extends BodyComponent with InitialPosition {
majorRadius: _majorRadius, majorRadius: _majorRadius,
minorRadius: _minorRadius, minorRadius: _minorRadius,
)..rotate(1.29); )..rotate(1.29);
final fixtureDef = FixtureDef(shape) final fixtureDef = FixtureDef(
..friction = 0 shape,
..restitution = 4; restitution: 4,
);
final bodyDef = BodyDef() final bodyDef = BodyDef(
..position = initialPosition position: initialPosition,
..userData = this; userData: this,
);
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }

@ -55,11 +55,15 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
@override @override
Body createBody() { Body createBody() {
final shape = CircleShape()..radius = size.x / 2; final shape = CircleShape()..radius = size.x / 2;
final fixtureDef = FixtureDef(shape)..density = 1; final fixtureDef = FixtureDef(
final bodyDef = BodyDef() shape,
..position = initialPosition density: 1,
..userData = this );
..type = BodyType.dynamic; final bodyDef = BodyDef(
position: initialPosition,
userData: this,
type: BodyType.dynamic,
);
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }

@ -87,10 +87,10 @@ class Baseboard extends BodyComponent with InitialPosition {
@override @override
Body createBody() { Body createBody() {
const angle = 37.1 * (math.pi / 180); const angle = 37.1 * (math.pi / 180);
final bodyDef = BodyDef(
final bodyDef = BodyDef() position: initialPosition,
..position = initialPosition angle: -angle * _side.direction,
..angle = _side.isLeft ? angle : -angle; );
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture); _createFixtureDefs().forEach(body.createFixture);

@ -54,12 +54,14 @@ class ChromeDino extends BodyComponent with InitialPosition {
// TODO(alestiago): Subject to change when sprites are added. // TODO(alestiago): Subject to change when sprites are added.
final box = PolygonShape()..setAsBoxXY(size.x / 2, size.y / 2); final box = PolygonShape()..setAsBoxXY(size.x / 2, size.y / 2);
final fixtureDef = FixtureDef(box) final fixtureDef = FixtureDef(
..shape = box box,
..density = 999 density: 999,
..friction = 0.3 friction: 0.3,
..restitution = 0.1 restitution: 0.1,
..isSensor = true; isSensor: true,
);
fixtureDefs.add(fixtureDef); fixtureDefs.add(fixtureDef);
// FIXME(alestiago): Investigate why adding these fixtures is considered as // FIXME(alestiago): Investigate why adding these fixtures is considered as
@ -93,10 +95,11 @@ class ChromeDino extends BodyComponent with InitialPosition {
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef() final bodyDef = BodyDef(
..gravityScale = Vector2.zero() position: initialPosition,
..position = initialPosition type: BodyType.dynamic,
..type = BodyType.dynamic; gravityScale: Vector2.zero(),
);
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture); _createFixtureDefs().forEach(body.createFixture);
@ -111,10 +114,7 @@ class ChromeDino extends BodyComponent with InitialPosition {
class _ChromeDinoAnchor extends JointAnchor { class _ChromeDinoAnchor extends JointAnchor {
/// {@macro flipper_anchor} /// {@macro flipper_anchor}
_ChromeDinoAnchor() { _ChromeDinoAnchor() {
initialPosition = Vector2( initialPosition = Vector2(ChromeDino.size.x / 2, 0);
ChromeDino.size.x / 2,
0,
);
} }
} }

@ -79,10 +79,10 @@ class BigDashNestBumper extends DashNestBumper {
minorRadius: 3.75, minorRadius: 3.75,
)..rotate(math.pi / 1.9); )..rotate(math.pi / 1.9);
final fixtureDef = FixtureDef(shape); final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef(
final bodyDef = BodyDef() position: initialPosition,
..position = initialPosition userData: this,
..userData = this; );
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }
@ -130,13 +130,14 @@ class SmallDashNestBumper extends DashNestBumper {
majorRadius: 3, majorRadius: 3,
minorRadius: 2.25, minorRadius: 2.25,
)..rotate(math.pi / 2); )..rotate(math.pi / 2);
final fixtureDef = FixtureDef(shape) final fixtureDef = FixtureDef(
..friction = 0 shape,
..restitution = 4; restitution: 4,
);
final bodyDef = BodyDef() final bodyDef = BodyDef(
..position = initialPosition position: initialPosition,
..userData = this; userData: this,
);
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }

@ -87,10 +87,11 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef() final bodyDef = BodyDef(
..userData = this position: initialPosition,
..position = initialPosition userData: this,
..type = BodyType.static; );
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach( _createFixtureDefs().forEach(
(fixture) => body.createFixture( (fixture) => body.createFixture(
@ -131,6 +132,7 @@ class _DinoBottomWall extends BodyComponent with InitialPosition {
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[]; final fixturesDef = <FixtureDef>[];
const restitution = 1.0;
final topStraightControlPoints = [ final topStraightControlPoints = [
Vector2(32.4, -8.3), Vector2(32.4, -8.3),
@ -141,7 +143,10 @@ class _DinoBottomWall extends BodyComponent with InitialPosition {
topStraightControlPoints.first, topStraightControlPoints.first,
topStraightControlPoints.last, topStraightControlPoints.last,
); );
final topStraightFixtureDef = FixtureDef(topStraightShape); final topStraightFixtureDef = FixtureDef(
topStraightShape,
restitution: restitution,
);
fixturesDef.add(topStraightFixtureDef); fixturesDef.add(topStraightFixtureDef);
final topLeftCurveControlPoints = [ final topLeftCurveControlPoints = [
@ -152,7 +157,11 @@ class _DinoBottomWall extends BodyComponent with InitialPosition {
final topLeftCurveShape = BezierCurveShape( final topLeftCurveShape = BezierCurveShape(
controlPoints: topLeftCurveControlPoints, controlPoints: topLeftCurveControlPoints,
); );
fixturesDef.add(FixtureDef(topLeftCurveShape)); final topLeftCurveFixtureDef = FixtureDef(
topLeftCurveShape,
restitution: restitution,
);
fixturesDef.add(topLeftCurveFixtureDef);
final bottomLeftStraightControlPoints = [ final bottomLeftStraightControlPoints = [
topLeftCurveControlPoints.last, topLeftCurveControlPoints.last,
@ -163,7 +172,10 @@ class _DinoBottomWall extends BodyComponent with InitialPosition {
bottomLeftStraightControlPoints.first, bottomLeftStraightControlPoints.first,
bottomLeftStraightControlPoints.last, bottomLeftStraightControlPoints.last,
); );
final bottomLeftStraightFixtureDef = FixtureDef(bottomLeftStraightShape); final bottomLeftStraightFixtureDef = FixtureDef(
bottomLeftStraightShape,
restitution: restitution,
);
fixturesDef.add(bottomLeftStraightFixtureDef); fixturesDef.add(bottomLeftStraightFixtureDef);
final bottomStraightControlPoints = [ final bottomStraightControlPoints = [
@ -175,7 +187,10 @@ class _DinoBottomWall extends BodyComponent with InitialPosition {
bottomStraightControlPoints.first, bottomStraightControlPoints.first,
bottomStraightControlPoints.last, bottomStraightControlPoints.last,
); );
final bottomStraightFixtureDef = FixtureDef(bottomStraightShape); final bottomStraightFixtureDef = FixtureDef(
bottomStraightShape,
restitution: restitution,
);
fixturesDef.add(bottomStraightFixtureDef); fixturesDef.add(bottomStraightFixtureDef);
return fixturesDef; return fixturesDef;
@ -183,19 +198,13 @@ class _DinoBottomWall extends BodyComponent with InitialPosition {
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef() final bodyDef = BodyDef(
..userData = this position: initialPosition,
..position = initialPosition userData: this,
..type = BodyType.static; );
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach( _createFixtureDefs().forEach(body.createFixture);
(fixture) => body.createFixture(
fixture
..restitution = 0.1
..friction = 0,
),
);
return body; return body;
} }

@ -103,9 +103,11 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
Vector2(smallCircleShape.position.x, -smallCircleShape.radius), Vector2(smallCircleShape.position.x, -smallCircleShape.radius),
]; ];
final trapezium = PolygonShape()..set(trapeziumVertices); final trapezium = PolygonShape()..set(trapeziumVertices);
final trapeziumFixtureDef = FixtureDef(trapezium) final trapeziumFixtureDef = FixtureDef(
..density = 50.0 // TODO(alestiago): Use a proper density. trapezium,
..friction = .1; // TODO(alestiago): Use a proper friction. density: 50, // TODO(alestiago): Use a proper density.
friction: .1, // TODO(alestiago): Use a proper friction.
);
fixturesDef.add(trapeziumFixtureDef); fixturesDef.add(trapeziumFixtureDef);
return fixturesDef; return fixturesDef;
@ -120,10 +122,12 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef() final bodyDef = BodyDef(
..position = initialPosition position: initialPosition,
..gravityScale = Vector2.zero() gravityScale: Vector2.zero(),
..type = BodyType.dynamic; type: BodyType.dynamic,
);
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture); _createFixtureDefs().forEach(body.createFixture);

@ -18,7 +18,9 @@ class FlutterSignPost extends BodyComponent with InitialPosition {
Body createBody() { Body createBody() {
final shape = CircleShape()..radius = 0.25; final shape = CircleShape()..radius = 0.25;
final fixtureDef = FixtureDef(shape); final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef()..position = initialPosition; final bodyDef = BodyDef(
position: initialPosition,
);
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }

@ -33,12 +33,14 @@ class GoogleLetter extends BodyComponent with InitialPosition {
@override @override
Body createBody() { Body createBody() {
final shape = CircleShape()..radius = 1.85; final shape = CircleShape()..radius = 1.85;
final fixtureDef = FixtureDef(shape)..isSensor = true; final fixtureDef = FixtureDef(
shape,
final bodyDef = BodyDef() isSensor: true,
..position = initialPosition );
..userData = this final bodyDef = BodyDef(
..type = BodyType.static; position: initialPosition,
userData: this,
);
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }

@ -22,7 +22,9 @@ class JointAnchor extends BodyComponent with InitialPosition {
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef()..position = initialPosition; final bodyDef = BodyDef(
position: initialPosition,
);
return world.createBody(bodyDef); return world.createBody(bodyDef);
} }
} }

@ -40,7 +40,7 @@ class Kicker extends BodyComponent with InitialPosition {
final upperCircle = CircleShape()..radius = 1.6; final upperCircle = CircleShape()..radius = 1.6;
upperCircle.position.setValues(0, upperCircle.radius / 2); upperCircle.position.setValues(0, upperCircle.radius / 2);
final upperCircleFixtureDef = FixtureDef(upperCircle)..friction = 0; final upperCircleFixtureDef = FixtureDef(upperCircle);
fixturesDefs.add(upperCircleFixtureDef); fixturesDefs.add(upperCircleFixtureDef);
final lowerCircle = CircleShape()..radius = 1.6; final lowerCircle = CircleShape()..radius = 1.6;
@ -48,7 +48,7 @@ class Kicker extends BodyComponent with InitialPosition {
size.x * -direction, size.x * -direction,
size.y + 0.8, size.y + 0.8,
); );
final lowerCircleFixtureDef = FixtureDef(lowerCircle)..friction = 0; final lowerCircleFixtureDef = FixtureDef(lowerCircle);
fixturesDefs.add(lowerCircleFixtureDef); fixturesDefs.add(lowerCircleFixtureDef);
final wallFacingEdge = EdgeShape() final wallFacingEdge = EdgeShape()
@ -60,7 +60,7 @@ class Kicker extends BodyComponent with InitialPosition {
), ),
Vector2(2.5 * direction, size.y - 2), Vector2(2.5 * direction, size.y - 2),
); );
final wallFacingLineFixtureDef = FixtureDef(wallFacingEdge)..friction = 0; final wallFacingLineFixtureDef = FixtureDef(wallFacingEdge);
fixturesDefs.add(wallFacingLineFixtureDef); fixturesDefs.add(wallFacingLineFixtureDef);
final bottomEdge = EdgeShape() final bottomEdge = EdgeShape()
@ -72,7 +72,7 @@ class Kicker extends BodyComponent with InitialPosition {
lowerCircle.radius * math.sin(quarterPi), lowerCircle.radius * math.sin(quarterPi),
), ),
); );
final bottomLineFixtureDef = FixtureDef(bottomEdge)..friction = 0; final bottomLineFixtureDef = FixtureDef(bottomEdge);
fixturesDefs.add(bottomLineFixtureDef); fixturesDefs.add(bottomLineFixtureDef);
final bouncyEdge = EdgeShape() final bouncyEdge = EdgeShape()
@ -89,10 +89,11 @@ class Kicker extends BodyComponent with InitialPosition {
), ),
); );
final bouncyFixtureDef = FixtureDef(bouncyEdge) final bouncyFixtureDef = FixtureDef(
bouncyEdge,
// TODO(alestiago): Play with restitution value once game is bundled. // TODO(alestiago): Play with restitution value once game is bundled.
..restitution = 10.0 restitution: 10,
..friction = 0; );
fixturesDefs.add(bouncyFixtureDef); fixturesDefs.add(bouncyFixtureDef);
// TODO(alestiago): Evaluate if there is value on centering the fixtures. // TODO(alestiago): Evaluate if there is value on centering the fixtures.
@ -116,7 +117,9 @@ class Kicker extends BodyComponent with InitialPosition {
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef()..position = initialPosition; final bodyDef = BodyDef(
position: initialPosition,
);
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture); _createFixtureDefs().forEach(body.createFixture);

@ -103,9 +103,10 @@ class _LaunchRampBase extends BodyComponent with InitialPosition, Layered {
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef() final bodyDef = BodyDef(
..userData = this position: initialPosition,
..position = initialPosition; userData: this,
);
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture); _createFixtureDefs().forEach(body.createFixture);

@ -33,11 +33,12 @@ class Plunger extends BodyComponent with InitialPosition, Layered {
final fixtureDef = FixtureDef(shape)..density = 80; final fixtureDef = FixtureDef(shape)..density = 80;
final bodyDef = BodyDef() final bodyDef = BodyDef(
..position = initialPosition position: initialPosition,
..userData = this userData: this,
..type = BodyType.dynamic type: BodyType.dynamic,
..gravityScale = Vector2.zero(); gravityScale: Vector2.zero(),
);
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }

@ -65,12 +65,14 @@ abstract class RampOpening extends BodyComponent with InitialPosition, Layered {
@override @override
Body createBody() { Body createBody() {
final fixtureDef = FixtureDef(shape)..isSensor = true; final fixtureDef = FixtureDef(
shape,
final bodyDef = BodyDef() isSensor: true,
..userData = this );
..position = initialPosition final bodyDef = BodyDef(
..type = BodyType.static; position: initialPosition,
userData: this,
);
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }

@ -56,12 +56,12 @@ class Slingshot extends BodyComponent with InitialPosition {
final topCircleShape = CircleShape()..radius = circleRadius; final topCircleShape = CircleShape()..radius = circleRadius;
topCircleShape.position.setValues(0, -_length / 2); topCircleShape.position.setValues(0, -_length / 2);
final topCircleFixtureDef = FixtureDef(topCircleShape)..friction = 0; final topCircleFixtureDef = FixtureDef(topCircleShape);
fixturesDef.add(topCircleFixtureDef); fixturesDef.add(topCircleFixtureDef);
final bottomCircleShape = CircleShape()..radius = circleRadius; final bottomCircleShape = CircleShape()..radius = circleRadius;
bottomCircleShape.position.setValues(0, _length / 2); bottomCircleShape.position.setValues(0, _length / 2);
final bottomCircleFixtureDef = FixtureDef(bottomCircleShape)..friction = 0; final bottomCircleFixtureDef = FixtureDef(bottomCircleShape);
fixturesDef.add(bottomCircleFixtureDef); fixturesDef.add(bottomCircleFixtureDef);
final leftEdgeShape = EdgeShape() final leftEdgeShape = EdgeShape()
@ -69,9 +69,11 @@ class Slingshot extends BodyComponent with InitialPosition {
Vector2(circleRadius, _length / 2), Vector2(circleRadius, _length / 2),
Vector2(circleRadius, -_length / 2), Vector2(circleRadius, -_length / 2),
); );
final leftEdgeShapeFixtureDef = FixtureDef(leftEdgeShape) final leftEdgeShapeFixtureDef = FixtureDef(
..friction = 0 leftEdgeShape,
..restitution = 5; restitution: 5,
);
fixturesDef.add(leftEdgeShapeFixtureDef); fixturesDef.add(leftEdgeShapeFixtureDef);
final rightEdgeShape = EdgeShape() final rightEdgeShape = EdgeShape()
@ -79,9 +81,10 @@ class Slingshot extends BodyComponent with InitialPosition {
Vector2(-circleRadius, _length / 2), Vector2(-circleRadius, _length / 2),
Vector2(-circleRadius, -_length / 2), Vector2(-circleRadius, -_length / 2),
); );
final rightEdgeShapeFixtureDef = FixtureDef(rightEdgeShape) final rightEdgeShapeFixtureDef = FixtureDef(
..friction = 0 rightEdgeShape,
..restitution = 5; restitution: 5,
);
fixturesDef.add(rightEdgeShapeFixtureDef); fixturesDef.add(rightEdgeShapeFixtureDef);
return fixturesDef; return fixturesDef;
@ -89,10 +92,11 @@ class Slingshot extends BodyComponent with InitialPosition {
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef() final bodyDef = BodyDef(
..userData = this position: initialPosition,
..position = initialPosition userData: this,
..angle = _angle; angle: _angle,
);
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture); _createFixtureDefs().forEach(body.createFixture);

@ -71,17 +71,17 @@ class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {
@override @override
Body createBody() { Body createBody() {
final circleShape = CircleShape()..radius = 3; final shape = CircleShape()..radius = 3;
final fixtureDef = FixtureDef(
final bodyDef = BodyDef() shape,
..userData = this isSensor: true,
..position = initialPosition );
..type = BodyType.static; final bodyDef = BodyDef(
position: initialPosition,
return world.createBody(bodyDef) userData: this,
..createFixture(
FixtureDef(circleShape)..isSensor = true,
); );
return world.createBody(bodyDef)..createFixture(fixtureDef);
} }
} }
@ -104,10 +104,10 @@ class AndroidHead extends BodyComponent with InitialPosition, Layered {
Body createBody() { Body createBody() {
final circleShape = CircleShape()..radius = 2; final circleShape = CircleShape()..radius = 2;
final bodyDef = BodyDef() final bodyDef = BodyDef(
..userData = this position: initialPosition,
..position = initialPosition userData: this,
..type = BodyType.static; );
return world.createBody(bodyDef) return world.createBody(bodyDef)
..createFixture( ..createFixture(
@ -243,18 +243,16 @@ class SpaceshipWall extends BodyComponent with InitialPosition, Layered {
Body createBody() { Body createBody() {
renderBody = false; renderBody = false;
final wallShape = _SpaceshipWallShape(); final shape = _SpaceshipWallShape();
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef()
..userData = this
..position = initialPosition
..angle = -1.7
..type = BodyType.static;
return world.createBody(bodyDef) final bodyDef = BodyDef(
..createFixture( position: initialPosition,
FixtureDef(wallShape)..restitution = 1, userData: this,
angle: -1.7,
); );
return world.createBody(bodyDef)..createFixture(fixtureDef);
} }
} }

@ -122,9 +122,10 @@ class _SpaceshipRailRamp extends BodyComponent with InitialPosition, Layered {
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef() final bodyDef = BodyDef(
..userData = this position: initialPosition,
..position = initialPosition; userData: this,
);
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture); _createFixtureDefs().forEach(body.createFixture);
@ -189,12 +190,11 @@ class _SpaceshipRailBase extends BodyComponent with InitialPosition, Layered {
@override @override
Body createBody() { Body createBody() {
final shape = CircleShape()..radius = radius; final shape = CircleShape()..radius = radius;
final fixtureDef = FixtureDef(shape); final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef(
final bodyDef = BodyDef() position: initialPosition,
..position = initialPosition userData: this,
..userData = this; );
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }

@ -75,7 +75,6 @@ class _SpaceshipRampBackground extends BodyComponent
Vector2(-14.2, -71.25), Vector2(-14.2, -71.25),
], ],
); );
final outerLeftCurveFixtureDef = FixtureDef(outerLeftCurveShape); final outerLeftCurveFixtureDef = FixtureDef(outerLeftCurveShape);
fixturesDef.add(outerLeftCurveFixtureDef); fixturesDef.add(outerLeftCurveFixtureDef);
@ -86,7 +85,6 @@ class _SpaceshipRampBackground extends BodyComponent
Vector2(6.1, -44.9), Vector2(6.1, -44.9),
], ],
); );
final outerRightCurveFixtureDef = FixtureDef(outerRightCurveShape); final outerRightCurveFixtureDef = FixtureDef(outerRightCurveShape);
fixturesDef.add(outerRightCurveFixtureDef); fixturesDef.add(outerRightCurveFixtureDef);
@ -103,9 +101,10 @@ class _SpaceshipRampBackground extends BodyComponent
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef() final bodyDef = BodyDef(
..userData = this position: initialPosition,
..position = initialPosition; userData: this,
);
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture); _createFixtureDefs().forEach(body.createFixture);
@ -216,9 +215,10 @@ class _SpaceshipRampForegroundRailing extends BodyComponent
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef() final bodyDef = BodyDef(
..userData = this position: initialPosition,
..position = initialPosition; userData: this,
);
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture); _createFixtureDefs().forEach(body.createFixture);
@ -262,10 +262,10 @@ class _SpaceshipRampBase extends BodyComponent with InitialPosition, Layered {
], ],
); );
final fixtureDef = FixtureDef(baseShape); final fixtureDef = FixtureDef(baseShape);
final bodyDef = BodyDef(
final bodyDef = BodyDef() position: initialPosition,
..userData = this userData: this,
..position = initialPosition; );
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }

@ -90,10 +90,10 @@ class SparkyBumper extends BodyComponent with InitialPosition {
majorRadius: _majorRadius, majorRadius: _majorRadius,
minorRadius: _minorRadius, minorRadius: _minorRadius,
)..rotate(math.pi / 2.1); )..rotate(math.pi / 2.1);
final fixtureDef = FixtureDef(shape) final fixtureDef = FixtureDef(
..friction = 0 shape,
..restitution = 4; restitution: 4,
);
final bodyDef = BodyDef() final bodyDef = BodyDef()
..position = initialPosition ..position = initialPosition
..userData = this; ..userData = this;

@ -56,9 +56,10 @@ class _ComputerBase extends BodyComponent with InitialPosition {
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef() final bodyDef = BodyDef(
..userData = this position: initialPosition,
..position = initialPosition; userData: this,
);
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture); _createFixtureDefs().forEach(body.createFixture);

@ -21,7 +21,6 @@ void main() {
const GameState( const GameState(
score: 0, score: 0,
balls: 2, balls: 2,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
), ),
@ -41,14 +40,12 @@ void main() {
const GameState( const GameState(
score: 2, score: 2,
balls: 3, balls: 3,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
), ),
const GameState( const GameState(
score: 5, score: 5,
balls: 3, balls: 3,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
), ),
@ -69,21 +66,18 @@ void main() {
const GameState( const GameState(
score: 0, score: 0,
balls: 2, balls: 2,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
), ),
const GameState( const GameState(
score: 0, score: 0,
balls: 1, balls: 1,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
), ),
const GameState( const GameState(
score: 0, score: 0,
balls: 0, balls: 0,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
), ),
@ -91,136 +85,63 @@ void main() {
); );
}); });
group('BonusLetterActivated', () { group('DashNestActivated', () {
blocTest<GameBloc, GameState>( blocTest<GameBloc, GameState>(
'adds the letter to the state', 'adds the bonus when all nests are activated',
build: GameBloc.new, build: GameBloc.new,
act: (bloc) => bloc act: (bloc) => bloc
..add(const BonusLetterActivated(0)) ..add(const DashNestActivated('0'))
..add(const BonusLetterActivated(1)) ..add(const DashNestActivated('1'))
..add(const BonusLetterActivated(2)), ..add(const DashNestActivated('2')),
expect: () => const [ expect: () => const [
GameState( GameState(
score: 0, score: 0,
balls: 3, balls: 3,
activatedBonusLetters: [0], activatedDashNests: {'0'},
activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
), ),
GameState( GameState(
score: 0, score: 0,
balls: 3, balls: 3,
activatedBonusLetters: [0, 1], activatedDashNests: {'0', '1'},
activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
), ),
GameState( GameState(
score: 0, score: 0,
balls: 3, balls: 4,
activatedBonusLetters: [0, 1, 2],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [GameBonus.dashNest],
), ),
], ],
); );
});
group(
'BonusActivated',
() {
blocTest<GameBloc, GameState>( blocTest<GameBloc, GameState>(
'adds the bonus when the bonusWord is completed', 'adds bonus to history',
build: GameBloc.new, build: GameBloc.new,
act: (bloc) => bloc act: (bloc) => bloc
..add(const BonusLetterActivated(0)) ..add(const BonusActivated(GameBonus.googleWord))
..add(const BonusLetterActivated(1)) ..add(const BonusActivated(GameBonus.dashNest)),
..add(const BonusLetterActivated(2))
..add(const BonusLetterActivated(3))
..add(const BonusLetterActivated(4))
..add(const BonusLetterActivated(5)),
expect: () => const [ expect: () => const [
GameState( GameState(
score: 0, score: 0,
balls: 3, 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: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [GameBonus.googleWord],
), ),
GameState( GameState(
score: 0, score: 0,
balls: 3, balls: 3,
activatedBonusLetters: [0, 1, 2, 3],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [GameBonus.googleWord, GameBonus.dashNest],
),
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: 4,
activatedBonusLetters: [],
activatedDashNests: {},
bonusHistory: [GameBonus.dashNest],
),
],
); );
});
group('SparkyTurboChargeActivated', () { group('SparkyTurboChargeActivated', () {
blocTest<GameBloc, GameState>( blocTest<GameBloc, GameState>(
@ -231,7 +152,6 @@ void main() {
GameState( GameState(
score: 0, score: 0,
balls: 3, balls: 3,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [GameBonus.sparkyTurboCharge], bonusHistory: [GameBonus.sparkyTurboCharge],
), ),

@ -41,31 +41,22 @@ void main() {
}); });
}); });
group('BonusLetterActivated', () { group('BonusActivated', () {
test('can be instantiated', () { test('can be instantiated', () {
expect(const BonusLetterActivated(0), isNotNull); expect(const BonusActivated(GameBonus.dashNest), isNotNull);
}); });
test('supports value equality', () { test('supports value equality', () {
expect( expect(
BonusLetterActivated(0), BonusActivated(GameBonus.googleWord),
equals(BonusLetterActivated(0)), equals(const BonusActivated(GameBonus.googleWord)),
); );
expect( expect(
BonusLetterActivated(0), const BonusActivated(GameBonus.googleWord),
isNot(equals(BonusLetterActivated(1))), isNot(equals(const BonusActivated(GameBonus.dashNest))),
); );
}); });
});
test(
'throws assertion error if index is bigger than the word length',
() {
expect(
() => BonusLetterActivated(8),
throwsAssertionError,
);
},
);
}); });
group('DashNestActivated', () { group('DashNestActivated', () {
@ -97,5 +88,4 @@ void main() {
); );
}); });
}); });
});
} }

@ -10,7 +10,6 @@ void main() {
GameState( GameState(
score: 0, score: 0,
balls: 0, balls: 0,
activatedBonusLetters: const [],
activatedDashNests: const {}, activatedDashNests: const {},
bonusHistory: const [], bonusHistory: const [],
), ),
@ -18,7 +17,6 @@ void main() {
const GameState( const GameState(
score: 0, score: 0,
balls: 0, balls: 0,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
), ),
@ -32,7 +30,6 @@ void main() {
const GameState( const GameState(
score: 0, score: 0,
balls: 0, balls: 0,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
), ),
@ -49,7 +46,6 @@ void main() {
() => GameState( () => GameState(
balls: -1, balls: -1,
score: 0, score: 0,
activatedBonusLetters: const [],
activatedDashNests: const {}, activatedDashNests: const {},
bonusHistory: const [], bonusHistory: const [],
), ),
@ -66,7 +62,6 @@ void main() {
() => GameState( () => GameState(
balls: 0, balls: 0,
score: -1, score: -1,
activatedBonusLetters: const [],
activatedDashNests: const {}, activatedDashNests: const {},
bonusHistory: const [], bonusHistory: const [],
), ),
@ -82,7 +77,6 @@ void main() {
const gameState = GameState( const gameState = GameState(
balls: 0, balls: 0,
score: 0, score: 0,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
); );
@ -95,7 +89,6 @@ void main() {
const gameState = GameState( const gameState = GameState(
balls: 1, balls: 1,
score: 0, score: 0,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
); );
@ -103,36 +96,6 @@ void main() {
}); });
}); });
group('isLetterActivated', () {
test(
'is true when the letter is activated',
() {
const gameState = GameState(
balls: 3,
score: 0,
activatedBonusLetters: [1],
activatedDashNests: {},
bonusHistory: [],
);
expect(gameState.isLetterActivated(1), isTrue);
},
);
test(
'is false when the letter is not activated',
() {
const gameState = GameState(
balls: 3,
score: 0,
activatedBonusLetters: [1],
activatedDashNests: {},
bonusHistory: [],
);
expect(gameState.isLetterActivated(0), isFalse);
},
);
});
group('copyWith', () { group('copyWith', () {
test( test(
'throws AssertionError ' 'throws AssertionError '
@ -141,7 +104,6 @@ void main() {
const gameState = GameState( const gameState = GameState(
balls: 0, balls: 0,
score: 2, score: 2,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
); );
@ -159,7 +121,6 @@ void main() {
const gameState = GameState( const gameState = GameState(
balls: 0, balls: 0,
score: 2, score: 2,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
); );
@ -177,16 +138,14 @@ void main() {
const gameState = GameState( const gameState = GameState(
score: 2, score: 2,
balls: 0, balls: 0,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
); );
final otherGameState = GameState( final otherGameState = GameState(
score: gameState.score + 1, score: gameState.score + 1,
balls: gameState.balls + 1, balls: gameState.balls + 1,
activatedBonusLetters: const [0],
activatedDashNests: const {'1'}, activatedDashNests: const {'1'},
bonusHistory: const [GameBonus.word], bonusHistory: const [GameBonus.googleWord],
); );
expect(gameState, isNot(equals(otherGameState))); expect(gameState, isNot(equals(otherGameState)));
@ -194,7 +153,6 @@ void main() {
gameState.copyWith( gameState.copyWith(
score: otherGameState.score, score: otherGameState.score,
balls: otherGameState.balls, balls: otherGameState.balls,
activatedBonusLetters: otherGameState.activatedBonusLetters,
activatedDashNests: otherGameState.activatedDashNests, activatedDashNests: otherGameState.activatedDashNests,
bonusHistory: otherGameState.bonusHistory, bonusHistory: otherGameState.bonusHistory,
), ),

@ -1,376 +0,0 @@
// 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_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(EmptyPinballGameTest.new);
group('BonusWord', () {
flameTester.test(
'loads the letters correctly',
(game) async {
final bonusWord = BonusWord(
position: Vector2.zero(),
);
await game.ensureAdd(bonusWord);
final letters = bonusWord.descendants().whereType<BonusLetter>();
expect(letters.length, equals(GameBloc.bonusWord.length));
},
);
group('listenWhen', () {
final previousState = MockGameState();
final currentState = MockGameState();
test(
'returns true when there is a new word bonus awarded',
() {
when(() => previousState.bonusHistory).thenReturn([]);
when(() => currentState.bonusHistory).thenReturn([GameBonus.word]);
expect(
BonusWord(position: Vector2.zero()).listenWhen(
previousState,
currentState,
),
isTrue,
);
},
);
test(
'returns false when there is no new word bonus awarded',
() {
when(() => previousState.bonusHistory).thenReturn([GameBonus.word]);
when(() => currentState.bonusHistory).thenReturn([GameBonus.word]);
expect(
BonusWord(position: Vector2.zero()).listenWhen(
previousState,
currentState,
),
isFalse,
);
},
);
});
group('onNewState', () {
final state = MockGameState();
flameTester.test(
'adds sequence effect to the letters when the player receives a bonus',
(game) async {
when(() => state.bonusHistory).thenReturn([GameBonus.word]);
final bonusWord = BonusWord(position: Vector2.zero());
await game.ensureAdd(bonusWord);
await game.ready();
bonusWord.onNewState(state);
game.update(0); // Run one frame so the effects are added
final letters = bonusWord.children.whereType<BonusLetter>();
expect(letters.length, equals(GameBloc.bonusWord.length));
for (final letter in letters) {
expect(
letter.children.whereType<SequenceEffect>().length,
equals(1),
);
}
},
);
flameTester.test(
'plays the google bonus sound',
(game) async {
when(() => state.bonusHistory).thenReturn([GameBonus.word]);
final bonusWord = BonusWord(position: Vector2.zero());
await game.ensureAdd(bonusWord);
await game.ready();
bonusWord.onNewState(state);
verify(bonusWord.gameRef.audio.googleBonus).called(1);
},
);
flameTester.test(
'adds a color effect to reset the color when the sequence is finished',
(game) async {
when(() => state.bonusHistory).thenReturn([GameBonus.word]);
final bonusWord = BonusWord(position: Vector2.zero());
await game.ensureAdd(bonusWord);
await game.ready();
bonusWord.onNewState(state);
// Run the amount of time necessary for the animation to finish
game.update(3);
game.update(0); // Run one additional frame so the effects are added
final letters = bonusWord.children.whereType<BonusLetter>();
expect(letters.length, equals(GameBloc.bonusWord.length));
for (final letter in letters) {
expect(
letter.children.whereType<ColorEffect>().length,
equals(1),
);
}
},
);
});
});
group('BonusLetter', () {
final flameTester = FlameTester(EmptyPinballGameTest.new);
flameTester.test(
'loads correctly',
(game) async {
final bonusLetter = BonusLetter(
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
await game.ready();
expect(game.contains(bonusLetter), isTrue);
},
);
group('body', () {
flameTester.test(
'is static',
(game) async {
final bonusLetter = BonusLetter(
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(
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
expect(bonusLetter.body.fixtures[0], isA<Fixture>());
},
);
flameTester.test(
'is sensor',
(game) async {
final bonusLetter = BonusLetter(
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(
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(1.85));
},
);
});
group('bonus letter activation', () {
late GameBloc gameBloc;
late PinballAudio pinballAudio;
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballGameTest.new,
blocBuilder: () => gameBloc,
repositories: () => [
RepositoryProvider<PinballAudio>.value(value: pinballAudio),
],
);
setUp(() {
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
pinballAudio = MockPinballAudio();
when(pinballAudio.googleBonus).thenAnswer((_) {});
});
flameBlocTester.testGameWidget(
'adds BonusLetterActivated to GameBloc when not activated',
setUp: (game, tester) async {
final bonusWord = BonusWord(
position: Vector2.zero(),
);
await game.ensureAdd(bonusWord);
final bonusLetters =
game.descendants().whereType<BonusLetter>().toList();
for (var index = 0; index < bonusLetters.length; index++) {
final bonusLetter = bonusLetters[index];
bonusLetter.activate();
await game.ready();
verify(() => gameBloc.add(BonusLetterActivated(index))).called(1);
}
},
);
flameBlocTester.testGameWidget(
"doesn't add BonusLetterActivated to GameBloc when already activated",
setUp: (game, tester) async {
const state = GameState(
score: 0,
balls: 2,
activatedBonusLetters: [0],
activatedDashNests: {},
bonusHistory: [],
);
whenListen(
gameBloc,
Stream.value(state),
initialState: state,
);
final bonusLetter = BonusLetter(letter: '', index: 0);
await game.add(bonusLetter);
await game.ready();
bonusLetter.activate();
await game.ready();
},
verify: (game, tester) async {
verifyNever(() => gameBloc.add(const BonusLetterActivated(0)));
},
);
flameBlocTester.testGameWidget(
'adds a ColorEffect',
setUp: (game, tester) async {
const state = GameState(
score: 0,
balls: 2,
activatedBonusLetters: [0],
activatedDashNests: {},
bonusHistory: [],
);
final bonusLetter = BonusLetter(letter: '', index: 0);
await game.add(bonusLetter);
await game.ready();
bonusLetter.activate();
bonusLetter.onNewState(state);
await tester.pump();
},
verify: (game, tester) async {
// TODO(aleastiago): Look into making `testGameWidget` pass the
// subject.
final bonusLetter = game.descendants().whereType<BonusLetter>().last;
expect(
bonusLetter.children.whereType<ColorEffect>().length,
equals(1),
);
},
);
flameBlocTester.testGameWidget(
'listens when there is a change on the letter status',
setUp: (game, tester) async {
final bonusWord = BonusWord(
position: Vector2.zero(),
);
await game.ensureAdd(bonusWord);
final bonusLetters =
game.descendants().whereType<BonusLetter>().toList();
for (var index = 0; index < bonusLetters.length; index++) {
final bonusLetter = bonusLetters[index];
bonusLetter.activate();
await game.ready();
final state = GameState(
score: 0,
balls: 2,
activatedBonusLetters: [index],
activatedDashNests: const {},
bonusHistory: const [],
);
expect(
bonusLetter.listenWhen(const GameState.initial(), state),
isTrue,
);
}
},
);
});
group('BonusLetterBallContactCallback', () {
test('calls ball.activate', () {
final ball = MockBall();
final bonusLetter = MockBonusLetter();
final contactCallback = BonusLetterBallContactCallback();
when(() => bonusLetter.isEnabled).thenReturn(true);
contactCallback.begin(ball, bonusLetter, MockContact());
verify(bonusLetter.activate).called(1);
});
test("doesn't call ball.activate when letter is disabled", () {
final ball = MockBall();
final bonusLetter = MockBonusLetter();
final contactCallback = BonusLetterBallContactCallback();
when(() => bonusLetter.isEnabled).thenReturn(false);
contactCallback.begin(ball, bonusLetter, MockContact());
verifyNever(bonusLetter.activate);
});
});
});
}

@ -21,7 +21,6 @@ void main() {
score: 0, score: 0,
balls: 0, balls: 0,
bonusHistory: [], bonusHistory: [],
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
); );
whenListen(bloc, Stream.value(state), initialState: state); whenListen(bloc, Stream.value(state), initialState: state);

@ -22,7 +22,6 @@ void main() {
score: 0, score: 0,
balls: 0, balls: 0,
bonusHistory: [], bonusHistory: [],
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
); );
whenListen(bloc, Stream.value(state), initialState: state); whenListen(bloc, Stream.value(state), initialState: state);

@ -95,7 +95,6 @@ void main() {
const state = GameState( const state = GameState(
score: 0, score: 0,
balls: 3, balls: 3,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [GameBonus.dashNest], bonusHistory: [GameBonus.dashNest],
); );

@ -15,7 +15,6 @@ void main() {
final state = GameState( final state = GameState(
score: 10, score: 10,
balls: 0, balls: 0,
activatedBonusLetters: const [],
bonusHistory: const [], bonusHistory: const [],
activatedDashNests: const {}, activatedDashNests: const {},
); );
@ -66,7 +65,6 @@ void main() {
GameState( GameState(
score: 10, score: 10,
balls: 0, balls: 0,
activatedBonusLetters: const [],
bonusHistory: const [], bonusHistory: const [],
activatedDashNests: const {}, activatedDashNests: const {},
), ),

@ -0,0 +1,73 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockingjay/mockingjay.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('GoogleWord', () {
late GameBloc gameBloc;
setUp(() {
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameTester = FlameTester(EmptyPinballGameTest.new);
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballGameTest.new,
blocBuilder: () => gameBloc,
);
flameTester.test(
'loads the letters correctly',
(game) async {
const word = 'Google';
final googleWord = GoogleWord(position: Vector2.zero());
await game.ensureAdd(googleWord);
final letters = googleWord.children.whereType<GoogleLetter>();
expect(letters.length, equals(word.length));
},
);
flameBlocTester.testGameWidget(
'adds GameBonus.googleWord to the game when all letters are activated',
setUp: (game, _) async {
final ball = Ball(baseColor: const Color(0xFFFF0000));
final googleWord = GoogleWord(position: Vector2.zero());
await game.ensureAddAll([googleWord, ball]);
final letters = googleWord.children.whereType<GoogleLetter>();
expect(letters, isNotEmpty);
for (final letter in letters) {
beginContact(game, letter, ball);
await game.ready();
if (letter == letters.last) {
verify(
() => gameBloc.add(const BonusActivated(GameBonus.googleWord)),
).called(1);
} else {
verifyNever(
() => gameBloc.add(const BonusActivated(GameBonus.googleWord)),
);
}
}
},
);
});
}

@ -30,7 +30,6 @@ void main() {
const current = GameState( const current = GameState(
score: 10, score: 10,
balls: 3, balls: 3,
activatedBonusLetters: [],
bonusHistory: [], bonusHistory: [],
activatedDashNests: {}, activatedDashNests: {},
); );
@ -44,7 +43,6 @@ void main() {
const current = GameState( const current = GameState(
score: 10, score: 10,
balls: 3, balls: 3,
activatedBonusLetters: [],
bonusHistory: [], bonusHistory: [],
activatedDashNests: {}, activatedDashNests: {},
); );
@ -70,7 +68,6 @@ void main() {
const state = GameState( const state = GameState(
score: 10, score: 10,
balls: 3, balls: 3,
activatedBonusLetters: [],
bonusHistory: [], bonusHistory: [],
activatedDashNests: {}, activatedDashNests: {},
); );
@ -89,7 +86,6 @@ void main() {
const GameState( const GameState(
score: 10, score: 10,
balls: 3, balls: 3,
activatedBonusLetters: [],
bonusHistory: [], bonusHistory: [],
activatedDashNests: {}, activatedDashNests: {},
), ),
@ -99,7 +95,6 @@ void main() {
const GameState( const GameState(
score: 14, score: 14,
balls: 3, balls: 3,
activatedBonusLetters: [],
bonusHistory: [], bonusHistory: [],
activatedDashNests: {}, activatedDashNests: {},
), ),

@ -12,7 +12,6 @@ void main() {
const initialState = GameState( const initialState = GameState(
score: 10, score: 10,
balls: 2, balls: 2,
activatedBonusLetters: [],
activatedDashNests: {}, activatedDashNests: {},
bonusHistory: [], bonusHistory: [],
); );

@ -64,8 +64,6 @@ class MockTapUpInfo extends Mock implements TapUpInfo {}
class MockEventPosition extends Mock implements EventPosition {} class MockEventPosition extends Mock implements EventPosition {}
class MockBonusLetter extends Mock implements BonusLetter {}
class MockFilter extends Mock implements Filter {} class MockFilter extends Mock implements Filter {}
class MockFixture extends Mock implements Fixture {} class MockFixture extends Mock implements Fixture {}

Loading…
Cancel
Save