Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add game setup screen #8

Merged
merged 3 commits into from
Jul 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/lib/backend/models/dictionaries.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class Dictionaries {
return all.map((dict) => dict.language).toList();
}

static Dictionaries of(BuildContext context) {
return Provider.of<Dictionaries>(context, listen: false);
static Dictionaries of(BuildContext context, {bool listen = true}) {
return Provider.of<Dictionaries>(context, listen: listen);
}

static Future<Dictionary> _loadDictionaryFromDisk(Language language) async {
Expand Down
35 changes: 26 additions & 9 deletions app/lib/backend/models/game_settings.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'package:shiritori/backend/backend.dart';
import 'package:shiritori/backend/backend.dart' show Dictionary;

@immutable
abstract class GameSettings {
const GameSettings({
@required this.enemyType,
@required this.dictionary,
@required this.answeringDuration,
@required this.enemyType,
}) : assert(dictionary != null),
assert(answeringDuration != null),
assert(enemyType != null);

factory GameSettings.defaultFor({
@required GameEnemyType enemyType,
@required Dictionary dictionary,
}) {
return enemyType.isSingleplayer
? SingleplayerGameSettings(
dictionary: dictionary,
answeringDuration: defaultAnsweringDuration,
)
: MultiplayerGameSettings(
dictionary: dictionary,
answeringDuration: defaultAnsweringDuration,
);
}

final GameEnemyType enemyType;
final Dictionary dictionary;
final Duration answeringDuration;
final GameEnemyType enemyType;

static const Duration defaultAnsweringDuration = Duration(seconds: 10);
}

@immutable
Expand All @@ -28,13 +45,13 @@ class SingleplayerGameSettings implements GameSettings {
assert(startWithCpuMove != null);

@override
final Dictionary dictionary;
final GameEnemyType enemyType = GameEnemyType.singleplayer;

@override
final Duration answeringDuration;
final Dictionary dictionary;

@override
final GameEnemyType enemyType = GameEnemyType.singleplayer;
final Duration answeringDuration;

final bool startWithCpuMove;
}
Expand All @@ -48,13 +65,13 @@ class MultiplayerGameSettings implements GameSettings {
assert(answeringDuration != null);

@override
final Dictionary dictionary;
final GameEnemyType enemyType = GameEnemyType.multiplayer;

@override
final Duration answeringDuration;
final Dictionary dictionary;

@override
final GameEnemyType enemyType = GameEnemyType.multiplayer;
final Duration answeringDuration;
}

enum GameEnemyType {
Expand Down
12 changes: 12 additions & 0 deletions app/lib/intl/arb/intl_messages.arb
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
{
"newGame": "New Game",
"@newGame": {
"description": "The header for the game setup page where settings for a new game can be entered before playing.",
"type": "text",
"placeholders": {}
},
"start": "Start",
"@start": {
"description": "The button text for the start button at the bottom of the game setup page that will start a new game with the selected settings.",
"type": "text",
"placeholders": {}
},
"singleplayerTitle": "Singleplayer",
"@singleplayerTitle": {
"description": "The header for the Singleplayer page. Usually, this string should be the same as HomeStrings.singleplayerCardTitle.",
Expand Down
20 changes: 20 additions & 0 deletions app/lib/intl/strings/game_strings.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
import 'package:intl/intl.dart';

class GameStrings {
String get newGame {
return Intl.message(
'New Game',
name: 'newGame',
desc: 'The header for the game setup page where '
'settings for a new game can be entered before '
'playing.',
);
}

String get start {
return Intl.message(
'Start',
name: 'start',
desc: 'The button text for the start button at the '
'bottom of the game setup page that will start '
'a new game with the selected settings.',
);
}

String get singleplayerTitle {
return Intl.message(
'Singleplayer',
Expand Down
7 changes: 5 additions & 2 deletions app/lib/theme/theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ abstract class AppTheme {
static const cardTheme = CardTheme(
color: white,
elevation: elevationDefault,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(radiusCardDefault),
),
Expand Down Expand Up @@ -151,9 +152,11 @@ abstract class AppTheme {

static const elevationDefault = 8.0;
static const elevationDisabled = elevationDefault / 3;
static const radiusCardDefault = Radius.circular(16.0);
static const doubleRadiusCardDefault = 16.0;
static const radiusCardDefault = Radius.circular(doubleRadiusCardDefault);
static const borderRadiusCardDefault = BorderRadius.all(radiusCardDefault);
static const shapeDefault = RoundedRectangleBorder(
borderRadius: BorderRadius.all(radiusCardDefault),
borderRadius: borderRadiusCardDefault,
);

static const curveDefault = Curves.easeOutQuint;
Expand Down
57 changes: 43 additions & 14 deletions app/lib/ui/screens/game/game_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import 'package:shiritori/ui/screens/game/pages/pages.dart';
class GameScreen extends StatefulWidget {
const GameScreen({
Key key,
@required this.settings,
}) : assert(settings != null),
this.useDefaultSettings = false,
@required this.enemyType,
}) : assert(useDefaultSettings != null),
assert(enemyType != null),
super(key: key);

final GameSettings settings;
final bool useDefaultSettings;
final GameEnemyType enemyType;

@override
_GameScreenState createState() => _GameScreenState();
Expand All @@ -28,13 +31,21 @@ class _GameScreenState extends State<GameScreen> {
int _secondsRemaining;
Timer _playCountdownTimer;

bool get _showInGame => _secondsRemaining == 0;
GameSettings get _settings => _game?.settings;
set _settings(GameSettings settings) => _game ??= Game.startNew(settings);

bool get _isQuickPlay => widget.useDefaultSettings;
bool get _showSetup => _settings == null;
bool get _showInGame => !_showSetup && _secondsRemaining == 0;

@override
void initState() {
super.initState();
_game = Game.startNew(widget.settings);
_resetTimer();

if (_isQuickPlay) {
_setGameWithDefaultSettings();
_resetTimer();
}
}

@override
Expand All @@ -43,6 +54,13 @@ class _GameScreenState extends State<GameScreen> {
super.dispose();
}

void _setGameWithDefaultSettings() {
_settings = GameSettings.defaultFor(
enemyType: widget.enemyType,
dictionary: Dictionaries.of(context, listen: false).japanese,
);
}

void _resetTimer() {
_secondsRemaining = _playCountdownSeconds;
// Delaying the timer to sync up better with the surrounding animation.
Expand All @@ -63,8 +81,15 @@ class _GameScreenState extends State<GameScreen> {
});
}

void _onSubmitSettings(GameSettings settings) {
_settings = settings;
setState(_resetTimer);
}

void _onRestart() {
_game = Game.startNew(widget.settings);
if (_isQuickPlay) {
_setGameWithDefaultSettings();
}
setState(_resetTimer);
}

Expand All @@ -81,14 +106,18 @@ class _GameScreenState extends State<GameScreen> {
child: child,
);
},
child: !_showInGame
? Provider.value(
value: _secondsRemaining,
child: const CountdownPage(),
child: _showSetup
? SetupPage(
onSubmit: _onSubmitSettings,
)
: InGamePage(
onRestart: _onRestart,
),
: !_showInGame
? Provider.value(
value: _secondsRemaining,
child: const CountdownPage(),
)
: InGamePage(
onRestart: _onRestart,
),
),
);
}
Expand Down
16 changes: 1 addition & 15 deletions app/lib/ui/screens/game/pages/in_game/in_game_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,27 +109,13 @@ class _InGamePageState extends State<InGamePage> {
@override
Widget build(BuildContext context) {
final intl = ShiritoriLocalizations.of(context).game;
final uiIntl = ShiritoriLocalizations.of(context).ui;

final nextCharHint = Tuple(
_game.startingCharacterForNextGuess,
_game.language.mapFromLanguage(_game.startingCharacterForNextGuess),
);

final navBarColor = Theme.of(context).brightness == Brightness.light
? AppTheme.white
: AppTheme.black;

return CupertinoPageScaffold(
resizeToAvoidBottomInset: true,
navigationBar: CupertinoNavigationBar(
backgroundColor: navBarColor.withOpacity(0.5),
leading: TextButton(
color: AppTheme.colorSingleplayer,
onTap: Navigator.of(context).pop,
child: Text(uiIntl.back),
),
),
return AppScaffold(
child: Column(
children: [
Expanded(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ class _GuessDetailsCardState extends State<GuessDetailsCard>
},
child: Card(
shape: widget.shape,
margin: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.all(14.0),
child: Stack(
Expand Down
1 change: 1 addition & 0 deletions app/lib/ui/screens/game/pages/pages.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export 'countdown/countdown.dart';
export 'in_game/in_game.dart';
export 'setup/setup.dart';
1 change: 1 addition & 0 deletions app/lib/ui/screens/game/pages/setup/setup.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'setup_page.dart';
Loading