refactor: add `Backbox` asset and localized text (#319)
* refactor: clean up backboard * test: backboard changes * refactor: rename backbox * fix: tests * test: localization mocking * test: initials input display * chore: remove extra golden file * chore: small changes * test: scoreFormat * style: cascade * fix: no optimizations for main * fix: loading assets Co-authored-by: Tom Arra <tarra3@gmail.com>pull/336/head
@ -0,0 +1,56 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:pinball/game/components/backbox/displays/displays.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
/// {@template backbox}
|
||||||
|
/// The [Backbox] of the pinball machine.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class Backbox extends PositionComponent with HasGameRef, ZIndex {
|
||||||
|
/// {@macro backbox}
|
||||||
|
Backbox()
|
||||||
|
: super(
|
||||||
|
position: Vector2(0, -87),
|
||||||
|
anchor: Anchor.bottomCenter,
|
||||||
|
children: [
|
||||||
|
_BackboxSpriteComponent(),
|
||||||
|
],
|
||||||
|
) {
|
||||||
|
zIndex = ZIndexes.backbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Puts [InitialsInputDisplay] on the [Backbox].
|
||||||
|
Future<void> initialsInput({
|
||||||
|
required int score,
|
||||||
|
required String characterIconPath,
|
||||||
|
InitialsOnSubmit? onSubmit,
|
||||||
|
}) async {
|
||||||
|
removeAll(children.where((child) => child is! _BackboxSpriteComponent));
|
||||||
|
await add(
|
||||||
|
InitialsInputDisplay(
|
||||||
|
score: score,
|
||||||
|
characterIconPath: characterIconPath,
|
||||||
|
onSubmit: onSubmit,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BackboxSpriteComponent extends SpriteComponent with HasGameRef {
|
||||||
|
_BackboxSpriteComponent() : super(anchor: Anchor.bottomCenter);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final sprite = Sprite(
|
||||||
|
gameRef.images.fromCache(
|
||||||
|
Assets.images.backbox.marquee.keyName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.sprite = sprite;
|
||||||
|
size = sprite.originalSize / 20;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export 'initials_input_display.dart';
|
@ -0,0 +1,387 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
import 'package:pinball_ui/pinball_ui.dart';
|
||||||
|
|
||||||
|
/// Signature for the callback called when the used has
|
||||||
|
/// submitted their initials on the [InitialsInputDisplay].
|
||||||
|
typedef InitialsOnSubmit = void Function(String);
|
||||||
|
|
||||||
|
final _bodyTextPaint = TextPaint(
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 3,
|
||||||
|
color: PinballColors.white,
|
||||||
|
fontFamily: PinballFonts.pixeloidSans,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final _subtitleTextPaint = TextPaint(
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 1.8,
|
||||||
|
color: PinballColors.white,
|
||||||
|
fontFamily: PinballFonts.pixeloidSans,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// {@template initials_input_display}
|
||||||
|
/// Display that handles the user input on the game over view.
|
||||||
|
/// {@endtemplate}
|
||||||
|
// TODO(allisonryan0002): add mobile input buttons.
|
||||||
|
class InitialsInputDisplay extends Component with HasGameRef {
|
||||||
|
/// {@macro initials_input_display}
|
||||||
|
InitialsInputDisplay({
|
||||||
|
required int score,
|
||||||
|
required String characterIconPath,
|
||||||
|
InitialsOnSubmit? onSubmit,
|
||||||
|
}) : _onSubmit = onSubmit,
|
||||||
|
super(
|
||||||
|
children: [
|
||||||
|
_ScoreLabelTextComponent(),
|
||||||
|
_ScoreTextComponent(score.formatScore()),
|
||||||
|
_NameLabelTextComponent(),
|
||||||
|
_CharacterIconSpriteComponent(characterIconPath),
|
||||||
|
_DividerSpriteComponent(),
|
||||||
|
_InstructionsComponent(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
final InitialsOnSubmit? _onSubmit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
|
await add(
|
||||||
|
InitialsLetterPrompt(
|
||||||
|
position: Vector2(
|
||||||
|
11.4 + (2.3 * i),
|
||||||
|
-20,
|
||||||
|
),
|
||||||
|
hasFocus: i == 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await add(
|
||||||
|
KeyboardInputController(
|
||||||
|
keyUp: {
|
||||||
|
LogicalKeyboardKey.arrowLeft: () => _movePrompt(true),
|
||||||
|
LogicalKeyboardKey.arrowRight: () => _movePrompt(false),
|
||||||
|
LogicalKeyboardKey.enter: _submit,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current inputed initials
|
||||||
|
String get initials => children
|
||||||
|
.whereType<InitialsLetterPrompt>()
|
||||||
|
.map((prompt) => prompt.char)
|
||||||
|
.join();
|
||||||
|
|
||||||
|
bool _submit() {
|
||||||
|
_onSubmit?.call(initials);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _movePrompt(bool left) {
|
||||||
|
final prompts = children.whereType<InitialsLetterPrompt>().toList();
|
||||||
|
|
||||||
|
final current = prompts.firstWhere((prompt) => prompt.hasFocus)
|
||||||
|
..hasFocus = false;
|
||||||
|
var index = prompts.indexOf(current) + (left ? -1 : 1);
|
||||||
|
index = min(max(0, index), prompts.length - 1);
|
||||||
|
|
||||||
|
prompts[index].hasFocus = true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScoreLabelTextComponent extends TextComponent
|
||||||
|
with HasGameRef<PinballGame> {
|
||||||
|
_ScoreLabelTextComponent()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.centerLeft,
|
||||||
|
position: Vector2(-16.9, -24),
|
||||||
|
textRenderer: _bodyTextPaint.copyWith(
|
||||||
|
(style) => style.copyWith(
|
||||||
|
color: PinballColors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
text = gameRef.l10n.score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScoreTextComponent extends TextComponent {
|
||||||
|
_ScoreTextComponent(String score)
|
||||||
|
: super(
|
||||||
|
text: score,
|
||||||
|
anchor: Anchor.centerLeft,
|
||||||
|
position: Vector2(-16.9, -20),
|
||||||
|
textRenderer: _bodyTextPaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NameLabelTextComponent extends TextComponent
|
||||||
|
with HasGameRef<PinballGame> {
|
||||||
|
_NameLabelTextComponent()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(11.4, -24),
|
||||||
|
textRenderer: _bodyTextPaint.copyWith(
|
||||||
|
(style) => style.copyWith(
|
||||||
|
color: PinballColors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
text = gameRef.l10n.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CharacterIconSpriteComponent extends SpriteComponent with HasGameRef {
|
||||||
|
_CharacterIconSpriteComponent(String characterIconPath)
|
||||||
|
: _characterIconPath = characterIconPath,
|
||||||
|
super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(8.4, -20),
|
||||||
|
);
|
||||||
|
|
||||||
|
final String _characterIconPath;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
final sprite = Sprite(gameRef.images.fromCache(_characterIconPath));
|
||||||
|
this.sprite = sprite;
|
||||||
|
size = sprite.originalSize / 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@template initials_input_display}
|
||||||
|
/// Display that handles the user input on the game over view.
|
||||||
|
/// {@endtemplate}
|
||||||
|
@visibleForTesting
|
||||||
|
class InitialsLetterPrompt extends PositionComponent {
|
||||||
|
/// {@macro initials_input_display}
|
||||||
|
InitialsLetterPrompt({
|
||||||
|
required Vector2 position,
|
||||||
|
bool hasFocus = false,
|
||||||
|
}) : _hasFocus = hasFocus,
|
||||||
|
super(
|
||||||
|
position: position,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const _alphabetCode = 65;
|
||||||
|
static const _alphabetLength = 25;
|
||||||
|
var _charIndex = 0;
|
||||||
|
|
||||||
|
bool _hasFocus;
|
||||||
|
|
||||||
|
late RectangleComponent _underscore;
|
||||||
|
late TextComponent _input;
|
||||||
|
late TimerComponent _underscoreBlinker;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
_underscore = RectangleComponent(
|
||||||
|
size: Vector2(1.9, 0.4),
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(-0.1, 1.8),
|
||||||
|
);
|
||||||
|
|
||||||
|
await add(_underscore);
|
||||||
|
|
||||||
|
_input = TextComponent(
|
||||||
|
text: 'A',
|
||||||
|
textRenderer: _bodyTextPaint,
|
||||||
|
anchor: Anchor.center,
|
||||||
|
);
|
||||||
|
await add(_input);
|
||||||
|
|
||||||
|
_underscoreBlinker = TimerComponent(
|
||||||
|
period: 0.6,
|
||||||
|
repeat: true,
|
||||||
|
autoStart: _hasFocus,
|
||||||
|
onTick: () {
|
||||||
|
_underscore.paint.color = (_underscore.paint.color == Colors.white)
|
||||||
|
? Colors.transparent
|
||||||
|
: Colors.white;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await add(_underscoreBlinker);
|
||||||
|
|
||||||
|
await add(
|
||||||
|
KeyboardInputController(
|
||||||
|
keyUp: {
|
||||||
|
LogicalKeyboardKey.arrowUp: () => _cycle(true),
|
||||||
|
LogicalKeyboardKey.arrowDown: () => _cycle(false),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current selected character
|
||||||
|
String get char => String.fromCharCode(_alphabetCode + _charIndex);
|
||||||
|
|
||||||
|
bool _cycle(bool up) {
|
||||||
|
if (_hasFocus) {
|
||||||
|
final newCharCode =
|
||||||
|
min(max(_charIndex + (up ? 1 : -1), 0), _alphabetLength);
|
||||||
|
_input.text = String.fromCharCode(_alphabetCode + newCharCode);
|
||||||
|
_charIndex = newCharCode;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns if this prompt has focus on it
|
||||||
|
bool get hasFocus => _hasFocus;
|
||||||
|
|
||||||
|
/// Updates this prompt focus
|
||||||
|
set hasFocus(bool hasFocus) {
|
||||||
|
if (hasFocus) {
|
||||||
|
_underscoreBlinker.timer.resume();
|
||||||
|
} else {
|
||||||
|
_underscoreBlinker.timer.pause();
|
||||||
|
}
|
||||||
|
_underscore.paint.color = Colors.white;
|
||||||
|
_hasFocus = hasFocus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DividerSpriteComponent extends SpriteComponent with HasGameRef {
|
||||||
|
_DividerSpriteComponent()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(0, -17),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
final sprite = Sprite(
|
||||||
|
gameRef.images.fromCache(Assets.images.backbox.displayDivider.keyName),
|
||||||
|
);
|
||||||
|
this.sprite = sprite;
|
||||||
|
size = sprite.originalSize / 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InstructionsComponent extends PositionComponent with HasGameRef {
|
||||||
|
_InstructionsComponent()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(0, -12.3),
|
||||||
|
children: [
|
||||||
|
_EnterInitialsTextComponent(),
|
||||||
|
_ArrowsTextComponent(),
|
||||||
|
_AndPressTextComponent(),
|
||||||
|
_EnterReturnTextComponent(),
|
||||||
|
_ToSubmitTextComponent(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EnterInitialsTextComponent extends TextComponent
|
||||||
|
with HasGameRef<PinballGame> {
|
||||||
|
_EnterInitialsTextComponent()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(0, -2.4),
|
||||||
|
textRenderer: _subtitleTextPaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
text = gameRef.l10n.enterInitials;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ArrowsTextComponent extends TextComponent with HasGameRef<PinballGame> {
|
||||||
|
_ArrowsTextComponent()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(-13.2, 0),
|
||||||
|
textRenderer: _subtitleTextPaint.copyWith(
|
||||||
|
(style) => style.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
text = gameRef.l10n.arrows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AndPressTextComponent extends TextComponent
|
||||||
|
with HasGameRef<PinballGame> {
|
||||||
|
_AndPressTextComponent()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(-3.7, 0),
|
||||||
|
textRenderer: _subtitleTextPaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
text = gameRef.l10n.andPress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EnterReturnTextComponent extends TextComponent
|
||||||
|
with HasGameRef<PinballGame> {
|
||||||
|
_EnterReturnTextComponent()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(10, 0),
|
||||||
|
textRenderer: _subtitleTextPaint.copyWith(
|
||||||
|
(style) => style.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
text = gameRef.l10n.enterReturn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ToSubmitTextComponent extends TextComponent
|
||||||
|
with HasGameRef<PinballGame> {
|
||||||
|
_ToSubmitTextComponent()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(0, 2.4),
|
||||||
|
textRenderer: _subtitleTextPaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
text = gameRef.l10n.toSubmit;
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 955 KiB |
Before Width: | Height: | Size: 1.9 MiB |
Before Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 637 KiB |
@ -1,79 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
|
|
||||||
export 'backboard_game_over.dart';
|
|
||||||
export 'backboard_letter_prompt.dart';
|
|
||||||
export 'backboard_waiting.dart';
|
|
||||||
|
|
||||||
/// {@template backboard}
|
|
||||||
/// The [Backboard] of the pinball machine.
|
|
||||||
/// {@endtemplate}
|
|
||||||
class Backboard extends PositionComponent with HasGameRef {
|
|
||||||
/// {@macro backboard}
|
|
||||||
Backboard({
|
|
||||||
required Vector2 position,
|
|
||||||
}) : super(
|
|
||||||
position: position,
|
|
||||||
anchor: Anchor.bottomCenter,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// {@macro backboard}
|
|
||||||
///
|
|
||||||
/// Returns a [Backboard] initialized in the waiting mode
|
|
||||||
factory Backboard.waiting({
|
|
||||||
required Vector2 position,
|
|
||||||
}) {
|
|
||||||
return Backboard(position: position)..waitingMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// {@macro backboard}
|
|
||||||
///
|
|
||||||
/// Returns a [Backboard] initialized in the game over mode
|
|
||||||
factory Backboard.gameOver({
|
|
||||||
required Vector2 position,
|
|
||||||
required String characterIconPath,
|
|
||||||
required int score,
|
|
||||||
required BackboardOnSubmit onSubmit,
|
|
||||||
}) {
|
|
||||||
return Backboard(position: position)
|
|
||||||
..gameOverMode(
|
|
||||||
score: score,
|
|
||||||
characterIconPath: characterIconPath,
|
|
||||||
onSubmit: onSubmit,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [TextPaint] used on the [Backboard]
|
|
||||||
static final textPaint = TextPaint(
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 6,
|
|
||||||
color: Colors.white,
|
|
||||||
fontFamily: PinballFonts.pixeloidSans,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Puts the Backboard in waiting mode, where the scoreboard is shown.
|
|
||||||
Future<void> waitingMode() async {
|
|
||||||
children.removeWhere((_) => true);
|
|
||||||
await add(BackboardWaiting());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Puts the Backboard in game over mode, where the score input is shown.
|
|
||||||
Future<void> gameOverMode({
|
|
||||||
required int score,
|
|
||||||
required String characterIconPath,
|
|
||||||
BackboardOnSubmit? onSubmit,
|
|
||||||
}) async {
|
|
||||||
children.removeWhere((_) => true);
|
|
||||||
await add(
|
|
||||||
BackboardGameOver(
|
|
||||||
score: score,
|
|
||||||
characterIconPath: characterIconPath,
|
|
||||||
onSubmit: onSubmit,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,144 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
import 'package:pinball_flame/pinball_flame.dart';
|
|
||||||
|
|
||||||
/// Signature for the callback called when the used has
|
|
||||||
/// submettied their initials on the [BackboardGameOver]
|
|
||||||
typedef BackboardOnSubmit = void Function(String);
|
|
||||||
|
|
||||||
/// {@template backboard_game_over}
|
|
||||||
/// [PositionComponent] that handles the user input on the
|
|
||||||
/// game over display view.
|
|
||||||
/// {@endtemplate}
|
|
||||||
class BackboardGameOver extends PositionComponent with HasGameRef {
|
|
||||||
/// {@macro backboard_game_over}
|
|
||||||
BackboardGameOver({
|
|
||||||
required int score,
|
|
||||||
required String characterIconPath,
|
|
||||||
BackboardOnSubmit? onSubmit,
|
|
||||||
}) : _onSubmit = onSubmit,
|
|
||||||
super(
|
|
||||||
children: [
|
|
||||||
_BackboardSpriteComponent(),
|
|
||||||
_BackboardDisplaySpriteComponent(),
|
|
||||||
_ScoreTextComponent(score.formatScore()),
|
|
||||||
_CharacterIconSpriteComponent(characterIconPath),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
final BackboardOnSubmit? _onSubmit;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onLoad() async {
|
|
||||||
for (var i = 0; i < 3; i++) {
|
|
||||||
await add(
|
|
||||||
BackboardLetterPrompt(
|
|
||||||
position: Vector2(
|
|
||||||
24.3 + (4.5 * i),
|
|
||||||
-45,
|
|
||||||
),
|
|
||||||
hasFocus: i == 0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await add(
|
|
||||||
KeyboardInputController(
|
|
||||||
keyUp: {
|
|
||||||
LogicalKeyboardKey.arrowLeft: () => _movePrompt(true),
|
|
||||||
LogicalKeyboardKey.arrowRight: () => _movePrompt(false),
|
|
||||||
LogicalKeyboardKey.enter: _submit,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the current inputed initials
|
|
||||||
String get initials => children
|
|
||||||
.whereType<BackboardLetterPrompt>()
|
|
||||||
.map((prompt) => prompt.char)
|
|
||||||
.join();
|
|
||||||
|
|
||||||
bool _submit() {
|
|
||||||
_onSubmit?.call(initials);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _movePrompt(bool left) {
|
|
||||||
final prompts = children.whereType<BackboardLetterPrompt>().toList();
|
|
||||||
|
|
||||||
final current = prompts.firstWhere((prompt) => prompt.hasFocus)
|
|
||||||
..hasFocus = false;
|
|
||||||
var index = prompts.indexOf(current) + (left ? -1 : 1);
|
|
||||||
index = min(max(0, index), prompts.length - 1);
|
|
||||||
|
|
||||||
prompts[index].hasFocus = true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BackboardSpriteComponent extends SpriteComponent with HasGameRef {
|
|
||||||
_BackboardSpriteComponent() : super(anchor: Anchor.bottomCenter);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onLoad() async {
|
|
||||||
await super.onLoad();
|
|
||||||
final sprite = await gameRef.loadSprite(
|
|
||||||
Assets.images.backboard.backboardGameOver.keyName,
|
|
||||||
);
|
|
||||||
this.sprite = sprite;
|
|
||||||
size = sprite.originalSize / 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BackboardDisplaySpriteComponent extends SpriteComponent with HasGameRef {
|
|
||||||
_BackboardDisplaySpriteComponent()
|
|
||||||
: super(
|
|
||||||
anchor: Anchor.bottomCenter,
|
|
||||||
position: Vector2(0, -11.5),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onLoad() async {
|
|
||||||
await super.onLoad();
|
|
||||||
final sprite = await gameRef.loadSprite(
|
|
||||||
Assets.images.backboard.display.keyName,
|
|
||||||
);
|
|
||||||
this.sprite = sprite;
|
|
||||||
size = sprite.originalSize / 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ScoreTextComponent extends TextComponent {
|
|
||||||
_ScoreTextComponent(String score)
|
|
||||||
: super(
|
|
||||||
text: score,
|
|
||||||
anchor: Anchor.centerLeft,
|
|
||||||
position: Vector2(-34, -45),
|
|
||||||
textRenderer: Backboard.textPaint,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CharacterIconSpriteComponent extends SpriteComponent with HasGameRef {
|
|
||||||
_CharacterIconSpriteComponent(String characterIconPath)
|
|
||||||
: _characterIconPath = characterIconPath,
|
|
||||||
super(
|
|
||||||
anchor: Anchor.center,
|
|
||||||
position: Vector2(18.4, -45),
|
|
||||||
);
|
|
||||||
|
|
||||||
final String _characterIconPath;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onLoad() async {
|
|
||||||
await super.onLoad();
|
|
||||||
final sprite = Sprite(gameRef.images.fromCache(_characterIconPath));
|
|
||||||
this.sprite = sprite;
|
|
||||||
size = sprite.originalSize / 10;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
import 'package:pinball_flame/pinball_flame.dart';
|
|
||||||
|
|
||||||
/// {@template backboard_letter_prompt}
|
|
||||||
/// A [PositionComponent] that renders a letter prompt used
|
|
||||||
/// on the [BackboardGameOver]
|
|
||||||
/// {@endtemplate}
|
|
||||||
class BackboardLetterPrompt extends PositionComponent {
|
|
||||||
/// {@macro backboard_letter_prompt}
|
|
||||||
BackboardLetterPrompt({
|
|
||||||
required Vector2 position,
|
|
||||||
bool hasFocus = false,
|
|
||||||
}) : _hasFocus = hasFocus,
|
|
||||||
super(
|
|
||||||
position: position,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const _alphabetCode = 65;
|
|
||||||
static const _alphabetLength = 25;
|
|
||||||
var _charIndex = 0;
|
|
||||||
|
|
||||||
bool _hasFocus;
|
|
||||||
|
|
||||||
late RectangleComponent _underscore;
|
|
||||||
late TextComponent _input;
|
|
||||||
late TimerComponent _underscoreBlinker;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onLoad() async {
|
|
||||||
_underscore = RectangleComponent(
|
|
||||||
size: Vector2(3.8, 0.8),
|
|
||||||
anchor: Anchor.center,
|
|
||||||
position: Vector2(-0.3, 4),
|
|
||||||
);
|
|
||||||
|
|
||||||
await add(_underscore);
|
|
||||||
|
|
||||||
_input = TextComponent(
|
|
||||||
text: 'A',
|
|
||||||
textRenderer: Backboard.textPaint,
|
|
||||||
anchor: Anchor.center,
|
|
||||||
);
|
|
||||||
await add(_input);
|
|
||||||
|
|
||||||
_underscoreBlinker = TimerComponent(
|
|
||||||
period: 0.6,
|
|
||||||
repeat: true,
|
|
||||||
autoStart: _hasFocus,
|
|
||||||
onTick: () {
|
|
||||||
_underscore.paint.color = (_underscore.paint.color == Colors.white)
|
|
||||||
? Colors.transparent
|
|
||||||
: Colors.white;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await add(_underscoreBlinker);
|
|
||||||
|
|
||||||
await add(
|
|
||||||
KeyboardInputController(
|
|
||||||
keyUp: {
|
|
||||||
LogicalKeyboardKey.arrowUp: () => _cycle(true),
|
|
||||||
LogicalKeyboardKey.arrowDown: () => _cycle(false),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the current selected character
|
|
||||||
String get char => String.fromCharCode(_alphabetCode + _charIndex);
|
|
||||||
|
|
||||||
bool _cycle(bool up) {
|
|
||||||
if (_hasFocus) {
|
|
||||||
final newCharCode =
|
|
||||||
min(max(_charIndex + (up ? 1 : -1), 0), _alphabetLength);
|
|
||||||
_input.text = String.fromCharCode(_alphabetCode + newCharCode);
|
|
||||||
_charIndex = newCharCode;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns if this prompt has focus on it
|
|
||||||
bool get hasFocus => _hasFocus;
|
|
||||||
|
|
||||||
/// Updates this prompt focus
|
|
||||||
set hasFocus(bool hasFocus) {
|
|
||||||
if (hasFocus) {
|
|
||||||
_underscoreBlinker.timer.resume();
|
|
||||||
} else {
|
|
||||||
_underscoreBlinker.timer.pause();
|
|
||||||
}
|
|
||||||
_underscore.paint.color = Colors.white;
|
|
||||||
_hasFocus = hasFocus;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
|
|
||||||
/// [PositionComponent] that shows the leaderboard while the player
|
|
||||||
/// has not started the game yet.
|
|
||||||
class BackboardWaiting extends SpriteComponent with HasGameRef {
|
|
||||||
@override
|
|
||||||
Future<void> onLoad() async {
|
|
||||||
final sprite = await gameRef.loadSprite(
|
|
||||||
Assets.images.backboard.backboardScores.keyName,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.sprite = sprite;
|
|
||||||
size = sprite.originalSize / 10;
|
|
||||||
anchor = Anchor.bottomCenter;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
import 'package:flame/effects.dart';
|
|
||||||
import 'package:flame/input.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart' as components;
|
|
||||||
import 'package:pinball_theme/pinball_theme.dart';
|
|
||||||
import 'package:sandbox/common/common.dart';
|
|
||||||
|
|
||||||
class BackboardGameOverGame extends AssetsGame
|
|
||||||
with HasKeyboardHandlerComponents {
|
|
||||||
BackboardGameOverGame(this.score, this.character)
|
|
||||||
: super(
|
|
||||||
imagesFileNames: [
|
|
||||||
components.Assets.images.score.fiveThousand.keyName,
|
|
||||||
components.Assets.images.score.twentyThousand.keyName,
|
|
||||||
components.Assets.images.score.twoHundredThousand.keyName,
|
|
||||||
components.Assets.images.score.oneMillion.keyName,
|
|
||||||
...characterIconPaths.values.toList(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
static const description = '''
|
|
||||||
Shows how the Backboard in game over mode is rendered.
|
|
||||||
|
|
||||||
- Select a character to update the character icon.
|
|
||||||
''';
|
|
||||||
|
|
||||||
static final characterIconPaths = <String, String>{
|
|
||||||
'Dash': Assets.images.dash.leaderboardIcon.keyName,
|
|
||||||
'Sparky': Assets.images.sparky.leaderboardIcon.keyName,
|
|
||||||
'Android': Assets.images.android.leaderboardIcon.keyName,
|
|
||||||
'Dino': Assets.images.dino.leaderboardIcon.keyName,
|
|
||||||
};
|
|
||||||
|
|
||||||
final int score;
|
|
||||||
|
|
||||||
final String character;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onLoad() async {
|
|
||||||
await super.onLoad();
|
|
||||||
|
|
||||||
camera
|
|
||||||
..followVector2(Vector2.zero())
|
|
||||||
..zoom = 5;
|
|
||||||
|
|
||||||
await add(
|
|
||||||
components.Backboard.gameOver(
|
|
||||||
position: Vector2(0, 20),
|
|
||||||
score: score,
|
|
||||||
characterIconPath: characterIconPaths[character]!,
|
|
||||||
onSubmit: (initials) {
|
|
||||||
add(
|
|
||||||
components.ScoreComponent(
|
|
||||||
points: components.Points.values
|
|
||||||
.firstWhere((element) => element.value == score),
|
|
||||||
position: Vector2(0, 50),
|
|
||||||
effectController: EffectController(duration: 1),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
import 'package:sandbox/common/common.dart';
|
|
||||||
|
|
||||||
class BackboardWaitingGame extends AssetsGame {
|
|
||||||
BackboardWaitingGame()
|
|
||||||
: super(
|
|
||||||
imagesFileNames: [],
|
|
||||||
);
|
|
||||||
|
|
||||||
static const description = '''
|
|
||||||
Shows how the Backboard in waiting mode is rendered.
|
|
||||||
''';
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onLoad() async {
|
|
||||||
camera
|
|
||||||
..followVector2(Vector2.zero())
|
|
||||||
..zoom = 5;
|
|
||||||
|
|
||||||
await add(
|
|
||||||
Backboard.waiting(position: Vector2(0, 20)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
import 'package:dashbook/dashbook.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
import 'package:sandbox/common/common.dart';
|
|
||||||
import 'package:sandbox/stories/backboard/backboard_game_over_game.dart';
|
|
||||||
import 'package:sandbox/stories/backboard/backboard_waiting_game.dart';
|
|
||||||
|
|
||||||
void addBackboardStories(Dashbook dashbook) {
|
|
||||||
dashbook.storiesOf('Backboard')
|
|
||||||
..addGame(
|
|
||||||
title: 'Waiting',
|
|
||||||
description: BackboardWaitingGame.description,
|
|
||||||
gameBuilder: (_) => BackboardWaitingGame(),
|
|
||||||
)
|
|
||||||
..addGame(
|
|
||||||
title: 'Game over',
|
|
||||||
description: BackboardGameOverGame.description,
|
|
||||||
gameBuilder: (context) => BackboardGameOverGame(
|
|
||||||
context.listProperty(
|
|
||||||
'Score',
|
|
||||||
Points.values.first.value,
|
|
||||||
Points.values.map((score) => score.value).toList(),
|
|
||||||
),
|
|
||||||
context.listProperty(
|
|
||||||
'Character',
|
|
||||||
BackboardGameOverGame.characterIconPaths.keys.first,
|
|
||||||
BackboardGameOverGame.characterIconPaths.keys.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,185 +0,0 @@
|
|||||||
// ignore_for_file: unawaited_futures, cascade_invocations
|
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:flame_test/flame_test.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart' hide Assets;
|
|
||||||
import 'package:pinball_theme/pinball_theme.dart';
|
|
||||||
|
|
||||||
import '../../helpers/helpers.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('Backboard', () {
|
|
||||||
final characterIconPath = Assets.images.dash.leaderboardIcon.keyName;
|
|
||||||
final tester = FlameTester(() => KeyboardTestGame([characterIconPath]));
|
|
||||||
|
|
||||||
group('on waitingMode', () {
|
|
||||||
tester.testGameWidget(
|
|
||||||
'renders correctly',
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
game.camera.zoom = 2;
|
|
||||||
game.camera.followVector2(Vector2.zero());
|
|
||||||
await game.ensureAdd(Backboard.waiting(position: Vector2(0, 15)));
|
|
||||||
await tester.pump();
|
|
||||||
},
|
|
||||||
verify: (game, tester) async {
|
|
||||||
await expectLater(
|
|
||||||
find.byGame<TestGame>(),
|
|
||||||
matchesGoldenFile('golden/backboard/waiting.png'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
group('on gameOverMode', () {
|
|
||||||
tester.testGameWidget(
|
|
||||||
'renders correctly',
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
game.camera.zoom = 2;
|
|
||||||
game.camera.followVector2(Vector2.zero());
|
|
||||||
final backboard = Backboard.gameOver(
|
|
||||||
position: Vector2(0, 15),
|
|
||||||
score: 1000,
|
|
||||||
characterIconPath: characterIconPath,
|
|
||||||
onSubmit: (_) {},
|
|
||||||
);
|
|
||||||
await game.ensureAdd(backboard);
|
|
||||||
},
|
|
||||||
verify: (game, tester) async {
|
|
||||||
final prompts =
|
|
||||||
game.descendants().whereType<BackboardLetterPrompt>().length;
|
|
||||||
expect(prompts, equals(3));
|
|
||||||
|
|
||||||
final score = game.descendants().firstWhere(
|
|
||||||
(component) =>
|
|
||||||
component is TextComponent && component.text == '1,000',
|
|
||||||
);
|
|
||||||
expect(score, isNotNull);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
tester.testGameWidget(
|
|
||||||
'can change the initials',
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
final backboard = Backboard.gameOver(
|
|
||||||
position: Vector2(0, 15),
|
|
||||||
score: 1000,
|
|
||||||
characterIconPath: characterIconPath,
|
|
||||||
onSubmit: (_) {},
|
|
||||||
);
|
|
||||||
await game.ensureAdd(backboard);
|
|
||||||
|
|
||||||
// Focus is already on the first letter
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Move to the next an press up again
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
||||||
await tester.pump();
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// One more time
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
||||||
await tester.pump();
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Back to the previous and increase one more
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
|
||||||
await tester.pump();
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
|
||||||
await tester.pump();
|
|
||||||
},
|
|
||||||
verify: (game, tester) async {
|
|
||||||
final backboard = game
|
|
||||||
.descendants()
|
|
||||||
.firstWhere((component) => component is BackboardGameOver)
|
|
||||||
as BackboardGameOver;
|
|
||||||
|
|
||||||
expect(backboard.initials, equals('BCB'));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
String? submitedInitials;
|
|
||||||
tester.testGameWidget(
|
|
||||||
'submits the initials',
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
final backboard = Backboard.gameOver(
|
|
||||||
position: Vector2(0, 15),
|
|
||||||
score: 1000,
|
|
||||||
characterIconPath: characterIconPath,
|
|
||||||
onSubmit: (value) {
|
|
||||||
submitedInitials = value;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await game.ensureAdd(backboard);
|
|
||||||
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
|
||||||
await tester.pump();
|
|
||||||
},
|
|
||||||
verify: (game, tester) async {
|
|
||||||
expect(submitedInitials, equals('AAA'));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('BackboardLetterPrompt', () {
|
|
||||||
final tester = FlameTester(KeyboardTestGame.new);
|
|
||||||
|
|
||||||
tester.testGameWidget(
|
|
||||||
'cycles the char up and down when it has focus',
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
await game.ensureAdd(
|
|
||||||
BackboardLetterPrompt(hasFocus: true, position: Vector2.zero()),
|
|
||||||
);
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
|
||||||
await tester.pump();
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
|
||||||
await tester.pump();
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
|
||||||
await tester.pump();
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
||||||
await tester.pump();
|
|
||||||
},
|
|
||||||
verify: (game, tester) async {
|
|
||||||
final prompt = game.firstChild<BackboardLetterPrompt>();
|
|
||||||
expect(prompt?.char, equals('C'));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
tester.testGameWidget(
|
|
||||||
"does nothing when it doesn't have focus",
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
await game.ensureAdd(
|
|
||||||
BackboardLetterPrompt(position: Vector2.zero()),
|
|
||||||
);
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
|
||||||
await tester.pump();
|
|
||||||
},
|
|
||||||
verify: (game, tester) async {
|
|
||||||
final prompt = game.firstChild<BackboardLetterPrompt>();
|
|
||||||
expect(prompt?.char, equals('A'));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
tester.testGameWidget(
|
|
||||||
'blinks the prompt when it has the focus',
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
await game.ensureAdd(
|
|
||||||
BackboardLetterPrompt(position: Vector2.zero(), hasFocus: true),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
verify: (game, tester) async {
|
|
||||||
final underscore = game.descendants().whereType<ShapeComponent>().first;
|
|
||||||
expect(underscore.paint.color, Colors.white);
|
|
||||||
|
|
||||||
game.update(2);
|
|
||||||
expect(underscore.paint.color, Colors.transparent);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
Before Width: | Height: | Size: 844 KiB |
@ -0,0 +1,10 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ScoreX', () {
|
||||||
|
test('formatScore correctly formats int', () {
|
||||||
|
expect(1000000.formatScore(), '1,000,000');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/components/backbox/displays/initials_input_display.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball/l10n/l10n.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart' as theme;
|
||||||
|
|
||||||
|
import '../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class _MockAppLocalizations extends Mock implements AppLocalizations {
|
||||||
|
@override
|
||||||
|
String get score => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterInitials => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get arrows => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get andPress => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterReturn => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get toSubmit => '';
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final characterIconPath = theme.Assets.images.dash.leaderboardIcon.keyName;
|
||||||
|
final assets = [
|
||||||
|
characterIconPath,
|
||||||
|
Assets.images.backbox.marquee.keyName,
|
||||||
|
Assets.images.backbox.displayDivider.keyName,
|
||||||
|
];
|
||||||
|
final flameTester = FlameTester(
|
||||||
|
() => EmptyPinballTestGame(
|
||||||
|
assets: assets,
|
||||||
|
l10n: _MockAppLocalizations(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
group('Backbox', () {
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final backbox = Backbox();
|
||||||
|
await game.ensureAdd(backbox);
|
||||||
|
|
||||||
|
expect(game.children, contains(backbox));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'renders correctly',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
await game.images.loadAll(assets);
|
||||||
|
game.camera
|
||||||
|
..followVector2(Vector2(0, -130))
|
||||||
|
..zoom = 6;
|
||||||
|
await game.ensureAdd(Backbox());
|
||||||
|
await tester.pump();
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
await expectLater(
|
||||||
|
find.byGame<EmptyPinballTestGame>(),
|
||||||
|
matchesGoldenFile('../golden/backbox.png'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'initialsInput adds InitialsInputDisplay',
|
||||||
|
(game) async {
|
||||||
|
final backbox = Backbox();
|
||||||
|
await game.ensureAdd(backbox);
|
||||||
|
await backbox.initialsInput(
|
||||||
|
score: 0,
|
||||||
|
characterIconPath: characterIconPath,
|
||||||
|
onSubmit: (_) {},
|
||||||
|
);
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
expect(backbox.firstChild<InitialsInputDisplay>(), isNotNull);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,189 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/components/backbox/displays/initials_input_display.dart';
|
||||||
|
import 'package:pinball/l10n/l10n.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_theme/pinball_theme.dart' as theme;
|
||||||
|
|
||||||
|
import '../../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class _MockAppLocalizations extends Mock implements AppLocalizations {
|
||||||
|
@override
|
||||||
|
String get score => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterInitials => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get arrows => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get andPress => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterReturn => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get toSubmit => '';
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final characterIconPath = theme.Assets.images.dash.leaderboardIcon.keyName;
|
||||||
|
final assets = [
|
||||||
|
characterIconPath,
|
||||||
|
Assets.images.backbox.displayDivider.keyName,
|
||||||
|
];
|
||||||
|
final flameTester = FlameTester(
|
||||||
|
() => EmptyKeyboardPinballTestGame(
|
||||||
|
assets: assets,
|
||||||
|
l10n: _MockAppLocalizations(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
group('InitialsInputDisplay', () {
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final initialsInputDisplay = InitialsInputDisplay(
|
||||||
|
score: 0,
|
||||||
|
characterIconPath: characterIconPath,
|
||||||
|
onSubmit: (_) {},
|
||||||
|
);
|
||||||
|
await game.ensureAdd(initialsInputDisplay);
|
||||||
|
|
||||||
|
expect(game.children, contains(initialsInputDisplay));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'can change the initials',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
await game.images.loadAll(assets);
|
||||||
|
final initialsInputDisplay = InitialsInputDisplay(
|
||||||
|
score: 1000,
|
||||||
|
characterIconPath: characterIconPath,
|
||||||
|
onSubmit: (_) {},
|
||||||
|
);
|
||||||
|
await game.ensureAdd(initialsInputDisplay);
|
||||||
|
|
||||||
|
// Focus is already on the first letter
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Move to the next an press up again
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// One more time
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Back to the previous and increase one more
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||||
|
await tester.pump();
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
final initialsInputDisplay =
|
||||||
|
game.descendants().whereType<InitialsInputDisplay>().single;
|
||||||
|
|
||||||
|
expect(initialsInputDisplay.initials, equals('BCB'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
String? submitedInitials;
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'submits the initials',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
await game.images.loadAll(assets);
|
||||||
|
final initialsInputDisplay = InitialsInputDisplay(
|
||||||
|
score: 1000,
|
||||||
|
characterIconPath: characterIconPath,
|
||||||
|
onSubmit: (value) {
|
||||||
|
submitedInitials = value;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await game.ensureAdd(initialsInputDisplay);
|
||||||
|
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
||||||
|
await tester.pump();
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
expect(submitedInitials, equals('AAA'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('BackboardLetterPrompt', () {
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'cycles the char up and down when it has focus',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
await game.images.loadAll(assets);
|
||||||
|
await game.ensureAdd(
|
||||||
|
InitialsLetterPrompt(hasFocus: true, position: Vector2.zero()),
|
||||||
|
);
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
||||||
|
await tester.pump();
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
final prompt = game.firstChild<InitialsLetterPrompt>();
|
||||||
|
expect(prompt?.char, equals('C'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
"does nothing when it doesn't have focus",
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
await game.images.loadAll(assets);
|
||||||
|
await game.ensureAdd(
|
||||||
|
InitialsLetterPrompt(position: Vector2.zero()),
|
||||||
|
);
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||||
|
await tester.pump();
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
final prompt = game.firstChild<InitialsLetterPrompt>();
|
||||||
|
expect(prompt?.char, equals('A'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'blinks the prompt when it has the focus',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
await game.images.loadAll(assets);
|
||||||
|
await game.ensureAdd(
|
||||||
|
InitialsLetterPrompt(position: Vector2.zero(), hasFocus: true),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
final underscore =
|
||||||
|
game.descendants().whereType<ShapeComponent>().first;
|
||||||
|
expect(underscore.paint.color, Colors.white);
|
||||||
|
|
||||||
|
game.update(2);
|
||||||
|
expect(underscore.paint.color, Colors.transparent);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
After Width: | Height: | Size: 1.4 MiB |