この記事で扱っていること
- サイモンゲームを作る方法
を紹介しています。
注意:すべてのエラーを確認しているわけではないので、記事の内容を実装する際には自己責任でお願いします。また、エラー配線は適当な部分があるので適宜修正してください。
複数の色のボタンがあって、それらがランダムな順番で点灯したあと、その点灯した順番にボタンを押していき、クリアすると点灯する数が増えてまた順番通りに押していく、ということを繰り返す、サイモンゲームをLabVIEWで作ってみます。
なるべく単純な構造にするために、ループは一つで、ステートマシンのデザインをベースにしています。
このゲームでは色のボタンを押した際の誤動作を防ぐため、一つ一つのボタン押下を確実に検出するためにイベントストラクチャを使用するのが都合がいいので、イベントストラクチャを間に挟んでいますが、プログラム的にボタンを点灯(ブールをTrueにする)必要があるため、色のボタンはラッチ動作ではなくスイッチ動作にする必要があります。
どんな結果になるか
フロントパネルには4色に光るブールボタンがあり、他にはステータスを表示する文字列表示器とプログラムを終了するためのボタンがあります。

実行すると、適当に3回どれかが光るので、それが終わったら光った順に押していきます。
クリアすると、これまでに光った3つが再び光った後、一つ追加されて4つ光るので、再び順に押していきます。
これをクリアすると今までの4つが光ってその後追加されて、ということを繰り返し、間違えるまで続きます。

プログラムの構造
まずはフロントパネルの構成からです。
今回は、モダンスタイルのブール、「四角LED」を使用しています。
後のプログラムの都合上、各LEDのラベルは「1」、「2」、「3」、「4」としておいてください。

まぁ制御器のスタイルは別にどのブールでもいいのですが、ボタンを押したときの光り方、色については定義をしておく必要があります。
一つのブール制御器には、True、Falseの2色を設定するだけでいいかと思いきや、実は4色設定することができます。
それは、「Trueの状態」、「Falseの状態」、「Trueのときに押している状態」、「Falseのときに押している状態」の4つで、機械的動作の「放すまで~」を使用していると顕著にになります。
今回のゲームで各LEDを「放すまで~」動作にしているときに色が紛らわしくなるのは不都合なので、ここは「True」と「Trueのときに押している状態」、「False」と「Falseのときに押している状態」をそれぞれ同じ色に設定しておきます。
4色を決定するのは、「カラー[4]」というプロパティで設定でできます。
設定上、「前景色」と「後景色」の二つを一つの状態に対して設定するので、これらのペアがクラスタとなっており、これが4つあるクラスタ配列として色を指定します。
下の図の、一番上のように、4色を全て書いてもいいのですが、他3つのように、2つの色(のペア)を決めておいて、指標2と3の要素を指標0と1の要素で置換する、と書くのが手っ取り早いです。
もし各色を決めるのがメンドクサイ場合には下の図の値を参考にしてみてください。(カラーをU32で表示しています)

色の準備はこれで終わりで、あとはメインのプログラムの方に入っていきます。
全体の構造はステートマシンをベースにしています。
ステートとしては6つあります。
今回は途中イベントストラクチャがあるステートがありますが、ステートマシンのステートを決めるケースストラクチャの右側に停止ボタンを設けて、いつでもfinishステートに移れるようにしています。

まずはinitステートです。
各ブールのリファレンスを配列にし、またユーザーが最初ブールを押しても反応しないように無効プロパティをDisabledに設定、および値をFalseとして初期化しておきます。
また、スコアの目安となるTurnの値も0で初期化しておきます。
ステートマシンの各ステートで使用する情報を持っているクラスタもここで各値を初期化していますが、プログラム実行時の最初の色変化の数を変えたい場合にはnumber of quesitionの値を変更してください(今回は3としています)。

次にcreate questionステートです。
question cluster arrayに、新しく作成した問題、つまりどの色を光らせるかという情報と、一つの色を光らせた後にどれくらい待つかという情報の二つを配列連結追加で加えています。
つまり、question cluster arrayは「1番目に光る色とその時間」「1番目2番目の間の時間」「2番目に光る色とその時間」「2番目3番目の間の時間」・・・という情報が入っています。
どのLEDを光らせるかは、LEDのラベルで決定します。
下の図で、colorに0を入れているのは、どのLEDも指定していない=どれも光らない=「間の時間」を指定している、という意味になります。
なので、例えば難易度を変えるのであれば、colorに0を入れているクラスタのtimeを大きくしたり小さくさせます。

上のブロックダイアグラムで使用しているquestion maker.viは、どのLEDを光らせるかを乱数で決定しています。
今回は4色のLEDのいずれか、なので、1~4のどれかを乱数で出すために以下のようにプログラムを書いています。

