🧠
【Flutter】記憶力ゲームを作りました
はじめに
Flutterで、コンピューターが示した順番を覚えてタップしていく記憶力ゲームを作りました。
元ネタはGoogleの「記憶力ゲーム」、アセットは「いらすとや」です。
パッケージは使わず、素のFlutterで作ってみました。
ブラウザで遊べるので、触ってみてください。
memory_game
(キャッシュがないと初回読み込みが遅いです。Flutter Webに共通でしょうか??)
ソースコード
実装
特にゲームのロジックと呼べるものはないので、以下のようなシンプルな構成になっています。
domain
GameSequence
がランダムなインデックスのリストを管理し、
game_sequence.dart
class GameSequence {
final int _itemCount;
late List<int> _indices;
List<int> get indices => List<int>.unmodifiable(_indices);
GameSequence({required int itemCount}) : _itemCount = itemCount;
int get _randomIndex => Random().nextInt(_itemCount);
void refresh() => _indices = [_randomIndex];
void next() => _indices.add(_randomIndex);
}
InputChecker
が入力をチェックして、InputResult
を返します。
input_checker.dart
class InputChecker {
final GameSequence _gameSequence;
int _inputCount = 0;
InputChecker({required GameSequence gameSequence}) : _gameSequence = gameSequence;
InputResult check(int index) {
_inputCount++;
if (_gameSequence.indices[_inputCount - 1] != index) {
_inputCount = 0;
return InputResult.notMatch(score: _gameSequence.indices.length - 1);
} else if (_gameSequence.indices.length == _inputCount) {
_inputCount = 0;
return InputResult.match(isAnswerCompleted: true);
} else {
return InputResult.match(isAnswerCompleted: false);
}
}
}
input_result.dart
sealed class InputResult {
const InputResult();
static InputResult notMatch({required int score}) => NotMatch._(score);
static InputResult match({required bool isAnswerCompleted}) => Match._(isAnswerCompleted);
}
class NotMatch extends InputResult {
final int score;
const NotMatch._(this.score) : super();
}
class Match extends InputResult {
final bool isAnswerCompleted;
const Match._(this.isAnswerCompleted) : super();
}
presentaion
返ってきたInputResult
によって、GameState
を更新します。
ディレイを入れながら、ゲームの進行の間隔を調整します。
GameState
に応じて画面が更新されます。
game_state.dart
enum GameState {
initial,
answerShowing,
answering,
answerCompleted,
cleared,
result;
...
}
memory_game_widget.dart
class MemoryGameWidgetState extends State<MemoryGameWidget> {
late final _gameSequence = GameSequence(itemCount: widget.items.length);
late final _inputChecker = InputChecker(gameSequence: _gameSequence);
var _state = GameState.initial;
int? _scaleAnimatingItemIndex;
late int? _score;
...
Widget build(BuildContext context) {
// GameStateに応じた画面の実装
...
}
void _onReceiveInputResult(InputResult result) async {
switch (result) {
case NotMatch(:int score):
await _delay(amount: 1);
setState(() {
_score = score;
_state = GameState.result;
});
case Match(:bool isAnswerCompleted):
if (!isAnswerCompleted) break;
setState(() => _state = GameState.answerCompleted);
await _delay(amount: 1);
setState(() => _state = GameState.cleared);
await _delay(amount: 4);
_gameSequence.next();
_showAnswer();
}
}
void _showAnswer() async {
setState(() => _state = GameState.answerShowing);
await _delay(amount: 3);
for (int index in _gameSequence.indices) {
setState(() => _scaleAnimatingItemIndex = index);
await _delay(amount: 1);
setState(() => _scaleAnimatingItemIndex = null);
await _delay(amount: 1);
}
setState(() => _state = GameState.answering);
}
Future<void> _delay({required double amount}) async {
final duration = Duration(milliseconds: (Const.baseDurationMs * amount).toInt());
await Future.delayed(duration);
}
}
おわりに
ゲームらしいアニメーションを実装するためのAnimationControllerやAnimated系のAPIが使いやすく、改めてFlutterの生産性の高さを感じました。
Discussion