1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Power Apps】「どうぶつしょうぎ」を作ってみた

Last updated at Posted at 2025-04-02

概要

長女と時々遊んでいる 「どうぶつしょうぎ」を、Power Apps で再現してみました。

どうぶつしょうぎのルールは下記を参照ください。

画面

3×4の盤面を用意して、各プレーヤーの駒を配置しています。各プレーヤーの持ち駒を表示する欄も作ってあります。

駒の画像は、いらすとやの画像を使って PowerPoint で作成しました。

スタートボタン押下時

初期設定として、コレクション・変数を作成します。

盤面情報

row, col でマスの位置、selectable で選択可否、piece で駒の種類、owner でその駒の持ち主を示しています。

ClearCollect(colBoard,
    {row:1, col:1, selectable:false, piece:"きりん", owner: 1},
    {row:1, col:2, selectable:false, piece:"らいおん", owner: 1},
    {row:1, col:3, selectable:false, piece:"ぞう", owner: 1},
    {row:2, col:1, selectable:false, piece:Blank(), owner: 0},
    {row:2, col:2, selectable:false, piece:"ひよこ", owner: 1},
    {row:2, col:3, selectable:false, piece:Blank(), owner: 0},
    {row:3, col:1, selectable:false, piece:Blank(), owner: 0},
    {row:3, col:2, selectable:false, piece:"ひよこ", owner: -1},
    {row:3, col:3, selectable:false, piece:Blank(), owner: 0},
    {row:4, col:1, selectable:false, piece:"ぞう", owner: -1},
    {row:4, col:2, selectable:false, piece:"らいおん", owner: -1},
    {row:4, col:3, selectable:false, piece:"きりん", owner: -1}
);

owner は、下から上に攻めるプレイヤーを -1、上から下に攻めるプレイヤーを 1 としています。こうしておくと、「ひよこ」の row にプレイヤーの番号(1/-1)を足すことで、「ひよこ」の進めるマスを割り出すことができます。プレイヤーの手番交替の際は、プレイヤー番号に -1 を掛け算することで、1/-1 を切り替えることができます。

持ち駒情報

Clear(colCaptured);
/*
//持ち駒追加テスト
Collect(colCaptured,
    {owner:-1, piece:"ひよこ", using: false, id:1},
    {owner:-1, piece:"きりん", using: false, id:2},
    {owner:1, piece:"ひよこ", using: false, id:3}
);
*/

持ち駒情報には、利用中状態を示す using と、一意の id を設定しています。id を設定することで、同じ駒を 2 つ持っているときに、どちらを使ったかが判別できるようになります。使った持ち駒を削除する際に、LookUp 関数で参照すると 1つ目の駒が削除されてしまい、2つ目の駒をクリックしたときの見え方が気持ち悪かったため。

また、プレイヤーごとに持ち駒情報を分けずに、両プレイヤーの持ち駒をひとつのコレクションに格納しています。owner 列の値でどちらのプレイヤーの持ち駒かがわかるようにしてあります。

変数

UpdateContext(
    {   
        locActivePlayer: -1,
        locMovingPiece: {row: Blank(), col: Blank(), piece: Blank(), owner: Blank()},
        locWinner: Blank(),
        locUsingCaptured: false
    }
);

locMovingPiece は、移動中の駒の情報を表す配列です。駒の移動は「駒を選択する→移動先の駒をクリックする」の 2段階になるので、移動元のマスの情報を記録しておく必要があります。

他の説明は割愛。

主なロジック

マスをクリックした時

持ち駒をクリックした時

「持ち駒利用中」の変数を作らずとも、「利用中状態の持ち駒がある」かどうかで持ち駒利用の判定はできたなー。

駒の移動範囲

自分の駒のあるマスを選択したとき、その駒の移動可能範囲のマスを計算し、選択可能状態に変更します。なお、owner = locActivePlayer であるマスは、自分の駒があるマスなので、移動可能範囲にはなりません。

