ラングトンのアリ
提供:kuhalaboWiki
目次 |
Processing
平面が格子状に構成され、各マスが白または黒で塗られる。 アリは各ステップで上下左右のいずれかのマスに移動することができる。アリは以下の規則に従って移動する。
- 白いマスにアリがいた場合、90°右に方向転換し、そのマスの色を反転させ、1マス前進する。
- 黒いマスにアリがいた場合、90°左に方向転換し、そのマスの色を反転させ、1マス前進する。
この単純な規則で驚くほど複雑な動作をする。
ソース例
参考 ラングトンの蟻 Processing / tado Langton's ant /
- boolean型の変数の値は、true か falseの2値
- この場合は、cellsの色が、黒(true)か白(false)かの2値
- ! 演算子は、NOT(否定)で、!(true)はfalseのことで !(false)はtrueになる。
- 輪郭の色の指定はstroke()、塗りつぶしの色の指定は fill()
- アリの座標は、PVector型のantで表す。
- アリの進行方向は、ant_directionで表す。0(上),1(右),2(下),3(左)の4方向。
- turn rightは現在の方向に1足した値の4で割った余り。
- (ant_direction + 1) % 4; 例: 2(下)のとき、turn rightで2+1=3(左)
- turn leftは現在の方向に3足した値の4で割った余り。
- (ant_direction + 3) % 4; 例: 2(下)のとき、turn leftで(2+3)%4=1(右)
- turn rightは現在の方向に1足した値の4で割った余り。
- 初期状態 initialize_cells()
- 0 すべて 白 :しばらく彷徨った後、行進する。
- 1 すべて 黒:しばらく彷徨った後、行進する。
- 2 中央に黒い長方形 :長方形の周囲に城壁を築く。その後、行進する。
- 3 ランダム :
int CELL_NUM = 100; int CELL_SIZE = 5; boolean[][] cells = new boolean[CELL_NUM][CELL_NUM]; PVector ant; int ant_direction; // 0=up, 1=right, 2=down, 3=left void settings() { size(CELL_NUM * CELL_SIZE, CELL_NUM * CELL_SIZE); } void setup(){ // initialize cells, 0: all white, 1: all black, 2: put black rectangle, 3: random initialize_cells(0); // initialize ant position ant = new PVector(int(CELL_NUM / 2),int(CELL_NUM / 2)); ant_direction = 0; } void draw(){ // clear screen fill(255); rect(0, 0, width, height); // draw cells noStroke(); fill(30); for(int w = 0; w < CELL_NUM; w++){ for(int h = 0; h < CELL_NUM; h++){ if(cells[w][h] == true){ rect(w * CELL_SIZE, h * CELL_SIZE, CELL_SIZE, CELL_SIZE); } } } // draw lines stroke(128); strokeWeight(1); for(int i = 0; i < CELL_NUM; i++){ line(i * CELL_SIZE, 0, i * CELL_SIZE, height); line(0, i * CELL_SIZE, width, i * CELL_SIZE); } // draw ant fill(255, 0, 0); rect(ant.x * CELL_SIZE, ant.y * CELL_SIZE, CELL_SIZE, CELL_SIZE); //act ant act_ant(); } void act_ant(){ if(cells[(int)ant.x][(int)ant.y] == true){ ant_direction = (ant_direction + 3) % 4; // turn left }else { ant_direction = (ant_direction + 1) % 4; // turn right } cells[(int)ant.x][(int)ant.y] = !cells[(int)ant.x][(int)ant.y]; forward_ant(); } void forward_ant(){ switch(ant_direction){ case 0: ant.y = (ant.y - 1 + CELL_NUM) % CELL_NUM; break; case 1: ant.x = (ant.x + 1) % CELL_NUM; break; case 2: ant.y = (ant.y + 1) % CELL_NUM; break; case 3: ant.x = (ant.x - 1 + CELL_NUM) % CELL_NUM; break; } } void initialize_cells(int method){ switch(method){ case 0: // all white for(int w = 0; w < CELL_NUM; w++){ for(int h = 0; h < CELL_NUM; h++){ cells[w][h] = false; } } break; case 1: // all black for(int w = 0; w < CELL_NUM; w++){ for(int h = 0; h < CELL_NUM; h++){ cells[w][h] = true; } } break; case 2: // put black rectangle for(int w = 0; w < CELL_NUM; w++){ for(int h = 0; h < CELL_NUM; h++){ cells[w][h] = false; } } int rect_x = 40; int rect_y = 20; for(int w = (CELL_NUM / 2) - int(rect_x / 2); w < (CELL_NUM / 2) + int(rect_x / 2); w++){ for(int h = (CELL_NUM / 2) - int(rect_y / 2); h < (CELL_NUM / 2) + int(rect_y / 2); h++){ cells[w][h] = true; } } break; case 3: // random for(int w = 0; w < CELL_NUM; w++){ for(int h = 0; h < CELL_NUM; h++){ if(random(10) > 5){ cells[w][h] = true; }else{ cells[w][h] = false; } } } break; } }
ラングトンのアリの拡張
多数の色を使うように拡張する。色の変化は、循環となり、アリの動きは各色ごとに右か左に向きを変えて1マス進むことになる。これを色の順に L(左)と R(右)を並べて表す。
- 以下のソース例
- mod = 4で、4の剰余系(0,1,2,3)で色を表す
- 色はHSB系で表す。H:色相、S:彩度、B:明度
- この例では、colorMode(HSB, mod, 100, 100)として、色相(mod=)4段階、彩度100段階、明度100段階で表す。
- 参考:色彩
- 各色(0123)による進路「LRLR」の時、彷徨った後、行進する(通常のラングトンのアリ)。
- 各色(0123)による進路「RLLR」の時、正方形を描きながら彷徨う。
- 以下のソース例は「RLLR」としている。
int CELL_NUM = 100; int CELL_SIZE = 5; int[][] cells = new int[CELL_NUM][CELL_NUM]; int mod = 4; PVector ant; int ant_direction; // 0=up, 1=right, 2=down, 3=left void settings() { size(CELL_NUM * CELL_SIZE, CELL_NUM * CELL_SIZE); } void setup(){ // initialize cells, 0: all white, 1: all black, 2: put rectangle, 3:random colorMode(HSB, mod, 100, 100); initialize_cells(0); // initialize ant position ant = new PVector(int(CELL_NUM / 2),int(CELL_NUM / 2)); ant_direction = 0; } void draw(){ // clear screen fill(0,0,95); rect(0, 0, width, height); // draw cells noStroke(); for(int w = 0; w < CELL_NUM; w++){ for(int h = 0; h < CELL_NUM; h++){ if(cells[w][h] > 0){ fill(cells[w][h], 75, 75); // セルの色 rect(w * CELL_SIZE, h * CELL_SIZE, CELL_SIZE, CELL_SIZE); } } } // draw lines stroke(0,0,90); strokeWeight(1); for(int i = 0; i < CELL_NUM; i++){ line(i * CELL_SIZE, 0, i * CELL_SIZE, height); line(0, i * CELL_SIZE, width, i * CELL_SIZE); } // draw ant fill(0, 100, 100); rect(ant.x * CELL_SIZE, ant.y * CELL_SIZE, CELL_SIZE, CELL_SIZE); //act ant act_ant(); } void act_ant(){ switch(cells[(int)ant.x][(int)ant.y]){ // RLLR case 0: ant_direction = (ant_direction + 1) % 4; // turn right break; case 1: ant_direction = (ant_direction + 3) % 4; // turn left break; case 2: ant_direction = (ant_direction + 3) % 4; // turn left break; case 3: ant_direction = (ant_direction + 1) % 4; // turn right break; } cells[(int)ant.x][(int)ant.y] = ( cells[(int)ant.x][(int)ant.y] + 1 ) % mod; forward_ant(); } void forward_ant(){ switch(ant_direction){ case 0: ant.y = (ant.y - 1 + CELL_NUM) % CELL_NUM; break; case 1: ant.x = (ant.x + 1) % CELL_NUM; break; case 2: ant.y = (ant.y + 1) % CELL_NUM; break; case 3: ant.x = (ant.x - 1 + CELL_NUM) % CELL_NUM; break; } } void initialize_cells(int method){ switch(method){ case 0: // all white for(int w = 0; w < CELL_NUM; w++){ for(int h = 0; h < CELL_NUM; h++){ cells[w][h] = 0; } } break; case 1: // all black for(int w = 0; w < CELL_NUM; w++){ for(int h = 0; h < CELL_NUM; h++){ cells[w][h] = mod/2; } } break; case 2: // put black rectangle for(int w = 0; w < CELL_NUM; w++){ for(int h = 0; h < CELL_NUM; h++){ cells[w][h] = 0; } } int rect_x = 40; int rect_y = 20; for(int w = (CELL_NUM / 2) - int(rect_x / 2); w < (CELL_NUM / 2) + int(rect_x / 2); w++){ for(int h = (CELL_NUM / 2) - int(rect_y / 2); h < (CELL_NUM / 2) + int(rect_y / 2); h++){ cells[w][h] = mod/2; } } break; case 3: // random for(int w = 0; w < CELL_NUM; w++){ for(int h = 0; h < CELL_NUM; h++){ cells[w][h] = (int)random(mod); } } break; } }
openFrameworks
- アリは以下の規則に従って移動する。
- 色ありのマス(1)にアリがいた場合、90°右に方向転換し、そのマスの色を反転させ、1マス前進する。
- 色なしのマス(0)にアリがいた場合、90°左に方向転換し、そのマスの色を反転させ、1マス前進する。
- ofImageを使って、アリの動いた軌跡を描画している。
- ofImageのマニュアル http://www.openframeworks.cc/documentation/graphics/ofImage.html
- 教科書の3-2-7 「画像ファイルを扱う」(p172)
- アリの現在位置の色はアリごとに異なる色を設定
- アリの軌跡の色は1色に設定
- pixels = myImage.getPixels(); はエラーになるので、
- pixels = myImage.getPixels().getData(); に修正
- ofApp.h
- ofApp.hの中にAntクラスを記述している。
- Antクラスにはプロパティだけで、メソッドがない。
#pragma once #include "ofMain.h" class Ant { // Antクラス public: int row; // アリの x座標 int col; // アリの y座標 ofColor bcolor; // アリの色 int dir; // アリの進行方向 }; class ofApp : public ofBaseApp{ public: void setup(); void update(); void draw(); void keyPressed(int key); void keyReleased(int key); void mouseMoved(int x, int y ); void mouseDragged(int x, int y, int button); void mousePressed(int x, int y, int button); void mouseReleased(int x, int y, int button); void windowResized(int w, int h); void dragEvent(ofDragInfo dragInfo); void gotMessage(ofMessage msg); static const int FIELD_RATE = 4; // マス目の大きさ static const int DRAW_RATE = 10; // 描画を間引く間隔 static const int WIDTH = 1024 / FIELD_RATE; static const int HEIGHT = 768 / FIELD_RATE; int field[WIDTH][HEIGHT]; // アリが動くマス目 色あり 1 色なし 0 static const int ANTS_NUM = 3; // アリの数 Ant ants[ANTS_NUM]; ofImage myImage; //マス目(アリの歩いた跡)の描画用 unsigned char * pixels; };
- ofApp.cpp
#include "ofApp.h" //-------------------------------------------------------------- void ofApp::setup(){ ofBackground(0, 0, 0); ofSetFrameRate(30); myImage.allocate(WIDTH, HEIGHT, OF_IMAGE_GRAYSCALE); // pixels = myImage.getPixels().getData(); for (int i = 0; i < WIDTH * HEIGHT; i++) { pixels[i] = 0; } for (int i = 0; i < WIDTH; i++) { for (int j = 0; j < HEIGHT; j++) { field[i][j] = 0; } } myImage.update(); for (int i = 0; i < ANTS_NUM; i++) { ants[i].row = int(ofRandom(WIDTH)); ants[i].col = int(ofRandom(HEIGHT)); ants[i].bcolor = ofColor(ofRandom(200,255), ofRandom(0,255), ofRandom(0,200), 255); ants[i].dir = i % 4; } } //-------------------------------------------------------------- void ofApp::update(){ for (int i = 0; i < ANTS_NUM; i++) { //アリの個体 for(int j = 0; j < DRAW_RATE; j++){ // 描画間隔 if(field[ants[i].row][ants[i].col] == 1){ //fieldに色があるとき ants[i].dir = ( ants[i].dir + 1 ) % 4; // 右に90度方向転換 field[ants[i].row][ants[i].col] = 0; pixels[ants[i].col * WIDTH + ants[i].row] = 0; } else { //fieldに色がないとき ants[i].dir = ( ants[i].dir + 3 ) % 4; // 左に90度方向転換 field[ants[i].row][ants[i].col] = 1; pixels[ants[i].col * WIDTH + ants[i].row] = 255; } switch(ants[i].dir) { case 0: // 東(X軸の正)へ進む ants[i].row += 1; break; case 1: // 南(Y軸の正)へ進む ants[i].col += 1; break; case 2: // 西(X軸の負)へ進む ants[i].row -= 1; break; case 3: // 北(Y軸の負)へ進む ants[i].col -= 1; break; } ants[i].row = (ants[i].row + WIDTH) % WIDTH; // X方向境界処理 ants[i].col = (ants[i].col + HEIGHT) % HEIGHT; // Y方向境界処理 } } myImage.update(); } //-------------------------------------------------------------- void ofApp::draw(){ ofSetColor(0, 0, 255); myImage.draw(0, 0, WIDTH * FIELD_RATE, HEIGHT * FIELD_RATE); // fieldの描画 for (int i = 0; i < ANTS_NUM; i++) { // antの描画 ofSetColor(ants[i].bcolor); ofRect(ants[i].row * FIELD_RATE, ants[i].col * FIELD_RATE, FIELD_RATE, FIELD_RATE); } }
アリごとに異なる軌跡色を設定
- Antクラスのプロパティに 歩いた軌跡の色である tcolorを設定
- ofImageをOF_IMAGE_COLORに設定する。
- myImageは RGBの3プレーンで構成される。
- Antクラス
class Ant { public: int row; int col; ofColor bcolor; // antの現在位置の色 ofColor tcolor; // antの軌跡の色 int dir; };
- ofApp.cpp
- アリは、色のないフィールドにいると、自分の軌跡色をmyImageにセルに設定する。
//-------------------------------------------------------------- void ofApp::setup(){ ofBackground(0, 0, 0); ofSetFrameRate(30); myImage.allocate(WIDTH, HEIGHT, OF_IMAGE_COLOR); //ofImageをカラーモードで割当 pixels = myImage.getPixels().getData(); for (int i = 0; i < WIDTH * HEIGHT * 3 ; i++) { //RGB 3 planeのピクセル pixels[i] = 0; } for (int i = 0; i < WIDTH; i++) { for (int j = 0; j < HEIGHT; j++) { field[i][j] = 0; } } myImage.update(); for (int i = 0; i < ANTS_NUM; i++) { ants[i].row = int(ofRandom(WIDTH)); ants[i].col = int(ofRandom(HEIGHT)); ants[i].bcolor = ofColor(ofRandom(0, 255), ofRandom(0, 255), ofRandom(0, 255) ); ants[i].tcolor = ofColor(ofRandom(0, 255) , ofRandom(0, 255) , ofRandom(0, 255) ); ants[i].dir = i % 4; } } //-------------------------------------------------------------- void ofApp::update(){ for (int i = 0; i < ANTS_NUM; i++) { //アリの個体 for(int j = 0; j < DRAW_RATE; j++){ // 描画間隔 if(field[ants[i].row][ants[i].col] == 1){ //fieldに色があるとき ants[i].dir = ( ants[i].dir + 1 ) % 4; field[ants[i].row][ants[i].col] = 0; pixels[ants[i].col * 3 * WIDTH + ants[i].row * 3] = 0; // R値 pixels[ants[i].col * 3 * WIDTH + ants[i].row * 3 + 1] = 0; // G値 pixels[ants[i].col * 3 * WIDTH + ants[i].row * 3 + 2] = 0; // B値 } else { //fieldに色がないとき ants[i].dir = ( ants[i].dir + 3 ) % 4; field[ants[i].row][ants[i].col] = 1; pixels[ants[i].col * 3 * WIDTH + ants[i].row * 3] = ants[i].tcolor.r; // R値 pixels[ants[i].col * 3 * WIDTH + ants[i].row * 3 + 1] = ants[i].tcolor.g; // G値 pixels[ants[i].col * 3 * WIDTH + ants[i].row * 3 + 2] = ants[i].tcolor.b; // B値 } switch(ants[i].dir) { case 0: // 東(X軸の正)へ進む ants[i].row += 1; break; case 1: // 南(Y軸の正)へ進む ants[i].col += 1; break; case 2: // 西(X軸の負)へ進む ants[i].row -= 1; break; case 3: // 北(Y軸の負)へ進む ants[i].col -= 1; break; } ants[i].row = (ants[i].row + WIDTH) % WIDTH; // X方向境界処理 ants[i].col = (ants[i].col + HEIGHT) % HEIGHT; // Y方向境界処理 } } myImage.update(); } //-------------------------------------------------------------- void ofApp::draw(){ ofSetColor(255, 255, 255); // 描画色をリセット ofSetRectMode(OF_RECTMODE_CORNER); myImage.draw(0, 0, WIDTH * FIELD_RATE, HEIGHT * FIELD_RATE); // fieldの描画 ofSetRectMode(OF_RECTMODE_CENTER); for (int i = 0; i < ANTS_NUM; i++) {// antの描画 ofSetColor(ants[i].bcolor); ofRect(ants[i].row * FIELD_RATE, ants[i].col * FIELD_RATE, FIELD_RATE, FIELD_RATE); } }
動的配列vectorの利用
アリは、初めはいないが、マウスクリックした場所にAntインスタンスが動的に現れる。
- 動的配列(vector)は、定義時に、配列の要素数を決めなくてもよい。プログラム実行中に動的に追加や削除ができる。
従来は、予め配列数を決めて、例えば
クラス名 インスタンス名[100]
と宣言していたが、vectorでは。
vector <クラス名> インスタンス名
と宣言し、配列の個数は、動的に追加、削除できる。
今回の例では、
Ant ants[100];
とするところを
vector <Ant> ants;
とすると、アリを後から追加できる。 配列の個数(大きさは)
ants.size()
で参照できる。
#define文を使って、文字列を一気に置き換えることができる。
#define ANTS_NUM ants.size()
インスタンスを追加するには、
ants.push_back()
を使う。
マウスクリックした位置に新しいアリを生成する部分は以下のとおり。
oid ofApp::mouseReleased(int x, int y, int button){ Ant a; //新しくアリのインスタンスを作成 a.row = mouseX * WIDTH / ofGetWidth(); //マウスの位置に配置 a.col = mouseY * HEIGHT / ofGetHeight(); a.bcolor = ofColor(ofRandom(0, 255), ofRandom(0, 255), ofRandom(0, 255) ); //色をランダムに選ぶ a.tcolor = ofColor(ofRandom(0, 255) , ofRandom(0, 255) , ofRandom(0, 255) ); a.dir = mouseX % 4; //方向を決める ants.push_back(a); // 生成したaをantsに追加 }
すべての要素を削除するには、
ants.clear()
とする。
Cを押すと、アリと画面をクリアする場合
void ofApp::keyReleased(int key){ if( key == 'C'){ ants.clear(); for (int i = 0; i < WIDTH * HEIGHT * 3 ; i++) { //RGB 3 planeのピクセル pixels[i] = 0; } for (int i = 0; i < WIDTH; i++) { for (int j = 0; j < HEIGHT; j++) { field[i][j] = 0; } } myImage.update(); } }