marunomaruno-memo

marunomaruno-memo

Lego Mindstoms NXT - NXC プログラミング (2) 制御文、スクリーン表示

2008年01月08日 | LEGO
実際に、いくつかプログラムを作って、NXC の制御文や変数の使い
方をまとめてみる。
また、スクリーンへの文字列の表示も行う。

なお、前回と同じように★は、推測で記している箇所なので、注意
されたい。ご存知の方は、よろしければご教授ください。

「Lego Mindstomes NXT - NXC プログラミング (1)」に記したが、
制御文のところだけ再掲する。


■ 制御文

□ if 文

if (条件式) 条件式の結果が true のとき実行するボディ
if (条件式) 条件式の結果が true のとき実行するボディ else そ
うでないときに実行するボディ


□ switch 文

switch (式) ボディ

case 定数式 :
default :


□ while 文

while (条件式) ボディ


□ do while 文

do ボディ while (条件式)


□ for 文

for (初期値化式 ; 条件式 ; 更新式) ボディ


□ repeat 文

repeat (式) ボディ

C 言語にはない制御文です。式の結果の回数分だけ繰り返します。
なお、式の結果が 0 以下の場合、ボディを実行することはありま
せん。


---
repeat (10) {
// 繰り返すコード
}
---


□ until マクロ

つぎのように定義されています。センサーを使うときによく使いま
す。

#define until(c) while(!(c))


したがって、while キーワードの代わりに、

until (条件式) ボディ
do ボディ until (条件式)

と書くことができます。このとき、条件式の意味は、「~まで」と
いうことになるので、「条件式が true になったらループ終了」と
いうことです。


■ くり返し制御文のサンプル

それでは、TrunRight01 プログラムをくり返しの制御文を使ってそ
れぞれ作ってみましょう。繰り返す回数は 3 回です。

----------- -----------
プログラム 使用制御文
----------- -----------
TrunRight02 repeat
TrunRight03 for
TrunRight04 while
TrunRight05 do - until
----------- -----------

---
/**
 * TrunRight02.nxc
 * 「トライボット」使用
 * 500ミリ秒間前進し、その後右に180度曲がる。これを3回繰り返
す。
 * @author maruno
 * @version 1.0, 2008-01-05
 * @since 1.0
 */
#include "NXCDefs.h"

#define MOVE_TIME 500
#define TURN_TIME 360

task main()
{
    repeat (3) {
        OnFwd(OUT_BC, 75);
        Wait(MOVE_TIME);
        OnRev(OUT_C, 75);
        Wait(TURN_TIME);
    }
    Off(OUT_BC);
}

---


---
/**
 * TrunRight03.nxc
 * 「トライボット」使用
 * 500ミリ秒間前進し、その後右に180度曲がる。これを3回繰り返
す。
 * @author maruno
 * @version 1.0, 2008-01-05
 * @since 1.0
 */
#include "NXCDefs.h"

#define MOVE_TIME 500
#define TURN_TIME 360

