コルネの進捗や備忘録が記されたなにか

進捗や成果物や備忘録てきななにかを雑に更新していきます。

【Power Apps】パズルゲームを作成してみよう!


スポンサードリンク

はじめに

今回は随分前に作成して動画もアップするとか宣言しておいてアップしていなかったPower Apps で作成したパズルゲームの作成方法について纏めようと思います。
動画は収録していたものがありましたが、今回改めて撮りなおしました。

2020年のやり残し(他多数)を2021年に行うことになるとは...

YouTube

動画での紹介はこちらになります。

youtu.be

よろしければ、 チャンネル登録、高評価お願いします!!

パズルゲームを作成する

ホーム画面を作成する

ホーム画面は単純で

  • ラベル
  • ボタン

の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. パズルの盤面を作成する
  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 を利用したいですが、そのまま使用してしまうと"やり直し"ができなくなってしまいます。
なので originBoardplayBoard にコピーして、 playBoardItems に設定したいと思います。

コレクションの内容のコピーは以下のように行います。

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

ヒントボタンを作成する

ヒントボタンは、押されると最短手数を表示するようにします。
先ほど作成した経過ターンのラベルですね。

isHinttrue に設定すれば、表示されるよう先ほど設定していたのでそのように設定します。

星のアイコンを設定して以下のように設定します。

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 とユーザーが自力で解けずリタイアしたことを表すためのフラグ _isRetiretrue に設定しています。

答え表示のためのフラグ _dispAnstrue に設定されたことをトリガーにユーザーに答えを示したいと思います。

答えの表示処理にはタイマーを利用します。

タイマーを設定して以下のように設定します。

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 の値が answertrue の数以下(選択すべきマス以下)であった場合、 _ansnum 番目の選択すべきマスを選択しています。
_ansnum の値が answertrue の数以上であった場合は、 _dispAnsfalse に設定しています。

答えの表示が全て終了したらループを止めたいので、リピート処理を上記のように設定しています。

このままでは、ユーザーがどのマスを選択すればよかったのか?視覚的にわかりにくいので、答えの表示時に選択されたマスを赤丸で示したいと思います。

ギャラリー内にサークルを追加して、以下のように設定します。

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 はそういった用途のアプリじゃないですが‼


スポンサードリンク