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

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

【Power Apps】あみだくじを作成してみよう!-その1-


スポンサードリンク

今回作成するもの

今回は以下のようなあみだくじをPower Apps で作成しようと思います!

使用している要素

Power Apps のオブジェクトとして利用しているのはたったのこれだけです。

f:id:koruneko:20201101224935p:plain

ではどうやってあみだくじを作成したり、アニメーションをつけているかというと、SVG を活用しています。
SVG の詳しい解説はこのシリーズでは行う予定はありません。
その解説についてはまたの機会に行おうと思いますー。(私も最近少し触ってみただけですが...)

ホーム画面を作成する

まずは最初に表示されたホーム画面を作成しようと思います。

f:id:koruneko:20201101225539p:plain

この画面では特に難しい操作は行っておらず、SVG も利用していません。

参加者入力欄を作成する

参加者入力欄を作成するために以下のオブジェクトを配置します。

f:id:koruneko:20201101230113p:plain

それぞれ以下のような役割をもっています。

オブジェクト 役割
ParticipantTitleLabel 参加人数入力欄用ラベル
ParticipantDropdown 参加人数を選択するためのドロップダウン
ParticipantGallery 参加者の名前入力用ギャラリー
ParticipantLabel X人目の参加者入力用ラベル
ParticipantTextInput X人目の参加者名入力欄

それぞれ以下のように設定しましょう。
(座標などの細かい設定値の説明は今回省きます。)

まずドロップダウンは2~15まで選択可能なようにしたいので以下のように設定します。

ParticipantDropdown.Items

Sequence(14, 2)

Sequence 関数に関しては以前纏めたこちらの記事をご覧ください。
【Power Apps】Sequence 関数を使ってみよう!

参加者の名前入力欄はドロップダウンで選択した値と同じ数だけ表示させたいです。
なので以下のように設定します。

ParticipantGallery.Items

Sequence(ParticipantDropdown.Selected.Value)

以上で参加者入力欄の作成は完了です。
あとは適宜座標やサイズを調整してください。

結果入力欄を作成する

結果入力欄を作成するために以下のオブジェクトを配置します。

f:id:koruneko:20201101232058p:plain

それぞれ以下のような役割をもっています。

オブジェクト 役割
ResultTitleLabel 結果のタイプ選択用ラベル
ResultDropdown 結果のタイプ選択用ドロップダウン
True-FalseLabel 〇の数入力欄用ラベル
True-FalseDropdown 〇の数を選択するためのドロップダウン
ResultGallery 結果(カスタム値)入力用ギャラリー
ResultLabel X個目の結果入力用ラベル
ResultTextInput X個目の結果入力欄

参加者入力欄とはぼ同じですが、以下のように設定します。

結果のタイプには、"番号", "〇×", "カスタム" の3種類の選択肢を用意したいと思うので、以下のように設定します。

ResultDropdown.Items

["番号", "〇×", "カスタム"]

このドロップダウンで選択された結果によって、〇の数入力欄とカスタム結果入力欄の表示を制御したいと思います。
そのため以下のように設定します。

True-FalseLabel.Visible & True-FalseDropdown.Visible

ResultDropdown.SelectedText.Value = "〇×"

ResultGallery.Visible

ResultDropdown.SelectedText.Value = "カスタム"

結果に設定する〇の数は最低1で最大が参加者の人数 - 1となるかと思います。
なので以下のように設定します。

True-FalseDropdown.Items

Sequence(ParticipantDropdown.Selected.Value - 1)

カスタム結果入力欄は参加人数と同じだけ用意する必要があります。
なので以下のように設定します。

ResultGallery.Items

Sequence(ParticipantDropdown.Selected.Value)

以上で結果入力欄の作成は完了です。
あとは適宜座標やサイズを調整してください。

あみだくじ!ボタンを作成する

あみだくじ!ボタンを押すとあみだくじの画面に遷移します。
なのでこのボタンが押せる状態にある = ホーム画面で必要な値の入力/選択がすべて完了している という状態にしたいと思います。

では、「ホーム画面で必要な値の入力/選択がすべて完了している」とはどのような状態でしょうか?