task main()
{
    for (int i = 0; i <3; i++) {
---

for 文を使っています。C 言語と違い、初期値化式でローカル変数
を宣言することもできます。


---
/**
 * TrunRight04.nxc
 * 「トライボット」使用
 * 500ミリ秒間前進し、その後右に180度曲がる。これを3回繰り返
す。
 * @author maruno
 * @version 1.0, 2008-01-05
 * @since 1.0
 */
#include "NXCDefs.h"

#define MOVE_TIME 500
#define TURN_TIME 360

task main()
{
    int i = 0;
    while (i <3) {
---


---
/**
 * TrunRight05.nxc
 * 「トライボット」使用
 * 500ミリ秒間前進し、その後右に180度曲がる。これを3回繰り返
す。
 * @author maruno
 * @version 1.0, 2008-01-05
 * @since 1.0
 */
#include "NXCDefs.h"

#define MOVE_TIME 500
#define TURN_TIME 360

task main()
{
    int i = 0;
    do {
        OnFwd(OUT_BC, 75);
        Wait(MOVE_TIME);
        OnRev(OUT_C, 75);
        Wait(TURN_TIME);
        i++;
    } until (i >= 3);

    Off(OUT_BC);
}

---

until マクロを使っています。マクロ展開後は、

do {
...
} while (!(i >= 3));

になります。



■ 分岐制御文を使ったサンプル

ランダムにトライボットが 10 秒間動きます。
ランダムな時間値やパワー値の取得は、Random 関数を使っていま
す。

---
/**
 * Random01.nxc
 * 「トライボット」使用
 * ランダムに10秒間動く。
 * @author maruno
 * @version 1.0, 2008-01-05
 * @since 1.0
 */
#include "NXCDefs.h"

#define MOVE_LIMIT_TIME 1000 * 10
#define TURN_TIME 180

task main()
{
    int move_total_time = 0;
    do {
        int outputs;
        switch (Random(3)) {  // (1)
        case 0:
            outputs = OUT_B;
            break;
        case 1:
            outputs = OUT_C;
            break;
        default:
            outputs = OUT_BC;
            break;
        }

        int move_time = Random(500);
        OnFwd(outputs, Random(201) - 100);  // (2)
        Wait(move_time);
        move_total_time += move_time;

        Off(OUT_BC);  // (3)
    } while (move_total_time <MOVE_LIMIT_TIME);
---

□ switch (Random(3)) { // (1)

モーター B、C、BC を動かすので、どのモーターを動かすかを乱数
(0, 1, 2)を使って決めます。


□ Random 関数

Random 関数は、オーバーロードされています。上限を引数で渡し
て、「0 以上 上限未満」の値を返す上の関数の方が使い勝手はい
いでしょう。

---
Random(n) Value
16 ビットの符号なしの乱数値(0 以上 n 未満)を返します。

例:
x = Random(10); // return a value of 0..9
---

---
Random() Value
16 ビットの符号つきの乱数値を返します。

例:
x = Random();
---


□ OnFwd(outputs, Random(201) - 100); // (2)

乱数で決まったモーターを、また、乱数値で指定されたパワーで動
かします。
OnFwd 関数のパワーは、-100 ~ 100 です。負数の場合は、後退す
る OnRev 関数と同じ意味になります。


□ Off(OUT_BC); // (3)

今回はループの中に入れています。一度、モーターを停止させない
と、停止させられるまで動き続けるからです。すなわち、モーター
B を動かしてからつぎに モーター C を動かす場合、モーター B
も停止しないと、つぎのモーター C では、モーター BC が動くこ
とになります。(これはこれでおもしろい動きになるかもしれませ
んが)



■ 情報をスクリーンに表示する

C 言語や C++ 言語の最初に学習するのは、標準出力に "Hello Wor
ld!" を表示する、というものでしょう。CUI のアプリケーション
では、何かしらの形で表示しないと動きがわかりません。でも、ロ
ボットを使ったプログラムでは、モーターなど、標準出力に表示し
なくても動きがわかったりします。
ただ、センサーから入力した値などは、表示しないとわかりません。
ここでは、スクリーンに文字列を表示してみましょう。
描画や画像を表示することもできますが、これは別の機会に。


□ "Hello World!" の表示

まずは、定番の "Hello World!" を表示してみます。

---
/**
 * HelloWorld01.nxc
 * 「トライボット」使用
 * スクリーンに "Hello World!" を示する。
 * @author maruno
 * @version 1.0, 2008-01-05
 * @since 1.0
 */
#include "NXCDefs.h"
task main(){
     TextOut(0, LCD_LINE3, "Hello World!");  // (1)
     TextOut(0, LCD_LINE6, "12345678901234567890");  // (2)
     Wait(5000);
}

---

このプログラムで、スクリーンの 3 行目に "Hello World!" が、
また、6 行目に "1234567890123456" が 5 秒間表示されたと思い
ます。

文字列の表示には、TextOut 関数を使います。


□ TextOut
---
TextOut(x, y, msg, clear = false) Function

文字列をスクリーンの座標 (x ,y) から書きます。y にはつぎの値
を指定します。
LCD_LINE1、LCD_LINE2、...、LCD_LINE8

引数 clear は、この文字列を表示する前にスクリーンをクリアす
るかどうかを指定します。指定がなければ、クリアしません(デフ
ォルト値が false です)

例:
TextOut(0, LCD_LINE3, "Hello World!");
---

なお、引数の最後に「clear = false」とあるのは、この引数は省
略してもいいし、省略しなくてもいい。省略した場合は、デフォル
ト値として、「false」を指定したのと同じことになる、というこ
とです。これも、C++ ではおなじみの「デフォルト引数」というも
のですね。


□ TextOut(0, LCD_LINE3, "Hello World!"); // (1)

TextOut 関数によって、指定された文字列を 3 行目に表示します。


□ TextOut(0, LCD_LINE6, "12345678901234567890"); // (2)

20 文字分指定されていますが、実際に表示されるのは最初の 16
文字だけです。
スクリーンは、一行 16 文字分だけ表示されます。



■ 文字列を編集して表示する

プログラム Random01 は、10 秒間ランダムに動きます。どのよう
に動くかは、実際に動かしてみないとわかりません。このときの動
く値を、スクリーンに表示してみましょう。

---
/**
 * Random02.nxc
 * 「トライボット」使用
 * ランダムに10秒間動く。そのときの情報をスクリーンに表示す
る。
 * @author maruno
 * @version 1.0, 2008-01-05
 * @since 1.0
 */
#include "NXCDefs.h"

#define MOVE_LIMIT_TIME 1000 * 10
#define TURN_TIME 180

task main()
{
    int move_total_time = 0;
    do {
        int outputs;
        string moter;
        switch (Random(3)) {
        case 0:
            outputs = OUT_B;
            moter = "B";
            break;
        case 1:
            outputs = OUT_C;
            moter = "C";
            break;
        default:
            outputs = OUT_BC;
            moter = "BC";
            break;
        }
        int power = Random(201) - 100;
        int move_time = Random(100) * 10;
        OnFwd(outputs, power);

        string msg;
        string num;
        
        msg = StrCat("moter = ", moter);  // (1)
        TextOut(0, LCD_LINE3, msg);

        num = NumToStr(power);  // (2)
        msg = StrCat("power = ", num);
        TextOut(0, LCD_LINE4, msg);

        num = NumToStr(move_time);
        msg = StrCat("time = ", num);
//        TextOut(0, LCD_LINE5, StrCat("time = ", NumToStr(m
ove_time)));  // (3)
        TextOut(0, LCD_LINE5, msg);

        Wait(move_time);
        move_total_time += move_time;

        Off(OUT_BC);

    } while (move_total_time <MOVE_LIMIT_TIME);
---

□ msg = StrCat("moter = ", moter); // (1)

StrCat 関数は、文字列を連結し、その結果を返す関数です。


□ StrCat 関数
---
StrCat(str1, str2, ..., strN) Value

引数で指定された複数の文字列を連結して返します。

例:
msg = StrCat("test", "please"); // returns "testplease"
---


□ num = NumToStr(power); // (2)

NumToStr 関数は、数値を文字列に変換する関数です。


□ NumToStr 関数
---
NumToStr(value) Value

指定された数値 num を文字列に変換して返します。

例:
msg = NumToStr(-2); // returns "-2" in a string
---


□ TextOut(0, LCD_LINE5, StrCat("time = ", NumToStr(move_tim
e)));

なお、このように指定したいところですが、コンパイルエラーにな
ります。
どうやら、NXC は、関数のネストができないようです。★



以上