Merge branch 'main' into feat/add-plunger

pull/25/head
Allison Ryan 4 years ago
commit 9117ae26f6

@ -0,0 +1,18 @@
name: geometry
on:
push:
paths:
- "packages/geometry/**"
- ".github/workflows/geometry.yaml"
pull_request:
paths:
- "packages/geometry/**"
- ".github/workflows/geometry.yaml"
jobs:
build:
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1
with:
working_directory: packages/geometry

@ -14,6 +14,8 @@ class GameBloc extends Bloc<GameEvent, GameState> {
on<BonusLetterActivated>(_onBonusLetterActivated); on<BonusLetterActivated>(_onBonusLetterActivated);
} }
static const bonusWord = 'GOOGLE';
void _onBallLost(BallLost event, Emitter emit) { void _onBallLost(BallLost event, Emitter emit) {
if (state.balls > 0) { if (state.balls > 0) {
emit(state.copyWith(balls: state.balls - 1)); emit(state.copyWith(balls: state.balls - 1));
@ -27,13 +29,25 @@ class GameBloc extends Bloc<GameEvent, GameState> {
} }
void _onBonusLetterActivated(BonusLetterActivated event, Emitter emit) { void _onBonusLetterActivated(BonusLetterActivated event, Emitter emit) {
emit( final newBonusLetters = [
state.copyWith( ...state.activatedBonusLetters,
bonusLetters: [ event.letterIndex,
...state.bonusLetters, ];
event.letter,
], if (newBonusLetters.length == bonusWord.length) {
), emit(
); state.copyWith(
activatedBonusLetters: [],
bonusHistory: [
...state.bonusHistory,
GameBonus.word,
],
),
);
} else {
emit(
state.copyWith(activatedBonusLetters: newBonusLetters),
);
}
} }
} }

@ -34,10 +34,14 @@ class Scored extends GameEvent {
} }
class BonusLetterActivated extends GameEvent { class BonusLetterActivated extends GameEvent {
const BonusLetterActivated(this.letter); const BonusLetterActivated(this.letterIndex)
: assert(
letterIndex < GameBloc.bonusWord.length,
'Index must be smaller than the length of the word',
);
final String letter; final int letterIndex;
@override @override
List<Object?> get props => [letter]; List<Object?> get props => [letterIndex];
} }

