Skip to content

Commit

Permalink
feat: add puzzle bloc, and bloc tests
Browse files Browse the repository at this point in the history
  • Loading branch information
thisissandipp committed Jul 20, 2024
1 parent 00fb889 commit 2a24f17
Show file tree
Hide file tree
Showing 12 changed files with 815 additions and 0 deletions.
12 changes: 12 additions & 0 deletions lib/models/sudoku.dart
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,18 @@ class Sudoku extends Equatable {
return blocks.every((block) => block.correctValue == block.currentValue);
}

/// Returns the blocks to highlight when a block in the sudoku is
/// selected. This is a set of blocks from same row, blocks from same column,
/// and blocks from same sub-grid.
List<Block> blocksToHighlight(Block block) {
final highlightedBlocksSet = {
...getRowBlocks(block),
...getColumnBlocks(block),
...getSubGridBlocks(block),
};
return highlightedBlocksSet.toList();
}

@override
List<Object?> get props => [blocks];
}
124 changes: 124 additions & 0 deletions lib/puzzle/bloc/puzzle_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:sudoku/models/models.dart';
import 'package:sudoku/puzzle/puzzle.dart';

part 'puzzle_event.dart';
part 'puzzle_state.dart';

class PuzzleBloc extends Bloc<PuzzleEvent, PuzzleState> {
PuzzleBloc({
required PuzzleRepository puzzleRepository,
}) : _puzzleRepository = puzzleRepository,
super(const PuzzleState()) {
on<PuzzleInitialized>(_onPuzzleInitialized);
on<SudokuBlockSelected>(_onSudokuBlockSelected);
on<SudokuInputEntered>(_onSudokuInputEntered);
on<SudokuInputErased>(_onSudokuInputErased);
}

final PuzzleRepository _puzzleRepository;

void _onPuzzleInitialized(
PuzzleInitialized event,
Emitter<PuzzleState> emit,
) {
final puzzle = _puzzleRepository.getPuzzle()!;
emit(state.copyWith(puzzle: () => puzzle));
}

void _onSudokuBlockSelected(
SudokuBlockSelected event,
Emitter<PuzzleState> emit,
) {
emit(
state.copyWith(
selectedBlock: () => event.block,
highlightedBlocks: () =>
state.puzzle.sudoku.blocksToHighlight(event.block),
),
);
}

void _onSudokuInputEntered(
SudokuInputEntered event,
Emitter<PuzzleState> emit,
) {
final selectedBlock = state.selectedBlock;
if (selectedBlock == null || selectedBlock.isGenerated == true) return;

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

final updatedSudoku = mutableSudoku.updateBlock(
selectedBlock,
event.input,
);

// If the entered input is wrong.
if (selectedBlock.correctValue != event.input) {
final remainingMistakes = state.puzzle.remainingMistakes - 1;
// If there are no more remaining mistakes
if (remainingMistakes <= 0) {
emit(
state.copyWith(
puzzle: () => state.puzzle.copyWith(
sudoku: updatedSudoku,
remainingMistakes: remainingMistakes,
),
puzzleStatus: () => PuzzleStatus.failed,
),
);
} else {
emit(
state.copyWith(
puzzle: () => state.puzzle.copyWith(
sudoku: updatedSudoku,
remainingMistakes: remainingMistakes,
),
),
);
}
} else if (updatedSudoku.isComplete()) {
emit(
state.copyWith(
puzzle: () => state.puzzle.copyWith(
sudoku: updatedSudoku,
),
puzzleStatus: () => PuzzleStatus.complete,
highlightedBlocks: () => const [],
selectedBlock: () => null,
),
);
} else {
emit(
state.copyWith(
puzzle: () => state.puzzle.copyWith(
sudoku: updatedSudoku,
),
),
);
}
}

void _onSudokuInputErased(
SudokuInputErased event,
Emitter<PuzzleState> emit,
) {
final selectedBlock = state.selectedBlock;
if (selectedBlock == null || selectedBlock.isGenerated == true) return;

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

emit(
state.copyWith(
puzzle: () => state.puzzle.copyWith(
sudoku: mutableSudoku.updateBlock(selectedBlock, -1),
),
),
);
}
}
34 changes: 34 additions & 0 deletions lib/puzzle/bloc/puzzle_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
part of 'puzzle_bloc.dart';

sealed class PuzzleEvent extends Equatable {
const PuzzleEvent();

@override
List<Object> get props => [];
}

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

final class SudokuBlockSelected extends PuzzleEvent {
const SudokuBlockSelected(this.block);

final Block block;

@override
List<Object> get props => [block];
}

final class SudokuInputEntered extends PuzzleEvent {
const SudokuInputEntered(this.input);

final int input;

@override
List<Object> get props => [input];
}

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

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

final Puzzle puzzle;
final PuzzleStatus puzzleStatus;
final List<Block> highlightedBlocks;
final Block? selectedBlock;

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

