概要
長女と時々遊んでいる 「どうぶつしょうぎ」を、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 の学習!)