2018年11月
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30  

Amazonウィジェット

  • お気に入り
  • 書籍ランキング

AdSense

  • 広告
無料ブログはココログ

« 遺伝的アルゴリズムで育てる「お掃除ロボット」 その3 ~1次元配列で2次元マップを作る~ | トップページ | コラム:CUIの文字の縦横比(アスペクト比)を調べた。 »

2018年3月 7日 (水)

遺伝的アルゴリズムで育てる「お掃除ロボット」 その4 ~遺伝子の作成~

まずは、ロボットの構造体に遺伝子つまり行動パターンをセットして、動かすための準備をする。


遺伝子に入れる塩基一個は移動の向きと移動回数がセットになった構造体で、それぞれに乱数を使って入れる。移動の向きは上下左右の4種類。移動の回数は最低1~MAX_MOVLENとした。

とりあえずここまで作って、Ideoneで動かしてみた。
IdeoneはオンラインでC言語がコンパイルできる開発環境で、ちょっとだけ動かして試すのにとても便利。

使い方はこちらをご参照ください。

#define MAX_MOVLEN 5

	while (1) {
		int d = rand() % 4;//kind of dir = 4
		int n = 1 + rand() % MAX_MOVLEN;
		printf("%d,%d\n",d,n);
	}

出力はこんな感じになった。while(1)の無限ループなので、
途中で処理が打ち切られてRuntimeエラーとなったようだ。

Runtime error	#stdin #stdout 0s 4336KB
3,2
1,1
1,1
2,3
1,2
2,3
2,5
3,2
0,2
0,2
3,4
3,5
2,1
2,4
3,1
1,3

どこかで
srand(time(NULL));
を呼び出して、乱数のシードをセットする。

srand(time(NULL));

int d = rand();

これはPCの持っている時刻timeを乱数のシードとしてセットする関数。
この操作をしないと、何度やっても同じ値が出てきてしまう。

反対に、この操作をrandを使う度に呼び出してしまうと、
これも同じ値が出てくる現象を起こしてしまう。
時刻が変わらないうちに同じ時刻をシードとしてセットすると、
出てくる乱数も変わらないため。

久しぶりに使うとその辺の知識を忘れてしまい、いろいろ不具合を起こすことが多い。
evernoteにメモしておいて、キーワード検索できるようにしておくと楽。



ロボットを作成する関数。

void makeRobot(indiv_t *ind) {
	ind->x = INI_X;
	ind->y = INI_Y;
	ind->curr = 0;

	for(int i=0;i < GEN_LEN;i++){
		int d = rand() % 4; //kind of dir = 4
		int n = 1+rand() % (MAX_MOVLEN);
		
		ind->gen[i].dir = d;
		ind->gen[i].num = n;
	}
	//-------------------debug
	//test data
	//ind->gen[0].dir = right; //テストケース1:右端左端を2往復し、左端でちょうどlife=0となる。
	//ind->gen[0].num = 10;
	//ind->gen[1].dir = left;
	//ind->gen[1].num = 10;

	//ind->gen[0].dir = down; //上下端を2往復し、上端でちょうどlife=0となる。
	//ind->gen[0].num = 10;
	//ind->gen[1].dir = up;
	//ind->gen[1].num = 10;
	//-------------------debug

	ind->life = INI_LIFE;
	ind->movCnt = ind->gen[0].num;//カウンタ初期値セット
}

移動回数のカウントはmovCntでやることにした。
移動を実行する関数の中でインクリメントし、0になったら次のgen_tを取り出す。
gen_tの配列genのいくつ目の要素を取り出すかはcurr変数でやることにし、
こちらもインクリメントしていってgen_tの最後まで来たら0に戻す操作をすることにした。

途中に書いたテストケースは、
いきなり乱数がONになった状態でロボットの移動をテストすると、
テスト実行のたびに移動パターンが変わってしまって、効率が悪いから。
最初から全機能を連結してテストせず、機能を絞ってからやるのはとても重要。


次はロボットの移動をする関数。

void moveAgent(indiv_t *ind){
	int dx=0;
	int dy=0;
	
	switch(ind->gen[ind->curr].dir){
		case up:
			dy = -1;
			break;
		case right:
			dx = 1;
			break;
		case down:
			dy = 1;
			break;
		case left:
			dx = -1;
			break;
	}
	
	char c = getMapVal(ind->x + dx, ind->y + dy);
	if (c != CH_WALL) {
		map[getIndex(ind->x, ind->y)] = ' ';//元居た場所を消す
		ind->x += dx;
		ind->y += dy;
		map[getIndex(ind->x, ind->y)] = CH_AGENT;//新しい位置に書き込む

		if (c == CH_ITEM) {
			ind->life += ITEM_UP_MOUNT;
		}
	}
	ind->life--;
	ind->movCnt--;

	if(ind->movCnt < 1){
		ind->curr++;
		if(ind->curr > GEN_LEN-1){
			ind->curr = 0;
		}

		ind->movCnt = ind->gen[ind->curr].num;
	}
}

gen[curr]から取り出した、移動方向のデータを使ってswitchしている。
switch文はたまに使うと、結構トラブルを起こす。
breakがなくてもコンパイルエラーとならず、それらしく動いてしまうので、
文法チェッカなどがないgcc環境などで使うと、いつまでも不具合に気付かなかったりする。

if文で書いてしまってもよかったのだが、それはそれで結構見通しが悪い。
switch文は最も苦手な書き方なので、毎回どこかのHPでサンプルコードを見ながら書く。

こういうのは、配列を使って置き換えてもよい。
たいていswitchを使うより、なにか別の手段を使って書くほうが好みだ。
配列を使うなら、

//上右下左の順番にdx,dyを格納
const int ddx[] = {0,1,0,-1};
const int ddy[] = {-1,0,1,0};

//移動方向を取り出す。
int idx = ind->gen[ind->curr].dir;

//配列から、対応するdx,dyを取り出す。
dx = ddx[idx];
dy = ddy[idx];


最初に作ったときはテストをほったらかして、
とりあえずそれらしく動くまでデバッグし、
厳密な正しさをごまかしながら雰囲気で楽しんでいた。
しかしちょっと遺伝のさせ方をいじろうとしたとき、
ちっとも思ったようにいかず、そのプログラムは成長が止まってしまった。

今作っているプログラムは初代版のコードを部品に切り出してテストしながら、
基本動作ができるようになった先を見据えて作っている。
Q学習やらもっと高度な人工知能が試せるようになるとよいな。

次は表示の部分を作っていく。

次回に続く

« 遺伝的アルゴリズムで育てる「お掃除ロボット」 その3 ~1次元配列で2次元マップを作る~ | トップページ | コラム:CUIの文字の縦横比(アスペクト比)を調べた。 »

C言語」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/518723/66384914

この記事へのトラックバック一覧です: 遺伝的アルゴリズムで育てる「お掃除ロボット」 その4 ~遺伝子の作成~:

« 遺伝的アルゴリズムで育てる「お掃除ロボット」 その3 ~1次元配列で2次元マップを作る~ | トップページ | コラム:CUIの文字の縦横比(アスペクト比)を調べた。 »