feat: addressing PR comments

pull/9/head
Erick Zanardo 4 years ago
parent fbbd04d91b
commit dba7761e01

@ -26,6 +26,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;
/// Determines when player has only one chance left.
bool get isLastBall => balls == 1;
GameState copyWith({ GameState copyWith({
int? score, int? score,
int? balls, int? balls,

@ -29,18 +29,15 @@ class Ball extends BodyComponent<PinballGame>
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }
@override void ballLost() {
void onRemove() {
final bloc = gameRef.read<GameBloc>(); final bloc = gameRef.read<GameBloc>();
final shouldBallrespwan = bloc.state.balls > 1; final shouldBallRespwan = bloc.state.balls > 1;
bloc.add(const BallLost()); bloc.add(const BallLost());
if (shouldBallrespwan) { if (shouldBallRespwan) {
gameRef.resetBall(); gameRef.spawnBall();
} }
super.onRemove();
} }
} }

@ -1,10 +1,50 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/components/components.dart';
class BallWallContactCallback extends ContactCallback<Ball, Wall> {
@override
void begin(Ball ball, Wall wall, Contact contact) {
if (wall.type == WallType.bottom) {
ball
..ballLost()
..shouldRemove = true;
}
}
@override
void end(_, __, ___) {}
}
enum WallType {
top,
bottom,
left,
right,
}
class Wall extends BodyComponent { class Wall extends BodyComponent {
Wall(this.start, this.end); Wall({
required this.type,
required this.start,
required this.end,
});
factory Wall.bottom(Forge2DGame game) {
final bottomRight = game.screenToWorld(game.camera.viewport.effectiveSize);
final bottomLeft = Vector2(0, bottomRight.y);
return Wall(
type: WallType.bottom,
start: bottomRight,
end: bottomLeft,
);
}
final Vector2 start; final Vector2 start;
final Vector2 end; final Vector2 end;
final WallType type;
@override @override
Body createBody() { Body createBody() {

@ -1,21 +1,9 @@
// ignore_for_file: avoid_renaming_method_parameters
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:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
class BallWallContactCallback extends ContactCallback<Ball, Wall> {
@override
void begin(Ball ball, Wall wall, Contact contact) {
ball.shouldRemove = true;
}
@override
void end(_, __, ___) {}
}
class PinballGame extends Forge2DGame with FlameBloc { class PinballGame extends Forge2DGame with FlameBloc {
void resetBall() { void spawnBall() {
add(Ball(position: ballStartingPosition)); add(Ball(position: ballStartingPosition));
} }
@ -25,25 +13,14 @@ class PinballGame extends Forge2DGame with FlameBloc {
camera.viewport.effectiveSize.y - 20, camera.viewport.effectiveSize.y - 20,
), ),
) - ) -
Vector2( Vector2(0, -20);
0,
-20,
);
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); spawnBall();
addContactCallback(BallScorePointsCallback()); addContactCallback(BallScorePointsCallback());
final topLeft = Vector2.zero(); await add(Wall.bottom(this));
final bottomRight = screenToWorld(camera.viewport.effectiveSize);
final bottomLeft = Vector2(topLeft.x, bottomRight.y);
await add(
Wall(bottomRight, bottomLeft),
);
addContactCallback(BallWallContactCallback()); addContactCallback(BallWallContactCallback());
resetBall();
} }
} }

@ -14,15 +14,7 @@ class PinballGameView extends StatelessWidget {
showDialog<void>( showDialog<void>(
context: context, context: context,
builder: (_) { builder: (_) {
return const Dialog( return const GameOverDialog();
child: SizedBox(
width: 200,
height: 200,
child: Center(
child: Text('Game Over'),
),
),
);
}, },
); );
} }

@ -1,2 +1,3 @@
export 'pinball_game_page.dart'; export 'pinball_game_page.dart';
export 'pinball_game_view.dart'; export 'pinball_game_view.dart';
export 'widgets/widgets.dart';