今回は以下のような2つの条件だと思います。

  • 参加者名が参加人数分全て埋まっている。
  • 結果のタイプで「カスタム」が選択されていた場合、カスタム値が参加人数分全て埋まっている。

上記条件を式に表すと例えば以下のように表現することができます。

ボタンを配置して以下のように設定します。

AmidakujiButton.DisplayMode

If(
    CountIf(
        ForAll(
            Sequence(ParticipantDropdown.Selected.Value),
            Last(FirstN(ParticipantGallery.AllItems, Value)).ParticipantTextInput.Text
        ),
        Value <> ""
    ) = ParticipantDropdown.Selected.Value &&
    If(
        ResultDropdown.SelectedText.Value = "カスタム",
        CountIf(
            ForAll(
                Sequence(ParticipantDropdown.Selected.Value),
                Last(FirstN(ResultGallery.AllItems, Value)).ResultTextInput.Text
            ),
            Value <> ""
        ) = ParticipantDropdown.Selected.Value,
        true
    ),
    DisplayMode.Edit,
    DisplayMode.Disabled
)

ForAll 関数内ではギャラリー内のテキスト入力の値を1~参加人数分まで取得しています。
これにより、参加人数分のギャラリー内のテキスト入力の値が設定されたコレクションが作成されます。

Countif 関数内ではForAll 関数で作成されたコレクションに対して、値が空白("")でないカラムが何個あるのか数えています。
この値が、参加人数と同じだった = 参加人数分テキスト入力が入力されている と判定できますね。

続いてこのボタンが押されたときの処理です。
このボタンが押されたときには以下の処理をしたいと思います。

  1. あみだくじのイメージ(どこに横線が引かれるか?)を判定するためのコレクションを作成する。
  2. 結果を格納したコレクションを作成する。
  3. あみだくじ画面へ遷移する。

上記を実現するために以下のように設定しました。

AmidakujiButton.OnSelect

Clear(amidakuji);
ForAll(
    Sequence(15) As cnt,
    ForAll(
        Sequence(ParticipantDropdown.Selected.Value - 1) As num,
        Collect(
            amidakuji,
            {num:num.Value, cnt:cnt.Value, isline:If(!Last(amidakuji).isline, RoundDown(Rand() * 5, 0) = 0)}
        )
    )
);
Clear(result);
ForAll(
    Sequence(ParticipantDropdown.Selected.Value),
    Switch(
        ResultDropdown.SelectedText.Value,
        "番号", Collect(result, {result:Text(Value)}),
        "〇×", Collect(result, {result:If(Value <= 'True-FalseDropdown'.Selected.Value, "〇", "×")}),
        "カスタム", Collect(result, {result:Last(FirstN(ResultGallery.AllItems, Value)).ResultTextInput.Text})
    )
);
Navigate(Amidakuji, ScreenTransition.Cover)

急に式がちょっと難しくなっちゃいましたが、1つずつみていきましょう。

まずは、「あみだくじのイメージ(どこに横線が引かれるか?)を判定するためのコレクションを作成する。」です。
これは、

Clear(amidakuji);
ForAll(
    Sequence(15) As cnt,
    ForAll(
        Sequence(ParticipantDropdown.Selected.Value - 1) As num,
        Collect(
            amidakuji,
            {num:num.Value, cnt:cnt.Value, isline:If(!Last(amidakuji).isline, RoundDown(Rand() * 5, 0) = 0)}
        )
    )
);

ここの式で表現しています。
ForAll 関数が入れ子にねっちゃっていますね...

まず今回作成するあみだくじですが、以下ルールに基づいて作成を行っています。

  • 横線は一つの縦線につき最大15本引かれる
  • 横線は隣接する網掛けとは重ならないようにする
  • 横線がどこに引かれるかはランダムとする

まず最初の条件の「横線は一つの縦線につき最大15本引かれる」ですが、これを実現するためにまず最初のForAll で15回ループするようにしています。
Sequence(15) As cnt
このときなぜAs 句を用いているかというと、その次に宣言するForAll 関数で値が被らないようにするためです。
この1つ目のForAll の値を用いたい場合は cnt.Value と記載することで利用することができます。

