Skip to content

Commit

Permalink
More wip on custom game: create custom games
Browse files Browse the repository at this point in the history
  • Loading branch information
veloce committed Jul 31, 2023
1 parent cffc51a commit 949302b
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 44 deletions.
13 changes: 2 additions & 11 deletions lib/src/model/game/create_game_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import 'package:lichess_mobile/src/model/common/socket.dart';
import 'package:lichess_mobile/src/model/lobby/lobby_repository.dart';
import 'package:lichess_mobile/src/model/lobby/game_seek.dart';
import 'package:lichess_mobile/src/model/auth/auth_socket.dart';
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
import 'package:lichess_mobile/src/model/settings/play_preferences.dart';

part 'create_game_service.g.dart';

Expand All @@ -27,15 +25,12 @@ class CreateGameService {

StreamSubscription<SocketEvent>? _socketSubscription;

/// Create a new online game seek based on saved preferences.
Future<GameFullId> newLobbyGame() async {
Future<GameFullId> newLobbyGame(GameSeek seek) async {
if (_socketSubscription != null) {
throw StateError('Already creating a game.');
}

final session = ref.read(authSessionProvider);
final socket = ref.read(authSocketProvider);
final playPref = ref.read(playPreferencesProvider);
final lobbyRepo = ref.read(lobbyRepositoryProvider);
final (stream, socketReady) = socket.connect(Uri(path: '/lobby/socket/v5'));
final completer = Completer<GameFullId>();
Expand All @@ -55,11 +50,7 @@ class CreateGameService {

await Result.release(
lobbyRepo.createSeek(
GameSeek(
time: Duration(seconds: playPref.timeIncrement.time),
increment: Duration(seconds: playPref.timeIncrement.increment),
rated: session != null,
),
seek,
sri: socket.sri,
),
);
Expand Down
41 changes: 41 additions & 0 deletions lib/src/model/lobby/game_seek.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:dartchess/dartchess.dart';

import 'package:lichess_mobile/src/model/auth/auth_session.dart';
import 'package:lichess_mobile/src/model/common/time_increment.dart';
import 'package:lichess_mobile/src/model/common/speed.dart';
import 'package:lichess_mobile/src/model/common/chess.dart';
import 'package:lichess_mobile/src/model/settings/play_preferences.dart';

part 'game_seek.freezed.dart';

@freezed
class GameSeek with _$GameSeek {
const GameSeek._();

const factory GameSeek({
required Duration time,
required Duration increment,
Expand All @@ -16,6 +21,42 @@ class GameSeek with _$GameSeek {
Side? side,
}) = _GameSeek;

factory GameSeek.fastPairingSeekFromPrefs(
PlayPrefs playPref,
AuthSessionState? session,
) {
return GameSeek(
time: Duration(seconds: playPref.timeIncrement.time),
increment: Duration(seconds: playPref.timeIncrement.increment),
rated: session != null,
);
}

factory GameSeek.customSeekFromPrefs(
PlayPrefs playPref,
AuthSessionState? session,
) {
return GameSeek(
time: Duration(seconds: playPref.customTimeSeconds),
increment: Duration(seconds: playPref.customIncrementSeconds),
rated: session != null && playPref.customRated,
variant: playPref.customVariant,
side: playPref.customRated == true ||
playPref.customSide == PlayableSide.random
? null
: playPref.customSide == PlayableSide.white
? Side.white
: Side.black,
);
}

TimeIncrement get timeIncrement => TimeIncrement(
time.inSeconds,
increment.inSeconds,
);

Speed get speed => Speed.fromTimeIncrement(timeIncrement);

Map<String, String> get requestBody => {
'time': (time.inSeconds / 60).toString(),
'increment': increment.inSeconds.toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/game/create_game_service.dart';

import 'game_seek.dart';

part 'lobby_game.g.dart';

/// The [LobbyGame] provider is used to create a new online game from the lobby
Expand All @@ -16,20 +18,20 @@ class LobbyGame extends _$LobbyGame {
late Object? _key;

@override
Future<GameFullId> build() {
Future<GameFullId> build(GameSeek seek) {
_key = Object();
ref.onDispose(() {
_service.cancel();
_key = null;
});
return _service.newLobbyGame();
return _service.newLobbyGame(seek);
}

Future<void> newOpponent() async {
final key = _key;
state = const AsyncValue.loading();
final newState = await AsyncValue.guard(() {
return _service.newLobbyGame();
return _service.newLobbyGame(seek);
});
// mounted property check logic from:
// https://github.com/rrousselGit/riverpod/issues/1879#issuecomment-1303189191
Expand Down
12 changes: 5 additions & 7 deletions lib/src/model/settings/play_preferences.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:dartchess/dartchess.dart';

import 'package:lichess_mobile/src/db/shared_preferences.dart';
import 'package:lichess_mobile/src/model/common/time_increment.dart';
Expand All @@ -13,6 +11,8 @@ part 'play_preferences.g.dart';

const _prefKey = 'preferences.play';

enum PlayableSide { random, white, black }

@Riverpod(keepAlive: true)
class PlayPreferences extends _$PlayPreferences {
@override
Expand Down Expand Up @@ -46,7 +46,7 @@ class PlayPreferences extends _$PlayPreferences {
return _save(state.copyWith(customRated: rated));
}

Future<void> setCustomSide(Side? side) {
Future<void> setCustomSide(PlayableSide side) {
return _save(state.copyWith(customSide: side));
}

Expand All @@ -73,7 +73,7 @@ class PlayPrefs with _$PlayPrefs {
required int customIncrementSeconds,
required Variant customVariant,
required bool customRated,
Side? customSide,
required PlayableSide customSide,
}) = _PlayPrefs;

static const defaults = PlayPrefs(
Expand All @@ -82,7 +82,7 @@ class PlayPrefs with _$PlayPrefs {
customIncrementSeconds: 0,
customVariant: Variant.standard,
customRated: false,
customSide: null,
customSide: PlayableSide.random,
);

factory PlayPrefs.fromJson(Map<String, dynamic> json) {
Expand All @@ -92,8 +92,6 @@ class PlayPrefs with _$PlayPrefs {
return defaults;
}
}

IconData get speedIcon => timeIncrement.speed.icon;
}

const kAvailableTimesInSeconds = [
Expand Down
51 changes: 35 additions & 16 deletions lib/src/view/game/game_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dartchess/dartchess.dart';
import 'package:chessground/chessground.dart' as cg;

import 'package:lichess_mobile/src/model/auth/auth_session.dart';
import 'package:lichess_mobile/src/model/auth/auth_socket.dart';
import 'package:lichess_mobile/src/model/game/game_ctrl.dart';
import 'package:lichess_mobile/src/model/game/lobby_game.dart';
import 'package:lichess_mobile/src/model/game/game_status.dart';
import 'package:lichess_mobile/src/model/lobby/lobby_game.dart';
import 'package:lichess_mobile/src/model/lobby/game_seek.dart';
import 'package:lichess_mobile/src/model/settings/play_preferences.dart';
import 'package:lichess_mobile/src/model/settings/board_preferences.dart';
import 'package:lichess_mobile/src/model/settings/general_preferences.dart';
Expand Down Expand Up @@ -39,9 +39,12 @@ final RouteObserver<PageRoute<void>> gameRouteObserver =

class GameScreen extends ConsumerStatefulWidget {
const GameScreen({
required this.seek,
super.key,
});

final GameSeek seek;

@override
ConsumerState<GameScreen> createState() => _GameScreenState();
}
Expand Down Expand Up @@ -70,7 +73,8 @@ class _GameScreenState extends ConsumerState<GameScreen>

@override
Widget build(BuildContext context) {
final gameId = ref.watch(lobbyGameProvider);
final gameProvider = lobbyGameProvider(widget.seek);
final gameId = ref.watch(gameProvider);
final playPrefs = ref.watch(playPreferencesProvider);

return gameId.when(
Expand All @@ -79,7 +83,11 @@ class _GameScreenState extends ConsumerState<GameScreen>
final gameState = ref.watch(ctrlProvider);
return gameState.when(
data: (state) {
final body = _Body(gameState: state, ctrlProvider: ctrlProvider);
final body = _Body(
gameState: state,
ctrlProvider: ctrlProvider,
gameProvider: gameProvider,
);
return PlatformWidget(
androidBuilder: (context) => _androidBuilder(
context: context,
Expand Down Expand Up @@ -158,7 +166,7 @@ class _GameScreenState extends ConsumerState<GameScreen>
child: PingRating(size: 24.0),
)
: null,
title: _GameTitle(playPrefs: playPrefs),
title: _GameTitle(seek: widget.seek),
actions: [
SettingsButton(
onPressed: () => showAdaptiveBottomSheet<void>(
Expand Down Expand Up @@ -188,7 +196,7 @@ class _GameScreenState extends ConsumerState<GameScreen>
child: PingRating(size: 24.0),
)
: null,
middle: _GameTitle(playPrefs: playPrefs),
middle: _GameTitle(seek: widget.seek),
trailing: SettingsButton(
onPressed: () => showAdaptiveBottomSheet<void>(
context: context,
Expand All @@ -204,24 +212,24 @@ class _GameScreenState extends ConsumerState<GameScreen>

class _GameTitle extends ConsumerWidget {
const _GameTitle({
required this.playPrefs,
required this.seek,
});

final PlayPrefs playPrefs;
final GameSeek seek;

@override
Widget build(BuildContext context, WidgetRef ref) {
final session = ref.watch(authSessionProvider);
final mode = session == null ? '' : ' • ${context.l10n.rated}';
final mode =
seek.rated ? ' • ${context.l10n.rated}' : ' • ${context.l10n.casual}';
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
playPrefs.speedIcon,
seek.speed.icon,
color: DefaultTextStyle.of(context).style.color,
),
const SizedBox(width: 4.0),
Text('${playPrefs.timeIncrement.display}$mode'),
Text('${seek.timeIncrement.display}$mode'),
],
);
}
Expand All @@ -231,10 +239,12 @@ class _Body extends ConsumerWidget {
const _Body({
required this.gameState,
required this.ctrlProvider,
required this.gameProvider,
});

final GameCtrlState gameState;
final GameCtrlProvider ctrlProvider;
final LobbyGameProvider gameProvider;

@override
Widget build(BuildContext context, WidgetRef ref) {
Expand All @@ -247,6 +257,7 @@ class _Body extends ConsumerWidget {
context: context,
builder: (context) => _GameEndDialog(
ctrlProvider: ctrlProvider,
gameProvider: gameProvider,
),
barrierDismissible: true,
);
Expand All @@ -268,7 +279,7 @@ class _Body extends ConsumerWidget {
// Be sure to pop any dialogs that might be on top of the game screen.
Navigator.of(context).popUntil((route) => route is! RawDialogRoute);
ref
.read(lobbyGameProvider.notifier)
.read(gameProvider.notifier)
.rematch(state.requireValue.redirectGameId!);
}
}
Expand Down Expand Up @@ -365,7 +376,11 @@ class _Body extends ConsumerWidget {
),
),
),
_GameBottomBar(gameState: gameState, ctrlProvider: ctrlProvider),
_GameBottomBar(
gameState: gameState,
ctrlProvider: ctrlProvider,
gameProvider: gameProvider,
),
],
);

Expand Down Expand Up @@ -440,10 +455,12 @@ class _GameBottomBar extends ConsumerWidget {
const _GameBottomBar({
required this.gameState,
required this.ctrlProvider,
required this.gameProvider,
});

final GameCtrlState gameState;
final GameCtrlProvider ctrlProvider;
final LobbyGameProvider gameProvider;

@override
Widget build(BuildContext context, WidgetRef ref) {
Expand Down Expand Up @@ -694,7 +711,7 @@ class _GameBottomBar extends ConsumerWidget {
BottomSheetAction(
label: Text(context.l10n.newOpponent),
onPressed: (_) {
ref.read(lobbyGameProvider.notifier).newOpponent();
ref.read(gameProvider.notifier).newOpponent();
},
),
],
Expand All @@ -705,9 +722,11 @@ class _GameBottomBar extends ConsumerWidget {
class _GameEndDialog extends ConsumerStatefulWidget {
const _GameEndDialog({
required this.ctrlProvider,
required this.gameProvider,
});

final GameCtrlProvider ctrlProvider;
final LobbyGameProvider gameProvider;

@override
ConsumerState<_GameEndDialog> createState() => _GameEndDialogState();
Expand Down Expand Up @@ -796,7 +815,7 @@ class _GameEndDialogState extends ConsumerState<_GameEndDialog> {
semanticsLabel: context.l10n.newOpponent,
onPressed: _activateButtons
? () {
ref.read(lobbyGameProvider.notifier).newOpponent();
ref.read(widget.gameProvider.notifier).newOpponent();
// Other alert dialogs may be shown before this one, so be sure to pop them all
Navigator.of(context)
.popUntil((route) => route is! RawDialogRoute);
Expand Down
Loading

0 comments on commit 949302b

Please sign in to comment.