Merge branch 'main' into feat/spaceship-ramp-shot-logic

pull/296/head
RuiAlonso 3 years ago
commit 1175c9e7ab

@ -17,12 +17,13 @@ class GameBloc extends Bloc<GameEvent, GameState> {
} }
void _onRoundLost(RoundLost event, Emitter emit) { void _onRoundLost(RoundLost event, Emitter emit) {
final score = state.score * state.multiplier; final score = state.totalScore + state.roundScore * state.multiplier;
final roundsLeft = math.max(state.rounds - 1, 0); final roundsLeft = math.max(state.rounds - 1, 0);
emit( emit(
state.copyWith( state.copyWith(
score: score, totalScore: score,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: roundsLeft, rounds: roundsLeft,
), ),
@ -32,7 +33,7 @@ class GameBloc extends Bloc<GameEvent, GameState> {
void _onScored(Scored event, Emitter emit) { void _onScored(Scored event, Emitter emit) {
if (!state.isGameOver) { if (!state.isGameOver) {
emit( emit(
state.copyWith(score: state.score + event.points), state.copyWith(roundScore: state.roundScore + event.points),
); );
} }
} }

@ -26,22 +26,32 @@ enum GameBonus {
class GameState extends Equatable { class GameState extends Equatable {
/// {@macro game_state} /// {@macro game_state}
const GameState({ const GameState({
required this.score, required this.totalScore,
required this.roundScore,
required this.multiplier, required this.multiplier,
required this.rounds, required this.rounds,
required this.bonusHistory, required this.bonusHistory,
}) : assert(score >= 0, "Score can't be negative"), }) : assert(totalScore >= 0, "TotalScore can't be negative"),
assert(roundScore >= 0, "Round score can't be negative"),
assert(multiplier > 0, 'Multiplier must be greater than zero'), assert(multiplier > 0, 'Multiplier must be greater than zero'),
assert(rounds >= 0, "Number of rounds can't be negative"); assert(rounds >= 0, "Number of rounds can't be negative");
const GameState.initial() const GameState.initial()
: score = 0, : totalScore = 0,
roundScore = 0,
multiplier = 1, multiplier = 1,
rounds = 3, rounds = 3,
bonusHistory = const []; bonusHistory = const [];
/// The current score of the game. /// The score for the current round of the game.
final int score; ///
/// Multipliers are only applied to the score for the current round once is
/// lost. Then the [roundScore] is added to the [totalScore] and reset to 0
/// for the next round.
final int roundScore;
/// The total score of the game.
final int totalScore;
/// The current multiplier for the score. /// The current multiplier for the score.
final int multiplier; final int multiplier;
@ -58,20 +68,25 @@ class GameState extends Equatable {
/// Determines when the game is over. /// Determines when the game is over.
bool get isGameOver => rounds == 0; bool get isGameOver => rounds == 0;
/// The score displayed at the game.
int get displayScore => roundScore + totalScore;
GameState copyWith({ GameState copyWith({
int? score, int? totalScore,
int? roundScore,
int? multiplier, int? multiplier,
int? balls, int? balls,
int? rounds, int? rounds,
List<GameBonus>? bonusHistory, List<GameBonus>? bonusHistory,
}) { }) {
assert( assert(
score == null || score >= this.score, totalScore == null || totalScore >= this.totalScore,
"Score can't be decreased", "Total score can't be decreased",
); );
return GameState( return GameState(
score: score ?? this.score, totalScore: totalScore ?? this.totalScore,
roundScore: roundScore ?? this.roundScore,
multiplier: multiplier ?? this.multiplier, multiplier: multiplier ?? this.multiplier,
rounds: rounds ?? this.rounds, rounds: rounds ?? this.rounds,
bonusHistory: bonusHistory ?? this.bonusHistory, bonusHistory: bonusHistory ?? this.bonusHistory,
@ -80,7 +95,8 @@ class GameState extends Equatable {
@override @override
List<Object?> get props => [ List<Object?> get props => [
score, totalScore,
roundScore,
multiplier, multiplier,
rounds, rounds,
bonusHistory, bonusHistory,

@ -30,7 +30,7 @@ class GameFlowController extends ComponentController<PinballGame>
// TODO(erickzanardo): implement score submission and "navigate" to the // TODO(erickzanardo): implement score submission and "navigate" to the
// next page // next page
component.descendants().whereType<Backbox>().first.initialsInput( component.descendants().whereType<Backbox>().first.initialsInput(
score: state?.score ?? 0, score: state?.displayScore ?? 0,
characterIconPath: component.characterTheme.leaderboardIcon.keyName, characterIconPath: component.characterTheme.leaderboardIcon.keyName,
); );
component.firstChild<CameraController>()!.focusOnGameOverBackbox(); component.firstChild<CameraController>()!.focusOnGameOverBackbox();

@ -81,7 +81,7 @@ class PinballGame extends PinballForge2DGame
await super.onLoad(); await super.onLoad();
} }
BoardSide? focusedBoardSide; final focusedBoardSide = <int, BoardSide>{};
@override @override
void onTapDown(int pointerId, TapDownInfo info) { void onTapDown(int pointerId, TapDownInfo info) {
@ -94,9 +94,10 @@ class PinballGame extends PinballForge2DGame
descendants().whereType<Plunger>().single.pullFor(2); descendants().whereType<Plunger>().single.pullFor(2);
} else { } else {
final leftSide = info.eventPosition.widget.x < canvasSize.x / 2; final leftSide = info.eventPosition.widget.x < canvasSize.x / 2;
focusedBoardSide = leftSide ? BoardSide.left : BoardSide.right; focusedBoardSide[pointerId] =
leftSide ? BoardSide.left : BoardSide.right;
final flippers = descendants().whereType<Flipper>().where((flipper) { final flippers = descendants().whereType<Flipper>().where((flipper) {
return flipper.side == focusedBoardSide; return flipper.side == focusedBoardSide[pointerId];
}); });
flippers.first.moveUp(); flippers.first.moveUp();
} }
@ -107,23 +108,23 @@ class PinballGame extends PinballForge2DGame
@override @override
void onTapUp(int pointerId, TapUpInfo info) { void onTapUp(int pointerId, TapUpInfo info) {
_moveFlippersDown(); _moveFlippersDown(pointerId);
super.onTapUp(pointerId, info); super.onTapUp(pointerId, info);
} }
@override @override
void onTapCancel(int pointerId) { void onTapCancel(int pointerId) {
_moveFlippersDown(); _moveFlippersDown(pointerId);
super.onTapCancel(pointerId); super.onTapCancel(pointerId);
} }
void _moveFlippersDown() { void _moveFlippersDown(int pointerId) {
if (focusedBoardSide != null) { if (focusedBoardSide[pointerId] != null) {
final flippers = descendants().whereType<Flipper>().where((flipper) { final flippers = descendants().whereType<Flipper>().where((flipper) {
return flipper.side == focusedBoardSide; return flipper.side == focusedBoardSide[pointerId];
}); });
flippers.first.moveDown(); flippers.first.moveDown();
focusedBoardSide = null; focusedBoardSide.remove(pointerId);
} }
} }
} }

@ -7,8 +7,8 @@ import 'package:pinball_ui/pinball_ui.dart';
/// {@template game_hud} /// {@template game_hud}
/// Overlay on the [PinballGame]. /// Overlay on the [PinballGame].
/// ///
/// Displays the current [GameState.score], [GameState.rounds] and animates when /// Displays the current [GameState.displayScore], [GameState.rounds] and
/// the player gets a [GameBonus]. /// animates when the player gets a [GameBonus].
/// {@endtemplate} /// {@endtemplate}
class GameHud extends StatefulWidget { class GameHud extends StatefulWidget {
/// {@macro game_hud} /// {@macro game_hud}

@ -69,7 +69,7 @@ class _ScoreText extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final score = context.select((GameBloc bloc) => bloc.state.score); final score = context.select((GameBloc bloc) => bloc.state.displayScore);
return Text( return Text(
score.formatScore(), score.formatScore(),

@ -52,7 +52,8 @@ void main() {
blocBuilder: () { blocBuilder: () {
bloc = _MockGameBloc(); bloc = _MockGameBloc();
const state = GameState( const state = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],

@ -6,7 +6,7 @@ void main() {
group('GameBloc', () { group('GameBloc', () {
test('initial state has 3 rounds and empty score', () { test('initial state has 3 rounds and empty score', () {
final gameBloc = GameBloc(); final gameBloc = GameBloc();
expect(gameBloc.state.score, equals(0)); expect(gameBloc.state.roundScore, equals(0));
expect(gameBloc.state.rounds, equals(3)); expect(gameBloc.state.rounds, equals(3));
}); });
@ -19,21 +19,17 @@ void main() {
bloc.add(const RoundLost()); bloc.add(const RoundLost());
}, },
expect: () => [ expect: () => [
const GameState( isA<GameState>()..having((state) => state.rounds, 'rounds', 2),
score: 0,
multiplier: 1,
rounds: 2,
bonusHistory: [],
),
], ],
); );
blocTest<GameBloc, GameState>( blocTest<GameBloc, GameState>(
'apply multiplier to score ' 'apply multiplier to roundScore and add it to totalScore '
'when round is lost', 'when round is lost',
build: GameBloc.new, build: GameBloc.new,
seed: () => const GameState( seed: () => const GameState(
score: 5, totalScore: 10,
roundScore: 5,
multiplier: 3, multiplier: 3,
rounds: 2, rounds: 2,
bonusHistory: [], bonusHistory: [],
@ -43,8 +39,8 @@ void main() {
}, },
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 15) ..having((state) => state.totalScore, 'totalScore', 25)
..having((state) => state.rounds, 'rounds', 1), ..having((state) => state.roundScore, 'roundScore', 0)
], ],
); );
@ -53,7 +49,8 @@ void main() {
'when round is lost', 'when round is lost',
build: GameBloc.new, build: GameBloc.new,
seed: () => const GameState( seed: () => const GameState(
score: 5, totalScore: 10,
roundScore: 5,
multiplier: 3, multiplier: 3,
rounds: 2, rounds: 2,
bonusHistory: [], bonusHistory: [],
@ -62,9 +59,7 @@ void main() {
bloc.add(const RoundLost()); bloc.add(const RoundLost());
}, },
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.rounds, 'rounds', 1),
], ],
); );
}); });
@ -79,10 +74,10 @@ void main() {
..add(const Scored(points: 3)), ..add(const Scored(points: 3)),
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 2) ..having((state) => state.roundScore, 'roundScore', 2)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 5) ..having((state) => state.roundScore, 'roundScore', 5)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
], ],
); );
@ -99,15 +94,15 @@ void main() {
}, },
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0) ..having((state) => state.roundScore, 'roundScore', 0)
..having((state) => state.rounds, 'rounds', 2) ..having((state) => state.rounds, 'rounds', 2)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0) ..having((state) => state.roundScore, 'roundScore', 0)
..having((state) => state.rounds, 'rounds', 1) ..having((state) => state.rounds, 'rounds', 1)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0) ..having((state) => state.roundScore, 'roundScore', 0)
..having((state) => state.rounds, 'rounds', 0) ..having((state) => state.rounds, 'rounds', 0)
..having((state) => state.isGameOver, 'isGameOver', true), ..having((state) => state.isGameOver, 'isGameOver', true),
], ],
@ -124,11 +119,9 @@ void main() {
..add(const MultiplierIncreased()), ..add(const MultiplierIncreased()),
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 2) ..having((state) => state.multiplier, 'multiplier', 2)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 3) ..having((state) => state.multiplier, 'multiplier', 3)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
], ],
@ -139,7 +132,8 @@ void main() {
'when multiplier is 6 and game is not over', 'when multiplier is 6 and game is not over',
build: GameBloc.new, build: GameBloc.new,
seed: () => const GameState( seed: () => const GameState(
score: 0, totalScore: 10,
roundScore: 0,
multiplier: 6, multiplier: 6,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
@ -160,15 +154,12 @@ void main() {
}, },
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 1) ..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 1) ..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 1) ..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.isGameOver, 'isGameOver', true), ..having((state) => state.isGameOver, 'isGameOver', true),
], ],

