-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add puzzle mode, repository, and cache
- Loading branch information
1 parent
4392814
commit 00fb889
Showing
10 changed files
with
297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/// {@template cache_client} | ||
/// An in-memory cache client. | ||
/// {@endtemplate} | ||
class CacheClient { | ||
/// {@macro cache_client} | ||
CacheClient() : _cache = <String, Object>{}; | ||
|
||
final Map<String, Object> _cache; | ||
|
||
/// Writes the [key] and [value] pair into the cache. | ||
void write<T extends Object>({required String key, required T value}) { | ||
_cache[key] = value; | ||
} | ||
|
||
/// Find the value for the specified key. | ||
/// | ||
/// If the value is not found, it returns `null`. | ||
T? read<T extends Object>({required String key}) { | ||
final value = _cache[key]; | ||
if (value is T) return value; | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export 'puzzle.dart'; | ||
export 'puzzle_status.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// ignore_for_file: public_member_api_docs, sort_constructors_first | ||
import 'package:equatable/equatable.dart'; | ||
import 'package:flutter/widgets.dart'; | ||
import 'package:sudoku/models/models.dart'; | ||
|
||
/// {@template puzzle} | ||
/// Defines the model for a [Sudoku] puzzle. | ||
/// {@endtemplate} | ||
@immutable | ||
class Puzzle extends Equatable { | ||
/// {@macro puzzle} | ||
const Puzzle({ | ||
required this.sudoku, | ||
required this.difficulty, | ||
this.totalSecondsElapsed = 0, | ||
this.remainingMistakes = 3, | ||
this.remainingHints = 3, | ||
}); | ||
|
||
/// Sudoku for this puzzle. | ||
final Sudoku sudoku; | ||
|
||
/// The difficulty of the puzzle. | ||
final Difficulty difficulty; | ||
|
||
/// Total seconds elapsed on this game. | ||
/// | ||
/// Default is set to 0. | ||
final int totalSecondsElapsed; | ||
|
||
/// Defines the remaining mistakes available for this sudoku. | ||
/// | ||
/// Default is set to 3. | ||
final int remainingMistakes; | ||
|
||
/// Defines the remaining hints available for this puzzle. | ||
/// | ||
/// Defaults to 3. | ||
final int remainingHints; | ||
|
||
@override | ||
List<Object?> get props => [ | ||
sudoku, | ||
difficulty, | ||
totalSecondsElapsed, | ||
remainingMistakes, | ||
remainingHints, | ||
]; | ||
|
||
/// Returns an updated copy of this [Puzzle] with the | ||
/// updated parameters. | ||
/// | ||
/// {@macro puzzle} | ||
Puzzle copyWith({ | ||
Sudoku? sudoku, | ||
Difficulty? difficulty, | ||
int? totalSecondsElapsed, | ||
int? remainingMistakes, | ||
int? remainingHints, | ||
}) { | ||
return Puzzle( | ||
sudoku: sudoku ?? this.sudoku, | ||
difficulty: difficulty ?? this.difficulty, | ||
totalSecondsElapsed: totalSecondsElapsed ?? this.totalSecondsElapsed, | ||
remainingMistakes: remainingMistakes ?? this.remainingMistakes, | ||
remainingHints: remainingHints ?? this.remainingHints, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/// Defines the status of the puzzle. | ||
enum PuzzleStatus { | ||
/// The puzzle is in progress. | ||
incomplete, | ||
|
||
/// The puzzle is successfully solved. | ||
complete, | ||
|
||
/// The puzzle is over. | ||
/// | ||
/// One reason could be the user has exhausted all | ||
/// the remaining mistakes. | ||
failed, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export 'models/models.dart'; | ||
export 'repository/puzzle_repository.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import 'package:flutter/widgets.dart'; | ||
import 'package:sudoku/cache/cache.dart'; | ||
import 'package:sudoku/puzzle/puzzle.dart'; | ||
|
||
/// {@template puzzle_repository} | ||
/// A repository that handles `puzzle` related data. | ||
/// | ||
/// Used to pass data from home page to puzzle page. | ||
/// {@endtemplate} | ||
class PuzzleRepository { | ||
/// {@macro puzzle_repository} | ||
const PuzzleRepository({ | ||
required CacheClient cacheClient, | ||
}) : _cacheClient = cacheClient; | ||
|
||
final CacheClient _cacheClient; | ||
|
||
/// The key used for storing the puzzle in-memory. | ||
/// | ||
/// This is only exposed for testing and shouldn't be used by consumers of | ||
/// this library. | ||
@visibleForTesting | ||
static const kPuzzleKey = '__puzzle_key__'; | ||
|
||
/// Provides the puzzle stored in-memory. | ||
/// | ||
/// Returns null, if there is no puzzle. | ||
Puzzle? getPuzzle() => _cacheClient.read<Puzzle>(key: kPuzzleKey); | ||
|
||
/// Saves a puzzle in-memory. | ||
/// | ||
/// If there's already a puzzle there, it will be replaced. | ||
void storePuzzle({required Puzzle puzzle}) => | ||
_cacheClient.write<Puzzle>(key: kPuzzleKey, value: puzzle); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// ignore_for_file: prefer_const_constructors | ||
|
||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:sudoku/cache/cache.dart'; | ||
|
||
void main() { | ||
group('CacheClient', () { | ||
test('can read and write value for a given key', () { | ||
final cache = CacheClient(); | ||
const key = '__key__'; | ||
const value = '__value__'; | ||
|
||
expect(cache.read<String>(key: key), isNull); | ||
cache.write<String>(key: key, value: value); | ||
expect(cache.read<String>(key: key), equals(value)); | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// ignore_for_file: prefer_const_constructors, avoid_redundant_argument_values | ||
|
||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:sudoku/models/models.dart'; | ||
import 'package:sudoku/puzzle/puzzle.dart'; | ||
|
||
void main() { | ||
group('Puzzle', () { | ||
Puzzle createSubject({ | ||
Sudoku? sudoku, | ||
Difficulty? difficulty, | ||
int? totalSecondsElapsed, | ||
int? remainingMistakes, | ||
int? remainingHints, | ||
}) { | ||
return Puzzle( | ||
sudoku: sudoku ?? Sudoku(blocks: const []), | ||
difficulty: difficulty ?? Difficulty.medium, | ||
totalSecondsElapsed: totalSecondsElapsed ?? 0, | ||
remainingMistakes: remainingMistakes ?? 2, | ||
remainingHints: remainingHints ?? 1, | ||
); | ||
} | ||
|
||
test('constructor works correctly', () { | ||
expect(createSubject, returnsNormally); | ||
}); | ||
|
||
test('supports value equality', () { | ||
expect(createSubject(), equals(createSubject())); | ||
}); | ||
|
||
test('props are correct', () { | ||
expect( | ||
createSubject().props, | ||
equals(<Object?>[ | ||
Sudoku(blocks: const []), | ||
Difficulty.medium, | ||
0, | ||
2, | ||
1, | ||
]), | ||
); | ||
}); | ||
|
||
group('copyWith', () { | ||
test('returns same object if no argument is passed', () { | ||
expect(createSubject().copyWith(), equals(createSubject())); | ||
}); | ||
|
||
test('returns the old value for each parameter if null is provided', () { | ||
expect( | ||
createSubject().copyWith( | ||
sudoku: null, | ||
difficulty: null, | ||
totalSecondsElapsed: null, | ||
remainingMistakes: null, | ||
remainingHints: null, | ||
), | ||
equals(createSubject()), | ||
); | ||
}); | ||
|
||
test('returns the updated copy of this for every non-null parameter', () { | ||
expect( | ||
createSubject().copyWith( | ||
sudoku: Sudoku(blocks: const []), | ||
difficulty: Difficulty.expert, | ||
totalSecondsElapsed: 15, | ||
remainingMistakes: 1, | ||
remainingHints: 0, | ||
), | ||
equals( | ||
createSubject( | ||
sudoku: Sudoku(blocks: const []), | ||
difficulty: Difficulty.expert, | ||
totalSecondsElapsed: 15, | ||
remainingMistakes: 1, | ||
remainingHints: 0, | ||
), | ||
), | ||
); | ||
}); | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:mocktail/mocktail.dart'; | ||
import 'package:sudoku/cache/cache.dart'; | ||
import 'package:sudoku/puzzle/puzzle.dart'; | ||
|
||
import '../../helpers/helpers.dart'; | ||
|
||
void main() { | ||
group('PuzzleRepository', () { | ||
late Puzzle puzzle; | ||
late CacheClient cacheClient; | ||
late PuzzleRepository puzzleRepository; | ||
|
||
setUp(() { | ||
puzzle = MockPuzzle(); | ||
cacheClient = MockCacheClient(); | ||
puzzleRepository = PuzzleRepository(cacheClient: cacheClient); | ||
when(() => cacheClient.read(key: any(named: 'key'))).thenReturn(puzzle); | ||
}); | ||
|
||
group('getPuzzle', () { | ||
test('calls the read method from [CacheClient]', () { | ||
puzzleRepository.getPuzzle(); | ||
verify( | ||
() => cacheClient.read<Puzzle>(key: PuzzleRepository.kPuzzleKey), | ||
).called(1); | ||
}); | ||
}); | ||
|
||
group('storePuzzle', () { | ||
test('calls the write method from [CacheClient]', () { | ||
puzzleRepository.storePuzzle(puzzle: puzzle); | ||
verify( | ||
() => cacheClient.write<Puzzle>( | ||
key: PuzzleRepository.kPuzzleKey, | ||
value: puzzle, | ||
), | ||
).called(1); | ||
}); | ||
}); | ||
}); | ||
} |