Skip to content

Commit

Permalink
feat: add bloc events and impl for hint management
Browse files Browse the repository at this point in the history
  • Loading branch information
thisissandipp committed Jul 28, 2024
1 parent dbcdd18 commit f6c38ca
Show file tree
Hide file tree
Showing 8 changed files with 337 additions and 4 deletions.
73 changes: 73 additions & 0 deletions lib/puzzle/bloc/puzzle_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:sudoku/api/api.dart';
import 'package:sudoku/models/models.dart';
import 'package:sudoku/puzzle/puzzle.dart';

Expand All @@ -9,16 +12,22 @@ part 'puzzle_state.dart';
class PuzzleBloc extends Bloc<PuzzleEvent, PuzzleState> {
PuzzleBloc({
required PuzzleRepository puzzleRepository,
required SudokuAPI apiClient,
}) : _puzzleRepository = puzzleRepository,
_apiClient = apiClient,
super(const PuzzleState()) {
on<PuzzleInitialized>(_onPuzzleInitialized);
on<SudokuBlockSelected>(_onSudokuBlockSelected);
on<SudokuInputEntered>(_onSudokuInputEntered);
on<SudokuInputErased>(_onSudokuInputErased);
on<SudokuHintRequested>(_onSudokuHintRequested);
on<HintInteractioCompleted>(_onHintInteractioCompleted);
}

final PuzzleRepository _puzzleRepository;

final SudokuAPI _apiClient;

void _onPuzzleInitialized(
PuzzleInitialized event,
Emitter<PuzzleState> emit,
Expand Down Expand Up @@ -121,4 +130,68 @@ class PuzzleBloc extends Bloc<PuzzleEvent, PuzzleState> {
),
);
}

FutureOr<void> _onSudokuHintRequested(
SudokuHintRequested event,
Emitter<PuzzleState> emit,
) async {
if (state.puzzle.remainingHints <= 0) return;

emit(
state.copyWith(
hintStatus: () => HintStatus.fetchInProgress,
hint: () => null,
),
);

try {
final hint = await _apiClient.generateHint(sudoku: state.puzzle.sudoku);
final selectedBlock = state.puzzle.sudoku.blocks.firstWhere(
(block) => block.position == hint.cell,
);
final highlightedBlocks =
state.puzzle.sudoku.blocksToHighlight(selectedBlock);
emit(
state.copyWith(
puzzle: () => state.puzzle.copyWith(
remainingHints: state.puzzle.remainingHints - 1,
),
hintStatus: () => HintStatus.fetchSuccess,
hint: () => hint,
selectedBlock: () => selectedBlock,
highlightedBlocks: () => highlightedBlocks,
),
);
} catch (_) {
emit(
state.copyWith(
hintStatus: () => HintStatus.fetchFailed,
),
);
}
}

void _onHintInteractioCompleted(
HintInteractioCompleted event,
Emitter<PuzzleState> emit,
) {
final selectedBlock = state.selectedBlock;
final invalidBlock = selectedBlock == null || selectedBlock.isGenerated;

final hint = state.hint;
if (invalidBlock || hint == null) return;

final mutableSudoku = Sudoku(
blocks: [...state.puzzle.sudoku.blocks],
);

emit(
state.copyWith(
puzzle: () => state.puzzle.copyWith(
sudoku: mutableSudoku.updateBlock(selectedBlock, hint.entry),
),
hintStatus: () => HintStatus.interactionEnded,
),
);
}
}
8 changes: 8 additions & 0 deletions lib/puzzle/bloc/puzzle_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,11 @@ final class SudokuInputEntered extends PuzzleEvent {
final class SudokuInputErased extends PuzzleEvent {
const SudokuInputErased();
}

final class SudokuHintRequested extends PuzzleEvent {
const SudokuHintRequested();
}

final class HintInteractioCompleted extends PuzzleEvent {
const HintInteractioCompleted();
}
28 changes: 28 additions & 0 deletions lib/puzzle/bloc/puzzle_state.dart
Original file line number Diff line number Diff line change
@@ -1,44 +1,72 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
part of 'puzzle_bloc.dart';

enum HintStatus {
initial,
fetchInProgress,
fetchSuccess,
fetchFailed,
interactionEnded,
}

class PuzzleState extends Equatable {
const PuzzleState({
this.puzzle = const Puzzle(
sudoku: Sudoku(blocks: []),
difficulty: Difficulty.easy,
),
this.puzzleStatus = PuzzleStatus.incomplete,
this.hintStatus = HintStatus.initial,
this.highlightedBlocks = const [],
this.selectedBlock,
this.hint,
});

final Puzzle puzzle;
final PuzzleStatus puzzleStatus;
final HintStatus hintStatus;
final List<Block> highlightedBlocks;
final Block? selectedBlock;
final Hint? hint;

@override
List<Object?> get props => [
puzzle,
puzzleStatus,
hintStatus,
highlightedBlocks,
selectedBlock,
hint,
];

PuzzleState copyWith({
Puzzle Function()? puzzle,
PuzzleStatus Function()? puzzleStatus,
HintStatus Function()? hintStatus,
List<Block> Function()? highlightedBlocks,
Block? Function()? selectedBlock,
Hint? Function()? hint,
}) {
return PuzzleState(
puzzle: puzzle != null ? puzzle() : this.puzzle,
puzzleStatus: puzzleStatus != null ? puzzleStatus() : this.puzzleStatus,
hintStatus: hintStatus != null ? hintStatus() : this.hintStatus,
highlightedBlocks: highlightedBlocks != null
? highlightedBlocks()
: this.highlightedBlocks,
selectedBlock:
selectedBlock != null ? selectedBlock() : this.selectedBlock,
hint: hint != null ? hint() : this.hint,
);
}
}

extension HintStatusExtension on HintStatus {
bool get isFetchInProgress {
return this == HintStatus.fetchInProgress;
}

bool get isInteractionEnded {
return this == HintStatus.interactionEnded;
}
}
2 changes: 2 additions & 0 deletions lib/puzzle/view/puzzle_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sudoku/api/api.dart';
import 'package:sudoku/colors/colors.dart';
import 'package:sudoku/l10n/l10n.dart';
import 'package:sudoku/layout/layout.dart';
Expand All @@ -21,6 +22,7 @@ class PuzzlePage extends StatelessWidget {
BlocProvider<PuzzleBloc>(
create: (context) => PuzzleBloc(
puzzleRepository: context.read<PuzzleRepository>(),
apiClient: context.read<SudokuAPI>(),
)..add(const PuzzleInitialized()),
),
BlocProvider<TimerBloc>(
Expand Down
2 changes: 2 additions & 0 deletions test/helpers/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ class MockPuzzleBloc extends MockBloc<PuzzleEvent, PuzzleState>
class MockPuzzleState extends Mock implements PuzzleState {}

class MockSharedPreferences extends Mock implements SharedPreferences {}

class MockHint extends Mock implements Hint {}
Loading

0 comments on commit f6c38ca

Please sign in to comment.