♟️

Flutter で将棋ゲームを作ろう #4 相手の駒を取ろう

2023/07/06に公開
2

はじめに

こんにちは!水瀬ひろと申します。
趣味で Flutter を楽しんでいるエンジニアです。

僕はソシャゲ(特にHoYoverse作品)が好きで、自分でもゲームを作ってみたいと思い、まずは将棋ゲームに挑戦してみました。
Flame などのライブラリは使用せず、自前実装のみでおこなったので学びが多かったです。

将棋ゲームの制作を通じて、以下のようなことを学ぶことができたので、シリーズ化して残しておこうと思います。

  • 将棋ゲームの作り方やルールの理解
  • 応用としてボードゲーム系アプリの作成手法の習得
  • ゲームの基本的な動作原理についての理解

このシリーズでは、実際に行った実装内容を細かく解説していきます。
最終的には以下のような将棋ゲームが完成する予定です!
将棋ゲーム

持ち駒を管理できるようにしよう

前回の記事では前編後編に分けて駒を動かせるようにしました。
https://zenn.dev/flutteruniv_dev/articles/d84f5b822dba21
https://zenn.dev/flutteruniv_dev/articles/3507e5a99d21fa

今回は相手の駒を取れるようにしていきます。

main.dart
List<ShogiPiece> piecesTakenByAlly = []; // 味方が獲得した駒の配列
List<ShogiPiece> piecesTakenByEnemy = []; // 敵が獲得した駒の配列

まずはお互いの持ち駒を管理する配列を用意します。

相手の駒を取って持ち駒に入れよう

次に駒の移動メソッドに処理を加えていきます。

main.dart
// 駒を移動
void movePiece(int newRow, int newCol) {
  // 相手の駒があれば取得
  if (shogiBoard[newRow][newCol] != null) {
    var capturedPiece = shogiBoard[newRow][newCol];

    // 駒の取得
    if (capturedPiece!.isAlly) {
      piecesTakenByEnemy.add(capturedPiece);
    } else {
      piecesTakenByAlly.add(capturedPiece);
    }
  }

移動先に相手の駒があれば持ち駒として取得できるようにしました。

その持ち駒を一覧表示できるようにします。

main.dart

Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        GridView.builder(
          shrinkWrap: true,
          itemCount: piecesTakenByEnemy.length,
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 9),
          physics: const NeverScrollableScrollPhysics(),
          itemBuilder: (context, index) => GestureDetector(
            onTap: () {},
            child: Padding(
              padding: const EdgeInsets.all(5.0),
              child: Image.asset(piecesTakenByEnemy[index].imagePath),
            ),
          ),
        ),
        // ターン表示
        Text(
          isAllyTurn ? "あなたのターンです" : "相手のターンです",
          style: const TextStyle(
            fontWeight: FontWeight.bold,
          ),
        ),
        GridView.builder(
          shrinkWrap: true, // GridView のサイズを itemBuilder で表示するコンテンツによって決定
          itemCount: 9 * 9,
          physics: const NeverScrollableScrollPhysics(), // スクロールさせない
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 9), // 9列で表示
          itemBuilder: (context, index) {
            int row = index ~/ 9;
            int col = index % 9;
            bool isSelected = row == selectedRow && col == selectedCol; // 座標が選択されているかどうか
            bool isValidMove = false;

            // 選択中の駒が移動可能な座標かどうか
            for (var position in validMoves) {
              if (position[0] == row && position[1] == col) {
                isValidMove = true;
              }
            }

            return Square(
              shogiPiece: shogiBoard[row][col],
              isSelected: isSelected,
              onTap: () => selectPiece(row, col),
              isValidMove: isValidMove,
            );
          },
        ),
        GridView.builder(
          shrinkWrap: true,
          itemCount: piecesTakenByAlly.length,
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 9),
          physics: const NeverScrollableScrollPhysics(),
          itemBuilder: (context, index) => GestureDetector(
            onTap: () {},
            child: Padding(
              padding: const EdgeInsets.all(5.0),
              child: Image.asset(piecesTakenByAlly[index].imagePath),
            ),
          ),
        ),
      ],
    )
  );
}

