第47回 プログラミングについて『C++での複数の芋虫』

前回は芋虫を画面に這い回らせました。今回は複数の芋虫をC++を使って這い回らせてみましょう。
C++で複数の芋虫を這い回らせようとしたときに前回のC言語で作成したプログラムを拡張すればできないことはありませんが、任意の数の芋虫を這い回らせるにはプログラムを無理して記述しなければなりません。そこで、C++で記述するからには芋虫をオブジェクトとして作成し、芋虫を這い回らせることにしてみましょう。前回説明した関数については特に説明はしませんので注意して下さい。
まず芋虫のオブジェクトを考えてみましょう。構造は前回と殆ど変りませんが、芋虫は自分がどこにいて、どの方向に移動するのかを自分自身で知っているようにします。
またどの方向にどれだけ移動するのかも自分で判断できるようにすると、
class Warm{
struct warm_data_strct{
char c;
int x,y;
};
private:
struct warm_data_strct data[MAX_SIZE];
int drc,step;
int dx,dy;
public:
Warm(char h='@',char n1='x',char n2='o');
void Move();
};
このようなクラスの宣言になります。複数の芋虫をスムーズに這い回らせるには一匹の芋虫が一歩(芋虫では一歩とは言いませんが ...)動いたら、次に他の芋虫が一歩動くようにするため、芋虫のクラスには方向と歩数を保持する変数、またXとYの変化量を保持するための変数を持たせます(変化量は特に持っている必要はありませんが、含めておきました)。
この芋虫をおおよそ次のようにして這い回らせようという魂胆です。
main()
{
Warm warm1;
Warm warm2;
while(1){
warm1.Move();
warm2.Move();
}
}
次に芋虫のコンストラクタを作りましょう。
Warm::Warm(char h,char n1,char n2)
{
int i,x,y;
x=((int)this)%SCREEN_XSIZE;
y=((int)this)%SCREEN_YSIZE;
for(i=0;i<MAX_SIZE;i++){
if(i==0 )data[i].c=h;
else if(i==MAX_SIZE-1)data[i].c=' ';
else if(i%4==1 )data[i].c=n1;
else data[i].c=n2;
data[i].x=x;
data[i].y=y;
}
drc = -1;
step= 0;
}
このコンストラクタで芋虫の各節の位置の初期値を this ポインタの値を画面のXまたはYのサイズでの除算の余りを使用しているのは、芋虫が複数のときに最初の位置が同じ位置にならないようにするためで、各芋虫でユニークなこのポインタの値を使用するのが安易な方法です。コンストラクタの引数には芋虫の各節に使用する文字を指定することができるようにしています。例えば、
Warm w1;
として芋虫を作れば前回と同じように、
@xoooxoooxooo
になり、
Warm w2('?','.','/');
とすれば、
?.///.///.///
という芋虫になります。
次に芋虫を移動させるメンバー関数 Move を作りましょう。ちょっと長いのですが我慢して下さい。
void Warm::Move()
{
int tdrc,i;
while(1){
if(step==0){ //移動すべき歩数がないときには、新たに方向と歩数を
//決定します。
do{
tdrc=rand() % 4;
}while((tdrc%2)==(drc%2));
while((step=rand() % 20)<=0);
if(tdrc==0){ dx= 1; dy= 0; }
else if(tdrc==1){ dx= 0; dy= 1; }
else if(tdrc==2){ dx= -1; dy= 0; }
else if(tdrc==3){ dx= 0; dy= -1; }
}
else{
tdrc=drc;
}
//これ以上移動できないときには、現在の方向に移動してい
//たときには現在の方向の移動を止め、そうでないときには
//もう一度方向と歩数を決定します。
if(data[0].x+dx<=0 || data[0].x+dx>SCREEN_XSIZE
|| data[0].y+dy<=0 || data[0].y+dy>SCREEN_YSIZE){
step=0;
if(drc!=tdrc)continue;
else return;
}
step--;
drc=tdrc;
for(i=MAX_SIZE-1;i>=0;i--){
if(i==0){
data[i].x+=dx;
data[i].y+=dy;
}
else{
data[i].x=data[i-1].x;
data[i].y=data[i-1].y;
}
move_cursor(data[i].x,data[i].y);
putchar(data[i].c);
fflush(stdout);
}
return; //一歩移動したら次の芋虫を移動させるために、呼び出
//し側にもどります。
}
}
芋虫のオブジェクトとしてはこれだけです。あとは全体を作成するだけですので、プログラム全体を次に示します。
#include <stdio.h>
#include <stdlib.h>
#include <sys\timeb.h>
#include <time.h>
#include <signal.h>
#define SCREEN_XSIZE 80
#define SCREEN_YSIZE 25
#define MAX_SIZE 30
//芋虫のクラス宣言。
class Warm{
struct warm_data_strct{
char c;
int x,y;
};
private:
struct warm_data_strct data[MAX_SIZE];
int drc,step;
int dx,dy;
public:
Warm(char h='@',char n1='x',char n2='o');
void Move();
};
//関数のプロトタイプの宣言。
void delay(double delta);
void clear_screen();
void hide_cursor();
void move_cursor(int x,int y);
void ctrlc_handler(int sig);
void (*ctrlc_prev_handler)(int sig)=NULL;
//芋虫のコンストラクタ。
Warm::Warm(char h,char n1,char n2)
{
int i,x,y;
x=((int)this)%SCREEN_XSIZE;
y=((int)this)%SCREEN_YSIZE;
for(i=0;i<MAX_SIZE;i++){
if(i==0 )data[i].c=h;
else if(i==MAX_SIZE-1)data[i].c=' ';
else if(i%4==1 )data[i].c=n1;
else data[i].c=n2;
data[i].x=x;
data[i].y=y;
}
drc = -1;
step= 0;
}
//芋虫の移動。
void Warm::Move()
{
int tdrc,i;
while(1){
if(step==0){
do{
tdrc=rand() % 4;
}while((tdrc%2)==(drc%2));
while((step=rand() % 20)<=0);
if(tdrc==0){ dx= 1; dy= 0; }
else if(tdrc==1){ dx= 0; dy= 1; }
else if(tdrc==2){ dx= -1; dy= 0; }
else if(tdrc==3){ dx= 0; dy= -1; }
}
else{
tdrc=drc;
}
if(data[0].x+dx<=0 || data[0].x+dx>SCREEN_XSIZE
|| data[0].y+dy<=0 || data[0].y+dy>SCREEN_YSIZE){
step=0;
if(drc!=tdrc)continue;
else return;
}
step--;
drc=tdrc;
for(i=MAX_SIZE-1;i>=0;i--){
if(i==0){
data[i].x+=dx;
data[i].y+=dy;
}
else{
data[i].x=data[i-1].x;
data[i].y=data[i-1].y;
}
move_cursor(data[i].x,data[i].y);
putchar(data[i].c);
fflush(stdout);
}
return;
}
}
//指定時間の遅延。
void delay(double delta)
{
struct _timeb to,tn;
int dt;
dt=(int)(delta*1000.0);
_ftime(&to);
while(1){
_ftime(&tn);
if(tn.millitm-to.millitm>dt)break;
}
}
//画面のクリア。
void clear_screen()
{
printf("%c[2J",0x1b);
fflush(stdout);
}
//カーソルを非表示。
void hide_cursor()
{
printf("%c[>5h",0x1b);
fflush(stdout);
}
//カーソルを移動。
void move_cursor(int x,int y)
{
printf("%c[%d;%dH",0x1b,y,x);
fflush(stdout);
}
//ctrl/c ハンドラ。
void ctrlc_handler(int sig)
{
printf("%c[>5l",0x1b);
fflush(stdout);
if(ctrlc_prev_handler)ctrlc_prev_handler(sig);
exit(0);
}
//メイン関数。
void main()
{
Warm warm1;
Warm warm2('%','.','|');
Warm warm3('#','+','/');
ctrlc_prev_handler=signal(SIGINT,ctrlc_handler);
clear_screen();
hide_cursor();
while(1){
warm1.Move();
warm2.Move();
warm3.Move();
delay(0.1);
}
}
これで、3匹の芋虫が這い回ります。各芋虫が自分自身のことは自分で決定して勝手に動いてくれるので、メインの関数は拍子抜けするほど簡単になっています。
もし、芋虫の数をプログラムの起動時に指定できるようにするには、
void main(int argc,char *argv[])
{
Warm *warm;
int total=1;
int i;
if(argc>1){
total=atoi(argv[1]);
if(total<=0)total=1;
}
warm=new Warm [total];
ctrlc_prev_handler=signal(SIGINT,ctrlc_handler);
clear_screen();
hide_cursor();
while(1){
for(i=0;i<total;i++)warm[i].Move();
delay(0.1);
}
}
などとすれば、パラメータに 5 を指定すると5匹の芋虫が這い回ります。
この例のように、芋虫の定義から複数の芋虫を任意に発生させることができることがC++で記述したときの面白いところです。Windowsのプログラムを作る場合に
マルチドキュメントインターフェイスを実現するにはこのような手法で子供になるウインドウを作成することで、同時に複数の画面を表示させることが可能になる訳です。
それではまた次回。