2.1K Views
May 19, 23
スライド概要
イベント(https://jppgb.connpass.com/event/280997/ )での登壇資料です。
Power Appsで Yavalath作ってみた! JPPGB #5
自己紹介 { “DisplayName” : “おいしみ”, “ID” : “ksgiksg”, “Introduce” : [ おいしみ “今年から情シス1年生”, “前職は「プラントエンジニア」と、非IT職”, “社内ではPower PlatformをはじめDX推進の講師役として活動中”, “Qiitaでアウトプット始めてみました(主にPower Automate関連)” ], “ContentURL” : [ “Twitter” : “https://twitter.com/ksgiksg”, @ksgiksg “Qiita” : “https://qiita.com/ksgiksg” ] }
ヤバラス(Yavalath)ってどんなゲーム?
なんでPower Appsで作ったの? • 日本での入手が困難だった • 転職前有給消化中のタイミングで時間があった • Power Apps で凝ったものを作ったことがなかった せっかくだからチャレンジしてみよう💪 ※習作をそのまま公開しています。 「なんでこうなってるの?」「こうした方がよかったんじゃない?」 など、ツッコミ大歓迎です!! …というかリファクタリングしたかったのが間に合いませんでした😭
デモ!
盤面の作成
作成したい盤面 盤面は左図のように 正六角形が規則的に並んだ ハニカム構造の盤面を作成します。
まずは1つ正六角形を作成する Power Apps の図形コントロールには 正六角形がありません。 なので、何かしら自前で用意する必要があります。 今回は色・大きさ・線の太さなど自由度が効く SVGで作成することにしました! 参考にさせていただいた記事 Power Apps で SVGを利用したレーダーチャートコンポー ネントの作成 - MoreBeerMorePower (hatenablog.com)
まずは1つ正六角形を作成する メディアコントロールから画像(Image)を追加し “Image”プロパティに以下の式を追加します。
まずは1つ正六角形を作成する ポイントはこの部分。Nはここでは正六角形の6なので、2*Pi()/N つまり、度数法でいうと60°ずつ回転させながら頂点を描画しています。 Concat( Sequence(N), 60° A*Cos(2*Pi()*Value/N) &", " &A*Sin(2*Pi()*Value/N), "" ) sinΘ cosΘ
ギャラリーで並べて配置する 次に、1マス1マスをテーブルの1行としてデータを作成し、 ギャラリーを使ってマスを並べていきます。 自由にアイテムを配置するには TemplateSize=0の手法 を使用します。 参考にさせていただいた記事 わかれば簡単! #PowerApps のTemplateSize=0の手法 - Qiita
ギャラリーで並べて配置する 正方形であれば縦横のX,Y座標を順に並べていくだけでよいのですが、 正六角形ではそう単純ではありません。 X X (X,Y)=(4,2) Y (X,Y)=??? Y
ギャラリーで並べて配置する のちに、勝利判定を行うための向きの取り方が3方向あるので マスの並べ方もこれに合わせ3方向のベクトルで表現します。
3方向のベクトルで配置を決める ということで、テーブルデータは連番のidとx,y,z座標を定義して 以下のようになります。(手入力です。。。要改善点。。。)
3方向のベクトルで配置を決める テーブルデータの座標用のプロパティ(x,y,z)から、 ギャラリーの中に正六角形を敷き詰めて並べます。 y (X,Y,Z)=(0,0,0) X z x 単位ベクトル X,Y座標系 ՜ 𝑥 (1Τ2 , 1Τ2) ՜ (0, −1) ՜ (− 1Τ2 , 1Τ2) 𝑦 𝑧 Y
3方向のベクトルで配置を決める 正六角形のX,Yプロパティはそれぞれ次の通りです。 X Self.Width*(3^0.5)/2*( Y Self.Height/2*( ThisItem.x*(1/2) ThisItem.x*(1/2) +ThisItem.y*0 +ThisItem.y*(-1) +ThisItem.z*(-1/2) +ThisItem.z*(1/2) ) ) +(G_Stage.Width-Self.Width)/2 +(G_Stage.Height-Self.Height)/2 -ThisItem.id
駒の置き方
駒の用意 駒は初めからギャラリーに追加しておきます。 これを、表示/非表示を切り替えたり、色を変えたりして 駒を置くという動作を表現します。
駒の塗りつぶし 駒の塗りつぶし Fillプロパティは、プレイヤーごとに定義しています。 初期状態をP:0とし、1Pが駒を置いたらP:1に更新するようにしています。 Fill With( LookUp(colors,id=ThisItem.P) As color, RGBA(color.R,color.G,color.B,color.A) )
駒の塗りつぶし 同様に、HoverFillも定義しています。 HoverFill If( ThisItem.P=0, With( LookUp(colors,id=WhoseTurn) As color, RGBA(color.R,color.G,color.B,0.5) ), Self.Fill )
ターン数の制御
ターン数の制御(途中抜け無し) ターン数を1ずつ増やしていき、プレイ人数で割った余りを使えば、 そのターンがどのプレイヤーの手番かを判断することができます。 ターン数 1 2 Mod 2 1 0 2人プレイ時の手番 1P 2P Mod 3 1 2 3人プレイ時の手番 1P 2P 3 4 5 1 0 1 1P 2P 1P 0 1 2 3P 1P 2P 6 0 2P 0 3P … … … … …
ターン数の制御(途中抜けあり) このゲームの場合、特に3人プレイの場合は、途中失格がありえるため その後は手番がずれてしまいます。 ターン数 1 2 Mod 2 1 0 2人プレイ時の手番 1P 2P Mod 3 1 2 3 4 5 1 0 1 1P 2P 1P 0 1 2 6 0 2P 0 … … … … 3人プレイ時の手番 1P 2P❌失格 3P 1P 2P3P 3P1P …
ポインタ付きのリスト構造で表現 プレイヤーに、自分の次のプレイヤーを覚えさせることにしました。 プレイヤー:1P 2P プレイヤー:2P 3P プレイヤー:3P 1P プレイヤー:1P 2P プレイヤー:2P 3P プレイヤー:3P 1P プレイヤー:1P 2P プレイヤー:2P 3P プレイヤー:3P 1P プレイヤー:1P 3P プレイヤー:3P 1P プレイヤー:2P 3P
プレイ順の初期設定 ForAll( Sequence(Dr_UserCount.Selected.No), Collect( PlayerList, { No:Value, Status:"プレイ中", Next:Mod(Value,Dr_UserCount.Selected.No)+1 } ) )
失格&ポインタの修正処理
//失格にしてプレイ順を修正
Patch(
PlayerList,
LookUp(PlayerList,No=WhoseTurn),
{Status:"失格"}
);
UpdateIf(
PlayerList,
Next=WhoseTurn,
{Next:LookUp(PlayerList,No=WhoseTurn).Next}
);
ターン終了時の判定
処理の流れ ぐちゃぐちゃでゴメン… 盤面クリック No 3連のマス はあるか? (∵) 空白のマス? Yes プレイヤーを 失格 Yes 終了 No マスを塗る Nextプレイ ヤーを更新 4連のマス はあるか? No Yes 残り人数は 1人か? Yes 勝利 No 手番を更新 次のターンへ
処理の流れ 盤面クリック No パワポでフロー図,書きにくくない? 3連のマス はあるか? (∵) 空白のマス? Yes プレイヤーを 失格 Yes 終了 No マスを塗る Nextプレイ ヤーを更新 4連のマス はあるか? No Yes 残り人数は 1人か? Yes 勝利 はじめはこの処理すべてを GallaryのOnSelectに入れようとして No 無残に散りました。 手番を更新 次のターンへ
処理ごとに切り出してボタンコントロールに 子フローを作成するようなイメージで 処理の塊ごとにボタンコントロールを作成し、 Select(ボタン)で子フローを呼び出すようにして処理を分割しました。 いい方法があれば教えて (∵)
勝利(4目並べ)の判定
確認のための下処理 前述のとおり、3方向から確認する必要があります。 下処理として、駒の並びをテキストに変換します。 10201231
確認のための下処理
Clear(tb_judge);
ForAll(
Sequence(9,-4) As S,
Collect(
tb_judge,
{
x:Concat(Sort(Filter(tb,x=S.Value),y),P&""),
y:Concat(Sort(Filter(tb,y=S.Value),z),P&""),
z:Concat(Sort(Filter(tb,z=S.Value),x),P&"")
}
)
);
確認のための下処理
Clear(tb_judge);
ForAll(
Sequence(9,-4) As S,
Collect(
tb_judge,
{
x:Concat(Sort(Filter(tb,x=S.Value),y),P&""),
y:Concat(Sort(Filter(tb,y=S.Value),z),P&""),
z:Concat(Sort(Filter(tb,z=S.Value),x),P&"")
}
)
);
確認のための下処理 • Sequence(9,-4) As S y 4 • y:Concat(Sort(Filter(tb,y=S.Value),z),P&"") Sequence Filter 3 2 1 0 1 3 1 0 2 4 0 1 2 -1 -2 -3 -4 z 4 3 2 1 0 -1 -2 -3 -4 Sort Concat 101324012
4目並べの判定 続いて、同じ数字が4連続で並んでいるレコードがないか確認します。 !IsEmpty( Filter( tb_judge, Or( Find(Concat(Sequence(4),WhoseTurn&""),x)>0, Find(Concat(Sequence(4),WhoseTurn&""),y)>0, Find(Concat(Sequence(4),WhoseTurn&""),z)>0 ) ) )
失格(3目並べ)の判定
3目並べの判定 判定は4目と同じ方法で判定します。 !IsEmpty( Filter( tb_judge, Or( Find(Concat(Sequence(3),WhoseTurn&""),x)>0, Find(Concat(Sequence(3),WhoseTurn&""),y)>0, Find(Concat(Sequence(3),WhoseTurn&""),z)>0 ) ) )
失格処理&残りの人数確認
3目並んで、失格者が出た場合は以下の処理が続きます。
//失格にしてプレイ順を修正(前述のため省略)
//1人を残して失格ならゲーム終了
With({W:Filter(PlayerList,Status="プレイ中")},
If(
CountRows(W)=1,
Set(Winner,First(W).No);
Set(IsGamePlaying,false);
Patch(PlayerList,First(W),{Status:"勝利"}) //勝者決定・ゲーム終了
)
)
今後の課題
発表スライド作って改めて感じたこと • コントロール名や変数名が適当でつらかった • 盤面の作成で手入力した座標は、数学的に何とかできそう • 「1101」などのリーチ状態を可視化させたい • ゲームは遊んでて楽しいことが一番なので見た目にもこだわりたい • できれば1画面じゃなくオンラインで遊べるようにしたい