将棋盤の上下に GridView.builder を追加し、 piecesTakenByAlly, piecesTakenByEnemy をそれぞれ表示します。
駒をタップすることで盤面に出せるようにするため GestureDetector を使っています。

iOS シミュレータ
相手の歩を取ったので、無事に歩が自分の持ち駒欄に表示されました!
しかし向きが悪いですね。
自分の駒になったのに、相手の駒と同じ向きです。

helper_methods.dart
// 駒の画像を差し替える
ShogiPiece turnOverPiece(ShogiPiece piece) {
  String currentKeyString = piece.isAlly ? "up" : "down"; // 画像パスから検索する文字列
  String newKeyString = piece.isAlly ? "down" : "up"; // 置き換える文字列
  String newImagePath = piece.imagePath.replaceFirst(currentKeyString, newKeyString); // 画像パスの置き換え

  return ShogiPiece(
    type: newShogiPieceType,
    isAlly: !piece.isAlly,
    imagePath: newImagePath,
    isPromoted: false,
  );
}

helper_methods.dart に新しいメソッドを用意しました。
自分の駒画像と相手の駒画像を up_ down_ で分けているので、駒を取った時点で入れ替えられるようにします。

main.dart
// 駒を移動
void movePiece(int newRow, int newCol) {
  // 相手の駒があれば取得
  if (shogiBoard[newRow][newCol] != null) {
    var capturedPiece = shogiBoard[newRow][newCol];

    // 駒の取得
    if (capturedPiece!.isAlly) {
      piecesTakenByEnemy.add(turnOverPiece(capturedPiece));
    } else {
      piecesTakenByAlly.add(turnOverPiece(capturedPiece));
    }
  }

駒を取得するときに、画像パスを入れ替えて持ち駒に入れます。

iOS シミュレータ
うまくいきましたね!

終わりに

ここまでお疲れさまでした〜!
無事に相手の駒を取れるようになりました。

少しづつ本物の将棋に近づいていますね。
質問などあれば気軽にコメントください!

次回は持ち駒を盤面に出せるようにしていきます。

その後も以下のような記事を準備しているのでお付き合いいただけると嬉しいです。

  • 「成り」の実装
  • 王手の判定
  • 詰みの判定
  • CPUを実装する
  • 「二歩」の禁止
  • 棋譜を記録する

Twitter

https://twitter.com/minase_hiro_

Threads

https://www.threads.net/@minasehiro

Flutter大学

Discussion

覇王森健一覇王森健一

水瀬ひろ様、初めまして、森田健太と申します。

この度、私が考案した「召喚将棋」という将棋の新ルールを広めるための将棋アプリ開発において、水瀬ひろ様のこの記事のシリーズが非常に参考になりました。

どの記事もわかりやすく、多少抜けはありましたが、補完しながら真似して将棋アプリを作ってみました。

「召喚将棋」は、初め、盤面に王将と玉将のみが存在する将棋です。先手から、召喚するか、駒を動かすか、手駒を置くかを選べ、どれか一つだけを行い、相手のターンに移行します。おおよそ、王手か詰めろから始まることが多く、後手は不利なルールになっています。その分、短期決戦で終わるため、先手と後手を交代して数戦するのをひとセットとして、遊ぶのも良いかもしれません。そのあたりはバランスを見ながらやっていきたいと思っています。持ち時間を設定して、3戦を通して共通で引き継がれる持ち時間とするのも面白いなとここに書きながら、思いつきました。

長文失礼しました。文字壁になっていましたね。

持ち駒を出す部分の処理をどうやっていくのかが気になって、夜しか眠れません。

急かすつもりはありませんが、続きの記事をお待ちしています。

この度は、非常に参考になる記事を書いていただいてありがとうございました。

またよろしくお願いいたします。

森田

水瀬ひろ水瀬ひろ

森田さん
返信遅くなってすみません...!!

参考にしていただけたということでとても嬉しいです。
続きの記事も頑張って書いていきます!

ちなみに持ち駒を盤面に出すための記事はもう書いていて、こちらになります!
https://zenn.dev/flutteruniv_dev/articles/277196a4794397

不明確な点などあればお気軽にコメントください!