2. さらに様々な技法を学ぶ¶
ここまで、狼が羊を追いかけ捕食するモデルを作成してきました。しかし、羊も食べられてばかりではありません。羊は現実には牧草を食べていることでしょう。また、狼や羊が子孫を残すという様子も表現して、artisoc Cloudの中に簡易的な生態系をつくりましょう。
新しく登場する知識としては以下のようなものがあります。
空間変数
エージェント変数でエージェントの状態を管理する
エージェントがエージェントを生む
2.1. 空間変数の導入¶
ここでは空間変数というものが新しく登場します。空間変数は空間に直接追加する変数のことで、必ず空間座標の上に定義されています。
例えば広さが5×5の連続空間を想定してみましょう。この空間ではx=0, y=0からx=5, y=5に全部で36個の空間変数が敷き詰められているというイメージです。空間変数の座標は整数値しかとりません。
下の図で説明すると、空間変数に色を付けたとき、[0, 0]から[5, 5]まで敷き詰められたマス目が塗りつぶされます。マス目の座標は一部省略しています。
例えばx=1, y=1の空間変数を青くするというルールにするとこうなります。
今回は牧草の成長度を持つ空間変数grassを作成し、grassの数字が大きければ、すなわち牧草が良く育っていればマップ上で緑色が濃く見えるというふうにモデルを作成していきます。
早速、空間変数を追加して使ってみましょう。
(注意:出力要素は追加した順に上に重なって表示されます。そのため、空間変数を最後に追加するとエージェントが空間変数の下に隠れて見えなくなってしまいます。お手数ですが、一度sheepとwolfを出力要素から削除し、下記手順で空間変数の出力設定をした後で再度追加してください。なお、この仕様は修正中で、今後のアップデートで出力要素の追加しなおしは不要になる予定です。)
まず、空間plain右横の「+」マークを押して「空間変数を追加」を選択します。変数名はgrassとします。
次に空間変数をマップに出力されるように設定します。マップの編集画面を開き、マップ要素リストの「エージェント」の横の矢印をクリックします。ここで空間変数を選択し、「+」を押してください。
要素名で「牧草」と名付け、表示色>グラデーション指定の対象変数の矢印をクリックすると先ほど追加したgrassが表示されるはずですので選択してください。対応色のところは左側の数字は0のまま色は白、右側は数字を50に変えて色は緑にしておくと後で牧草っぽさが出ます。これはgrassの値が0に近いとき白っぽく表示されて、50に近ければ近いほど緑色で表示されるということです。
その他の設定は特に必要ないのでここまでできたらOKを押してマップ出力設定画面を閉じましょう。
今度はルールを書き加えていきます。
牧草が羊に食べられるルールは後で書くとして、今は牧草の成長するさまをルールに書いていきます。
grassには成長度を代入します。牧草は1ステップに1ずつ成長するとしましょう。ただし、先ほど出力設定で設定したように、grassの値が50の時に一番濃い緑になるのですから、最大値は50とします。grassの成長は狼や羊とは関係なく行われるのでUniverseのルールに書きます。以下のルールを書き加えましょう。今まで書いたルールは消さずに使いますが、univ_step_beginのpassは消し忘れないようにしましょう。
def univ_init(self):
# 最初はすべての牧草が成長しきっている
for x in range(51): # 座標の数は0~50の合計51個
for y in range(51):
Universe.plain.grass[x, y, 0] = 50
def univ_step_begin(self):
# 成長度が50より小さい場合は1足す
for x in range(51):
for y in range(51):
if Universe.plain.grass[x, y, 0] < 50:
Universe.plain.grass[x, y, 0] += 1
このルールでは説明しなければならないことがいくつかあります。
1つは空間変数の指定の仕方です。ルールのUniverse.plain.grass[x,y,0]
の部分です。xがx座標、yがy座標、0がレイヤーを表しています。前述したように、空間変数は各座標上に位置します。ですので特定の空間変数を表す場合は空間変数[x座標, y座標, レイヤー]
の形で書きます。レイヤーについては今回のモデルでは詳しく扱わないのでとりあえず0にしておいてください。
また、for文が入れ子構造になっている点にも注意してください。
univ_initルールでは、「空間変数grass[x, y, 0]に50を代入するというルールがjが0から50までの51回繰り返される」というルールが、xが0から50までの51回繰り返されるわけです。
具体的に考えていきます。最初は、x = 0, y = 0のときgrass[0, 0, 0]に50が代入されます。次に、for文によってyの数が1増えるのでx = 0, y = 1のときgrass[0, 1, 0]に50が代入されます。このように繰り返して、x = 0, y = 50まできたら、次はx = 1, y = 0(つまりgrass[1, 0, 0])となります。for x in range(51):
が51回分終わったら初めてxが増えるという形です。もちろんxが1増えたらまたfor y in range(51):
の部分が51回分繰り返されるということです。
さて、このようにするとx=0, y=0からx=50, y=50に位置するすべてのgrassに50を代入することができました。
univ_step_beginのルールも入れ子構造になっています。こちらは、x=0, y=0からx=50, y=50に位置するすべてのgrassの値を1つずつ調べ、もし50未満だったら1足すというルールになっています。
とりあえずここまでできたら保存をして実行してみましょう。マップ一面が緑になったでしょうか?
2.2. エージェント変数でエージェントの状態を管理する¶
続いては、羊に牧草を食べさせましょう。また、体力という概念を導入させましょう。体力は始めは30あって、毎ステップ1ずつ減り、牧草を食べると1増えます。また、体力が0になると羊は死にます。なお、今回は牧草を食べるという表現を牧草の成長度をマイナスすることで表現します。
それではまず、体力を表す変数powerをsheepのエージェント変数として追加します。 sheepのルールに下記のように書き加えましょう。今までのルールもそのまま残します。
def agt_init(self):
# self.direction = rand() * 360 に続けてルールを書く
self.power = 30
def agt_step(self):
# def agt_step(self): に続けてルールを書く
# 1ステップ毎に体力が1減る
self.power -= 1
# 体力が0以下になったら死ぬ
if self.power <= 0:
del_agt(self)
# 自分の場所にある牧草の成長度が20以上なら牧草を食べる
# 変数名が長いのでeaten_grassに今いる場所のgrassを代入
eaten_grass = Universe.plain.grass[round(self.x), round(self.y), 0]
if eaten_grass >= 20:
eaten_grass -= 20
self.power += 1
# grassにeaten_grassの値を戻す
Universe.plain.grass[round(self.x), round(self.y), 0] = eaten_grass
self.turn(rand() ...
このルールに登場するround()はround関数といって引数を四捨五入して整数値にして返す関数です。
空間変数grassの座標が必ず整数値であるのに対し、羊の座標は整数であるとは限りません。そのため、round関数を使い近くの牧草を調べることにします。
それでは保存をして実行しましょう。牧草が羊に食べられ、色が変わる様子が確認できることと思います。
2.3. エージェントを生む¶
次は羊が子孫を残すというルールを導入しましょう。体力が30以上の時、ある一定の確率で子を産むとします。ここでは5パーセントの確率で産むことにしましょう。また、子を産むと自分の体力は半分になるとしましょう。
sheepルールは次のように書きます。子を産むというルールはエージェントルールの中でcreate_agt関数を使えばいいわけです。こちらも今までのルールは消さずに書き加えてください。
agt_step(self):
# Universe.plain.grass[round(self.x), round(self.y), 0] = eaten_grass に続けてルールを書く
# 体力30以上かつ5%の確率で子を産む
if self.power >= 30 and rand() < 0.05:
daughter = create_agt(Universe.plain.sheep)
# 生まれた子は親と同じ場所に位置する
daughter.x = self.x
daughter.y = self.y
daughter.direction = rand() * 360
self.power *= 0.5
self.turn(rand() ...
この要領で狼にも体力という概念を持たせ、子を産むようなルールにしましょう。 狼も体力の初期値は30、1ステップごとに1減りますが、羊は牧草よりも食べられる機会が少なく栄養が多いという仮定を置いて羊を食べたときには体力が50増えることにしましょう。子を産むルールはとりあえず羊と同じにします。
狼のエージェント変数にもpowerを追加し、wolfのルールに以下の内容を書き加えます。agt_stepではここまでで書いてきたのルールも載せていますので、必要な部分だけ書き加えてください。
def agt_init(self):
# self.speed = 0 に続けてルールを書く
self.power = 30
def agt_step(self):
# 書き加える部分
# 1ステップ毎に体力が1減る
self.power -= 1
# 体力が0以下になったら死ぬ
if self.power <= 0:
del_agt(self)
sheep_set = self.make_agtset_around_own(5, False, agttype=Universe.plain.sheep)
num = count_agtset(sheep_set)
if num == 0: # 標的がいなければうろうろする
self.turn(rand() * 60 - 30)
self.speed = 0.5
else: # 標的がいれば
one = randchoice(sheep_set)
self.turn_agt(one) # 体を標的の方向に向ける
# 標的がすぐ近くなら、立ち止まって捕食する
if measure_agt_distance(self, one) < 1:
self.speed = 0
del_agt(one)
self.power += 50 # 書き加える部分
# 離れていれば限界速度まで加速
elif self.speed <= 3:
self.speed += 0.1
# 書き加える部分
# 体力30以上かつ5%の確率で子を産む
if self.power >= 30 and rand() < 0.05:
daughter = create_agt(Universe.plain.wolf)
# 生まれた子は親と同じ場所に位置する
daughter.x = self.x
daughter.y = self.y
daughter.direction = rand() * 360
self.power *= 0.5
if self.forward(self.speed) != -1:
self.turn(90)
保存して実行してみましょう。