FlutterとSEO、ときどき仕事

Dart, flutter, アプリ開発日記

【アプリ開発日記】Wordleの基本機能を実装する【part6】

本記事はFlutterを使ったWordleクローンアプリ開発日記のpart6になります

以前の記事:

【アプリ開発日記】アプリを開発するにあたっての流れとコツ【part1】

【アプリ開発日記】Widgetを切り出して作成する【part2】

【アプリ開発日記】ブロックのWidgetを作成する【part3】

【アプリ開発日記】キーボードのイベントを通知する【part4】

【アプリ開発日記】Wordleの基本機能を実装する【part5】

キー入力を検出したらUIに反映する

これまでの開発で必要なイベント類は実装できているはずです。最後に、StatefulWidgetの機能を使ってキー入力を検出した際にUIに反映するようにします。

StatefulWidget内でsetState関数を呼ぶと、カッコ内の処理が行われた後にbuildが再実行されます。今回はcreateBlocksList関数にWidgetを生成する処理をまとめて、可読性を下げないようにしていますが、簡単には以下の流れで処理が実行されることになります。

  • キーが押される
  • setStateが実行される
  • カッコ内のキーごとの処理が行われる
  • build関数が呼び出される
  • Blockに入力した文字や判定結果を反映してWidgetを再生成する
class _MyHomePageState extends State<MyHomePage> {
  final controller = WordController();

  @override
  Widget build(BuildContext context) {
    debugPrint('build');
    List<Widget> widgets = createBlocksList();
    widgets.add(SoftwareKeyBoard(
      onTapEnterKey: () {
        debugPrint('onTapEnterKey');
        setState(() {
          handleTapEnterKey();
        });
      },
      onTapKey: (String char) {
        debugPrint('onTap : $char');
        setState(() {
          controller.appendLetter(char);
        });
      },
      onTapDeleteKey: () {
        debugPrint('onTapDeleteKey');
        setState(() {
          controller.removeLetter();
        });
      },
    ));

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: widgets
        ),
      ),
    );
  }

  void handleTapEnterKey () {
    try {
      if (controller.enterLetter()) {
        // 正解
        debugPrint('正解');
        const snackBar = SnackBar(content: Text('正解です'));
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
      } else {
        // 不正解
        debugPrint('不正解');
        // TODO Widgetの更新
      }
    } on WordNotExistException catch(e) {
      debugPrint('WordNotExistException');
      const snackBar = SnackBar(content: Text('存在しない単語です'));
      ScaffoldMessenger.of(context).showSnackBar(snackBar);
    } on WordLengthException catch(e) {
      debugPrint('WordLengthException');
      const snackBar = SnackBar(content: Text('5文字入力してください'));
      ScaffoldMessenger.of(context).showSnackBar(snackBar);
    } catch(e) {
      // do nothing
      debugPrint('catch');
    }
  }

  List<Widget> createBlocksList() {
    List<Widget> blocksList = [];
    for (int i = 0; i < WordController.kWordCount; i++) {
      List<WordState> states;
      Blocks block;
      try {
        block = Blocks(
          word: controller.wordList.elementAt(i) ,
          wordStateList: controller.wordStatesList.elementAt(i),
        );
      } catch(e) {
        block = Blocks(
          word: controller.wordList.elementAt(i) ,
        );
      }
      blocksList.add(block);
    }
    return blocksList;
  }
}

文字の判定処理

ここまで書いておいて、文字の判定処理とそれを記憶しておく処理が完全に抜けていました。今回は、Stringを配列として扱うことができる点を利用し、for文を使って判定結果を行うようにしました。判定用の関数(_judgeWord)は、「5文字入力されていることが前提」の関数となっているため、事前文字長や文字の存在判定(現時点ではスタブ関数)を済ませるようにしています。private関数とすることで外からも呼べないようにすることで安全を担保しています。

/// 1文字を追加する
void appendLetter(String letter) {
  if (_checkWordLength()) {
    //5文字になっている場合は追加しない
    return;
  }
  if (letter.length == 1) {
    _wordList[_currentCount] += letter;
  } else {
    // FIXME throw exception ?
  }
}

/// 判定を行う
void _judgeWord(String word) {
  List<WordState> result = [];
  for (int i = 0; i < kWordLength; i++) {
    if (word[i] == _answer[i]) {
      // 文字も場所も正解なのでhit(緑)
      result.add(WordState.hit);
    } else if (_answer.contains(word[i])) {
      // hitではないが文字が含まれているためblow(黄)
      result.add(WordState.blow);
    } else {
      // 上記以外はハズレ(灰)
      result.add(WordState.miss);
    }
  }
  _wordStatesList.add(result);
}

見た目

こんな感じです。現状は回答が固定であったり、単語存在確認を行なっていなかったりなど、不十分な部分がありますが基本機能としては実装が完了しました。それ以外にも回答補助機能など、どんどん機能を追加していこうと思います。

part7に続く

コメントを残す