はじめに
おうじゃさんのイベントでPower Appsでゲーム作成していました。
私が作成したのは、世界のアソビ大全51でも収録されている、「コネクトフォー」です。
間に合った!#PowerApps でコネクトフォー作りましたー
— コルネ (@koruneko32767) 2022年12月31日
#おうじゃさんといっしょ pic.twitter.com/Jd3EkXvltJ
何とか2022年内に作成することができましたー
ゲームのルールは任天堂さんが公式サイトで解説してくれていますので、そちらをご参照ください。
今回はこれの作成方法をまとめています。
とはいえながらで作成したので、作りがだいぶ雑ですw
コネクトフォーを作成する
アプリの構成
アプリの構成はこんな感じです。
BordGalleryでボードを表現して、SelectGalleryでどこに駒を落とすか?を選択できるようにしています。
変数の初期化
変数は以下を用意しています。
変数名 | 役割 |
---|---|
Player1 | Player1の情報を宣言しています。 Enumみたいな使い方します。 |
Player2 | Player2の情報を宣言しています。 Enumみたいな使い方します。 |
turn | 現在のターン数 2で割ったあまりでどのPlayerのターンかを判断 |
boardCol | 盤面の情報 (6 * 7) Pieceの値は -1: 空白 Player[X].Id: そのPlayerの駒がある 空白を Blank() にしていないのは、型不整合のエラーを回避するためですね |
Set( Player1, { Id: 0, Color: RGBA(255, 40, 67, 1) } ); Set( Player2, { Id: 1, Color: RGBA(255, 205, 26, 1) } ); UpdateContext({turn: 0}); ClearCollect( boardCol, ForAll( Sequence(6 * 7), { Id: ThisRecord.Value, Piece: -1 } ) )
空白と数値で型不整合エラーでるのわからんでもないけど何とかして欲しい。
null許容型にしてくれ。
盤面の作成
盤面のギャラリー(BoardGallery)は各アイテムのサイズ(TemplateSize
)がサイズ100の正方形となるようにしています。
横7 * 縦6なので、折り返しの数は7ですね。
X
(Parent.Width - Self.Width) / 2
Y
Parent.Height - Self.Height
Width
100 * 7
Height
100 * 6
WrapCount
7
TemplateSize
100
TemplatePadding
0
あとはギャラリー内に区切り線と駒を表す円を表示してあげるだけですね。
処理や見た目に影響しそうな箇所だけピックアップして紹介します。
Circle1.Fill
Switch( ThisItem.Piece, Player1.Id, Player1.Color, Player2.Id, Player2.Color, RGBA(204, 231, 246, 0.1) )
Separator.Visible
Mod(ThisItem.Id - 1, 7) <> 6
簡易的にギャラリー使って盤面表現していますが、駒を落とすアニメーションを追加したりするのであれば、SVG使って表現したほうがいいですね。
今回はそこまで実装するやる気がなかったですw
駒を落とす
どこに駒を落とすか?を選択できるようにし、選択した列に駒を落とします。
これらはSelectGalleryで行っています。
まず、選択肢としては7つありますので、Items
には以下を設定します。
Gallery.Items
Sequence(7)
サイズとかは適当に合わせてください。
ここの駒はマウスホバーされたときに描画されるようにしたいので以下としています。
Circle2.Fill
RGBA(0, 0, 0, 0)
Circle2.HoverFill
Switch( Mod(turn, 2), Player1.Id, Player1.Color, Player2.Id, Player2.Color )
あとは選択されたときの処理ですね。
選択されたときは、
- 駒を盤面に落とす
- ターンを増やす
の2つの処理が必要です。
駒は選択した列で、駒がない一番下の箇所に落としたいです。
選択した列一覧は連番を7で割ったときの余りでN列目一覧が取得できます。
そのうち、駒が置かれていないところで、一番最後の箇所を取得すれば要件を満たせますね。
これを式にすると、例えば以下のようになります。
OnSelect
UpdateIf( boardCol, Id = Last( Filter( boardCol, Mod(Id - 1, 7) = ThisItem.Value - 1 && Piece = -1 ) ).Id, { Piece: Mod(turn, 2) } ); UpdateContext({turn: turn + 1})
これでゲームの動作部分ができました!
あとは判定ですね。
勝利判定を行う
勝利判定は(私が紹介するゲーム作成では)いつものごとくトグルコントロールのDefault
を利用します。
勝利となる条件は以下のパターンです。
- 縦に4個自身の駒が連続して揃ったとき
- 横に4個自身の駒が連続して揃ったとき
- 斜めに4個自身の駒が連続して揃ったとき
これらの判定を式に落とし込んであげます。
今から紹介する判定はでも勝利判定が行えますが、多分もっとスマートな方法もありそうな気がします。
今回は限られた時間で簡易的に実装したのでーということで。
もっといい方法思いついた方はコメント等で報告いただけると嬉しいです。
判定方法は簡単に、自身の駒が置かれている箇所だけフィルターして、左上から順にその駒が勝利条件を満たしているか?を判断していこうと思います。
左上から順に判断していくので、判定するのは以下方向のみでOKですね。
- 下方向
- 右方向
- 左下方向
- 右下方向
上方向の判定で勝利条件を満たしているときは、その組み合わせの一番上の駒からの下方向の判定ですでに判定できています。
左方向の判定で勝利条件を満たしているときは、その組み合わせの一番左の駒からの右方向の判定ですでに判定できています。
よって、上記だけの判定でいいのですね。
また、判定方向も簡易的なやり方とします。
式考えるのもめんどくさかったので判定方向に自身の駒が連続して存在しているか?(Idで判断できますね)
で簡単に判断します。
ただ単純にこの判定をやると、右方向の判定で以下のような場合も勝利として判定されてしまいます。(赤枠の箇所)
これは左から順にIdが降られているだけだからですね。
これを回避するために判定の開始の始点を絞りたいと思います。
下方向の判定であれば、この箇所が始点となっている場合のみに
右方向の判定であれば、この箇所が始点となっている場合のみに
左下方向の判定であれば、この箇所が始点となっている場合のみに
右下方向の判定であれば、この箇所が始点となっている場合のみに
絞って判断すればいいですね。
これらを考慮したうえで式にします。
Player1とPlayer2の判定の式は似たようなもの(Player1をPlayer2に変えるだけ)なので、Player1を代表で紹介します。
Default
// Player1の判定 CountIf( With( {player1Pieces: Filter(boardCol, Piece = Player1.Id)}, ForAll( player1Pieces As currentPieace, // 下の判定 If( currentPieace.Id < 22, CountRows( Filter( player1Pieces, Id = currentPieace.Id || Id = currentPieace.Id + 7 || Id = currentPieace.Id + 14 || Id = currentPieace.Id + 21 ) ) = 4 ) || // 右の判定 If( Mod(currentPieace.Id - 1, 7) < 4, CountRows( Filter( player1Pieces, Id = currentPieace.Id || Id = currentPieace.Id + 1 || Id = currentPieace.Id + 2 || Id = currentPieace.Id + 3 ) ) = 4 ) || // 左下の判定 If( currentPieace.Id < 22 && Mod(currentPieace.Id - 1, 7) > 3, CountRows( Filter( player1Pieces, Id = currentPieace.Id || Id = currentPieace.Id + 6 || Id = currentPieace.Id + 12 || Id = currentPieace.Id + 18 ) ) = 4 ) || // 右下の判定 If( currentPieace.Id < 22 && Mod(currentPieace.Id - 1, 7) < 4, CountRows( Filter( player1Pieces, Id = currentPieace.Id || Id = currentPieace.Id + 8 || Id = currentPieace.Id + 16 || Id = currentPieace.Id + 24 ) ) = 4 ) ) ), Value ) > 0
これでゲームの勝利判定までできました!
これらを組み合わせることでゲームを作ることができますねー
おわりに
これ作成しているときに自分が以前作成したリバーシのロジックをまとめた記事ないかなーと思って探したら、アプリの紹介している動画しかなかったんですよね。。。
なんでロジックまとめていないんですか。過去の私ーーーー
リバーシの作り方も、また今度纏めたいですね。
ただ今作り直すならもう少しいい作り方とかできそうなので公開するのはリメイク版ですね。(これ2年前のやつなんで)
あそび大全の他のゲームもPower Appsで作成してみたいですねー
それでは皆様本年もよろしくお願いいたします。