mirror of https://github.com/flutter/pinball.git
commit
e781b1bb38
@ -0,0 +1,18 @@
|
|||||||
|
name: pinball_theme
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "packages/pinball_theme/**"
|
||||||
|
- ".github/workflows/pinball_theme.yaml"
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "packages/pinball_theme/**"
|
||||||
|
- ".github/workflows/pinball_theme.yaml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
|
||||||
|
with:
|
||||||
|
working_directory: packages/pinball_theme
|
After Width: | Height: | Size: 27 KiB |
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"hosting": {
|
||||||
|
"public": "build/web",
|
||||||
|
"site": "ashehwkdkdjruejdnensjsjdne",
|
||||||
|
"ignore": [
|
||||||
|
"firebase.json",
|
||||||
|
"**/.*",
|
||||||
|
"**/node_modules/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
|
||||||
|
/// {@template anchor}
|
||||||
|
/// Non visual [BodyComponent] used to hold a [BodyType.dynamic] in [Joint]s
|
||||||
|
/// with this [BodyType.static].
|
||||||
|
///
|
||||||
|
/// It is recommended to [_position] the anchor first and then use the body
|
||||||
|
/// position as the anchor point when initializing a [JointDef].
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// initialize(
|
||||||
|
/// dynamicBody.body,
|
||||||
|
/// anchor.body,
|
||||||
|
/// anchor.body.position,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
/// {@endtemplate}
|
||||||
|
class Anchor extends BodyComponent {
|
||||||
|
/// {@macro anchor}
|
||||||
|
Anchor({
|
||||||
|
required Vector2 position,
|
||||||
|
}) : _position = position;
|
||||||
|
|
||||||
|
final Vector2 _position;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final bodyDef = BodyDef()..position = _position;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef);
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,5 @@
|
|||||||
|
export 'anchor.dart';
|
||||||
export 'ball.dart';
|
export 'ball.dart';
|
||||||
|
export 'plunger.dart';
|
||||||
export 'score_points.dart';
|
export 'score_points.dart';
|
||||||
|
export 'wall.dart';
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
|
/// {@template plunger}
|
||||||
|
/// [Plunger] serves as a spring, that shoots the ball on the right side of the
|
||||||
|
/// playfield.
|
||||||
|
///
|
||||||
|
/// [Plunger] ignores gravity so the player controls its downward [pull].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class Plunger extends BodyComponent {
|
||||||
|
/// {@macro plunger}
|
||||||
|
Plunger({required Vector2 position}) : _position = position;
|
||||||
|
|
||||||
|
final Vector2 _position;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final shape = PolygonShape()..setAsBoxXY(2.5, 1.5);
|
||||||
|
|
||||||
|
final fixtureDef = FixtureDef(shape);
|
||||||
|
|
||||||
|
final bodyDef = BodyDef()
|
||||||
|
..userData = this
|
||||||
|
..position = _position
|
||||||
|
..type = BodyType.dynamic
|
||||||
|
..gravityScale = 0;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a constant downward velocity on the [Plunger].
|
||||||
|
void pull() {
|
||||||
|
body.linearVelocity = Vector2(0, -7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set an upward velocity on the [Plunger].
|
||||||
|
///
|
||||||
|
/// The velocity's magnitude depends on how far the [Plunger] has been pulled
|
||||||
|
/// from its original [_position].
|
||||||
|
void release() {
|
||||||
|
final velocity = (_position.y - body.position.y) * 9;
|
||||||
|
body.linearVelocity = Vector2(0, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@template plunger_anchor_prismatic_joint_def}
|
||||||
|
/// [PrismaticJointDef] between a [Plunger] and an [Anchor] with motion on
|
||||||
|
/// the vertical axis.
|
||||||
|
///
|
||||||
|
/// The [Plunger] is constrained vertically between its starting position and
|
||||||
|
/// the [Anchor]. The [Anchor] must be below the [Plunger].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class PlungerAnchorPrismaticJointDef extends PrismaticJointDef {
|
||||||
|
/// {@macro plunger_anchor_prismatic_joint_def}
|
||||||
|
PlungerAnchorPrismaticJointDef({
|
||||||
|
required Plunger plunger,
|
||||||
|
required Anchor anchor,
|
||||||
|
}) : assert(
|
||||||
|
anchor.body.position.y < plunger.body.position.y,
|
||||||
|
'Anchor must be below the Plunger',
|
||||||
|
) {
|
||||||
|
initialize(
|
||||||
|
plunger.body,
|
||||||
|
anchor.body,
|
||||||
|
anchor.body.position,
|
||||||
|
Vector2(0, -1),
|
||||||
|
);
|
||||||
|
enableLimit = true;
|
||||||
|
lowerTranslation = double.negativeInfinity;
|
||||||
|
collideConnected = true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
// ignore_for_file: avoid_renaming_method_parameters
|
||||||
|
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball/game/components/components.dart';
|
||||||
|
|
||||||
|
/// {@template wall}
|
||||||
|
/// A continuos generic and [BodyType.static] barrier that divides a game area.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class Wall extends BodyComponent {
|
||||||
|
Wall({
|
||||||
|
required this.start,
|
||||||
|
required 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@template bottom_wall}
|
||||||
|
/// [Wall] located at the bottom of the board.
|
||||||
|
///
|
||||||
|
/// Collisions with [BottomWall] are listened by
|
||||||
|
/// [BottomWallBallContactCallback].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class BottomWall extends Wall {
|
||||||
|
BottomWall(Forge2DGame game)
|
||||||
|
: super(
|
||||||
|
start: game.screenToWorld(game.camera.viewport.effectiveSize),
|
||||||
|
end: Vector2(
|
||||||
|
0,
|
||||||
|
game.screenToWorld(game.camera.viewport.effectiveSize).y,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@template bottom_wall_ball_contact_callback}
|
||||||
|
/// Listens when a [Ball] falls into a [BottomWall].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class BottomWallBallContactCallback extends ContactCallback<Ball, BottomWall> {
|
||||||
|
@override
|
||||||
|
void begin(Ball ball, BottomWall wall, Contact contact) {
|
||||||
|
ball.lost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void end(_, __, ___) {}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
export 'bloc/game_bloc.dart';
|
export 'bloc/game_bloc.dart';
|
||||||
export 'components/components.dart';
|
export 'components/components.dart';
|
||||||
|
export 'game_assets.dart';
|
||||||
export 'pinball_game.dart';
|
export 'pinball_game.dart';
|
||||||
export 'view/pinball_game_page.dart';
|
export 'view/view.dart';
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
|
/// Add methods to help loading and caching game assets.
|
||||||
|
extension PinballGameAssetsX on PinballGame {
|
||||||
|
/// Pre load the initial assets of the game.
|
||||||
|
Future<void> preLoadAssets() async {
|
||||||
|
await Future.wait([
|
||||||
|
images.load(Ball.spritePath),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,38 @@
|
|||||||
// ignore_for_file: public_member_api_docs
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flame_bloc/flame_bloc.dart';
|
import 'package:flame_bloc/flame_bloc.dart';
|
||||||
import 'package:flame_forge2d/forge2d_game.dart';
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
import 'package:pinball/game/game.dart';
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
class PinballGame extends Forge2DGame with FlameBloc {
|
class PinballGame extends Forge2DGame with FlameBloc {
|
||||||
|
void spawnBall() {
|
||||||
|
add(
|
||||||
|
Ball(position: ballStartingPosition),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(erickzanardo): Change to the plumber position
|
||||||
|
late final ballStartingPosition = screenToWorld(
|
||||||
|
Vector2(
|
||||||
|
camera.viewport.effectiveSize.x / 2,
|
||||||
|
camera.viewport.effectiveSize.y - 20,
|
||||||
|
),
|
||||||
|
) -
|
||||||
|
Vector2(0, -20);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
addContactCallback(BallScorePointsCallback());
|
addContactCallback(BallScorePointsCallback());
|
||||||
|
|
||||||
|
await add(BottomWall(this));
|
||||||
|
addContactCallback(BottomWallBallContactCallback());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onAttach() {
|
||||||
|
super.onAttach();
|
||||||
|
spawnBall();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
|
/// {@template game_hud}
|
||||||
|
/// Overlay of a [PinballGame] that displays the current [GameState.score] and
|
||||||
|
/// [GameState.balls].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class GameHud extends StatelessWidget {
|
||||||
|
/// {@macro game_hud}
|
||||||
|
const GameHud({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final state = context.watch<GameBloc>().state;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: Colors.redAccent,
|
||||||
|
width: 200,
|
||||||
|
height: 100,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${state.score}',
|
||||||
|
style: Theme.of(context).textTheme.headline3,
|
||||||
|
),
|
||||||
|
Wrap(
|
||||||
|
direction: Axis.vertical,
|
||||||
|
children: [
|
||||||
|
for (var i = 0; i < state.balls; i++)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(top: 6, right: 6),
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 8,
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export 'game_hud.dart';
|
||||||
|
export 'pinball_game_page.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';
|
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
part 'theme_state.dart';
|
||||||
|
|
||||||
|
class ThemeCubit extends Cubit<ThemeState> {
|
||||||
|
ThemeCubit() : super(const ThemeState.initial());
|
||||||
|
|
||||||
|
void characterSelected(CharacterTheme characterTheme) {
|
||||||
|
emit(ThemeState(PinballTheme(characterTheme: characterTheme)));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
part of 'theme_cubit.dart';
|
||||||
|
|
||||||
|
class ThemeState extends Equatable {
|
||||||
|
const ThemeState(this.theme);
|
||||||
|
|
||||||
|
const ThemeState.initial()
|
||||||
|
: theme = const PinballTheme(characterTheme: DashTheme());
|
||||||
|
|
||||||
|
final PinballTheme theme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [theme];
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export 'cubit/theme_cubit.dart';
|
@ -0,0 +1,39 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# VSCode related
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Web related
|
||||||
|
lib/generated_plugin_registrant.dart
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
@ -0,0 +1,11 @@
|
|||||||
|
# pinball_theme
|
||||||
|
|
||||||
|
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
|
||||||
|
[![License: MIT][license_badge]][license_link]
|
||||||
|
|
||||||
|
Package containing themes for pinball game.
|
||||||
|
|
||||||
|
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||||
|
[license_link]: https://opensource.org/licenses/MIT
|
||||||
|
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
|
||||||
|
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
|
@ -0,0 +1 @@
|
|||||||
|
include: package:very_good_analysis/analysis_options.2.4.0.yaml
|
@ -0,0 +1,4 @@
|
|||||||
|
library pinball_theme;
|
||||||
|
|
||||||
|
export 'src/pinball_theme.dart';
|
||||||
|
export 'src/themes/themes.dart';
|
@ -0,0 +1,23 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
/// {@template pinball_theme}
|
||||||
|
/// Defines all theme assets and attributes.
|
||||||
|
///
|
||||||
|
/// Game components should have a getter specified here to load their
|
||||||
|
/// corresponding assets for the game.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class PinballTheme extends Equatable {
|
||||||
|
/// {@macro pinball_theme}
|
||||||
|
const PinballTheme({
|
||||||
|
required CharacterTheme characterTheme,
|
||||||
|
}) : _characterTheme = characterTheme;
|
||||||
|
|
||||||
|
final CharacterTheme _characterTheme;
|
||||||
|
|
||||||
|
/// [CharacterTheme] for the chosen character.
|
||||||
|
CharacterTheme get characterTheme => _characterTheme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [_characterTheme];
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
/// {@template android_theme}
|
||||||
|
/// Defines Android character theme assets and attributes.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class AndroidTheme extends CharacterTheme {
|
||||||
|
/// {@macro android_theme}
|
||||||
|
const AndroidTheme();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color get ballColor => Colors.green;
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// {@template character_theme}
|
||||||
|
/// Base class for creating character themes.
|
||||||
|
///
|
||||||
|
/// Character specific game components should have a getter specified here to
|
||||||
|
/// load their corresponding assets for the game.
|
||||||
|
/// {@endtemplate}
|
||||||
|
abstract class CharacterTheme extends Equatable {
|
||||||
|
/// {@macro character_theme}
|
||||||
|
const CharacterTheme();
|
||||||
|
|
||||||
|
/// Ball color for this theme.
|
||||||
|
Color get ballColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [ballColor];
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
/// {@template dash_theme}
|
||||||
|
/// Defines Dash character theme assets and attributes.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class DashTheme extends CharacterTheme {
|
||||||
|
/// {@macro dash_theme}
|
||||||
|
const DashTheme();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color get ballColor => Colors.blue;
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
/// {@template dino_theme}
|
||||||
|
/// Defines Dino character theme assets and attributes.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class DinoTheme extends CharacterTheme {
|
||||||
|
/// {@macro dino_theme}
|
||||||
|
const DinoTheme();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color get ballColor => Colors.grey;
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
/// {@template sparky_theme}
|
||||||
|
/// Defines Sparky character theme assets and attributes.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class SparkyTheme extends CharacterTheme {
|
||||||
|
/// {@macro sparky_theme}
|
||||||
|
const SparkyTheme();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color get ballColor => Colors.orange;
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
export 'android_theme.dart';
|
||||||
|
export 'character_theme.dart';
|
||||||
|
export 'dash_theme.dart';
|
||||||
|
export 'dino_theme.dart';
|
||||||
|
export 'sparky_theme.dart';
|
@ -0,0 +1,17 @@
|
|||||||
|
name: pinball_theme
|
||||||
|
description: Package containing themes for pinball game.
|
||||||
|
version: 1.0.0+1
|
||||||
|
publish_to: none
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.16.0 <3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
equatable: ^2.0.3
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
very_good_analysis: ^2.4.0
|
@ -0,0 +1,28 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('PinballTheme', () {
|
||||||
|
const characterTheme = SparkyTheme();
|
||||||
|
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(PinballTheme(characterTheme: characterTheme), isNotNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports value equality', () {
|
||||||
|
expect(
|
||||||
|
PinballTheme(characterTheme: characterTheme),
|
||||||
|
equals(PinballTheme(characterTheme: characterTheme)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('characterTheme is correct', () {
|
||||||
|
expect(
|
||||||
|
PinballTheme(characterTheme: characterTheme).characterTheme,
|
||||||
|
equals(characterTheme),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('AndroidTheme', () {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(AndroidTheme(), isNotNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports value equality', () {
|
||||||
|
expect(AndroidTheme(), equals(AndroidTheme()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ballColor is correct', () {
|
||||||
|
expect(AndroidTheme().ballColor, equals(Colors.green));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('DashTheme', () {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(DashTheme(), isNotNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports value equality', () {
|
||||||
|
expect(DashTheme(), equals(DashTheme()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ballColor is correct', () {
|
||||||
|
expect(DashTheme().ballColor, equals(Colors.blue));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('DinoTheme', () {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(DinoTheme(), isNotNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports value equality', () {
|
||||||
|
expect(DinoTheme(), equals(DinoTheme()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ballColor is correct', () {
|
||||||
|
expect(DinoTheme().ballColor, equals(Colors.grey));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('SparkyTheme', () {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(SparkyTheme(), isNotNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports value equality', () {
|
||||||
|
expect(SparkyTheme(), equals(SparkyTheme()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ballColor is correct', () {
|
||||||
|
expect(SparkyTheme().ballColor, equals(Colors.orange));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
// 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('Anchor', () {
|
||||||
|
final flameTester = FlameTester(PinballGame.new);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final anchor = Anchor(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(anchor);
|
||||||
|
|
||||||
|
expect(game.contains(anchor), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('body', () {
|
||||||
|
flameTester.test(
|
||||||
|
'positions correctly',
|
||||||
|
(game) async {
|
||||||
|
final position = Vector2.all(10);
|
||||||
|
final anchor = Anchor(position: position);
|
||||||
|
await game.ensureAdd(anchor);
|
||||||
|
game.contains(anchor);
|
||||||
|
|
||||||
|
expect(anchor.body.position, position);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'is static',
|
||||||
|
(game) async {
|
||||||
|
final anchor = Anchor(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(anchor);
|
||||||
|
|
||||||
|
expect(anchor.body.bodyType, equals(BodyType.static));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('fixtures', () {
|
||||||
|
flameTester.test(
|
||||||
|
'has none',
|
||||||
|
(game) async {
|
||||||
|
final anchor = Anchor(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(anchor);
|
||||||
|
|
||||||
|
expect(anchor.body.fixtures, isEmpty);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,302 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
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';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(PinballGame.new);
|
||||||
|
|
||||||
|
group('Plunger', () {
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final plunger = Plunger(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
|
||||||
|
expect(game.contains(plunger), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('body', () {
|
||||||
|
flameTester.test(
|
||||||
|
'positions correctly',
|
||||||
|
(game) async {
|
||||||
|
final position = Vector2.all(10);
|
||||||
|
final plunger = Plunger(position: position);
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
game.contains(plunger);
|
||||||
|
|
||||||
|
expect(plunger.body.position, position);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'is dynamic',
|
||||||
|
(game) async {
|
||||||
|
final plunger = Plunger(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
|
||||||
|
expect(plunger.body.bodyType, equals(BodyType.dynamic));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'ignores gravity',
|
||||||
|
(game) async {
|
||||||
|
final plunger = Plunger(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
|
||||||
|
expect(plunger.body.gravityScale, isZero);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('first fixture', () {
|
||||||
|
flameTester.test(
|
||||||
|
'exists',
|
||||||
|
(game) async {
|
||||||
|
final plunger = Plunger(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
|
||||||
|
expect(plunger.body.fixtures[0], isA<Fixture>());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'shape is a polygon',
|
||||||
|
(game) async {
|
||||||
|
final plunger = Plunger(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
|
||||||
|
final fixture = plunger.body.fixtures[0];
|
||||||
|
expect(fixture.shape.shapeType, equals(ShapeType.polygon));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'pull sets a negative linear velocity',
|
||||||
|
(game) async {
|
||||||
|
final plunger = Plunger(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
|
||||||
|
plunger.pull();
|
||||||
|
|
||||||
|
expect(plunger.body.linearVelocity.y, isNegative);
|
||||||
|
expect(plunger.body.linearVelocity.x, isZero);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('release', () {
|
||||||
|
flameTester.test(
|
||||||
|
'does not set a linear velocity '
|
||||||
|
'when plunger is in starting position',
|
||||||
|
(game) async {
|
||||||
|
final plunger = Plunger(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
|
||||||
|
plunger.release();
|
||||||
|
|
||||||
|
expect(plunger.body.linearVelocity.y, isZero);
|
||||||
|
expect(plunger.body.linearVelocity.x, isZero);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'sets a positive linear velocity '
|
||||||
|
'when plunger is below starting position',
|
||||||
|
(game) async {
|
||||||
|
final plunger = Plunger(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
|
||||||
|
plunger.body.setTransform(Vector2(0, -1), 0);
|
||||||
|
plunger.release();
|
||||||
|
|
||||||
|
expect(plunger.body.linearVelocity.y, isPositive);
|
||||||
|
expect(plunger.body.linearVelocity.x, isZero);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('PlungerAnchorPrismaticJointDef', () {
|
||||||
|
late GameBloc gameBloc;
|
||||||
|
late Plunger plunger;
|
||||||
|
late Anchor anchor;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
gameBloc = MockGameBloc();
|
||||||
|
whenListen(
|
||||||
|
gameBloc,
|
||||||
|
const Stream<GameState>.empty(),
|
||||||
|
initialState: const GameState.initial(),
|
||||||
|
);
|
||||||
|
plunger = Plunger(position: Vector2.zero());
|
||||||
|
anchor = Anchor(position: Vector2(0, -1));
|
||||||
|
});
|
||||||
|
|
||||||
|
final flameTester = flameBlocTester(
|
||||||
|
gameBlocBuilder: () {
|
||||||
|
return gameBloc;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'throws AssertionError '
|
||||||
|
'when anchor is above plunger',
|
||||||
|
(game) async {
|
||||||
|
final anchor = Anchor(position: Vector2(0, 1));
|
||||||
|
await game.ensureAddAll([plunger, anchor]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => PlungerAnchorPrismaticJointDef(
|
||||||
|
plunger: plunger,
|
||||||
|
anchor: anchor,
|
||||||
|
),
|
||||||
|
throwsAssertionError,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'throws AssertionError '
|
||||||
|
'when anchor is in same position as plunger',
|
||||||
|
(game) async {
|
||||||
|
final anchor = Anchor(position: Vector2.zero());
|
||||||
|
await game.ensureAddAll([plunger, anchor]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => PlungerAnchorPrismaticJointDef(
|
||||||
|
plunger: plunger,
|
||||||
|
anchor: anchor,
|
||||||
|
),
|
||||||
|
throwsAssertionError,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('initializes with', () {
|
||||||
|
flameTester.test(
|
||||||
|
'plunger body as bodyA',
|
||||||
|
(game) async {
|
||||||
|
await game.ensureAddAll([plunger, anchor]);
|
||||||
|
|
||||||
|
final jointDef = PlungerAnchorPrismaticJointDef(
|
||||||
|
plunger: plunger,
|
||||||
|
anchor: anchor,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(jointDef.bodyA, equals(plunger.body));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'anchor body as bodyB',
|
||||||
|
(game) async {
|
||||||
|
await game.ensureAddAll([plunger, anchor]);
|
||||||
|
|
||||||
|
final jointDef = PlungerAnchorPrismaticJointDef(
|
||||||
|
plunger: plunger,
|
||||||
|
anchor: anchor,
|
||||||
|
);
|
||||||
|
game.world.createJoint(jointDef);
|
||||||
|
|
||||||
|
expect(jointDef.bodyB, equals(anchor.body));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'limits enabled',
|
||||||
|
(game) async {
|
||||||
|
await game.ensureAddAll([plunger, anchor]);
|
||||||
|
|
||||||
|
final jointDef = PlungerAnchorPrismaticJointDef(
|
||||||
|
plunger: plunger,
|
||||||
|
anchor: anchor,
|
||||||
|
);
|
||||||
|
game.world.createJoint(jointDef);
|
||||||
|
|
||||||
|
expect(jointDef.enableLimit, isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'lower translation limit as negative infinity',
|
||||||
|
(game) async {
|
||||||
|
await game.ensureAddAll([plunger, anchor]);
|
||||||
|
|
||||||
|
final jointDef = PlungerAnchorPrismaticJointDef(
|
||||||
|
plunger: plunger,
|
||||||
|
anchor: anchor,
|
||||||
|
);
|
||||||
|
game.world.createJoint(jointDef);
|
||||||
|
|
||||||
|
expect(jointDef.lowerTranslation, equals(double.negativeInfinity));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'connected body collison enabled',
|
||||||
|
(game) async {
|
||||||
|
await game.ensureAddAll([plunger, anchor]);
|
||||||
|
|
||||||
|
final jointDef = PlungerAnchorPrismaticJointDef(
|
||||||
|
plunger: plunger,
|
||||||
|
anchor: anchor,
|
||||||
|
);
|
||||||
|
game.world.createJoint(jointDef);
|
||||||
|
|
||||||
|
expect(jointDef.collideConnected, isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.widgetTest(
|
||||||
|
'plunger cannot go below anchor',
|
||||||
|
(game, tester) async {
|
||||||
|
await game.ensureAddAll([plunger, anchor]);
|
||||||
|
|
||||||
|
// Giving anchor a shape for the plunger to collide with.
|
||||||
|
anchor.body.createFixtureFromShape(PolygonShape()..setAsBoxXY(2, 1));
|
||||||
|
|
||||||
|
final jointDef = PlungerAnchorPrismaticJointDef(
|
||||||
|
plunger: plunger,
|
||||||
|
anchor: anchor,
|
||||||
|
);
|
||||||
|
game.world.createJoint(jointDef);
|
||||||
|
|
||||||
|
plunger.pull();
|
||||||
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
expect(plunger.body.position.y > anchor.body.position.y, isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.widgetTest(
|
||||||
|
'plunger cannot excessively exceed starting position',
|
||||||
|
(game, tester) async {
|
||||||
|
await game.ensureAddAll([plunger, anchor]);
|
||||||
|
|
||||||
|
final jointDef = PlungerAnchorPrismaticJointDef(
|
||||||
|
plunger: plunger,
|
||||||
|
anchor: anchor,
|
||||||
|
);
|
||||||
|
game.world.createJoint(jointDef);
|
||||||
|
|
||||||
|
plunger.pull();
|
||||||
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
plunger.release();
|
||||||
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
expect(plunger.body.position.y < 1, isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
// 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:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('Wall', () {
|
||||||
|
group('BottomWallBallContactCallback', () {
|
||||||
|
test(
|
||||||
|
'removes the ball on begin contact when the wall is a bottom one',
|
||||||
|
() {
|
||||||
|
final game = MockPinballGame();
|
||||||
|
final wall = MockBottomWall();
|
||||||
|
final ball = MockBall();
|
||||||
|
|
||||||
|
when(() => ball.gameRef).thenReturn(game);
|
||||||
|
|
||||||
|
BottomWallBallContactCallback()
|
||||||
|
// Remove once https://github.com/flame-engine/flame/pull/1415
|
||||||
|
// is merged
|
||||||
|
..end(MockBall(), MockBottomWall(), MockContact())
|
||||||
|
..begin(ball, wall, MockContact());
|
||||||
|
|
||||||
|
verify(ball.lost).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
final flameTester = FlameTester(PinballGame.new);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final wall = Wall(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: Vector2(100, 0),
|
||||||
|
);
|
||||||
|
await game.ensureAdd(wall);
|
||||||
|
|
||||||
|
expect(game.contains(wall), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('body', () {
|
||||||
|
flameTester.test(
|
||||||
|
'positions correctly',
|
||||||
|
(game) async {
|
||||||
|
final wall = Wall(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: 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(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: 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(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: 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(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: 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(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: Vector2(100, 0),
|
||||||
|
);
|
||||||
|
await game.ensureAdd(wall);
|
||||||
|
|
||||||
|
final fixture = wall.body.fixtures[0];
|
||||||
|
expect(fixture.friction, greaterThan(0));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('GameHud', () {
|
||||||
|
late GameBloc gameBloc;
|
||||||
|
const initialState = GameState(score: 10, balls: 2, bonusLetters: []);
|
||||||
|
|
||||||
|
void _mockState(GameState state) {
|
||||||
|
whenListen(
|
||||||
|
gameBloc,
|
||||||
|
Stream.value(state),
|
||||||
|
initialState: state,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pumpHud(WidgetTester tester) async {
|
||||||
|
await tester.pumpApp(
|
||||||
|
GameHud(),
|
||||||
|
gameBloc: gameBloc,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
gameBloc = MockGameBloc();
|
||||||
|
_mockState(initialState);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'renders the current score',
|
||||||
|
(tester) async {
|
||||||
|
await _pumpHud(tester);
|
||||||
|
expect(find.text(initialState.score.toString()), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'renders the current ball number',
|
||||||
|
(tester) async {
|
||||||
|
await _pumpHud(tester);
|
||||||
|
expect(
|
||||||
|
find.byType(CircleAvatar),
|
||||||
|
findsNWidgets(initialState.balls),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('updates the score', (tester) async {
|
||||||
|
await _pumpHud(tester);
|
||||||
|
expect(find.text(initialState.score.toString()), findsOneWidget);
|
||||||
|
|
||||||
|
_mockState(initialState.copyWith(score: 20));
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text('20'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('updates the ball number', (tester) async {
|
||||||
|
await _pumpHud(tester);
|
||||||
|
expect(
|
||||||
|
find.byType(CircleAvatar),
|
||||||
|
findsNWidgets(initialState.balls),
|
||||||
|
);
|
||||||
|
|
||||||
|
_mockState(initialState.copyWith(balls: 1));
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
expect(
|
||||||
|
find.byType(CircleAvatar),
|
||||||
|
findsNWidgets(1),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
|
FlameTester<PinballGame> flameBlocTester({
|
||||||
|
required GameBloc Function() gameBlocBuilder,
|
||||||
|
}) {
|
||||||
|
return FlameTester<PinballGame>(
|
||||||
|
PinballGame.new,
|
||||||
|
pumpWidget: (gameWidget, tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
BlocProvider.value(
|
||||||
|
value: gameBlocBuilder(),
|
||||||
|
child: gameWidget,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
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 MockBottomWall extends Mock implements BottomWall {}
|
||||||
|
|
||||||
|
class MockBall extends Mock implements Ball {}
|
||||||
|
|
||||||
|
class MockContact extends Mock implements Contact {}
|
||||||
|
|
||||||
|
class MockGameBloc extends Mock implements GameBloc {}
|
@ -0,0 +1,22 @@
|
|||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball/theme/theme.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ThemeCubit', () {
|
||||||
|
test('initial state has Dash character theme', () {
|
||||||
|
final themeCubit = ThemeCubit();
|
||||||
|
expect(themeCubit.state.theme.characterTheme, equals(const DashTheme()));
|
||||||
|
});
|
||||||
|
|
||||||
|
blocTest<ThemeCubit, ThemeState>(
|
||||||
|
'charcterSelected emits selected character theme',
|
||||||
|
build: ThemeCubit.new,
|
||||||
|
act: (bloc) => bloc.characterSelected(const SparkyTheme()),
|
||||||
|
expect: () => [
|
||||||
|
const ThemeState(PinballTheme(characterTheme: SparkyTheme())),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball/theme/theme.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ThemeState', () {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(const ThemeState.initial(), isNotNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports value equality', () {
|
||||||
|
expect(
|
||||||
|
ThemeState.initial(),
|
||||||
|
equals(const ThemeState.initial()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in new issue