With({sel: galBoard.Selected},
// 中略
    Switch(sel.piece,
        "ひよこ",
        // ひよこ:前方のマスを選択可能に
        UpdateIf(colBoard,
            owner <> locActivePlayer &&
            (row = sel.row + locActivePlayer && col = sel.col),
            {selectable: true}
        ),
        "にわとり",
        // にわとり:前3マス・左右と、後ろのマスを選択可能に
        UpdateIf(colBoard,
            owner <> locActivePlayer &&
            ((row = sel.row + locActivePlayer || row = sel.row) &&
            (col = sel.col + 1 || col = sel.col || col = sel.col - 1)) ||
            (row = sel.row - locActivePlayer && col = sel.col),
            {selectable: true}
        ),
        "きりん",
        // きりん:前後左右のマスを選択可能に
        UpdateIf(colBoard,
            owner <> locActivePlayer &&
            ((row = sel.row + 1 && col = sel.col) ||
            (row = sel.row - 1 && col = sel.col) ||
            (row = sel.row && col = sel.col + 1) ||
            (row = sel.row && col = sel.col - 1)),
            {selectable: true}
        ),
        "ぞう",
        // ぞう:前後左右ナナメのマスを選択可能に
        UpdateIf(colBoard,
            owner <> locActivePlayer &&
            ((row = sel.row + 1 || row = sel.row - 1) &&
            (col = sel.col + 1 || col = sel.col - 1)),
            {selectable: true}
        ),
        "らいおん",
        // らいおん:周囲のマスを選択可能に
        UpdateIf(colBoard,
            owner <> locActivePlayer &&
            ((row = sel.row + 1 || row = sel.row || row = sel.row - 1) &&
            (col = sel.col + 1 || col = sel.col || col = sel.col - 1)),
            {selectable: true}
        )
    )
// 中略
)

もっと短いコードで判定する方法はありそうですが、とりあえず。

最終列に到達したときの判定

プレイヤー -1の場合は 1列目、プレイヤー 1の場合は4列目に達した「ひよこ」を、「にわとり」に変更します(持ち駒から打った場合はのぞく)。同様に、「らいおん」が最終列に到達したとき、locWinner を設定してゲーム終了にしています。

// ひよこが最終列に到達したとき、にわとりに変更
If(locMovingPiece.piece = "ひよこ" && !locUsingCaptured && ((sel.row = 1 && locActivePlayer = -1) || (sel.row = 4 && locActivePlayer = 1)),
    UpdateIf(colBoard,
        row = sel.row && col = sel.col,
        {piece: "にわとり"}
    )
);

最終列の判定は sel.row = 2.5 + locActivePlayer * 1.5 でまとめられそうですが、わかりにくいのでやめました。

手番交替

プレイヤー番号を 1 と -1 にしたことで、-1 を掛け算すれば切り替えられます。各マスの owner の値も兼ねており、誰の駒もないときは 0 です。3つの値を取るので、true / false は使いませんでした。

UpdateContext({locActivePlayer: locActivePlayer * -1})

// UpdateContext({locActivePlayer: If(locActivePlayer = 1, 2, 1)})

プレイヤー番号を 1 と 2 にした場合は If関数を使うことになりますが、1 と -1 にした方がわかりやすいのではと。

画像を反対向きにする

画像の ImageRotation プロパティ、初めて使いました。上から下に攻めるプレイヤー 1 の駒は、180度回転させて表示する必要があります。

If(ThisItem.owner = 1,
    ImageRotation.Rotate180,
    ImageRotation.None
)

アイコンは Rotation プロパティが数値型なので自由に回転できますが、画像の場合は 90度単位(None, Rotate90, Rotate180, Rotate270) でしか回転できない?

気づき

  • マスを選択する場面が「動かす駒のマスを選択する」「動かす先のマスを選択する」の 2種類あり、リバーシなどの前者がないゲームよりも処理が複雑になった
  • 作りながら設計すると、不要な変数が残ったりしてしまう。大まかな設計をもう少し立ててから作るべきだった
  • 無理にコードを短くするより、わかりやすいコードの方がいい
  • オリジナル駒を追加したり、初期盤面のエディット機能を追加したりできそう
  • 本将棋も作れそう。「どうぶつしょうぎ」にない要素は下記のとおり
    • 「成る」かどうかを選択できるようにする
    • 長距離移動可能な駒(香・角・飛) のロジック
    • 二歩・打ち歩詰めになるときの処理(打てないようにする or 打つ前に戻す or 強制敗北)
    • 移動できない場所に持ち駒を打てないように(どうぶつしょうぎでは、最奥の列に「ひよこ」を打ってもいいらしい)
  • アプリを作るのに数時間かかったが、段ボールを切って絵を描いたら 30分くらいで作れそう…(目的は Power Apps の学習!)
1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?