続いて2つ目のForAll 関数では、横線を引くべき行分だけループさせています。
文章では伝え難いので図で説明すると以下の箇所のことを指しています。

f:id:koruneko:20201102002411p:plain

これは、縦線の数(参加者の数) - 1個存在すると思います。
なので、
Sequence(ParticipantDropdown.Selected.Value - 1) As num
としています。

これらのループ内でコレクションを作成します。

amidakuji というコレクション名には、num, cnt, isline というアイテムが存在します。
それぞれ以下のような値が入ることを想定しています。

アイテム名 役割
num x番目の箇所
cnt y番目の箇所
isline 線を引くかどうか

これにより、どこに線が引かれるか判断できますね。

num にはx番目の箇所を設定したいので、2つ目のループの num.Value を設定します。

cnt にはy番目の箇所を設定したいので、1つ目のループの cnt.Value を設定します。

isline には上で説明したようなルールを適用したいので、1つ前のレコードの値を確認して線が引かれているようなら線を引かず、引かれていないようなら、5分の1の確率で線を引くようにします。
これを式に表すと以下のようになります。
If(!Last(amidakuji).isline, RoundDown(Rand() * 5, 0) = 0)

これで、これまで説明した要件を満たせますが、この実装では以下のような問題もあります。

  • 右端に横線が引かれた場合、その1つ下の左端には線が確定で引かれなくなる。
  • (4 / 5)15 * ([参加人数] - 1) の確率でどこにも線が引かれない。

今回簡単に作成するため、また、上記問題はそこまであみだくじの作成に影響しないため無視しましたが、気になる方は上記問題を解決した実装を行ってみてください!

続いて、「結果を格納したコレクションを作成する。」ですが、これは以下で行っています。

Clear(result);
ForAll(
    Sequence(ParticipantDropdown.Selected.Value),
    Switch(
        ResultDropdown.SelectedText.Value,
        "番号", Collect(result, {result:Text(Value)}),
        "〇×", Collect(result, {result:If(Value <= 'True-FalseDropdown'.Selected.Value, "〇", "×")}),
        "カスタム", Collect(result, {result:Last(FirstN(ResultGallery.AllItems, Value)).ResultTextInput.Text})
    )
);

結果は、

  • 参加者と同じ数だけ用意
  • ドロップダウで選択されたタイプによって設定する値が異なる

という特性があります。

まず、「参加者と同じ数だけ用意」ですが、これは参加者の数分だけループを実施すればよいですね。
Sequence(ParticipantDropdown.Selected.Value)

続いて、「ドロップダウで選択されたタイプによって設定する値が異なる」ですが、これはswitch 関数を活用すれば実現できます。

「番号」が選択されたときは、ループのカウントを設定すればOKです。
ただし、他のタイプが選択されたときは、数値型だけでなく文字型が入るということに注意が必要です。
そのため、数値型ではなく文字型にするためにText 関数を利用しましょう。
Text(Value)

「〇×」が選択されたときは、〇の数を決めるドロップダウンで選択された値の数とループのカウントを比較し、ループのカウントが〇の数を決めるドロップダウンで選択された値の数以下だった場合は〇を、より大きかった場合は×をそれぞれ設定すれば、要件を満たすことができます。
If(Value <= 'True-FalseDropdown'.Selected.Value, "〇", "×")

「カスタム」が選択されたときは、ギャラリー内のテキスト入力の値を設定するだけで、OKです。
以下のように設定すれば要件を満たすことが可能です。
Last(FirstN(ResultGallery.AllItems, Value)).ResultTextInput.Text

これまで説明したForAll の前には、それぞれClear 関数を用いて対象のコレクションをクリアしていますが、これはそのままの意味で、ここに設定する値は毎回新規で作成したいためです。

最後にNavigate 関数であみだくじ画面へ遷移を行っています。
Navigate(Amidakuji, ScreenTransition.Cover)

おわり

以上でホーム画面の作成は完了です!
おつかれさまですっ!!!

これであみだくじの作成に必要な情報はほぼほぼ作成することがきました。

次回はいよいよあみだくじの作成方法を解説したいと思います。
SVG の解説がメインになりそうです。

それではーノシ


スポンサードリンク