@ -2,6 +2,13 @@
part of 'game_bloc.dart'; part of 'game_bloc.dart';
/// Defines bonuses that a player can gain during a PinballGame.
enum GameBonus {
/// Bonus achieved when the user activate all of the bonus
/// letters on the board, forming the bonus word
word,
}
/// {@template game_state} /// {@template game_state}
/// Represents the state of the pinball game. /// Represents the state of the pinball game.
/// {@endtemplate} /// {@endtemplate}
@ -10,14 +17,16 @@ class GameState extends Equatable {
const GameState({ const GameState({
required this.score, required this.score,
required this.balls, required this.balls,
required this.bonusLetters, required this.activatedBonusLetters,
required this.bonusHistory,
}) : assert(score >= 0, "Score can't be negative"), }) : assert(score >= 0, "Score can't be negative"),
assert(balls >= 0, "Number of balls can't be negative"); assert(balls >= 0, "Number of balls can't be negative");
const GameState.initial() const GameState.initial()
: score = 0, : score = 0,
balls = 3, balls = 3,
bonusLetters = const []; activatedBonusLetters = const [],
bonusHistory = const [];
/// The current score of the game. /// The current score of the game.
final int score; final int score;
@ -28,7 +37,11 @@ class GameState extends Equatable {
final int balls; final int balls;
/// Active bonus letters. /// Active bonus letters.
final List<String> bonusLetters; final List<int> activatedBonusLetters;
/// Holds the history of all the [GameBonus]es earned by the player during a
/// PinballGame.
final List<GameBonus> bonusHistory;
/// Determines when the game is over. /// Determines when the game is over.
bool get isGameOver => balls == 0; bool get isGameOver => balls == 0;
@ -39,7 +52,8 @@ class GameState extends Equatable {
GameState copyWith({ GameState copyWith({
int? score, int? score,
int? balls, int? balls,
List<String>? bonusLetters, List<int>? activatedBonusLetters,
List<GameBonus>? bonusHistory,
}) { }) {
assert( assert(
score == null || score >= this.score, score == null || score >= this.score,
@ -49,7 +63,9 @@ class GameState extends Equatable {
return GameState( return GameState(
score: score ?? this.score, score: score ?? this.score,
balls: balls ?? this.balls, balls: balls ?? this.balls,
bonusLetters: bonusLetters ?? this.bonusLetters, activatedBonusLetters:
activatedBonusLetters ?? this.activatedBonusLetters,
bonusHistory: bonusHistory ?? this.bonusHistory,
); );
} }
@ -57,6 +73,7 @@ class GameState extends Equatable {
List<Object?> get props => [ List<Object?> get props => [
score, score,
balls, balls,
bonusLetters, activatedBonusLetters,
bonusHistory,
]; ];
} }

@ -53,7 +53,7 @@ class Ball extends PositionBodyComponent<PinballGame, SpriteComponent> {
final bloc = gameRef.read<GameBloc>()..add(const BallLost()); final bloc = gameRef.read<GameBloc>()..add(const BallLost());
final shouldBallRespwan = !bloc.state.isLastBall; final shouldBallRespwan = !bloc.state.isLastBall && !bloc.state.isGameOver;
if (shouldBallRespwan) { if (shouldBallRespwan) {
gameRef.spawnBall(); gameRef.spawnBall();
} }

@ -22,9 +22,4 @@ class BallScorePointsCallback extends ContactCallback<Ball, ScorePoints> {
) { ) {
ball.gameRef.read<GameBloc>().add(Scored(points: scorePoints.points)); ball.gameRef.read<GameBloc>().add(Scored(points: scorePoints.points));
} }
// TODO(alestiago): remove once this issue is closed.
// https://github.com/flame-engine/flame/issues/1414
@override
void end(Ball _, ScorePoints __, Contact ___) {}
} }

@ -77,7 +77,4 @@ class BottomWallBallContactCallback extends ContactCallback<Ball, BottomWall> {
void begin(Ball ball, BottomWall wall, Contact contact) { void begin(Ball ball, BottomWall wall, Contact contact) {
ball.lost(); ball.lost();
} }
@override
void end(_, __, ___) {}
} }

@ -155,3 +155,12 @@ class PinballGame extends Forge2DGame
); );
} }
} }
class DebugPinballGame extends PinballGame with TapDetector {
DebugPinballGame({required PinballTheme theme}) : super(theme: theme);
@override
void onTapUp(TapUpInfo info) {
add(Ball(position: info.eventPosition.game));
}
}

