Skip to content

Commit

Permalink
Merge branch 'fix/storm-streak-invalidating-provider' of https://gith…
Browse files Browse the repository at this point in the history
…ub.com/julien4215/mobile into julien4215-fix/storm-streak-invalidating-provider
  • Loading branch information
veloce committed May 7, 2024
2 parents 8aabc40 + d708c8c commit fa1d182
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 28 deletions.
8 changes: 8 additions & 0 deletions lib/src/model/puzzle/puzzle_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class PuzzleRepository {
(e) => PuzzleId(e),
),
),
timestamp: DateTime.now(),
);
},
);
Expand All @@ -119,6 +120,7 @@ class PuzzleRepository {
),
highscore: pick(json['high']).letOrNull(_stormHighScoreFromPick),
key: pick(json['key']).asStringOrNull(),
timestamp: DateTime.now(),
);
},
);
Expand Down Expand Up @@ -251,6 +253,9 @@ class PuzzleStreakResponse with _$PuzzleStreakResponse {
const factory PuzzleStreakResponse({
required Puzzle puzzle,
required Streak streak,
// This field is not returned by the API but it is used to force the
// [puzzleControllerProvider] to recompute
required DateTime timestamp,
}) = _PuzzleStreakResponse;
}

Expand All @@ -260,6 +265,9 @@ class PuzzleStormResponse with _$PuzzleStormResponse {
required IList<LitePuzzle> puzzles,
required String? key,
required PuzzleStormHighScore? highscore,
// This field is not returned by the API but it is used to force the
// stormControllerProvider to recompute
required DateTime timestamp,
}) = _PuzzleStormResponse;
}

Expand Down
1 change: 1 addition & 0 deletions lib/src/model/puzzle/puzzle_streak.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class PuzzleStreak with _$PuzzleStreak {
required int index,
required bool hasSkipped,
required bool finished,
required DateTime timestamp,
}) = _PuzzleStreak;

PuzzleId? get nextId => streak.getOrNull(index + 1);
Expand Down
72 changes: 45 additions & 27 deletions lib/src/model/puzzle/storm_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,47 +28,49 @@ const startTime = Duration(minutes: 3);