@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
class GameOverDialog extends StatelessWidget {
const GameOverDialog({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Dialog(
child: SizedBox(
width: 200,
height: 200,
child: Center(
child: Text('Game Over'),
),
),
);
}
}

@ -0,0 +1 @@
export 'game_over_dialog.dart';

@ -62,6 +62,32 @@ void main() {
}); });
}); });
group('isGameOver', () {
test(
'is true '
'when there is only on ball left',
() {
const gameState = GameState(
balls: 1,
score: 0,
);
expect(gameState.isLastBall, isTrue);
},
);
test(
'is false '
'when there are more balls left',
() {
const gameState = GameState(
balls: 2,
score: 0,
);
expect(gameState.isLastBall, isFalse);
},
);
});
group('copyWith', () { group('copyWith', () {
test( test(
'throws AssertionError ' 'throws AssertionError '

@ -1,10 +1,14 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.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';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
@ -79,5 +83,71 @@ void main() {
}, },
); );
}); });
group('resetting a ball', () {
late GameBloc gameBloc;
setUp(() {
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final tester = flameBlocTester(
gameBlocBuilder: () {
return gameBloc;
},
);
tester.widgetTest(
'adds BallLost to GameBloc',
(game, tester) async {
await game.ready();
game.children.whereType<Ball>().first.ballLost();
await tester.pump();
verify(() => gameBloc.add(const BallLost())).called(1);
},
);
tester.widgetTest(
'resets the ball if the game is not over',
(game, tester) async {
await game.ready();
game.children.whereType<Ball>().first.removeFromParent();
await game.ready(); // Making sure that all additions are done
expect(
game.children.whereType<Ball>().length,
equals(1),
);
},
);
tester.widgetTest(
'no ball is added on game over',
(game, tester) async {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState(score: 10, balls: 1),
);
await game.ready();
game.children.whereType<Ball>().first.removeFromParent();
await tester.pump();
expect(
game.children.whereType<Ball>().length,
equals(0),
);
},
);
});
}); });
} }

