mirror of https://github.com/flutter/pinball.git
commit
a5eb7ca118
@ -1 +1,2 @@
|
||||
export 'bloc/leaderboard_bloc.dart';
|
||||
export 'models/leader_board_entry.dart';
|
||||
|
@ -0,0 +1,80 @@
|
||||
import 'package:leaderboard_repository/leaderboard_repository.dart';
|
||||
import 'package:pinball_theme/pinball_theme.dart';
|
||||
|
||||
/// {@template leaderboard_entry}
|
||||
/// A model representing a leaderboard entry containing the ranking position,
|
||||
/// player's initials, score, and chosen character.
|
||||
///
|
||||
/// {@endtemplate}
|
||||
class LeaderboardEntry {
|
||||
/// {@macro leaderboard_entry}
|
||||
LeaderboardEntry({
|
||||
required this.rank,
|
||||
required this.playerInitials,
|
||||
required this.score,
|
||||
required this.character,
|
||||
});
|
||||
|
||||
/// Ranking position for [LeaderboardEntry].
|
||||
final String rank;
|
||||
|
||||
/// Player's chosen initials for [LeaderboardEntry].
|
||||
final String playerInitials;
|
||||
|
||||
/// Score for [LeaderboardEntry].
|
||||
final int score;
|
||||
|
||||
/// [CharacterTheme] for [LeaderboardEntry].
|
||||
final AssetGenImage character;
|
||||
}
|
||||
|
||||
/// Converts [LeaderboardEntryData] from repository to [LeaderboardEntry].
|
||||
extension LeaderboardEntryDataX on LeaderboardEntryData {
|
||||
/// Conversion method to [LeaderboardEntry]
|
||||
LeaderboardEntry toEntry(int position) {
|
||||
return LeaderboardEntry(
|
||||
rank: position.toString(),
|
||||
playerInitials: playerInitials,
|
||||
score: score,
|
||||
character: character.toTheme.characterAsset,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts [CharacterType] to [CharacterTheme] to show on UI character theme
|
||||
/// from repository.
|
||||
extension CharacterTypeX on CharacterType {
|
||||
/// Conversion method to [CharacterTheme]
|
||||
CharacterTheme get toTheme {
|
||||
switch (this) {
|
||||
case CharacterType.dash:
|
||||
return const DashTheme();
|
||||
case CharacterType.sparky:
|
||||
return const SparkyTheme();
|
||||
case CharacterType.android:
|
||||
return const AndroidTheme();
|
||||
case CharacterType.dino:
|
||||
return const DinoTheme();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts [CharacterTheme] to [CharacterType] to persist at repository the
|
||||
/// character theme from UI.
|
||||
extension CharacterThemeX on CharacterTheme {
|
||||
/// Conversion method to [CharacterType]
|
||||
CharacterType get toType {
|
||||
switch (runtimeType) {
|
||||
case DashTheme:
|
||||
return CharacterType.dash;
|
||||
case SparkyTheme:
|
||||
return CharacterType.sparky;
|
||||
case AndroidTheme:
|
||||
return CharacterType.android;
|
||||
case DinoTheme:
|
||||
return CharacterType.dino;
|
||||
default:
|
||||
return CharacterType.dash;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'leaderboard_entry.dart';
|
||||
part of 'leaderboard_entry_data.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
LeaderboardEntry _$LeaderboardEntryFromJson(Map<String, dynamic> json) =>
|
||||
LeaderboardEntry(
|
||||
LeaderboardEntryData _$LeaderboardEntryFromJson(Map<String, dynamic> json) =>
|
||||
LeaderboardEntryData(
|
||||
playerInitials: json['playerInitials'] as String,
|
||||
score: json['score'] as int,
|
||||
character: $enumDecode(_$CharacterTypeEnumMap, json['character']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$LeaderboardEntryToJson(LeaderboardEntry instance) =>
|
||||
Map<String, dynamic> _$LeaderboardEntryToJson(LeaderboardEntryData instance) =>
|
||||
<String, dynamic>{
|
||||
'playerInitials': instance.playerInitials,
|
||||
'score': instance.score,
|
@ -1,2 +1,2 @@
|
||||
export 'leaderboard_entry.dart';
|
||||
export 'leaderboard_entry_data.dart';
|
||||
export 'leaderboard_ranking.dart';
|
||||
|
@ -1 +1,4 @@
|
||||
include: package:very_good_analysis/analysis_options.2.4.0.yaml
|
||||
include: package:very_good_analysis/analysis_options.2.4.0.yaml
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/**/*.gen.dart
|
||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
@ -0,0 +1,68 @@
|
||||
/// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
/// *****************************************************
|
||||
/// FlutterGen
|
||||
/// *****************************************************
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class $AssetsImagesGen {
|
||||
const $AssetsImagesGen();
|
||||
|
||||
AssetGenImage get ball => const AssetGenImage('assets/images/ball.png');
|
||||
}
|
||||
|
||||
class Assets {
|
||||
Assets._();
|
||||
|
||||
static const $AssetsImagesGen images = $AssetsImagesGen();
|
||||
}
|
||||
|
||||
class AssetGenImage extends AssetImage {
|
||||
const AssetGenImage(String assetName)
|
||||
: super(assetName, package: 'pinball_components');
|
||||
|
||||
Image image({
|
||||
Key? key,
|
||||
ImageFrameBuilder? frameBuilder,
|
||||
ImageLoadingBuilder? loadingBuilder,
|
||||
ImageErrorWidgetBuilder? errorBuilder,
|
||||
String? semanticLabel,
|
||||
bool excludeFromSemantics = false,
|
||||
double? width,
|
||||
double? height,
|
||||
Color? color,
|
||||
BlendMode? colorBlendMode,
|
||||
BoxFit? fit,
|
||||
AlignmentGeometry alignment = Alignment.center,
|
||||
ImageRepeat repeat = ImageRepeat.noRepeat,
|
||||
Rect? centerSlice,
|
||||
bool matchTextDirection = false,
|
||||
bool gaplessPlayback = false,
|
||||
bool isAntiAlias = false,
|
||||
FilterQuality filterQuality = FilterQuality.low,
|
||||
}) {
|
||||
return Image(
|
||||
key: key,
|
||||
image: this,
|
||||
frameBuilder: frameBuilder,
|
||||
loadingBuilder: loadingBuilder,
|
||||
errorBuilder: errorBuilder,
|
||||
semanticLabel: semanticLabel,
|
||||
excludeFromSemantics: excludeFromSemantics,
|
||||
width: width,
|
||||
height: height,
|
||||
color: color,
|
||||
colorBlendMode: colorBlendMode,
|
||||
fit: fit,
|
||||
alignment: alignment,
|
||||
repeat: repeat,
|
||||
centerSlice: centerSlice,
|
||||
matchTextDirection: matchTextDirection,
|
||||
gaplessPlayback: gaplessPlayback,
|
||||
isAntiAlias: isAntiAlias,
|
||||
filterQuality: filterQuality,
|
||||
);
|
||||
}
|
||||
|
||||
String get path => assetName;
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
library pinball_components;
|
||||
|
||||
export 'gen/assets.gen.dart';
|
||||
export 'src/pinball_components.dart';
|
||||
|
@ -0,0 +1,72 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template ball}
|
||||
/// A solid, [BodyType.dynamic] sphere that rolls and bounces around
|
||||
/// {@endtemplate}
|
||||
class Ball<T extends Forge2DGame> extends BodyComponent<T>
|
||||
with Layered, InitialPosition {
|
||||
/// {@macro ball_body}
|
||||
Ball({
|
||||
required this.baseColor,
|
||||
}) {
|
||||
// TODO(ruimiguel): while developing Ball can be launched by clicking mouse,
|
||||
// and default layer is Layer.all. But on final game Ball will be always be
|
||||
// be launched from Plunger and LauncherRamp will modify it to Layer.board.
|
||||
// We need to see what happens if Ball appears from other place like nest
|
||||
// bumper, it will need to explicit change layer to Layer.board then.
|
||||
layer = Layer.board;
|
||||
}
|
||||
|
||||
/// The size of the [Ball]
|
||||
final Vector2 size = Vector2.all(2);
|
||||
|
||||
/// The base [Color] used to tint this [Ball]
|
||||
final Color baseColor;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
final sprite = await gameRef.loadSprite(Assets.images.ball.keyName);
|
||||
final tint = baseColor.withOpacity(0.5);
|
||||
await add(
|
||||
SpriteComponent(
|
||||
sprite: sprite,
|
||||
size: size,
|
||||
anchor: Anchor.center,
|
||||
)..tint(tint),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = CircleShape()..radius = size.x / 2;
|
||||
|
||||
final fixtureDef = FixtureDef(shape)..density = 1;
|
||||
|
||||
final bodyDef = BodyDef()
|
||||
..position = initialPosition
|
||||
..userData = this
|
||||
..type = BodyType.dynamic;
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
|
||||
/// Immediatly and completly [stop]s the ball.
|
||||
///
|
||||
/// The [Ball] will no longer be affected by any forces, including it's
|
||||
/// weight and those emitted from collisions.
|
||||
void stop() {
|
||||
body.setType(BodyType.static);
|
||||
}
|
||||
|
||||
/// Allows the [Ball] to be affected by forces.
|
||||
///
|
||||
/// If previously [stop]ed, the previous ball's velocity is not kept.
|
||||
void resume() {
|
||||
body.setType(BodyType.dynamic);
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export 'ball.dart';
|
||||
export 'initial_position.dart';
|
||||
export 'layer.dart';
|
@ -1,7 +1 @@
|
||||
/// {@template pinball_components}
|
||||
/// Package with the UI game components for the Pinball Game
|
||||
/// {@endtemplate}
|
||||
class PinballComponents {
|
||||
/// {@macro pinball_components}
|
||||
const PinballComponents();
|
||||
}
|
||||
export 'components/components.dart';
|
||||
|
@ -0,0 +1 @@
|
||||
export 'test_game.dart';
|
@ -0,0 +1,7 @@
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
|
||||
class TestGame extends Forge2DGame {
|
||||
TestGame() {
|
||||
images.prefix = '';
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
import '../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final flameTester = FlameTester(TestGame.new);
|
||||
|
||||
group('Ball', () {
|
||||
flameTester.test(
|
||||
'loads correctly',
|
||||
(game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ready();
|
||||
await game.ensureAdd(ball);
|
||||
|
||||
expect(game.contains(ball), isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
group('body', () {
|
||||
flameTester.test(
|
||||
'is dynamic',
|
||||
(game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ensureAdd(ball);
|
||||
|
||||
expect(ball.body.bodyType, equals(BodyType.dynamic));
|
||||
},
|
||||
);
|
||||
|
||||
group('can be moved', () {
|
||||
flameTester.test('by its weight', (game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ensureAdd(ball);
|
||||
|
||||
game.update(1);
|
||||
expect(ball.body.position, isNot(equals(ball.initialPosition)));
|
||||
});
|
||||
|
||||
flameTester.test('by applying velocity', (game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ensureAdd(ball);
|
||||
|
||||
ball.body.gravityScale = 0;
|
||||
ball.body.linearVelocity.setValues(10, 10);
|
||||
game.update(1);
|
||||
expect(ball.body.position, isNot(equals(ball.initialPosition)));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('fixture', () {
|
||||
flameTester.test(
|
||||
'exists',
|
||||
(game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ensureAdd(ball);
|
||||
|
||||
expect(ball.body.fixtures[0], isA<Fixture>());
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'is dense',
|
||||
(game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ensureAdd(ball);
|
||||
|
||||
final fixture = ball.body.fixtures[0];
|
||||
expect(fixture.density, greaterThan(0));
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'shape is circular',
|
||||
(game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ensureAdd(ball);
|
||||
|
||||
final fixture = ball.body.fixtures[0];
|
||||
expect(fixture.shape.shapeType, equals(ShapeType.circle));
|
||||
expect(fixture.shape.radius, equals(1));
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'has Layer.all as default filter maskBits',
|
||||
(game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ready();
|
||||
await game.ensureAdd(ball);
|
||||
await game.ready();
|
||||
|
||||
final fixture = ball.body.fixtures[0];
|
||||
expect(fixture.filterData.maskBits, equals(Layer.board.maskBits));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('stop', () {
|
||||
group("can't be moved", () {
|
||||
flameTester.test('by its weight', (game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ensureAdd(ball);
|
||||
ball.stop();
|
||||
|
||||
game.update(1);
|
||||
expect(ball.body.position, equals(ball.initialPosition));
|
||||
});
|
||||
});
|
||||
|
||||
flameTester.test('by applying velocity', (game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ensureAdd(ball);
|
||||
ball.stop();
|
||||
|
||||
ball.body.linearVelocity.setValues(10, 10);
|
||||
game.update(1);
|
||||
expect(ball.body.position, equals(ball.initialPosition));
|
||||
});
|
||||
});
|
||||
|
||||
group('resume', () {
|
||||
group('can move', () {
|
||||
flameTester.test(
|
||||
'by its weight when previously stopped',
|
||||
(game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ensureAdd(ball);
|
||||
ball.stop();
|
||||
ball.resume();
|
||||
|
||||
game.update(1);
|
||||
expect(ball.body.position, isNot(equals(ball.initialPosition)));
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'by applying velocity when previously stopped',
|
||||
(game) async {
|
||||
final ball = Ball(baseColor: Colors.blue);
|
||||
await game.ensureAdd(ball);
|
||||
ball.stop();
|
||||
ball.resume();
|
||||
|
||||
ball.body.gravityScale = 0;
|
||||
ball.body.linearVelocity.setValues(10, 10);
|
||||
game.update(1);
|
||||
expect(ball.body.position, isNot(equals(ball.initialPosition)));
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
void main() {
|
||||
group('PinballComponents', () {
|
||||
test('can be instantiated', () {
|
||||
expect(PinballComponents(), isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in new issue