次にgameステートです。
先ほど作ったquestion cluster arrayの配列の中身を要素0から取り出し、colorとtimeの値をそれぞれ使って、どのブールを光らせるか、そして何ミリ秒その状態を継続するかを基に処理を実行していきます。
colorについては、0であれば全てのブールをFalseに、1以上であればその数-1の指標番号のリファレンスを取得してTrueの値を指定します。
timeについてはquestion counterの値が切り替わったタイミングでティックカウントによって計測を開始し、timeで指定した時間が過ぎたらquestion counterを更新するために使用します。
この動作を、number of questionの2倍に1を足したものがquestion counterの数に等しくなるまで続けます。
(question counterは0から始まっていること、各問題の間には必ず「間の時間」があること、そしてquestion cluster arrayの最後の待ち時間を除くためにこのような判断処理としています)
また、いずれかのボタンを光らせる(Trueにする)仕組みをこのようにしておくと、例えば色を4つではなく5つ、6つにするようなゲームを作りたいと思った時でも、initステートでのリファレンス配列を増やし、question maker.viに入力するnumber of colorの値を増やす等の少ない改変で対応できます。

次のselectステートでは、ユーザーがLEDを押していくのをイベントストラクチャで待機しています。
どれかの色が押される必要があるので、このときには全てのブールの無効プロパティをEnabledにしています。
押されたブールのリファレンスを取得してラベルのテキストプロパティからラベルの数値情報を取得し、question cluster arrayの該当番号と比較して、次にどのステートに進むかを決めています。

一つのループの途中にイベントストラクチャを入れている場合、停止ボタンをこのイベントストラクチャの値変更イベントにしてしまうと、このイベントストラクチャが実行されるステートにいくまで停止できなくなります。
今回停止ボタンはイベントとしてではなくステートマシンのステートを決めるケースストラクチャの外に置いていつでも停止できるようにしていますが、「いつでも」停止できるようにするためには、イベントストラクチャにタイムアウトイベントを設けることで、イベントが何もなくても(タイムアウトしても)ステートマシンのループ全体が回り続ける必要があります。

次はjudge continueステートです。
ユーザーが押したLEDボタンが正解の場合このステートに進み、再びユーザーの入力を待つ状態にするのかあるいは問題が出終わって次の問題(1つ光る色が追加される状態)に進むかを判断しています。
このステートの中身自体はselectステートの後半に組み込んでもいいのですが、今回は何となく分けてみました。
例えば、正解したら「正解」とステータスの文字列表示器に表示させることもできますが今回は特に何もしていません。
また、このステートは正解しないと実行されないステートなので、このステートが実行された回数をTurnとして毎回インクリメントを実行するようにもしています。

最後はfinishステートです。
押すLEDを間違えるかあるいは停止ボタンを押したときに実行されます。

入力に時間制限を設ける場合
上記のプログラムだけでも普通に楽しめるものの、制限時間を設けることでゲーム性があがります。
一つの入力ごとに制限時間を設けて時間が過ぎたらゲームオーバーになるといった場合のプログラムは比較的シンプルに書けます。

修正を加えるのはselectステートだけでよく、player select iterationの値が変わったらティックカウントで時間を数え始めて、残りの時間の情報を文字列にフォーマット関数で作りこれをステータスの文字列表示器に渡しています。
今回は各LEDを押すまでの時間を5秒と設定していますが、以下の図の「5000」(ミリ秒)になっている部分を変更すれば異なる制限時間に設定できます。

本記事ではサイモンゲームを作る方法を紹介しました。
サイモンゲームを単に作るだけならもっとシンプルにプログラムを書けることと思います(ステートマシンにしなくても)。
ただ、どんなプログラムでも同じアーキテクチャ(ステートマシン)で対応できることを知っておくと、新しいプログラムを作る際にもとっかかりがつかめやすいかなと思うので、ある程度簡単なプログラムであっても同じような組み立て方を意識してプログラムを書くと慣れていきやすいかなと思います。
ここまで読んでいただきありがとうございました。
コメント
毎週楽しみにしてます!車のゲームのプログラムとかあったらやってみたいです!!
コメントいただきありがとうございます。また、毎週楽しみにしていただいているとのこと、励みになります。
車のゲーム、レーシングゲームなどでしょうか?
今までいくつか挙げてきたプログラムからお分かりの通り、LabVIEWでゲームを作る時は大体ピクチャや配列を使うことが多いので、車のゲームもピクチャで表現できれば
可能かと思いますが、ピクチャ上の描画に特化している環境でもないので、「滑らかな」動作はなかなか難しいかなと思います。
とはいえ、今後の記事の参考にさせていただきます。
今後もゲーム、もといLabVIEWの具体的なプログラム例はいくつか記事にする予定の物があるので、今後もブログ覗いてみて頂けるとうれしいです。