本シリーズでは、このブログで紹介してきた「まずこれ」のシリーズを凝縮して、LabVIEWによるプログラミングはこんな風にできるんだ、ということを比較的すぐに体験できるよう、内容を絞って紹介しています。
また、紹介する画面はLabVIEW Community Editionという、非商用の目的であれば無料で使用できるエディションを使っています。なので、LabVIEWに興味を持ったらCommunity Editionを実際にダウンロード、インストールして触りながらプログラムを作り、LabVIEWの楽しさに触れてもらえればと思います。
第五回の今回は、LabVIEWでプログラムを書くためのテクニックであるデザインテンプレートを使用する方法を紹介します。
ここで紹介するステートマシンというテンプレートは、LabVIEWで本格的なプログラムを書く場合であっても基本になるほど、とても重要なものです。
とはいえ、構造自体は別に複雑なわけではなく、繰り返しと条件分岐がわかっていれば理解することができるはずです。
なお、前の記事はこちらです。
テンプレートなんてとあなどるなかれ
この記事で紹介するのは、LabVIEWでプログラムを書く際に使用する「テンプレート」の一種であるステートマシンです。
これまでの話で、繰り返し、やら、条件分岐、やらプログラムを作成するうえで基礎となる事柄を紹介してきました。
実はこれらのことがわかればもうプログラムは書けてしまいます。
なので、皆さんあとは自由にプログラムを書いてLabVIEWで大いに遊んでください!
・・・と言われても、実際これらをどう使ってプログラムを組めばいいか、自分がやりたいことを実現するか、わからないと思います。
右も左もわからないまま説明が終わって、「結局LabVIEWってよくわからんな」と諦めてしまう、そんなことにならないように、本記事では有名なテンプレートの一つであるステートマシンを紹介します。
「テンプレートなんて学んで何になるの?」「テンプレートなんて、その型にはまったことしかできないでしょ?」と思うかもしれません(私も最初はそうでした)が、大事なのはそのテンプレートの形だけでなく、テンプレートの考え方を理解することです。
また、テンプレートとは広く知られているものであるため、LabVIEWの経験がある人が見たときに共通の認識を持っていることで流れを追いやすいというメリットがあります。
他の人が書いたプログラムを読むのってなかなかに大変ですが、それでもテンプレート通りに組まれていると、そうでない場合に比べて読みやすさが全然違います。
テンプレート自体はステートマシン以外にもあります。知っておくに越したことはないと思いますが、初心者にはハードルが高すぎるのでこのシリーズでは扱いません。
確かに、ステートマシンのテンプレートだけでは、LabVIEWでありとあらゆるプログラムを組むことはできません。が、まずは手始めに(そして実は強力な)この基本的なテンプレートを理解することで、LabVIEWでプログラムを書く感覚を身に着けてもらえればと思います。
なお、「テンプレート」と表現はしていますが、文脈によっては「デザインパターン」と表現する場合もあります。特に注釈がない限り、この二つの表現は同じことを言っていると思ってください。
ステートマシンとは
ではそもそもステートマシンとは何かという話ですが、これは「状態遷移の様子をプログラムの形に落とし込んだもの」と言えます。
ちょっと表現が固いのでもう少しざっくりとした表現をすると「条件分岐の繰り返しをLabVIEWで効率よく実装したプログラムの典型的な形」と言えます。
下の図は適当に分岐を表したイメージ図ですが、開始から終了までの間の各プログラムの状態ごとに様々な分岐を経る様子を表しています。
白い丸が色々な状態を表し、矢印で他の状態に移動する様子を表しています。一つの状態から複数の矢印が出ている部分がありますが、これは条件分岐でどれかの矢印のみが選択されることになり、この一連の流れをひたすら繰り返し終了までたどりつけばプログラムが終わることになります。
ここで(わざと出した)「条件分岐」、そして「繰り返し」というキーワードは、まさに前々回、前回で扱った内容ですね。
条件分岐を繰り返すということで上の図のような全体構造を形作る、これがステートマシンの実態です。
そんなこと言われてもよくわからないな、という方のために以下、考え方について紹介していきます。
ステートマシンを組む際の考え方
状態遷移について簡単なイメージを持つために、一度LabVIEWのプログラムからは離れて、「二人でじゃんけんをする」という場面を想像してみてください。
じゃんけんをするからには、勝者が決まるまで続けます。なので、じゃんけんをするというプログラムがあったとしたら、そのプログラムが終了するのは、どちらかが勝つまで、ということになります。
プログラムの最初と最後はまぁ深く考えなくてもいいとして、下の図の真ん中の部分の中身を主に考えていくことにします。
では、じゃんけんをするということの一連の流れを考えていきます。これはステートマシンでプログラムを書く前に必要な準備となります。
まずじゃんけんは、「最初はグー」の掛け声で始めますね。
そして、「じゃんけん」の掛け声の後に、グーかチョキかパーどれかを出すかを決めます。
その後に、「ぽい」でお互いに手を出します。
例えば、自分がチョキを出したときのことを考えます。このとき、起こりえるパターンは次の3つのはずです。
- 相手がグーを出した場合:「自分が負け」の状態となり終了
- 相手がチョキを出した場合:「あいこ」の状態となり、再び「じゃんけん」の掛け声に戻る
- 相手がパーを出した倍:「自分が勝ち」の状態となり終了
あいこのときには掛け声は「あいこでしょ」になる、といった細かい部分には目をつぶってください。重要なのは、あいこであれば再びグーかチョキかパーどれかを出すかを決める状態に戻ることです。
そして、これは自分がグーあるいはパーを出したときも同じで、相手の出す手との組み合わせこそ違えど、どの状態になるかという選択肢は上の3つのどれか、ということは変わりません。
まとめると、お互いに手を出した後の状態としては「再びじゃんけんが続く」か「自分が勝って終了する」、「自分が負けて終了する」のどれかになる、ということです。
じゃんけんの流れがわかったところで、プログラムとしてこの流れを表現するために考えるポイントをいくつか挙げていきます。
最初はグー、の掛け声のところは、今回はプログラムの実行を開始(実行ボタンを押す)してまず一番最初の段階です。多くのプログラムの場合、「初期化」と呼ばれる操作をします。
その次に、出す手を決めるという工程は、3種類のボタンでも用意して、それぞれにグー、チョキ、パーを対応させることでできそうですね、
問題は、「ぽい」で自分と相手お互いが手を出した後の流れです。ここで重要なことが二つあります。
まず、勝ち、負け、あいこ、これら3つの状態はどれか一つしか起こりえません。
これは当たり前ですね。「自分が勝っているのに負けている」なんてことはないですし、「じゃんけんが続くのに自分が負けている(勝敗がついている)」ということもない、ということです。
もう少しわざとらしく言うと、3つの状態は、「やってみないとわからない」じゃんけんを行ったことによる、3つの独立した結果、となっています。
これらをプログラムの中で表すのに何が使えるか・・・じゃんけんの結果によって処理を変える、ここに条件分岐が使えます。
それぞれが独立した結果になっているので、これらをそれぞれの条件分岐の内容として書いていくことができるので、イメージとしては以下の図のような状態となります。
もう一つ重要なこと。それは、じゃんけんが1回で終わるとは限らないということです。
先ほど書いたように、あいこが続くとなった場合、勝敗がつくまでじゃんけんは終わりません。
言い方を変えると、あいこが続くとじゃんけんを繰り返す必要があるので、例えばじゃんけんを始める前から「3回でじゃんけんの勝敗がつく」などといったことは未来でも見えない限りわからないので「何回繰り返せばあいこじゃなくなるかわからない」と言えます。
繰り返す必要があり、かつあらかじめ回数がわかっていない・・・これにはWhileループが使用できそうです。
以下は実装の流れを示したものですが、実際にこのようにWhileループの中で条件分岐を行うために多数のケースストラクチャを置くわけではありません。あくまでイメージです。
少し強引な説明もあったかもですが、これで「じゃんけんには条件分岐とWhileループが必要」ということがわかりました。
では、なにはともあれこれら二つをブロックダイアグラムにおいて、プログラムを書いてみることにします。
さて、おもむろにWhileループの中にケースストラクチャをひとつだけ置いてみました。これはいま、条件分岐を繰り返している」というプログラムになっています。
本来は様々な状態があるはずなのに、条件分岐としてのケースストラクチャはこのように一つだけでいいの?と思われるかもしれないですが大丈夫です。
改めて、ここでいう状態というのは、じゃんけんの例でいえば
- 「最初はグー」と言う:じゃんけんを始める「状態」
- 「じゃんけん」「ぽん」と言う:自分の出す手と相手が出した手を比べる「状態」
- 同じ手を出す:あいことなり、「じゃんけん」の状態に戻る「状態」
- 喜ぶor悲しむ:勝敗が決まり、じゃんけんが終わった「状態」
と分かれます。
ステートマシンでは、これら一つ一つの状態をケースストラクチャの中に記述し、その状態の結果を次の「状態」を決定する条件分岐の条件とします。
先ほど書いた図とほぼ同じですが、状態の内の一つ、「出す手を決める」の部分の中身はどうすればいいか、言葉で書くとすれば「自分と相手の出した手を比べる処理」であり、この処理の結果、3つのうちのどれかに進むことになります。
だんだんとLabVIEWプログラムに落とし込む準備が整ってきましたが、では「出す手を決める」部分で自分と相手が出した手を比べた後に3つの行先のどれかに移るという操作はどのようにおこなえばいいでしょうか?
ここで活躍するのが、シフトレジスタです。
これは、前のループの結果を次に渡すために使用することができるのでした。
3つのうちの処理のどれかに進む「前のループ」として、自分と相手の出した手を比べる処理があるわけなので、もう少しプログラムをそれっぽく書くとこうなります。
さて、勘のいい方はもう全体像が見えてきたかもしれませんね。
いま、状態を表すはずのケースストラクチャがWhileループに入っているため、「状態」が繰り返される、というプログラムになっています。そこに、シフトレジスタを組み合わせると、「前の状態の結果に応じて次の状態を決定する」というプログラムが書けるようになりました。
これにより、一つのケースストラクチャだけで、複雑な構造を記述することができるようになります。
さて、ステートマシンの大枠としてはこのWhileループとケースストラクチャで用意したので、あとはケースストラクチャの中身を実装していくことを改めて考えてみます。
シフトレジスタで次の状態を指定するイメージがついたので、もう少しこのイメージに沿った表現で、プログラム全体の流れをもう一度言葉で説明すると以下のような感じだと思います。
- 最初の「じゃんけんを始める状態」では、フロントパネルの状態を初期化します。この後は、自動的に「じゃんけんの手を決めて互いに比べる状態」に進みます。
- 「じゃんけんの手を決めて互いに比べる状態」では、自分と相手の出した手を比べて、もし自分が勝っていたら「自分が勝った状態」に進みます。もし自分が負けていたら「自分が負けた状態」、そしてあいこだった場合には「あいこの状態」に移ります。
- 「自分が勝った状態」では喜びます。その後「終了状態」に進みます。
- 「自分が負けた状態」では悲しみます。その後「終了状態」に進みます。
- 「あいこの状態」では、再び「じゃんけんの手を決めて互いに比べる状態」に移ります。
- 「終了状態」ではプログラムが終了します。
つまり、じゃんけんには上記6つの状態があり、それぞれの状態には前の状態で決まったある条件に依って移るということを繰り返していることになり、LabVIEWでこういった構造をケースストラクチャとWhileループ、シフトレジスタで表すことがじゃんけんプログラムを作るための作業になります。
じゃんけんプログラムを作る
では、ステートマシンの構造もわかったところで、実際にこのじゃんけんプログラムを作ってみることにします。
じゃんけんに勝って喜ぶ、とか負けて悲しむ、という部分は、代わりに勝ち負けをフロントパネル上に表示させることとします。
今回は、以下のようなプログラムを見よう見まねで作ってみてください。
まず、最初はグーの状態です。ここでは初期化として、勝ち負けを表すブールの表示器にFalseを入力しておきます。
初期化とはこのように、以前にプログラムを実行していたことがあったとしても表示をリセットさせるためによく使用します。
次にじゃんけんの手を決める状態です。相手がどの手を出すか、は、「乱数(Random Number)として1か2か3のどれが出るか」で毎回どの手になるかわからないようにしておきます。
ここでは、1をグー、2をチョキ、3をパーとしています。
(乱数の関数は結果を0~1の小数で出すため、この結果に3をかけて、値を切り上げる処理を加えることで整数になるようにしています)
そして相手の手と自分が選択した手との組み合わせでどの状態になるかを、さらに細かくケースストラクチャで分けていきます。
ここは少しテクニカルです。ステートマシンで条件分岐を行うのによく使用する、Select関数を使用しています。(関数パレットのComparisonパレットにあります)
このSelectは、ブールの入力と、そのブールがTrueだったときおよびFalseだったときに出力するものをそれぞれ入力するという使い方をします。
こうすることにより、どれか出す手を決めない限りはじゃんけんの手を決める状態を実行し続けるようにします。
グー、チョキ、パーのブール制御器であるボタンのいずれかが押されたかどうかを一定時間(上の図だとWaitの関数に50を入力しているので50ミリ秒)毎に確認し、どれかのボタンが押されたらそのボタンと乱数の結果によって次の状態を決定します。
相手がグーを出した以外の場合については以下のようにします。
少しややこしいですが、3つのブールの配線を間違えないようにしてください。
じゃんけんの結果あいこになった場合には再び出す手を決めます。
下の図のように待機(Wait)の関数で適当な時間(1000ミリ秒など)待たせるとそれっぽくなります。
自分が負けの場合には、負けの表示としてブールにTrueを入力して終了状態に進みます。
逆に自分が勝った場合には勝ちの表示としてブールにTrueを入力して終了状態に進みます。
最後、終了の状態でWhileループを止めるようにしています。
勝ちや負けの表示は、この終了の状態の一つ前(自分が勝ち、あるいは自分が負けの状態)と同じ表示をし続けることになるので、シフトレジスタで受け取った値をそのまま終了状態の出力トンネルに渡しています。
実装の細かい部分は説明していない部分もありますが、シフトレジスタやケースストラクチャの仕組みを考えれば、あまり複雑な処理を行っているわけではないことがわかると思います。
なお、上記のプログラムについて、勝ちあるいは負けの状態の時点でWhileループを止める、つまりわざわざ終了の状態を用意する必要はないのでは?と思うかもしれません。
確かにこのプログラムの場合には実際必要ないのですが、プログラムの最初に初期化があるように、終了時の「後始末」として独自に終了状態を設けることがあるので、今回はそれにならって敢えて終了状態を用意しました。
いかがでしたでしょうか。この「ケースストラクチャとWhileループ、シフトレジスタ」は、じゃんけんをするという遊びに限らず、およそLabVIEWで実装できる全てのプログラムにおいて基本的な作り方の基となっていて、これがステートマシンというプログラムテンプレートと呼ばれています。
この考え方をマスターするだけでも、様々なプログラムが書けるようになります。
(もちろん、この方法だけでは作れないプログラムも多くあります、が、それでも細かく見ればステートマシンの考え方を適用しているものばかりです)
実際は、このステートマシンへの落とし込み方が難しいんだという声も聞こえてきそうですが、それは次回、次々回でさらに別の具体的な例を紹介するので、少しでも慣れていってもらえるかなと思います。
なお、ステートマシンに大切な要素は実はあともう一つあり、タイプ定義という仕組みを設けることがほとんどです。が、これについてはもう少しステートマシンに慣れないとその「ありがたみ」がわからないと思うのでステートマシンの例を一通り紹介し終えた後に紹介することにします。
よければ次の記事もみていってください。
ここまで読んでいただきありがとうございました。
コメント