🧠

【Flutter】記憶力ゲームを作りました

2024/03/17に公開

はじめに

Flutterで、コンピューターが示した順番を覚えてタップしていく記憶力ゲームを作りました。
元ネタはGoogleの「記憶力ゲーム」、アセットは「いらすとや」です。
パッケージは使わず、素のFlutterで作ってみました。

ブラウザで遊べるので、触ってみてください。
memory_game
(キャッシュがないと初回読み込みが遅いです。Flutter Webに共通でしょうか??)

ソースコード

https://github.com/skw398/memory_game

実装

特にゲームのロジックと呼べるものはないので、以下のようなシンプルな構成になっています。

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の生産性の高さを感じました。

株式会社Never

Discussion