@ -8,14 +8,16 @@ void main() {
test('supports value equality', () { test('supports value equality', () {
expect( expect(
GameState( GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: const [], bonusHistory: const [],
), ),
equals( equals(
const GameState( const GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
@ -28,7 +30,8 @@ void main() {
test('can be instantiated', () { test('can be instantiated', () {
expect( expect(
const GameState( const GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
@ -40,11 +43,29 @@ void main() {
test( test(
'throws AssertionError ' 'throws AssertionError '
'when score is negative', 'when totalScore is negative',
() { () {
expect( expect(
() => GameState( () => GameState(
score: -1, totalScore: -1,
roundScore: 0,
multiplier: 1,
rounds: 3,
bonusHistory: const [],
),
throwsAssertionError,
);
},
);
test(
'throws AssertionError '
'when roundScore is negative',
() {
expect(
() => GameState(
totalScore: 0,
roundScore: -1,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: const [], bonusHistory: const [],
@ -60,7 +81,8 @@ void main() {
() { () {
expect( expect(
() => GameState( () => GameState(
score: 1, totalScore: 0,
roundScore: 1,
multiplier: 0, multiplier: 0,
rounds: 3, rounds: 3,
bonusHistory: const [], bonusHistory: const [],
@ -76,7 +98,8 @@ void main() {
() { () {
expect( expect(
() => GameState( () => GameState(
score: 1, totalScore: 0,
roundScore: 1,
multiplier: 1, multiplier: 1,
rounds: -1, rounds: -1,
bonusHistory: const [], bonusHistory: const [],
@ -91,7 +114,8 @@ void main() {
'is true ' 'is true '
'when no rounds are left', () { 'when no rounds are left', () {
const gameState = GameState( const gameState = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: [], bonusHistory: [],
@ -103,7 +127,8 @@ void main() {
'is false ' 'is false '
'when one 1 round left', () { 'when one 1 round left', () {
const gameState = GameState( const gameState = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 1, rounds: 1,
bonusHistory: [], bonusHistory: [],
@ -115,16 +140,17 @@ void main() {
group('copyWith', () { group('copyWith', () {
test( test(
'throws AssertionError ' 'throws AssertionError '
'when scored is decreased', 'when totalScore is decreased',
() { () {
const gameState = GameState( const gameState = GameState(
score: 2, totalScore: 2,
roundScore: 2,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
); );
expect( expect(
() => gameState.copyWith(score: gameState.score - 1), () => gameState.copyWith(totalScore: gameState.totalScore - 1),
throwsAssertionError, throwsAssertionError,
); );
}, },
@ -135,7 +161,8 @@ void main() {
'when no argument specified', 'when no argument specified',
() { () {
const gameState = GameState( const gameState = GameState(
score: 2, totalScore: 0,
roundScore: 2,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
@ -152,13 +179,15 @@ void main() {
'when all arguments specified', 'when all arguments specified',
() { () {
const gameState = GameState( const gameState = GameState(
score: 2, totalScore: 0,
roundScore: 2,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
); );
final otherGameState = GameState( final otherGameState = GameState(
score: gameState.score + 1, totalScore: gameState.totalScore + 1,
roundScore: gameState.roundScore + 1,
multiplier: gameState.multiplier + 1, multiplier: gameState.multiplier + 1,
rounds: gameState.rounds + 1, rounds: gameState.rounds + 1,
bonusHistory: const [GameBonus.googleWord], bonusHistory: const [GameBonus.googleWord],
@ -167,7 +196,8 @@ void main() {
expect( expect(
gameState.copyWith( gameState.copyWith(
score: otherGameState.score, totalScore: otherGameState.totalScore,
roundScore: otherGameState.roundScore,
multiplier: otherGameState.multiplier, multiplier: otherGameState.multiplier,
rounds: otherGameState.rounds, rounds: otherGameState.rounds,
bonusHistory: otherGameState.bonusHistory, bonusHistory: otherGameState.bonusHistory,

@ -27,7 +27,8 @@ void main() {
blocBuilder: () { blocBuilder: () {
final bloc = _MockGameBloc(); final bloc = _MockGameBloc();
const state = GameState( const state = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: [], bonusHistory: [],

@ -22,7 +22,8 @@ void main() {
blocBuilder: () { blocBuilder: () {
final bloc = _MockGameBloc(); final bloc = _MockGameBloc();
const state = GameState( const state = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: [], bonusHistory: [],

@ -23,7 +23,8 @@ void main() {
group('listenWhen', () { group('listenWhen', () {
test('is true when the game over state has changed', () { test('is true when the game over state has changed', () {
final state = GameState( final state = GameState(
score: 10, totalScore: 0,
roundScore: 10,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: const [], bonusHistory: const [],
@ -79,7 +80,8 @@ void main() {
() { () {
gameFlowController.onNewState( gameFlowController.onNewState(
GameState( GameState(
score: 0, totalScore: 0,
roundScore: 10,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: const [], bonusHistory: const [],

@ -74,7 +74,8 @@ void main() {
test('is false when the bonusHistory state is the same', () { test('is false when the bonusHistory state is the same', () {
final previous = GameState.initial(); final previous = GameState.initial();
final state = GameState( final state = GameState(
score: 10, totalScore: 0,
roundScore: 10,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: const [], bonusHistory: const [],

@ -56,7 +56,8 @@ void main() {
group('listenWhen', () { group('listenWhen', () {
test('is true when the multiplier has changed', () { test('is true when the multiplier has changed', () {
final state = GameState( final state = GameState(
score: 10, totalScore: 0,
roundScore: 10,
multiplier: 2, multiplier: 2,
rounds: 0, rounds: 0,
bonusHistory: const [], bonusHistory: const [],
@ -71,7 +72,8 @@ void main() {
test('is false when the multiplier state is the same', () { test('is false when the multiplier state is the same', () {
final state = GameState( final state = GameState(
score: 10, totalScore: 0,
roundScore: 10,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: const [], bonusHistory: const [],

@ -415,6 +415,51 @@ void main() {
expect(flippers.first.body.linearVelocity.y, isPositive); expect(flippers.first.body.linearVelocity.y, isPositive);
}); });
flameTester.test(
'multiple touches control both flippers',
(game) async {
await game.ready();
final raw = _MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final leftEventPosition = _MockEventPosition();
when(() => leftEventPosition.game).thenReturn(Vector2.zero());
when(() => leftEventPosition.widget).thenReturn(Vector2.zero());
final rightEventPosition = _MockEventPosition();
when(() => rightEventPosition.game).thenReturn(Vector2.zero());
when(() => rightEventPosition.widget).thenReturn(game.canvasSize);
final leftTapDownEvent = _MockTapDownInfo();
when(() => leftTapDownEvent.eventPosition)
.thenReturn(leftEventPosition);
when(() => leftTapDownEvent.raw).thenReturn(raw);
final rightTapDownEvent = _MockTapDownInfo();
when(() => rightTapDownEvent.eventPosition)
.thenReturn(rightEventPosition);
when(() => rightTapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>();
final rightFlipper = flippers.elementAt(0);
final leftFlipper = flippers.elementAt(1);
game.onTapDown(0, leftTapDownEvent);
game.onTapDown(1, rightTapDownEvent);
expect(leftFlipper.body.linearVelocity.y, isNegative);
expect(leftFlipper.side, equals(BoardSide.left));
expect(rightFlipper.body.linearVelocity.y, isNegative);
expect(rightFlipper.side, equals(BoardSide.right));
expect(
game.focusedBoardSide,
equals({0: BoardSide.left, 1: BoardSide.right}),
);
},
);
}); });
group('plunger control', () { group('plunger control', () {

@ -1,9 +1,5 @@
// ignore_for_file: invalid_use_of_protected_member // ignore_for_file: invalid_use_of_protected_member
import 'dart:typed_data';
import 'package:flame/assets.dart';
import 'package:flame/flame.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@ -13,8 +9,6 @@ import 'package:pinball_flame/pinball_flame.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
class _MockImages extends Mock implements Images {}
class _MockCallback extends Mock { class _MockCallback extends Mock {
void call(); void call();
} }
@ -24,13 +18,7 @@ void main() {
const animationDuration = 6; const animationDuration = 6;
setUp(() async { setUp(() async {
// TODO(arturplaczek): need to find for a better solution for loading image await mockFlameImages();
// or use original images from BonusAnimation.loadAssets()
final image = await decodeImageFromList(Uint8List.fromList(fakeImage));
final images = _MockImages();
when(() => images.fromCache(any())).thenReturn(image);
when(() => images.load(any())).thenAnswer((_) => Future.value(image));
Flame.images = images;
}); });
group('loads SpriteAnimationWidget correctly for', () { group('loads SpriteAnimationWidget correctly for', () {

@ -1,11 +1,8 @@
// ignore_for_file: prefer_const_constructors // ignore_for_file: prefer_const_constructors
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/assets.dart';
import 'package:flame/flame.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
@ -18,8 +15,6 @@ import 'package:pinball_ui/pinball_ui.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
class _MockImages extends Mock implements Images {}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
void main() { void main() {
@ -27,22 +22,17 @@ void main() {
late GameBloc gameBloc; late GameBloc gameBloc;
const initialState = GameState( const initialState = GameState(
score: 1000, totalScore: 0,
roundScore: 1000,
multiplier: 1, multiplier: 1,
rounds: 1, rounds: 1,
bonusHistory: [], bonusHistory: [],
); );
setUp(() async { setUp(() async {
gameBloc = _MockGameBloc(); await mockFlameImages();
// TODO(arturplaczek): need to find for a better solution for loading gameBloc = _MockGameBloc();
// image or use original images from BonusAnimation.loadAssets()
final image = await decodeImageFromList(Uint8List.fromList(fakeImage));
final images = _MockImages();
when(() => images.fromCache(any())).thenReturn(image);
when(() => images.load(any())).thenAnswer((_) => Future.value(image));
Flame.images = images;
whenListen( whenListen(
gameBloc, gameBloc,
@ -81,7 +71,10 @@ void main() {
gameBloc: gameBloc, gameBloc: gameBloc,
); );
expect(find.text(initialState.score.formatScore()), findsOneWidget); expect(
find.text(initialState.roundScore.formatScore()),
findsOneWidget,
);
}, },
); );

@ -1,10 +1,8 @@
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/flame.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_theme/pinball_theme.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
@ -21,14 +19,12 @@ void main() {
late CharacterThemeCubit characterThemeCubit; late CharacterThemeCubit characterThemeCubit;
setUp(() async { setUp(() async {
Flame.images.prefix = ''; await mockFlameImages();
await Flame.images.load(const DashTheme().animation.keyName);
await Flame.images.load(const AndroidTheme().animation.keyName);
await Flame.images.load(const DinoTheme().animation.keyName);
await Flame.images.load(const SparkyTheme().animation.keyName);
game = _MockPinballGame(); game = _MockPinballGame();
gameFlowController = _MockGameFlowController(); gameFlowController = _MockGameFlowController();
characterThemeCubit = _MockCharacterThemeCubit(); characterThemeCubit = _MockCharacterThemeCubit();
whenListen( whenListen(
characterThemeCubit, characterThemeCubit,
const Stream<CharacterThemeState>.empty(), const Stream<CharacterThemeState>.empty(),

@ -13,7 +13,8 @@ void main() {
group('RoundCountDisplay renders', () { group('RoundCountDisplay renders', () {
late GameBloc gameBloc; late GameBloc gameBloc;
const initialState = GameState( const initialState = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],

@ -15,9 +15,11 @@ class _MockGameBloc extends Mock implements GameBloc {}
void main() { void main() {
late GameBloc gameBloc; late GameBloc gameBloc;
late StreamController<GameState> stateController; late StreamController<GameState> stateController;
const score = 123456789; const totalScore = 123456789;
const roundScore = 1234;
const initialState = GameState( const initialState = GameState(
score: score, totalScore: totalScore,
roundScore: roundScore,
multiplier: 1, multiplier: 1,
rounds: 1, rounds: 1,
bonusHistory: [], bonusHistory: [],
@ -42,7 +44,10 @@ void main() {
); );
await tester.pump(); await tester.pump();
expect(find.text(score.formatScore()), findsOneWidget); expect(
find.text(initialState.displayScore.formatScore()),
findsOneWidget,
);
}); });
testWidgets('renders game over', (tester) async { testWidgets('renders game over', (tester) async {
@ -69,17 +74,23 @@ void main() {
gameBloc: gameBloc, gameBloc: gameBloc,
); );
expect(find.text(score.formatScore()), findsOneWidget); expect(
find.text(initialState.displayScore.formatScore()),
findsOneWidget,
);
final newState = initialState.copyWith( final newState = initialState.copyWith(
score: 987654321, roundScore: 5678,
); );
stateController.add(newState); stateController.add(newState);
await tester.pump(); await tester.pump();
expect(find.text(newState.score.formatScore()), findsOneWidget); expect(
find.text(newState.displayScore.formatScore()),
findsOneWidget,
);
}); });
}); });
} }

@ -5,70 +5,3 @@ import 'package:pinball/game/game.dart';
class FakeContact extends Fake implements Contact {} class FakeContact extends Fake implements Contact {}
class FakeGameEvent extends Fake implements GameEvent {} class FakeGameEvent extends Fake implements GameEvent {}
const fakeImage = <int>[
0x89,
0x50,
0x4E,
0x47,
0x0D,
0x0A,
0x1A,
0x0A,
0x00,
0x00,
0x00,
0x0D,
0x49,
0x48,
0x44,
0x52,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x01,
0x08,
0x06,
0x00,
0x00,
0x00,
0x1F,
0x15,
0xC4,
0x89,
0x00,
0x00,
0x00,
0x0A,
0x49,
0x44,
0x41,
0x54,
0x78,
0x9C,
0x63,
0x00,
0x01,
0x00,
0x00,
0x05,
0x00,
0x01,
0x0D,
0x0A,
0x2D,
0xB4,
0x00,
0x00,
0x00,
0x00,
0x49,
0x45,
0x4E,
0x44,
0xAE,
];

@ -2,6 +2,7 @@ export 'builders.dart';
export 'fakes.dart'; export 'fakes.dart';
export 'forge2d.dart'; export 'forge2d.dart';
export 'key_testers.dart'; export 'key_testers.dart';
export 'mock_flame_images.dart';
export 'pump_app.dart'; export 'pump_app.dart';
export 'test_games.dart'; export 'test_games.dart';
export 'text_span.dart'; export 'text_span.dart';

@ -0,0 +1,92 @@
import 'dart:typed_data';
import 'package:flame/assets.dart';
import 'package:flame/flame.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class _MockImages extends Mock implements Images {}
/// {@template mock_flame_images}
/// Mock for flame images instance.
///
/// Using real images blocks the tests, for this reason we need fake image
/// everywhere we use [Images.fromCache] or [Images.load].
/// {@endtemplate}
// TODO(arturplaczek): need to find for a better solution for loading image
// or use original images.
Future<void> mockFlameImages() async {
final image = await decodeImageFromList(Uint8List.fromList(_fakeImage));
final images = _MockImages();
when(() => images.fromCache(any())).thenReturn(image);
when(() => images.load(any())).thenAnswer((_) => Future.value(image));
Flame.images = images;
}
const _fakeImage = <int>[
0x89,
0x50,
0x4E,
0x47,
0x0D,
0x0A,
0x1A,
0x0A,
0x00,
0x00,
0x00,
0x0D,
0x49,
0x48,
0x44,
0x52,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x01,
0x08,
0x06,
0x00,
0x00,
0x00,
0x1F,
0x15,
0xC4,
0x89,
0x00,
0x00,
0x00,
0x0A,
0x49,
0x44,
0x41,
0x54,
0x78,
0x9C,
0x63,
0x00,
0x01,
0x00,
0x00,
0x05,
0x00,
0x01,
0x0D,
0x0A,
0x2D,
0xB4,
0x00,
0x00,
0x00,
0x00,
0x49,
0x45,
0x4E,
0x44,
0xAE,
];

@ -1,5 +1,4 @@
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/flame.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@ -17,11 +16,8 @@ void main() {
late CharacterThemeCubit characterThemeCubit; late CharacterThemeCubit characterThemeCubit;
setUp(() async { setUp(() async {
Flame.images.prefix = ''; await mockFlameImages();
await Flame.images.load(const DashTheme().animation.keyName);
await Flame.images.load(const AndroidTheme().animation.keyName);
await Flame.images.load(const DinoTheme().animation.keyName);
await Flame.images.load(const SparkyTheme().animation.keyName);
characterThemeCubit = _MockCharacterThemeCubit(); characterThemeCubit = _MockCharacterThemeCubit();
whenListen( whenListen(
characterThemeCubit, characterThemeCubit,

Loading…
Cancel
Save