はじめに
今回は随分前に作成して動画もアップするとか宣言しておいてアップしていなかったPower Apps で作成したパズルゲームの作成方法について纏めようと思います。
動画は収録していたものがありましたが、今回改めて撮りなおしました。
できぴ#PowerApps で作るパズルアプリ(前投稿した奴を少し直したやつ)
— コルネ (@koruneko32767) June 2, 2020
問題は自動生成にしてCDS 使わなくていいようにしました
その代わり問題の作成はできないけどね
明日はこいつを解説する動画作るぞー!
それともライブ配信したほうがいいのだろうか? pic.twitter.com/OcMrOo8kbv
2020年のやり残し(他多数)を2021年に行うことになるとは...
YouTube
動画での紹介はこちらになります。
よろしければ、 チャンネル登録、高評価お願いします!!
パズルゲームを作成する
ホーム画面を作成する
ホーム画面は単純で
- ラベル
- ボタン
の2つで構成されています。
まずはタイトルラベルを作成します。
ラベルを追加して画面上部に配置します。
Label.Text
"Puzzle Game"
Label.X
0
Label.Y
0
Label.Width
Parent.Width
Label.Height
200
Label.Color
RGBA(255, 255, 0, 1)
Label.Fill
RGBA(0, 121, 187, 1)
続いてゲーム開始を行うためのボタンを作成します。
このボタンには以下2つの役割があります。
- パズルの盤面を作成する
- ゲーム画面へ遷移する
1 の盤面を作成するために、ホーム画面が呼び出されたタイミングで盤面を初期化し、パズルの回答(どこを選択すればよいのか)を設定します。
上記を実現するために OnVisibel
に以下を設定します。
Screen1.OnVisible
ClearCollect( originBoard, {No:1, isFront:true}, {No:2, isFront:true}, {No:3, isFront:true}, {No:4, isFront:true}, {No:5, isFront:true}, {No:6, isFront:true}, {No:7, isFront:true}, {No:8, isFront:true}, {No:9, isFront:true}, {No:10, isFront:true}, {No:11, isFront:true}, {No:12, isFront:true}, {No:13, isFront:true}, {No:14, isFront:true}, {No:15, isFront:true}, {No:16, isFront:true} ); ClearCollect( answer, {No:1, isSelect:Rand() > 0.5}, {No:2, isSelect:Rand() > 0.5}, {No:3, isSelect:Rand() > 0.5}, {No:4, isSelect:Rand() > 0.5}, {No:5, isSelect:Rand() > 0.5}, {No:6, isSelect:Rand() > 0.5}, {No:7, isSelect:Rand() > 0.5}, {No:8, isSelect:Rand() > 0.5}, {No:9, isSelect:Rand() > 0.5}, {No:10, isSelect:Rand() > 0.5}, {No:11, isSelect:Rand() > 0.5}, {No:12, isSelect:Rand() > 0.5}, {No:13, isSelect:Rand() > 0.5}, {No:14, isSelect:Rand() > 0.5}, {No:15, isSelect:Rand() > 0.5}, {No:16, isSelect:Rand() > 0.5} )
originBoard
が問題の盤面です。
まずは初期化を行うのですべて true
(表)としています。
answer
がパズルの答えです。
1 / 2 の確率で対象のマスが選択すべきマスとして設定されることになります。
今回のパズルゲームのルールとして、選択したマスとそのマスに隣するすべてのマスの裏表が反転するというルールなので、 answer
で作成されて答えをもとに、 originBoard
のマスを作成しようと思います。
ボタンが選択されたときのアクションに以下を追加します。
Button.OnSelect
ForAll( RenameColumns(answer, "No", "ansNo"), If( isSelect, If(Mod(ansNo, 4) <> 1, UpdateIf(originBoard, No = ansNo - 5, {isFront:!LookUp(originBoard, No = ansNo - 5, isFront)})); UpdateIf(originBoard, No = ansNo - 4, {isFront:!LookUp(originBoard, No = ansNo - 4, isFront)}); If(Mod(ansNo, 4) <> 0, UpdateIf(originBoard, No = ansNo - 3, {isFront:!LookUp(originBoard, No = ansNo - 3, isFront)})); If(Mod(ansNo, 4) <> 1, UpdateIf(originBoard, No = ansNo - 1, {isFront:!LookUp(originBoard, No = ansNo - 1, isFront)})); UpdateIf(originBoard, No = ansNo, {isFront:!isFront}); If(Mod(ansNo, 4) <> 0, UpdateIf(originBoard, No = ansNo + 1, {isFront:!LookUp(originBoard, No = ansNo + 1, isFront)})); If(Mod(ansNo, 4) <> 1, UpdateIf(originBoard, No = ansNo + 3, {isFront:!LookUp(originBoard, No = ansNo + 3, isFront)})); UpdateIf(originBoard, No = ansNo + 4, {isFront:!LookUp(originBoard, No = ansNo + 4, isFront)}); If(Mod(ansNo, 4) <> 0, UpdateIf(originBoard, No = ansNo + 5, {isFront:!LookUp(originBoard, No = ansNo + 5, isFront)})); ) ); Navigate(PlayScreen, ScreenTransition.Cover)
盤面の作成方法は単純で答えで選択するマスとして設定されたマスとそれに隣するマスの裏表を逆転させればいいですね。
盤面の作成が完了したので次はパズル画面の作成を行いたいと思います。
パズル画面を作成する
パズルの盤面を作成する
パズルの盤面を作成します。
盤面の作成にはギャラリーを利用します。
Items
には先ほど作成した originBoard
を利用したいですが、そのまま使用してしまうと"やり直し"ができなくなってしまいます。
なので originBoard
を playBoard
にコピーして、 playBoard
を Items
に設定したいと思います。
コレクションの内容のコピーは以下のように行います。
Screen2.OnVisible
Clear(playBoard); ForAll( originBoard, Collect( playBoard, { No:No, isFront:isFront } ) )
なおこの OnVisible
では以下のように他変数も初期化しておいてください。
使用用途はその都度説明します。
Screen2.OnVisible
UpdateContext({_dispAns:false}); UpdateContext({_ansnum:1}); UpdateContext({_count:0}); UpdateContext({_isHint:false}); UpdateContext({_isRetire:false}); Clear(playBoard); ForAll( originBoard, Collect( playBoard, { No:No, isFront:isFront } ) )
ギャラリーを設定して以下のように設定します。
Gallery.Items
playBoard
Gallery.X
(Parent.Width - Self.Width) / 2
Gallery.Y
(Parent.Height - Self.Height) / 2
Gallery.Width
Self.TemplateHeight * 4
Gallery.Height
Self.TemplateHeight * 4
Gallery.WrapCount
4
Gallery.TemplateSize
150
Gallery.TemplatePadding
0
ギャラリー内にラベルを配置し、以下のように設定します。
Label.Text
If( ThisItem.isFront, "〇", "×" )
Label.Color
If( ThisItem.isFront, RGBA(255, 0, 0, 1), RGBA(0, 0, 255, 1) )
Label.Fill
If( ThisItem.isFront, Pink, Aqua )
Label.Size
80
このラベル(マス)が選択されたとき、そのマスとそれに隣するマス全ての裏表を逆転させたいので以下のように設定します。
Label.OnSelect
If(Mod(ThisItem.No, 4) <> 1, UpdateIf(playBoard, No = ThisItem.No - 5, {isFront:!LookUp(playBoard, No = ThisItem.No - 5, isFront)})); UpdateIf(playBoard, No = ThisItem.No - 4, {isFront:!LookUp(playBoard, No = ThisItem.No - 4, isFront)}); If(Mod(ThisItem.No, 4) <> 0, UpdateIf(playBoard, No = ThisItem.No - 3, {isFront:!LookUp(playBoard, No = ThisItem.No - 3, isFront)})); If(Mod(ThisItem.No, 4) <> 1, UpdateIf(playBoard, No = ThisItem.No - 1, {isFront:!LookUp(playBoard, No = ThisItem.No - 1, isFront)})); UpdateIf(playBoard, No = ThisItem.No, {isFront:!ThisItem.isFront}); If(Mod(ThisItem.No, 4) <> 0, UpdateIf(playBoard, No = ThisItem.No + 1, {isFront:!LookUp(playBoard, No = ThisItem.No + 1, isFront)})); If(Mod(ThisItem.No, 4) <> 1, UpdateIf(playBoard, No = ThisItem.No + 3, {isFront:!LookUp(playBoard, No = ThisItem.No + 3, isFront)})); UpdateIf(playBoard, No = ThisItem.No + 4, {isFront:!LookUp(playBoard, No = ThisItem.No + 4, isFront)}); If(Mod(ThisItem.No, 4) <> 0, UpdateIf(playBoard, No = ThisItem.No + 5, {isFront:!LookUp(playBoard, No = ThisItem.No + 5, isFront)})); UpdateContext({_count:_count + 1})
UpdateContext({_count:_count + 1})
では、ユーザーの手数をカウントしています。
OnVisble
で初期化を行っていますね。
以上で盤面の作成は完了です。
経過ターンを表示する
経過ターンを表示するためのラベルを作成します。
ラベルを配置して以下のように設定します。
Label.Text
_count & "ターン経過" & If(_isHint, "(最短" & CountRows(Filter(answer, isSelect = true)) & "回)")
Label.Width
Parent.Width
Label.Height
Gallery.Y
If(_isHint, "(最短" & CountRows(Filter(answer, isSelect = true)) & "回)")
は後に作成するヒント機能の実装部分になります。
ホームボタンを作成する
ホーム画面に戻るボタンを作成します。
家のアイコンを追加して以下のように設定します。
HomeIcon.OnSelect
Back()
今回画面は2画面だけで、この画面はホーム画面からのみ遷移されてくるので、 Back()
で戻ることとします。
リセットボタンを作成する
リセットボタンには OnVisible
と同じ内容を設定すればいいですね。
再読み込みのアイコンを設定して以下のように設定します。
ResetIcon.OnSelect
UpdateContext({_dispAns:false}); UpdateContext({_ansnum:1}); UpdateContext({_count:0}); UpdateContext({_isHint:false}); UpdateContext({_isRetire:false}); Clear(playBoard); ForAll( originBoard, Collect( playBoard, { No:No, isFront:isFront } ) )
ResetIcon.Y
HomeIcon.Y + HomeIcon.Height + 10
ヒントボタンを作成する
ヒントボタンは、押されると最短手数を表示するようにします。
先ほど作成した経過ターンのラベルですね。
isHint
を true
に設定すれば、表示されるよう先ほど設定していたのでそのように設定します。
星のアイコンを設定して以下のように設定します。
HintIcon.OnSelect
UpdateContext({_isHint:true});
HintIcon.Y
ResetIcon.Y + ResetIcon.Height + 10
答えを表示する
答えを表示するためのボタンを設定して以下のように設定します。
Button.OnSelect
UpdateContext({_count:0}); UpdateContext({_isRetire:true}); Clear(playBoard); ForAll( originBoard, Collect( playBoard, { No:No, isFront:isFront } ) ); UpdateContext({_dispAns:true});
答えを表示するために、一度カウントと盤面をリセットしています。
また、答え表示のためのフラグ _dispAns
とユーザーが自力で解けずリタイアしたことを表すためのフラグ _isRetire
を true
に設定しています。
答え表示のためのフラグ _dispAns
が true
に設定されたことをトリガーにユーザーに答えを示したいと思います。
答えの表示処理にはタイマーを利用します。
タイマーを設定して以下のように設定します。
Timer.Duration
1000
Timer.OnTimerEnd
If( CountRows(Filter(answer, isSelect = true)) >= _ansnum, UpdateContext({_dispAns:true}); Select(BoardGallery, Last(FirstN(Filter(answer, isSelect = true), _ansnum)).No, Panel), UpdateContext({_dispAns:false}) ); UpdateContext({_ansnum:_ansnum + 1});
TimerAutoStart
CountRows(Filter(answer, isSelect = true)) >= _ansnum && _dispAns
Timer.Repeat
CountRows(Filter(answer, isSelect = true)) >= _ansnum && _dispAns
タイマーは1000ms 間隔で繰り返し処理を行うようにします。
タイマーの処理終了時、 _ansnum
の値が answer
の true
の数以下(選択すべきマス以下)であった場合、 _ansnum
番目の選択すべきマスを選択しています。
_ansnum
の値が answer
の true
の数以上であった場合は、 _dispAns
を false
に設定しています。
答えの表示が全て終了したらループを止めたいので、リピート処理を上記のように設定しています。
このままでは、ユーザーがどのマスを選択すればよかったのか?視覚的にわかりにくいので、答えの表示時に選択されたマスを赤丸で示したいと思います。
ギャラリー内にサークルを追加して、以下のように設定します。
Circle.X
(Panel.Width - Self.Width) / 2
Circle.Y
(Panel.Height - Self.Height) / 2
Circle.Fill
RGBA(255, 0, 0, 1)
Circle.OnVisible
ThisItem.No = Last(FirstN(Filter(answer, isSelect = true), _ansnum - 1)).No && _dispAns && CountRows(Filter(answer, isSelect = true)) >= _ansnum - 1
(Panel は Label です。)
これで答えの表示を実装することができました!
クリア画面を作成する
最後にクリア画面を作成したいと思います。
このクリア画面は、答え表示で全てのパネルが表になったときではなく、ユーザーが自身で選択したことによって全てのマスが表になったときに表示するようにします。
Label.Text
"Clear!!"
Label.OnVisible
CountIf(playBoard, isFront = true) = CountRows(playBoard) && !_isRetire
Label.Size
250
最後にホーム画面へ戻るアイコンたちを最前面にして完成です!
これをし忘れると、クリアラベルが表示されたとき、アイコンたちより上にいた場合アイコンが選択できなくて画面を操作できなくなって詰みます。
おわりに
以上でパズルアプリの作成方法は終了です!
最近はSVG を利用したアプリが多めでしたが、こんな風にPower Apps の標準的?な機能だけで簡単にゲームが作成できてしまうので良い時代になりましたね。
Power Apps はそういった用途のアプリじゃないですが‼