@ -3,18 +3,46 @@
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.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';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
group('Wall', () { group('Wall', () {
group('BallWallContactCallback', () {
test(
'removes the ball on begin contact when the wall is a bottom one',
() {
final game = MockPinballGame();
final wall = MockWall();
final ball = MockBall();
when(() => wall.type).thenReturn(WallType.bottom);
when(() => ball.gameRef).thenReturn(game);
BallWallContactCallback()
// Remove once https://github.com/flame-engine/flame/pull/1415
// is merged
..end(MockBall(), MockWall(), MockContact())
..begin(ball, wall, MockContact());
verify(() => ball.shouldRemove = true).called(1);
},
);
});
final flameTester = FlameTester(PinballGame.new); final flameTester = FlameTester(PinballGame.new);
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',
(game) async { (game) async {
final wall = Wall(Vector2.zero(), Vector2(100, 0)); final wall = Wall(
type: WallType.bottom,
start: Vector2.zero(),
end: Vector2(100, 0),
);
await game.ensureAdd(wall); await game.ensureAdd(wall);
expect(game.contains(wall), isTrue); expect(game.contains(wall), isTrue);
@ -25,7 +53,11 @@ void main() {
flameTester.test( flameTester.test(
'positions correctly', 'positions correctly',
(game) async { (game) async {
final wall = Wall(Vector2.zero(), Vector2(100, 0)); final wall = Wall(
type: WallType.top,
start: Vector2.zero(),
end: Vector2(100, 0),
);
await game.ensureAdd(wall); await game.ensureAdd(wall);
game.contains(wall); game.contains(wall);
@ -36,7 +68,11 @@ void main() {
flameTester.test( flameTester.test(
'is static', 'is static',
(game) async { (game) async {
final wall = Wall(Vector2.zero(), Vector2(100, 0)); final wall = Wall(
type: WallType.top,
start: Vector2.zero(),
end: Vector2(100, 0),
);
await game.ensureAdd(wall); await game.ensureAdd(wall);
expect(wall.body.bodyType, equals(BodyType.static)); expect(wall.body.bodyType, equals(BodyType.static));
@ -48,7 +84,11 @@ void main() {
flameTester.test( flameTester.test(
'exists', 'exists',
(game) async { (game) async {
final wall = Wall(Vector2.zero(), Vector2(100, 0)); final wall = Wall(
type: WallType.top,
start: Vector2.zero(),
end: Vector2(100, 0),
);
await game.ensureAdd(wall); await game.ensureAdd(wall);
expect(wall.body.fixtures[0], isA<Fixture>()); expect(wall.body.fixtures[0], isA<Fixture>());
@ -58,7 +98,11 @@ void main() {
flameTester.test( flameTester.test(
'has restitution equals 0', 'has restitution equals 0',
(game) async { (game) async {
final wall = Wall(Vector2.zero(), Vector2(100, 0)); final wall = Wall(
type: WallType.top,
start: Vector2.zero(),
end: Vector2(100, 0),
);
await game.ensureAdd(wall); await game.ensureAdd(wall);
final fixture = wall.body.fixtures[0]; final fixture = wall.body.fixtures[0];
@ -69,7 +113,11 @@ void main() {
flameTester.test( flameTester.test(
'has friction', 'has friction',
(game) async { (game) async {
final wall = Wall(Vector2.zero(), Vector2(100, 0)); final wall = Wall(
type: WallType.top,
start: Vector2.zero(),
end: Vector2(100, 0),
);
await game.ensureAdd(wall); await game.ensureAdd(wall);
final fixture = wall.body.fixtures[0]; final fixture = wall.body.fixtures[0];

@ -1,108 +1,9 @@
import 'package:bloc_test/bloc_test.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:flutter_test/flutter_test.dart';
import 'package:mockingjay/mockingjay.dart';
import 'package:pinball/game/game.dart';
class MockPinballGame extends Mock implements PinballGame {}
class MockWall extends Mock implements Wall {}
class MockBall extends Mock implements Ball {}
class MockContact extends Mock implements Contact {}
class MockGameBloc extends Mock implements GameBloc {}
void main() { void main() {
// TODO(alestiago): test if [PinballGame] registers
// [BallScorePointsCallback] once the following issue is resolved:
// https://github.com/flame-engine/flame/issues/1416
group('PinballGame', () { group('PinballGame', () {
group('BallWallContactCallback', () { // TODO(alestiago): test if [PinballGame] registers
test('removes the ball on begin contact', () { // [BallScorePointsCallback] once the following issue is resolved:
final game = MockPinballGame(); // https://github.com/flame-engine/flame/issues/1416
final wall = MockWall();
final ball = MockBall();
when(() => ball.gameRef).thenReturn(game);
BallWallContactCallback()
// Remove once https://github.com/flame-engine/flame/pull/1415
// is merged
..end(MockBall(), MockWall(), MockContact())
..begin(ball, wall, MockContact());
verify(() => ball.shouldRemove = true).called(1);
});
});
group('resetting a ball', () {
late GameBloc gameBloc;
setUp(() {
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
FlameTester(
PinballGame.new,
pumpWidget: (gameWidget, tester) async {
await tester.pumpWidget(
BlocProvider.value(
value: gameBloc,
child: gameWidget,
),
);
},
)
..widgetTest('adds BallLost to GameBloc', (game, tester) async {
await game.ready();
game.children.whereType<Ball>().first.removeFromParent();
await tester.pump();
verify(() => gameBloc.add(const BallLost())).called(1);
})
..widgetTest(
'resets the ball if the game is not over',
(game, tester) async {
await game.ready();
game.children.whereType<Ball>().first.removeFromParent();
await game.ready(); // Making sure that all additions are done
expect(
game.children.whereType<Ball>().length,
equals(1),
);
},
)
..widgetTest(
'no ball is added on game over',
(game, tester) async {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState(score: 10, balls: 1),
);
await game.ready();
game.children.whereType<Ball>().first.removeFromParent();
await tester.pump();
expect(
game.children.whereType<Ball>().length,
equals(0),
);
},
);
});
}); });
} }

@ -0,0 +1,17 @@
import 'package:flame_test/flame_test.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
FlameTester flameBlocTester({required GameBloc Function() gameBlocBuilder}) {
return FlameTester(
PinballGame.new,
pumpWidget: (gameWidget, tester) async {
await tester.pumpWidget(
BlocProvider.value(
value: gameBlocBuilder(),
child: gameWidget,
),
);
},
);
}

@ -5,4 +5,6 @@
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
export 'builders.dart';
export 'mocks.dart';
export 'pump_app.dart'; export 'pump_app.dart';

@ -0,0 +1,13 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
class MockPinballGame extends Mock implements PinballGame {}
class MockWall extends Mock implements Wall {}
class MockBall extends Mock implements Ball {}
class MockContact extends Mock implements Contact {}
class MockGameBloc extends Mock implements GameBloc {}

@ -13,7 +13,7 @@ import 'package:mockingjay/mockingjay.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
class MockGameBloc extends Mock implements GameBloc {} import 'helpers.dart';
extension PumpApp on WidgetTester { extension PumpApp on WidgetTester {
Future<void> pumpApp( Future<void> pumpApp(

Loading…
Cancel
Save