PuzzleState copyWith({
Puzzle Function()? puzzle,
PuzzleStatus Function()? puzzleStatus,
List<Block> Function()? highlightedBlocks,
Block? Function()? selectedBlock,
}) {
return PuzzleState(
puzzle: puzzle != null ? puzzle() : this.puzzle,
puzzleStatus: puzzleStatus != null ? puzzleStatus() : this.puzzleStatus,
highlightedBlocks: highlightedBlocks != null
? highlightedBlocks()
: this.highlightedBlocks,
selectedBlock:
selectedBlock != null ? selectedBlock() : this.selectedBlock,
);
}
}
1 change: 1 addition & 0 deletions lib/puzzle/puzzle.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export 'bloc/puzzle_bloc.dart';
export 'models/models.dart';
export 'repository/puzzle_repository.dart';
1 change: 1 addition & 0 deletions test/helpers/helpers.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'mocks.dart';
export 'pump_app.dart';
export 'set_display_size.dart';
export 'sudoku_helpers.dart';
4 changes: 4 additions & 0 deletions test/helpers/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class MockSudokuState extends Mock implements SudokuState {}

class MockBlock extends Mock implements Block {}

class MockPosition extends Mock implements Position {}

class MockTicker extends Mock implements Ticker {}

class MockTimerBloc extends MockBloc<TimerEvent, TimerState>
Expand All @@ -32,3 +34,5 @@ class MockDio extends Mock implements Dio {}
class MockCacheClient extends Mock implements CacheClient {}

class MockPuzzle extends Mock implements Puzzle {}

class MockPuzzleRepository extends Mock implements PuzzleRepository {}
135 changes: 135 additions & 0 deletions test/helpers/sudoku_helpers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import 'package:sudoku/models/models.dart';

const sudoku2x2Block0 = Block(
position: Position(x: 0, y: 0),
correctValue: 4,
currentValue: -1,
);

const sudoku2x2Block1 = Block(
position: Position(x: 0, y: 1),
correctValue: 1,
currentValue: 1,
isGenerated: true,
);

const sudoku2x2Block2 = Block(
position: Position(x: 0, y: 2),
correctValue: 2,
currentValue: -1,
);

const sudoku2x2Block3 = Block(
position: Position(x: 0, y: 3),
correctValue: 3,
currentValue: -1,
);

const sudoku2x2Block4 = Block(
position: Position(x: 1, y: 0),
correctValue: 2,
currentValue: 2,
isGenerated: true,
);

const sudoku2x2Block5 = Block(
position: Position(x: 1, y: 1),
correctValue: 3,
currentValue: 3,
isGenerated: true,
);

const sudoku2x2Block6 = Block(
position: Position(x: 1, y: 2),
correctValue: 4,
currentValue: -1,
);

const sudoku2x2Block7 = Block(
position: Position(x: 1, y: 3),
correctValue: 1,
currentValue: -1,
);

const sudoku2x2Block8 = Block(
position: Position(x: 2, y: 0),
correctValue: 1,
currentValue: -1,
);

const sudoku2x2Block9 = Block(
position: Position(x: 2, y: 1),
correctValue: 4,
currentValue: -1,
);

const sudoku2x2Block10 = Block(
position: Position(x: 2, y: 2),
correctValue: 3,
currentValue: 3,
isGenerated: true,
);

const sudoku2x2Block11 = Block(
position: Position(x: 2, y: 3),
correctValue: 2,
currentValue: 2,
isGenerated: true,
);

const sudoku2x2Block12 = Block(
position: Position(x: 3, y: 0),
correctValue: 3,
currentValue: -1,
);

const sudoku2x2Block13 = Block(
position: Position(x: 3, y: 1),
correctValue: 2,
currentValue: -1,
);

const sudoku2x2Block14 = Block(
position: Position(x: 3, y: 2),
correctValue: 1,
currentValue: 1,
isGenerated: true,
);

const sudoku2x2Block15 = Block(
position: Position(x: 3, y: 3),
correctValue: 4,
currentValue: -1,
);

const sudoku2x2 = Sudoku(
blocks: [
sudoku2x2Block0,
sudoku2x2Block1,
sudoku2x2Block2,
sudoku2x2Block3,
sudoku2x2Block4,
sudoku2x2Block5,
sudoku2x2Block6,
sudoku2x2Block7,
sudoku2x2Block8,
sudoku2x2Block9,
sudoku2x2Block10,
sudoku2x2Block11,
sudoku2x2Block12,
sudoku2x2Block13,
sudoku2x2Block14,
sudoku2x2Block15,
],
);

final sudoku2x2WithOneRemaining = sudoku2x2
.updateBlock(sudoku2x2Block0, 4)
.updateBlock(sudoku2x2Block3, 3)
.updateBlock(sudoku2x2Block6, 4)
.updateBlock(sudoku2x2Block7, 1)
.updateBlock(sudoku2x2Block8, 1)
.updateBlock(sudoku2x2Block9, 4)
.updateBlock(sudoku2x2Block12, 3)
.updateBlock(sudoku2x2Block13, 2)
.updateBlock(sudoku2x2Block15, 4);
Loading

0 comments on commit 2a24f17

Please sign in to comment.