@ -1,6 +1,7 @@
// ignore_for_file: public_member_api_docs // ignore_for_file: public_member_api_docs
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flutter/foundation.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:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
@ -29,9 +30,15 @@ class PinballGamePage extends StatelessWidget {
} }
class PinballGameView extends StatefulWidget { class PinballGameView extends StatefulWidget {
const PinballGameView({Key? key, required this.theme}) : super(key: key); const PinballGameView({
Key? key,
required this.theme,
bool isDebugMode = kDebugMode,
}) : _isDebugMode = isDebugMode,
super(key: key);
final PinballTheme theme; final PinballTheme theme;
final bool _isDebugMode;
@override @override
State<PinballGameView> createState() => _PinballGameViewState(); State<PinballGameView> createState() => _PinballGameViewState();
@ -47,7 +54,10 @@ class _PinballGameViewState extends State<PinballGameView> {
// TODO(erickzanardo): Revisit this when we start to have more assets // TODO(erickzanardo): Revisit this when we start to have more assets
// this could expose a Stream (maybe even a cubit?) so we could show the // this could expose a Stream (maybe even a cubit?) so we could show the
// the loading progress with some fancy widgets. // the loading progress with some fancy widgets.
_game = PinballGame(theme: widget.theme)..preLoadAssets(); _game = (widget._isDebugMode
? DebugPinballGame(theme: widget.theme)
: PinballGame(theme: widget.theme))
..preLoadAssets();
} }
@override @override

@ -1,5 +1,6 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flame/extensions.dart';
import 'package:vector_math/vector_math_64.dart';
/// Calculates all [Vector2]s of a circumference. /// Calculates all [Vector2]s of a circumference.
/// ///

@ -7,13 +7,9 @@ environment:
sdk: ">=2.16.0 <3.0.0" sdk: ">=2.16.0 <3.0.0"
dependencies: dependencies:
flame: ^1.0.0 vector_math: ^2.1.1
flutter:
sdk: flutter
dev_dependencies: dev_dependencies:
flutter_test:
sdk: flutter
mocktail: ^0.2.0 mocktail: ^0.2.0
test: ^1.19.2 test: ^1.19.2
very_good_analysis: ^2.4.0 very_good_analysis: ^2.4.0

@ -1,7 +1,8 @@
// ignore_for_file: prefer_const_constructors, cascade_invocations // ignore_for_file: prefer_const_constructors, cascade_invocations
import 'package:flame/extensions.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:geometry/geometry.dart'; import 'package:geometry/geometry.dart';
import 'package:test/test.dart';
import 'package:vector_math/vector_math_64.dart';
class Binomial { class Binomial {
Binomial({required this.n, required this.k}); Binomial({required this.n, required this.k});
@ -42,18 +43,18 @@ void main() {
], ],
step: 2, step: 2,
), ),
throwsAssertionError, throwsA(isA<AssertionError>()),
); );
}); });
test('fails if not enough control points', () { test('fails if not enough control points', () {
expect( expect(
() => calculateBezierCurve(controlPoints: [Vector2.zero()]), () => calculateBezierCurve(controlPoints: [Vector2.zero()]),
throwsAssertionError, throwsA(isA<AssertionError>()),
); );
expect( expect(
() => calculateBezierCurve(controlPoints: []), () => calculateBezierCurve(controlPoints: []),
throwsAssertionError, throwsA(isA<AssertionError>()),
); );
}); });
@ -81,15 +82,24 @@ void main() {
group('binomial', () { group('binomial', () {
test('fails if k is negative', () { test('fails if k is negative', () {
expect(() => binomial(1, -1), throwsAssertionError); expect(
() => binomial(1, -1),
throwsA(isA<AssertionError>()),
);
}); });
test('fails if n is negative', () { test('fails if n is negative', () {
expect(() => binomial(-1, 1), throwsAssertionError); expect(
() => binomial(-1, 1),
throwsA(isA<AssertionError>()),
);
}); });
test('fails if n < k', () { test('fails if n < k', () {
expect(() => binomial(1, 2), throwsAssertionError); expect(
() => binomial(1, 2),
throwsA(isA<AssertionError>()),
);
}); });
test('for a specific input gives a correct value', () { test('for a specific input gives a correct value', () {
@ -131,7 +141,7 @@ void main() {
group('factorial', () { group('factorial', () {
test('fails if negative number', () { test('fails if negative number', () {
expect(() => factorial(-1), throwsAssertionError); expect(() => factorial(-1), throwsA(isA<AssertionError>()));
}); });
test('for a specific input gives a correct value', () { test('for a specific input gives a correct value', () {

@ -21,9 +21,24 @@ void main() {
} }
}, },
expect: () => [ expect: () => [
const GameState(score: 0, balls: 2, bonusLetters: []), const GameState(
const GameState(score: 0, balls: 1, bonusLetters: []), score: 0,
const GameState(score: 0, balls: 0, bonusLetters: []), balls: 2,
activatedBonusLetters: [],
bonusHistory: [],
),
const GameState(
score: 0,
balls: 1,
activatedBonusLetters: [],
bonusHistory: [],
),
const GameState(
score: 0,
balls: 0,
activatedBonusLetters: [],
bonusHistory: [],
),
], ],
); );
}); });
@ -37,8 +52,18 @@ void main() {
..add(const Scored(points: 2)) ..add(const Scored(points: 2))
..add(const Scored(points: 3)), ..add(const Scored(points: 3)),
expect: () => [ expect: () => [
const GameState(score: 2, balls: 3, bonusLetters: []), const GameState(
const GameState(score: 5, balls: 3, bonusLetters: []), score: 2,
balls: 3,
activatedBonusLetters: [],
bonusHistory: [],
),
const GameState(
score: 5,
balls: 3,
activatedBonusLetters: [],
bonusHistory: [],
),
], ],
); );
@ -53,9 +78,24 @@ void main() {
bloc.add(const Scored(points: 2)); bloc.add(const Scored(points: 2));
}, },
expect: () => [ expect: () => [
const GameState(score: 0, balls: 2, bonusLetters: []), const GameState(
const GameState(score: 0, balls: 1, bonusLetters: []), score: 0,
const GameState(score: 0, balls: 0, bonusLetters: []), balls: 2,
activatedBonusLetters: [],
bonusHistory: [],
),
const GameState(
score: 0,
balls: 1,
activatedBonusLetters: [],
bonusHistory: [],
),
const GameState(
score: 0,
balls: 0,
activatedBonusLetters: [],
bonusHistory: [],
),
], ],
); );
}); });
@ -65,42 +105,77 @@ void main() {
'adds the letter to the state', 'adds the letter to the state',
build: GameBloc.new, build: GameBloc.new,
act: (bloc) => bloc act: (bloc) => bloc
..add(const BonusLetterActivated('G')) ..add(const BonusLetterActivated(0))
..add(const BonusLetterActivated('O')) ..add(const BonusLetterActivated(1))
..add(const BonusLetterActivated('O')) ..add(const BonusLetterActivated(2)),
..add(const BonusLetterActivated('G')) expect: () => const [
..add(const BonusLetterActivated('L')) GameState(
..add(const BonusLetterActivated('E')),
expect: () => [
const GameState(
score: 0, score: 0,
balls: 3, balls: 3,
bonusLetters: ['G'], activatedBonusLetters: [0],
bonusHistory: [],
), ),
const GameState( GameState(
score: 0, score: 0,
balls: 3, balls: 3,
bonusLetters: ['G', 'O'], activatedBonusLetters: [0, 1],
bonusHistory: [],
), ),
const GameState( GameState(
score: 0, score: 0,
balls: 3, balls: 3,
bonusLetters: ['G', 'O', 'O'], activatedBonusLetters: [0, 1, 2],
bonusHistory: [],
), ),
const GameState( ],
);
blocTest<GameBloc, GameState>(
'adds the bonus when the bonusWord is completed',
build: GameBloc.new,
act: (bloc) => bloc
..add(const BonusLetterActivated(0))
..add(const BonusLetterActivated(1))
..add(const BonusLetterActivated(2))
..add(const BonusLetterActivated(3))
..add(const BonusLetterActivated(4))
..add(const BonusLetterActivated(5)),
expect: () => const [
GameState(
score: 0, score: 0,
balls: 3, balls: 3,
bonusLetters: ['G', 'O', 'O', 'G'], activatedBonusLetters: [0],
bonusHistory: [],
), ),
const GameState( GameState(
score: 0, score: 0,
balls: 3, balls: 3,
bonusLetters: ['G', 'O', 'O', 'G', 'L'], activatedBonusLetters: [0, 1],
bonusHistory: [],
), ),
const GameState( GameState(
score: 0,
balls: 3,
activatedBonusLetters: [0, 1, 2],
bonusHistory: [],
),
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [0, 1, 2, 3],
bonusHistory: [],
),
GameState(
score: 0,
balls: 3,
activatedBonusLetters: [0, 1, 2, 3, 4],
bonusHistory: [],
),
GameState(
score: 0, score: 0,
balls: 3, balls: 3,
bonusLetters: ['G', 'O', 'O', 'G', 'L', 'E'], activatedBonusLetters: [],
bonusHistory: [GameBonus.word],
), ),
], ],
); );

