feat: adding bonus letters to board

pull/35/head
Erick Zanardo 4 years ago
parent d2c1f60fb7
commit 3787a12bb0

@ -49,6 +49,11 @@ class GameState extends Equatable {
/// Determines when the player has only one ball left. /// Determines when the player has only one ball left.
bool get isLastBall => balls == 1; bool get isLastBall => balls == 1;
/// Shortcut method to check if the given [i]
/// is activated on the state
bool isLetterActivated(int i) =>
activatedBonusLetters.contains(i);
GameState copyWith({ GameState copyWith({
int? score, int? score,
int? balls, int? balls,

@ -1,6 +1,7 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -13,7 +14,6 @@ import 'package:pinball/game/game.dart';
/// {@endtemplate} /// {@endtemplate}
class BonusLetter extends BodyComponent<PinballGame> class BonusLetter extends BodyComponent<PinballGame>
with BlocComponent<GameBloc, GameState> { with BlocComponent<GameBloc, GameState> {
/// {@macro bonus_letter} /// {@macro bonus_letter}
BonusLetter({ BonusLetter({
required Vector2 position, required Vector2 position,
@ -22,14 +22,14 @@ class BonusLetter extends BodyComponent<PinballGame>
}) : _position = position, }) : _position = position,
_letter = letter, _letter = letter,
_index = index { _index = index {
paint = _disablePaint; paint = Paint()..color = _disableColor;
} }
/// The area size of this bonus letter /// The area size of this bonus letter
static final areaSize = Vector2.all(4); static final areaSize = Vector2.all(4);
static final _activePaint = Paint()..color = Colors.green; static const _activeColor = Colors.green;
static final _disablePaint = Paint()..color = Colors.red; static const _disableColor = Colors.red;
final Vector2 _position; final Vector2 _position;
final String _letter; final String _letter;
@ -41,7 +41,7 @@ class BonusLetter extends BodyComponent<PinballGame>
await add( await add(
TextComponent( TextComponent(
position: Vector2(-1, 1), position: Vector2(-1, -1),
text: _letter, text: _letter,
textRenderer: TextPaint( textRenderer: TextPaint(
style: const TextStyle(fontSize: 2, color: Colors.white), style: const TextStyle(fontSize: 2, color: Colors.white),
@ -64,12 +64,35 @@ class BonusLetter extends BodyComponent<PinballGame>
return world.createBody(bodyDef)..createFixture(fixtureDef); 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);
final color = isActive ? _activeColor : _disableColor;
add(
ColorEffect(
color,
const Offset(0, 1),
EffectController(duration: 0.25),
),
);
}
/// When called, will activate this letter, if still not activated /// When called, will activate this letter, if still not activated
void activate() { void activate() {
// TODO final isActive = state?.isLetterActivated(_index) ?? false;
//gameRef.read<GameBloc>().add(BonusLetterActivated(_index)); if (!isActive) {
gameRef.read<GameBloc>().add(BonusLetterActivated(_index));
paint = _activePaint; }
} }
} }

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

@ -63,6 +63,8 @@ class _PinballGameViewState extends State<PinballGameView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocListener<GameBloc, GameState>( return BlocListener<GameBloc, GameState>(
listenWhen: (previous, current) =>
previous.isGameOver != current.isGameOver,
listener: (context, state) { listener: (context, state) {
if (state.isGameOver) { if (state.isGameOver) {
showDialog<void>( showDialog<void>(

@ -126,6 +126,34 @@ void main() {
); );
}); });
group('isLetterActivated', () {
test(
'is true when the letter is activated',
() {
const gameState = GameState(
balls: 3,
score: 0,
activatedBonusLetters: [1],
bonusHistory: [],
);
expect(gameState.isLetterActivated(1), isTrue);
},
);
test(
'is false when the letter is not activated',
() {
const gameState = GameState(
balls: 3,
score: 0,
activatedBonusLetters: [1],
bonusHistory: [],
);
expect(gameState.isLetterActivated(0), isFalse);
},
);
});
group('copyWith', () { group('copyWith', () {
test( test(
'throws AssertionError ' 'throws AssertionError '

@ -114,7 +114,7 @@ void main() {
(game, tester) async { (game, tester) async {
await game.ready(); await game.ready();
game.children.whereType<Ball>().first.removeFromParent(); game.children.whereType<Ball>().first.lost();
await game.ready(); // Making sure that all additions are done await game.ready(); // Making sure that all additions are done
expect( expect(

@ -0,0 +1,221 @@
// 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_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('BonusLetter', () {
final flameTester = FlameTester(PinballGameTest.create);
flameTester.test(
'loads correctly',
(game) async {
final bonusLetter = BonusLetter(
position: Vector2.zero(),
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
await game.ready();
expect(game.contains(bonusLetter), isTrue);
},
);
group('body', () {
flameTester.test(
'positions correctly',
(game) async {
final position = Vector2.all(10);
final bonusLetter = BonusLetter(
position: position,
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
game.contains(bonusLetter);
expect(bonusLetter.body.position, position);
},
);
flameTester.test(
'is static',
(game) async {
final bonusLetter = BonusLetter(
position: Vector2.zero(),
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
expect(bonusLetter.body.bodyType, equals(BodyType.static));
},
);
});
group('first fixture', () {
flameTester.test(
'exists',
(game) async {
final bonusLetter = BonusLetter(
position: Vector2.zero(),
letter: 'G',
index: 0,
);
await game.ensureAdd(bonusLetter);
expect(bonusLetter.body.fixtures[0], isA<Fixture>());
},
);
flameTester.test(
'is sensor',
(game) async {
final bonusLetter = BonusLetter(
position: Vector2.zero(),
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(
position: Vector2.zero(),
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(2));
},
);
});
group('bonus letter activation', () {
final gameBloc = MockGameBloc();
setUp(() {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final tester = flameBlocTester(gameBloc: gameBloc);
tester.widgetTest(
'adds BonusLetterActivated to GameBloc when not activated',
(game, tester) async {
await game.ready();
game.children.whereType<BonusLetter>().first.activate();
await tester.pump();
verify(() => gameBloc.add(const BonusLetterActivated(0))).called(1);
},
);
tester.widgetTest(
"don't add BonusLetterActivated to GameBloc when is already activated",
(game, tester) async {
const state = GameState(
score: 0,
balls: 2,
activatedBonusLetters: [0],
bonusHistory: [],
);
whenListen(
gameBloc,
Stream.value(state),
initialState: state,
);
await game.ready();
game.children.whereType<BonusLetter>().first.activate();
await game.ready(); // Making sure that all additions are done
verifyNever(() => gameBloc.add(const BonusLetterActivated(0)));
},
);
tester.widgetTest(
'adds a ColorEffect when it gets activated',
(game, tester) async {
await game.ready();
await tester.pump();
const state = GameState(
score: 0,
balls: 2,
activatedBonusLetters: [0],
bonusHistory: [],
);
final bonusLetter = game.children.whereType<BonusLetter>().first;
bonusLetter.onNewState(state);
await tester.pump();
expect(
bonusLetter.children.whereType<ColorEffect>().length,
equals(1),
);
},
);
tester.widgetTest(
'only listen when there an change on the letter status',
(game, tester) async {
await game.ready();
await tester.pump();
const state = GameState(
score: 0,
balls: 2,
activatedBonusLetters: [0],
bonusHistory: [],
);
final bonusLetter = game.children.whereType<BonusLetter>().first;
expect(
bonusLetter.listenWhen(const GameState.initial(), state),
isTrue,
);
},
);
});
group('BonusLetterBallContactCallback', () {
test('calls ball.activate', () {
final ball = MockBall();
final bonusLetter = MockBonusLetter();
final contactCallback = BonusLetterBallContactCallback();
contactCallback.begin(ball, bonusLetter, MockContact());
verify(bonusLetter.activate).called(1);
});
});
});
}

@ -94,7 +94,7 @@ void main() {
whenListen( whenListen(
gameBloc, gameBloc,
Stream.value(state), Stream.value(state),
initialState: state, initialState: GameState.initial(),
); );
await tester.pumpApp( await tester.pumpApp(

@ -37,3 +37,5 @@ class MockRawKeyUpEvent extends Mock implements RawKeyUpEvent {
class MockTapUpInfo extends Mock implements TapUpInfo {} class MockTapUpInfo extends Mock implements TapUpInfo {}
class MockEventPosition extends Mock implements EventPosition {} class MockEventPosition extends Mock implements EventPosition {}
class MockBonusLetter extends Mock implements BonusLetter {}

Loading…
Cancel
Save