mirror of https://github.com/flutter/pinball.git
parent
403d997f17
commit
518e183ad8
@ -1,2 +1,3 @@
|
||||
export 'ball.dart';
|
||||
export 'score_points.dart';
|
||||
export 'wall.dart';
|
||||
|
@ -0,0 +1,24 @@
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
|
||||
class Wall extends BodyComponent {
|
||||
Wall(this.start, this.end);
|
||||
|
||||
final Vector2 start;
|
||||
final Vector2 end;
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = EdgeShape()..set(start, end);
|
||||
|
||||
final fixtureDef = FixtureDef(shape)
|
||||
..restitution = 0.0
|
||||
..friction = 0.3;
|
||||
|
||||
final bodyDef = BodyDef()
|
||||
..userData = this
|
||||
..position = Vector2.zero()
|
||||
..type = BodyType.static;
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export 'bloc/game_bloc.dart';
|
||||
export 'components/components.dart';
|
||||
export 'pinball_game.dart';
|
||||
export 'view/pinball_game_page.dart';
|
||||
export 'view/view.dart';
|
||||
|
@ -1,10 +1,49 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'package:flame_bloc/flame_bloc.dart';
|
||||
import 'package:flame_forge2d/forge2d_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 {
|
||||
void resetBall() {
|
||||
add(Ball(position: ballStartingPosition));
|
||||
}
|
||||
|
||||
late final ballStartingPosition = screenToWorld(
|
||||
Vector2(
|
||||
camera.viewport.effectiveSize.x / 2,
|
||||
camera.viewport.effectiveSize.y - 20,
|
||||
),
|
||||
) -
|
||||
Vector2(
|
||||
0,
|
||||
-20,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
addContactCallback(BallScorePointsCallback());
|
||||
|
||||
final topLeft = Vector2.zero();
|
||||
final bottomRight = screenToWorld(camera.viewport.effectiveSize);
|
||||
final bottomLeft = Vector2(topLeft.x, bottomRight.y);
|
||||
|
||||
await add(
|
||||
Wall(bottomRight, bottomLeft),
|
||||
);
|
||||
addContactCallback(BallWallContactCallback());
|
||||
|
||||
resetBall();
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,23 @@
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
|
||||
class PinballGamePage extends StatelessWidget {
|
||||
const PinballGamePage({Key? key}) : super(key: key);
|
||||
|
||||
static Route route() {
|
||||
return MaterialPageRoute<void>(builder: (_) => const PinballGamePage());
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) {
|
||||
return BlocProvider(
|
||||
create: (_) => GameBloc(),
|
||||
child: const PinballGamePage(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GameWidget(game: PinballGame());
|
||||
return const PinballGameView();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
|
||||
class PinballGameView extends StatelessWidget {
|
||||
const PinballGameView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<GameBloc, GameState>(
|
||||
listener: (context, state) {
|
||||
if (state.isGameOver) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return const Dialog(
|
||||
child: SizedBox(
|
||||
width: 200,
|
||||
height: 200,
|
||||
child: Center(
|
||||
child: Text('Game Over'),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
child: GameWidget<PinballGame>(game: PinballGame()),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export 'pinball_game_page.dart';
|
||||
export 'pinball_game_view.dart';
|
@ -0,0 +1,81 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Wall', () {
|
||||
final flameTester = FlameTester(PinballGame.new);
|
||||
|
||||
flameTester.test(
|
||||
'loads correctly',
|
||||
(game) async {
|
||||
final wall = Wall(Vector2.zero(), Vector2(100, 0));
|
||||
await game.ensureAdd(wall);
|
||||
|
||||
expect(game.contains(wall), isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
group('body', () {
|
||||
flameTester.test(
|
||||
'positions correctly',
|
||||
(game) async {
|
||||
final wall = Wall(Vector2.zero(), Vector2(100, 0));
|
||||
await game.ensureAdd(wall);
|
||||
game.contains(wall);
|
||||
|
||||
expect(wall.body.position, Vector2.zero());
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'is static',
|
||||
(game) async {
|
||||
final wall = Wall(Vector2.zero(), Vector2(100, 0));
|
||||
await game.ensureAdd(wall);
|
||||
|
||||
expect(wall.body.bodyType, equals(BodyType.static));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('first fixture', () {
|
||||
flameTester.test(
|
||||
'exists',
|
||||
(game) async {
|
||||
final wall = Wall(Vector2.zero(), Vector2(100, 0));
|
||||
await game.ensureAdd(wall);
|
||||
|
||||
expect(wall.body.fixtures[0], isA<Fixture>());
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'has restitution equals 0',
|
||||
(game) async {
|
||||
final wall = Wall(Vector2.zero(), Vector2(100, 0));
|
||||
await game.ensureAdd(wall);
|
||||
|
||||
final fixture = wall.body.fixtures[0];
|
||||
expect(fixture.restitution, equals(0));
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'has friction',
|
||||
(game) async {
|
||||
final wall = Wall(Vector2.zero(), Vector2(100, 0));
|
||||
await game.ensureAdd(wall);
|
||||
|
||||
final fixture = wall.body.fixtures[0];
|
||||
expect(fixture.friction, greaterThan(0));
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -1,9 +1,109 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flame/game.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: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() {
|
||||
// TODO(alestiago): test if [PinballGame] registers
|
||||
// [BallScorePointsCallback] once the following issue is resolved:
|
||||
// https://github.com/flame-engine/flame/issues/1416
|
||||
group('PinballGame', () {
|
||||
// TODO(alestiago): test if [PinballGame] registers
|
||||
// [BallScorePointsCallback] once the following issue is resolved:
|
||||
// https://github.com/flame-engine/flame/issues/1416
|
||||
group('BallWallContactCallback', () {
|
||||
test('removes the ball on begin contact', () {
|
||||
final game = MockPinballGame();
|
||||
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,46 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
|
||||
import '../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
group('PinballGameView', () {
|
||||
testWidgets('renders', (tester) async {
|
||||
final gameBloc = MockGameBloc();
|
||||
whenListen(
|
||||
gameBloc,
|
||||
Stream.value(const GameState.initial()),
|
||||
initialState: const GameState.initial(),
|
||||
);
|
||||
|
||||
await tester.pumpApp(const PinballGameView(), gameBloc: gameBloc);
|
||||
expect(
|
||||
find.byWidgetPredicate((w) => w is GameWidget<PinballGame>),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'renders a game over dialog when the user has lost',
|
||||
(tester) async {
|
||||
final gameBloc = MockGameBloc();
|
||||
const state = GameState(score: 0, balls: 0);
|
||||
whenListen(
|
||||
gameBloc,
|
||||
Stream.value(state),
|
||||
initialState: state,
|
||||
);
|
||||
|
||||
await tester.pumpApp(const PinballGameView(), gameBloc: gameBloc);
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
find.text('Game Over'),
|
||||
findsOneWidget,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
Loading…
Reference in new issue