@ -43,19 +43,29 @@ void main() {
group('BonusLetterActivated', () { group('BonusLetterActivated', () {
test('can be instantiated', () { test('can be instantiated', () {
expect(const BonusLetterActivated('A'), isNotNull); expect(const BonusLetterActivated(0), isNotNull);
}); });
test('supports value equality', () { test('supports value equality', () {
expect( expect(
BonusLetterActivated('A'), BonusLetterActivated(0),
equals(BonusLetterActivated('A')), equals(BonusLetterActivated(0)),
); );
expect( expect(
BonusLetterActivated('B'), BonusLetterActivated(0),
isNot(equals(BonusLetterActivated('A'))), isNot(equals(BonusLetterActivated(1))),
); );
}); });
test(
'throws assertion error if index is bigger than the word length',
() {
expect(
() => BonusLetterActivated(8),
throwsAssertionError,
);
},
);
}); });
}); });
} }

@ -10,13 +10,15 @@ void main() {
GameState( GameState(
score: 0, score: 0,
balls: 0, balls: 0,
bonusLetters: const [], activatedBonusLetters: const [],
bonusHistory: const [],
), ),
equals( equals(
const GameState( const GameState(
score: 0, score: 0,
balls: 0, balls: 0,
bonusLetters: [], activatedBonusLetters: [],
bonusHistory: [],
), ),
), ),
); );
@ -25,7 +27,12 @@ void main() {
group('constructor', () { group('constructor', () {
test('can be instantiated', () { test('can be instantiated', () {
expect( expect(
const GameState(score: 0, balls: 0, bonusLetters: []), const GameState(
score: 0,
balls: 0,
activatedBonusLetters: [],
bonusHistory: [],
),
isNotNull, isNotNull,
); );
}); });
@ -36,7 +43,12 @@ void main() {
'when balls are negative', 'when balls are negative',
() { () {
expect( expect(
() => GameState(balls: -1, score: 0, bonusLetters: const []), () => GameState(
balls: -1,
score: 0,
activatedBonusLetters: const [],
bonusHistory: const [],
),
throwsAssertionError, throwsAssertionError,
); );
}, },
@ -47,7 +59,12 @@ void main() {
'when score is negative', 'when score is negative',
() { () {
expect( expect(
() => GameState(balls: 0, score: -1, bonusLetters: const []), () => GameState(
balls: 0,
score: -1,
activatedBonusLetters: const [],
bonusHistory: const [],
),
throwsAssertionError, throwsAssertionError,
); );
}, },
@ -60,7 +77,8 @@ void main() {
const gameState = GameState( const gameState = GameState(
balls: 0, balls: 0,
score: 0, score: 0,
bonusLetters: [], activatedBonusLetters: [],
bonusHistory: [],
); );
expect(gameState.isGameOver, isTrue); expect(gameState.isGameOver, isTrue);
}); });
@ -71,7 +89,8 @@ void main() {
const gameState = GameState( const gameState = GameState(
balls: 1, balls: 1,
score: 0, score: 0,
bonusLetters: [], activatedBonusLetters: [],
bonusHistory: [],
); );
expect(gameState.isGameOver, isFalse); expect(gameState.isGameOver, isFalse);
}); });
@ -85,7 +104,8 @@ void main() {
const gameState = GameState( const gameState = GameState(
balls: 1, balls: 1,
score: 0, score: 0,
bonusLetters: [], activatedBonusLetters: [],
bonusHistory: [],
); );
expect(gameState.isLastBall, isTrue); expect(gameState.isLastBall, isTrue);
}, },
@ -98,7 +118,8 @@ void main() {
const gameState = GameState( const gameState = GameState(
balls: 2, balls: 2,
score: 0, score: 0,
bonusLetters: [], activatedBonusLetters: [],
bonusHistory: [],
); );
expect(gameState.isLastBall, isFalse); expect(gameState.isLastBall, isFalse);
}, },
@ -113,7 +134,8 @@ void main() {
const gameState = GameState( const gameState = GameState(
balls: 0, balls: 0,
score: 2, score: 2,
bonusLetters: [], activatedBonusLetters: [],
bonusHistory: [],
); );
expect( expect(
() => gameState.copyWith(score: gameState.score - 1), () => gameState.copyWith(score: gameState.score - 1),
@ -129,7 +151,8 @@ void main() {
const gameState = GameState( const gameState = GameState(
balls: 0, balls: 0,
score: 2, score: 2,
bonusLetters: [], activatedBonusLetters: [],
bonusHistory: [],
); );
expect( expect(
gameState.copyWith(), gameState.copyWith(),
@ -145,12 +168,14 @@ void main() {
const gameState = GameState( const gameState = GameState(
score: 2, score: 2,
balls: 0, balls: 0,
bonusLetters: [], activatedBonusLetters: [],
bonusHistory: [],
); );
final otherGameState = GameState( final otherGameState = GameState(
score: gameState.score + 1, score: gameState.score + 1,
balls: gameState.balls + 1, balls: gameState.balls + 1,
bonusLetters: const ['A'], activatedBonusLetters: const [0],
bonusHistory: const [GameBonus.word],
); );
expect(gameState, isNot(equals(otherGameState))); expect(gameState, isNot(equals(otherGameState)));
@ -158,7 +183,8 @@ void main() {
gameState.copyWith( gameState.copyWith(
score: otherGameState.score, score: otherGameState.score,
balls: otherGameState.balls, balls: otherGameState.balls,
bonusLetters: otherGameState.bonusLetters, activatedBonusLetters: otherGameState.activatedBonusLetters,
bonusHistory: otherGameState.bonusHistory,
), ),
equals(otherGameState), equals(otherGameState),
); );

@ -11,7 +11,7 @@ void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
group('Anchor', () { group('Anchor', () {
final flameTester = FlameTester(PinballGameX.initial); final flameTester = FlameTester(PinballGameTest.create);
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',

@ -13,7 +13,7 @@ void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
group('Ball', () { group('Ball', () {
final flameTester = FlameTester(PinballGameX.initial); final flameTester = FlameTester(PinballGameTest.create);
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',
@ -137,7 +137,8 @@ void main() {
initialState: const GameState( initialState: const GameState(
score: 10, score: 10,
balls: 1, balls: 1,
bonusLetters: [], activatedBonusLetters: [],
bonusHistory: [],
), ),
); );
await game.ready(); await game.ready();

@ -12,7 +12,7 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameX.initial); final flameTester = FlameTester(PinballGameTest.create);
group( group(
'Flipper', 'Flipper',
() { () {

@ -10,7 +10,7 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameX.initial); final flameTester = FlameTester(PinballGameTest.create);
group('Pathway', () { group('Pathway', () {
const width = 50.0; const width = 50.0;

@ -13,7 +13,7 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameX.initial); final flameTester = FlameTester(PinballGameTest.create);
group('Plunger', () { group('Plunger', () {
const compressionDistance = 0.0; const compressionDistance = 0.0;

@ -32,7 +32,7 @@ void main() {
}, },
); );
}); });
final flameTester = FlameTester(PinballGameX.initial); final flameTester = FlameTester(PinballGameTest.create);
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',

@ -3,6 +3,7 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import '../helpers/helpers.dart'; import '../helpers/helpers.dart';
@ -10,7 +11,8 @@ import '../helpers/helpers.dart';
void main() { void main() {
group('PinballGame', () { group('PinballGame', () {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameX.initial); final flameTester = FlameTester(PinballGameTest.create);
final debugModeFlameTester = FlameTester(DebugPinballGameTest.create);
// TODO(alestiago): test if [PinballGame] registers // TODO(alestiago): test if [PinballGame] registers
// [BallScorePointsCallback] once the following issue is resolved: // [BallScorePointsCallback] once the following issue is resolved:
@ -94,5 +96,23 @@ void main() {
}, },
); );
}); });
debugModeFlameTester.test('adds a ball on tap up', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2.all(10));
final tapUpEvent = MockTapUpInfo();
when(() => tapUpEvent.eventPosition).thenReturn(eventPosition);
game.onTapUp(tapUpEvent);
await game.ready();
expect(
game.children.whereType<Ball>().length,
equals(1),
);
});
}); });
} }

@ -9,7 +9,12 @@ import '../../helpers/helpers.dart';
void main() { void main() {
group('GameHud', () { group('GameHud', () {
late GameBloc gameBloc; late GameBloc gameBloc;
const initialState = GameState(score: 10, balls: 2, bonusLetters: []); const initialState = GameState(
score: 10,
balls: 2,
activatedBonusLetters: [],
bonusHistory: [],
);
void _mockState(GameState state) { void _mockState(GameState state) {
whenListen( whenListen(

@ -1,3 +1,5 @@
// ignore_for_file: prefer_const_constructors
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -20,7 +22,7 @@ void main() {
); );
await tester.pumpApp( await tester.pumpApp(
const PinballGamePage(theme: theme), PinballGamePage(theme: theme),
gameBloc: gameBloc, gameBloc: gameBloc,
); );
expect(find.byType(PinballGameView), findsOneWidget); expect(find.byType(PinballGameView), findsOneWidget);
@ -64,9 +66,10 @@ void main() {
); );
await tester.pumpApp( await tester.pumpApp(
const PinballGameView(theme: theme), PinballGameView(theme: theme),
gameBloc: gameBloc, gameBloc: gameBloc,
); );
expect( expect(
find.byWidgetPredicate((w) => w is GameWidget<PinballGame>), find.byWidgetPredicate((w) => w is GameWidget<PinballGame>),
findsOneWidget, findsOneWidget,
@ -81,7 +84,13 @@ void main() {
'renders a game over dialog when the user has lost', 'renders a game over dialog when the user has lost',
(tester) async { (tester) async {
final gameBloc = MockGameBloc(); final gameBloc = MockGameBloc();
const state = GameState(score: 0, balls: 0, bonusLetters: []); const state = GameState(
score: 0,
balls: 0,
activatedBonusLetters: [],
bonusHistory: [],
);
whenListen( whenListen(
gameBloc, gameBloc,
Stream.value(state), Stream.value(state),
@ -100,5 +109,45 @@ void main() {
); );
}, },
); );
testWidgets('renders the real game when not in debug mode', (tester) async {
final gameBloc = MockGameBloc();
whenListen(
gameBloc,
Stream.value(const GameState.initial()),
initialState: const GameState.initial(),
);
await tester.pumpApp(
const PinballGameView(theme: theme, isDebugMode: false),
gameBloc: gameBloc,
);
expect(
find.byWidgetPredicate(
(w) => w is GameWidget<PinballGame> && w.game is! DebugPinballGame,
),
findsOneWidget,
);
});
testWidgets('renders the debug game when on debug mode', (tester) async {
final gameBloc = MockGameBloc();
whenListen(
gameBloc,
Stream.value(const GameState.initial()),
initialState: const GameState.initial(),
);
await tester.pumpApp(
const PinballGameView(theme: theme),
gameBloc: gameBloc,
);
expect(
find.byWidgetPredicate(
(w) => w is GameWidget<PinballGame> && w.game is DebugPinballGame,
),
findsOneWidget,
);
});
}); });
} }

@ -1,13 +1,14 @@
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_theme/pinball_theme.dart';
import 'helpers.dart';
FlameTester<PinballGame> flameBlocTester({ FlameTester<PinballGame> flameBlocTester({
required GameBloc gameBloc, required GameBloc gameBloc,
}) { }) {
return FlameTester<PinballGame>( return FlameTester<PinballGame>(
PinballGameX.initial, PinballGameTest.create,
pumpWidget: (gameWidget, tester) async { pumpWidget: (gameWidget, tester) async {
await tester.pumpWidget( await tester.pumpWidget(
BlocProvider.value( BlocProvider.value(
@ -18,11 +19,3 @@ FlameTester<PinballGame> flameBlocTester({
}, },
); );
} }
extension PinballGameX on PinballGame {
static PinballGame initial() => PinballGame(
theme: const PinballTheme(
characterTheme: DashTheme(),
),
);
}

@ -0,0 +1,22 @@
import 'package:pinball/game/game.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// [PinballGame] extension to reduce boilerplate in tests.
extension PinballGameTest on PinballGame {
/// Create [PinballGame] with default [PinballTheme].
static PinballGame create() => PinballGame(
theme: const PinballTheme(
characterTheme: DashTheme(),
),
);
}
/// [DebugPinballGame] extension to reduce boilerplate in tests.
extension DebugPinballGameTest on DebugPinballGame {
/// Create [PinballGame] with default [PinballTheme].
static DebugPinballGame create() => DebugPinballGame(
theme: const PinballTheme(
characterTheme: DashTheme(),
),
);
}

@ -1,11 +1,11 @@
// Copyright (c) 2021, Very Good Ventures
// https://verygood.ventures
// //
// Copyright (c) 2021, Very Good Ventures
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
// https://verygood.ventures
// license that can be found in the LICENSE file or at
export 'builders.dart'; export 'builders.dart';
export 'extensions.dart';
export 'key_testers.dart'; export 'key_testers.dart';
export 'mocks.dart'; export 'mocks.dart';
export 'pump_app.dart'; export 'pump_app.dart';

@ -1,3 +1,4 @@
import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -32,3 +33,7 @@ class MockRawKeyUpEvent extends Mock implements RawKeyUpEvent {
return super.toString(); return super.toString();
} }
} }
class MockTapUpInfo extends Mock implements TapUpInfo {}
class MockEventPosition extends Mock implements EventPosition {}

Loading…
Cancel
Save