@riverpod
class StormController extends _$StormController {
int _nextPuzzleIndex = 0;
int _moves = 0;
int _errors = 0;
final _history = <PuzzleHistoryEntry>[];
Timer? _firstMoveTimer;

@override
StormState build(IList<LitePuzzle> puzzles) {
StormState build(IList<LitePuzzle> puzzles, DateTime timestamp) {
final pov = Chess.fromSetup(Setup.parseFen(puzzles.first.fen));
final clock = StormClock();

ref.onDispose(() {
_firstMoveTimer?.cancel();
state.clock.dispose();
clock.dispose();
});

final pov = Chess.fromSetup(Setup.parseFen(puzzles.first.fen));
final newState = StormState(
firstMovePlayed: false,
runOver: false,
runStarted: false,
puzzle: puzzles[_nextPuzzleIndex],
puzzleIndex: 0,
puzzle: puzzles.first,
moves: 0,
errors: 0,
history: const IList.empty(),
position: pov,
pov: pov.turn.opposite,
moveIndex: -1,
numSolved: 0,
clock: StormClock(),
clock: clock,
combo: const StormCombo(current: 0, best: 0),
stats: null,
lastSolvedTime: null,
);
_nextPuzzleIndex += 1;

_firstMoveTimer = Timer(
const Duration(seconds: 1),
() => _addMove(
state.expectedMove!,
newState.expectedMove!,
ComboState.noChange,
runStarted: false,
userMove: false,
isFirstMove: true,
),
);
newState.clock.timeStream.listen((e) {
if (e.$1 == Duration.zero && state.clock.endAt == null) {
if (e.$1 == Duration.zero && clock.endAt == null) {
end();
}
});
Expand All @@ -80,7 +82,7 @@ class StormController extends _$StormController {
state.clock.start();
final expected = state.expectedMove;
_addMove(move, ComboState.noChange, runStarted: true, userMove: true);
_moves += 1;
state = state.copyWith(moves: state.moves + 1);
if (state.position.isGameOver || move == expected) {
final bonus = state.combo.bonus(getNext: true);
if (bonus != null) {
Expand All @@ -105,7 +107,7 @@ class StormController extends _$StormController {
userMove: false,
);
} else {
_errors += 1;
state = state.copyWith(errors: state.errors + 1);
ref.read(soundServiceProvider).play(Sound.error);
HapticFeedback.heavyImpact();
state.clock.subtractTime(malus);
Expand Down Expand Up @@ -178,9 +180,12 @@ class StormController extends _$StormController {
newComboCurrent = state.combo.current;
}

final int newPuzzleIndex = state.puzzleIndex + 1;

state = state.copyWith(
puzzle: puzzles[_nextPuzzleIndex],
position: Chess.fromSetup(Setup.parseFen(puzzles[_nextPuzzleIndex].fen)),
puzzleIndex: newPuzzleIndex,
puzzle: puzzles[newPuzzleIndex],
position: Chess.fromSetup(Setup.parseFen(puzzles[newPuzzleIndex].fen)),
moveIndex: -1,
numSolved: result ? state.numSolved + 1 : state.numSolved,
lastSolvedTime: DateTime.now(),
Expand All @@ -189,7 +194,6 @@ class StormController extends _$StormController {
best: math.max(state.combo.best, state.combo.current + 1),
),
);
_nextPuzzleIndex += 1;
await Future<void>.delayed(moveDelay);
_addMove(
state.expectedMove!,
Expand Down Expand Up @@ -241,13 +245,13 @@ class StormController extends _$StormController {
}

StormRunStats _getStats() {
final wins = _history.where((e) => e.win == true).toList();
final mean =
_history.sumBy((e) => e.solvingTime!.inSeconds) / _history.length;
final wins = state.history.where((e) => e.win == true).toList();
final mean = state.history.sumBy((e) => e.solvingTime!.inSeconds) /
state.history.length;
final threshold = mean * 1.5;
return StormRunStats(
moves: _moves,
errors: _errors,
moves: state.moves,
errors: state.errors,
score: wins.length,
comboBest: state.combo.best,
time: state.clock.endAt!,
Expand All @@ -257,8 +261,8 @@ class StormController extends _$StormController {
(maxRating, rating) => rating > maxRating ? rating : maxRating,
)
: 0,
history: _history.toIList(),
slowPuzzleIds: _history
history: state.history,
slowPuzzleIds: state.history
.where((e) => e.solvingTime!.inSeconds > threshold)
.map((e) => e.id)
.toIList(),
Expand All @@ -269,20 +273,25 @@ class StormController extends _$StormController {
final timeTaken = state.lastSolvedTime != null
? DateTime.now().difference(state.lastSolvedTime!)
: DateTime.now().difference(state.clock.startAt!);
_history.add(
PuzzleHistoryEntry.fromLitePuzzle(state.puzzle, success, timeTaken),
state = state.copyWith(
history: state.history.add(
PuzzleHistoryEntry.fromLitePuzzle(state.puzzle, success, timeTaken),
),
);
}

bool _isNextPuzzleAvailable() {
return _nextPuzzleIndex < puzzles.length;
return state.puzzleIndex + 1 < puzzles.length;
}
}

@freezed
class StormState with _$StormState {
const StormState._();
const factory StormState({
/// Index of the current puzzle being played
required int puzzleIndex,

/// Current puzzle being played
required LitePuzzle puzzle,

Expand All @@ -304,6 +313,15 @@ class StormState with _$StormState {
/// A combo object which has the current and best combo
required StormCombo combo,

/// Number of moves made in the run
required int moves,

/// Number of errors made in the run
required int errors,

/// The history of puzzles played in the run
required IList<PuzzleHistoryEntry> history,

/// Stats of the storm run. Only initialisd after the run ends
required StormRunStats? stats,

Expand Down
2 changes: 1 addition & 1 deletion lib/src/view/puzzle/storm_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class _Body extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final ctrlProvider = stormControllerProvider(data.puzzles);
final ctrlProvider = stormControllerProvider(data.puzzles, data.timestamp);
final puzzleState = ref.watch(ctrlProvider);
ref.listen(ctrlProvider.select((state) => state.runOver), (_, s) {
if (s) {
Expand Down
1 change: 1 addition & 0 deletions lib/src/view/puzzle/streak_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class _Load extends ConsumerWidget {
index: 0,
hasSkipped: false,
finished: false,
timestamp: data.timestamp,
),
);
},
Expand Down
1 change: 1 addition & 0 deletions test/view/puzzle/storm_screen_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,5 @@ final mockStromRun = PuzzleStormResponse(
]),
key: null,
highscore: null,
timestamp: DateTime.now(),
);

0 comments on commit fa1d182

Please sign in to comment.