.NET テラリウム

■ 関連リンク

WWW

ローカル

.NET テラリウム

「テラリウム」。強いプログラムを競う Microsoft の異色なネットゲームらしいです・・・プログラムときいて、ちょっと興味がわきました。

どうやらシムアースのネット対応版と思えばよさそうです。「テラリウム」をインストールすると、誰もいない、あまり広くない土地が現れました。

これを箱庭として、じぶんの生物を育てるんですな〜。なにやら青いワープボールがゆっくりうろついています。こいつのせいで、何もしてなくても時間がたつにつれて、よそのパソコンからどんどん生物がワープしてきます。
数十分で、うじゃうじゃになりました。ワープボールと接触した生物は、ランダムでどこかのパソコン(専門用語で、ピア、と呼ぶらしい)に飛ばされるようです。こうやって、自分の作った種を他のピアにどんどんワープさせて、よそでも繁殖すれば勝ちなのですねえ。

えさをさがすアルゴリズム。いいタイミングで子供を産めるアルゴリズム。積極的に他人のパソコンにワープできるアルゴリズム。いろんなパソコンにワープしてこそ、個体数を増やせるというものです。すぐれたアルゴリズムを持つ生物は、あっというまに増えて世界中のパソコンに住み着くんですから、まるでウィルスです。

いまちょうどコンテスト期間で、6 月 28 日の時点でピア全体で一番個体数が多い上位100名が100 人が賞品をもらえるそうです。最優秀 1位〜10位にはなんと X-BOX だそうです!・・・あんまりうれしくない? まぁどんなもんなのか試してみましょう。。

プログラミング言語

自分が作る種は、能力値や動作などすべてを、Microsoft .NET Framework に対応した言語でプログラミングします。ドットネットを普及させるためのプロジェクトなんですな〜。お店で Visual Studio.NET を買うと、
  1. Visual Basic.NET
  2. Visual C++.NET
  3. Visual C#
  4. JScript.NET
この 4 種類の言語のコンパイラ+開発環境がついてきます。ほかにサードパーティー製で .NET に対応している言語は、
  1. APL
  2. Eiffel
  3. COBOL
  4. PASCAL
  5. Oberon
  6. Perl
  7. Python
  8. SmallTalk
  9. Haskell
  10. Scheme
  11. ML
  12. Objective Caml
  13. Mercury
  14. Java
以上があるそうですが、やってできないことはないけどやめたほうがよいらしいです。

な〜んにも買わなくても、.NET Framework SDK をダウンロードしてきてインストールすれば C# のコンパイラがもれなくついてるので、「秀丸エディタ」でプログラムを書いて、コンパイルさせれば「テラリウム」で遊ぶにはじゅうぶんそうです。

ちなみに、Microsoft は C# でプログラミングするように推奨しているようなので、その方向でいきます。

生物と、種の能力

生物は、 の3種類のどれかに属しています。コンテストの対象となるのは肉食か、草食かのどっちかです。植物をいくら増やしても入賞しません。

動物の種は、つぎの7種類の基本能力を持っています。いわゆるステータス。。

  1. 最大エネルギー
  2. 食べる速度
  3. 攻撃力
  4. 防御力
  5. 移動速度(すばやさ)
  6. カモフラージュ(身を隠す能力)
  7. 視界のひろさ
新しい種をプログラミングするとき、計100ポイントをこの7種類の能力にふりわけます。サンプルコードについてきた肉食動物は、
  1. 最大エネルギー : 0
  2. 食べる速度 : 0
  3. 攻撃力 : 20
  4. 防御力 : 10
  5. 移動速度 : 0
  6. カモフラージュ : 30
  7. 視界のひろさ : 40
となっていました。なんか 0 とか多いのですが、こんなんだとすぐに絶滅しそうな気がしますが、まあサンプルなので。

あとソースに書いてる固有情報は、「種の名前」「作者の名前」「作者のアドレス」「動物の外見」「動物のサイズ」。動物のサイズってのがよくわかんないですが。。まぁいいや。

成長:動物のレベルアップ

動物は、成長することで能力を高めることができます。成長する目的は、2つです。

成長するだけのエネルギーがあって、成長するだけのスペースがまわりにあれば、その瞬間、勝手に成長します。

アクション:動物のできること

動物がとれるアクションは、開発ガイドによると
  1. 移動アクション
  2. 摂食アクション(えさを食べて、エネルギー補給)
  3. 生殖アクション(子供をつくる)
  4. 戦闘アクション
  5. 治癒アクション
くらいかな?それぞれについてさらっとみていきます。
1. 移動アクション
動物が移動するためには、移動したい目的地の座標と、移動スピードの2つを指定してあげる。最高速度は、もちろん種で設定されている最高速度です。移動するとエネルギーが減る(つまりおなかがすく)。速く移動するほどエネルギー消費が激しいので、とくに急ぐ理由がなければ最高速度の50%で移動するのがよいらしい。もちろん、えさを追っかけたり、危険から逃げるときは最高スピードで逃げないとまずいので、そこはプログラマーの腕の見せどころらしいです。
いったん移動アクションしはじめると、目的地につくか、途中にいた生物にブロックされるまで、他のアクションを実行できません。
2. 摂食アクション
肉食動物は、肉食動物か草食動物を食べ、草食動物は植物を食べることで、エネルギーを貯蔵できます。植物は食われるまま・・? 植物は太陽光線で勝手にエネルギーが増えるみたい。
動物は、じぶんの視界範囲にいる生物をすべて知ることができるので、その中から好きなの(一番近い奴?いちばんエネルギーが多そうな奴?)を選択して、そばに行くようにプログラミングしないといけません。
最大エネルギー値の約80%以上あると「まんぷく」なので、摂食することができません。
植物を食べると植物のエネルギーが減ります。植物を食べ過ぎて殺してしまうのはもったいないので、植物は生かさず殺さずするようにプログラミングするのが、インテリジェントな草食動物のあり方である。(ううむ。。)
3. 生殖アクション
生殖すると子供を産めます。生殖するには3つの条件を満たしていないとダメです。
  1. 最大まで成長していること。最大まで成長するまでの時間は、周りにえさが十分にあった仮定したとき、だいたい寿命の半分の時間で到達できるらしい。
  2. さっき生殖してから、一定の時間がたっていること。
  3. 生殖するエネルギーがあること。
これを満たしているとき、いつでも子供が産めます。
親は、子供に好きな情報を遺伝させるようにプログラムすることができます。「食料の位置」「他の種に関する情報」「親の位置」などを遺伝させると、種が有利に働くでしょう。世代カウンタを持っていれば、「5世代目まではとにかく子供を生んで、6世代目は攻撃的になれ!」という命令も可能です。逆に、「もう死んでしまった生物の情報」を渡しても意味が無いし、「あまりに多すぎる遺伝情報」は処理に時間がかかりすぎて死んでしまうかもしれません。
4. 戦闘アクション
接近した 2 体の生物は、「戦闘」と呼ばれる作用が発生します。各ラウンドごとに属性に基づいて攻撃と防御うを繰り返しダメージが決まります。ダメージが最大になると死にます。(ダメージとエネルギーは、独立した別の概念です)
  • 肉食動物は、相手の動物を殺すことで、食べることができるわけです。攻撃属性と防御属性は、同じポイントを持つ草食動物より有利に働きます。
  • 草食動物は、たいがい肉食にはかなわない。戦闘が発生する前に逃げるようなプログラミングをするべき? 別の草食動物と戦闘して争うケースがあるかもしれません。
攻撃をするには、攻撃目標を追っかけて近くまでいけば攻撃できます。草食動物の場合は、自分が「腹減り」状態じゃないと攻撃できません(反撃時を除く)。おなじ種同士が争わないように、なんらかのチェックがいるでしょう。
防御をするには、ターゲットを指定するだけでOKです。そのターゲットの攻撃のダメージを減らし、回避する能力がアップします。ただし防御のターゲットとして指定できるのは1体のみ。相手のエネルギー状態を調べることができるので、近くにいる肉食動物が「腹減り」状態だったら、こいつは攻撃してくるかもしれないということで、防御するのは効果的でしょう。
5. 治癒アクション
動物は、非戦闘時、または戦闘中の各ラウンドごとに、治癒をしてダメージを回復できます。ダメージポイントを回復する代わりに、エネルギーを消費します。(ダメージを回復したのはいいけど、その結果エネルギーが空っぽになったら、まぬけなことに死んでしまいます。)

アクションについては、こんな感じでプログラミングします…。奥がふかそうです。

テレポーテーション

テレポーター(青い玉)に触れた瞬間、生物はテレポーテーションします。ランダムに他のピアに飛ばされる?まあとにかく、別のマシン(たまたま自分のマシンのときもある)にテレポーテーションします。生物はいったんデータの塊になりますが、このときにどのデータを保存するかを決定できます。8k よりも多くのデータを書き込むと切り捨てられて、生物はデータを失うことになります。

生物には、テレポーターの位置を知るような関数は用意されていないので、自ら積極的にテレポーテーションしようと思ったら、かなり難しい。自分の周囲を眺めて、ある生物が突然いなくなったら、それはテレポーターによって移動したのかもしれない、という判断でテレポーターの位置を知るしかない。

テラリウム

うちのテラリウム内は buu_sou3 なる草食生物が王者になって、うじゃうじゃ繁栄していました。ネット上のコミュニティを見ると、どこも buu_sou3 であふれかえってると書いてあります。よほどかしこくプログラミングされた種なのでしょう。公式サイトの統計情報を見てみると、この buu_sou3 の個体数が 11945 体でダントツ1位です。このままコンテストが終了すると、X-BOX は彼のものです。buu_sou3 に勝てるプログラムが書けるのでしょうか。

さて、昨日はプログラミングのことはいっさい書かなかったので、今日は一気にプログラミング寄りな話題になります。自分の種を作りたいときにどうするんでしょうか。

種のプログラミング

早い話、サンプルコードをながめないことにはよくわかりません。動物(草食でも肉食でも)のサンプルコード(C#)を開いてみると、
定数の宣言;

public class MyCreature : Animal 
{ 
 動物の中身
}
なんて書いてあって、ひとつしかない MyCreature クラスの中に、動物のふるまいが全部おさめられています。

このコードをコンパイルして dll にしたものが、生物の種のファイルになります。テラリウム上から「種の導入」を押してから、この dll を選ぶと、自分の作った種を10匹送り込むことができます。あとはがんばって増えてくれますように…。

サンプルコード

サンプルコードの先頭には、定数が宣言されています。ここに
// ベース ポイントの属性 
// 合計100ポイントを下記のいずれかに割り振ります。
[MaximumEnergyPoints(0)]    // 体力
[EatingSpeedPoints(0)]      // 植物を食べる速さ 
[AttackDamagePoints(0)]     // 攻撃力
[DefendDamagePoints(10)]    // 防御力 
[MaximumSpeedPoints(0)]     // 移動の速度 
[CamouflagePoints(30)]      // 偽装(身を隠す)能力 
[EyesightPoints(60)]        // 視野の広さ 
なんて書いてあるのは、合計100ポイントの基本能力を振り分けているところですね。どうもこのサンプル草食動物は、視野だけ広くて、あとの能力はほとんどありません。

さて、その次にはクラスの本体がかかれています。はしょって書くと、MyCreature クラスの全体像はこんな感じです。

public class MyCreature : Animal
{ 
	PlantState targetPlant = null;

	// イベントハンドラの登録 
	protected override void Initialize() { ... }

	// イベントハンドラ
	void LoadEvent() { ... } // ロード時最初に呼び出される
	void IdleEvent() { ... } // 定期的に呼び出される
	void AttackedEvent (e) { ... } // 攻撃を受けた時に防御のために呼び出される

	// 視界内のえさを探すルーチン
	bool ScanForTargetPlant() { ... }

	// ここより下はテラリウムを中断および再開させる際に実行されます
	public override void SerializeAnimal() { ... } 
 	public override void DeserializeAnimal() { ... }
}
ざーっと見たところ、ここで核になっているのが IdelEvent() で、生物がヒマで特にすることがないときに呼ばれるようです。このサンプルでは、ヒマなときにこういうアルゴリズムで行動しています。 とても単純です。この草食生物は、満腹時には動かずにエネルギー温存しているという、とんでもないなまけものです。しかもえさを探す効率的なロジックはなく、世界の適当な場所へ行って、そこにえさがあることを期待しつつ移動するだけです。戦闘のロジックはないので、今にも死にそうなライバル生物が隣にいても、こちらから積極的な戦闘をしかけることはありません。防御だけ書いてあります。

こんな生物は、生存競争の激しい場所では生き残れなさそうです。実際、10匹放ったところ、他のプレイヤーの生物にあっというまに絶滅させられてしまいました。ここからどう改良するのか…。

Animal クラス

Animal クラスが継承されているので、まずは Animal クラスの内容を調べることにしましょう。。これだけでも結構あります。

Animal クラスのメソッド一覧

Void BeginAttacking(AnimalState)   // 攻撃命令
Void BeginDefending(AnimalState)   // 防御命令
Void BeginEating(OrganismState)    // 摂食命令
Void BeginMoving(MovementVector)   // 移動命令
Void DeserializeAnimal(MemoryStream)
Void InternalMain()
Boolean IsMySpecies(OrganismState) // 相手生物が、自分と同種族か判定
OrganismState LookFor(OrganismState)
OrganismState RefreshState(String)
ArrayList Scan()                   // 視界内をサーチして全ての生物を得る。
Void SerializeAnimal(MemoryStream)
Void StopMoving()                  // 移動中止命令
Boolean WithinAttackingRange(AnimalState) //対象が、攻撃可能範囲にいるか?
Boolean WithinEatingRange(OrganismState)  //対象が、摂食可能範囲にいるか?
Animal クラスのプロパティ一覧
Boolean CanEat { get; }       // 満腹か、そうでないか
AttackAction CurrentAttackAction { get; } // 現在の攻撃状態
DefendAction CurrentDefendAction { get; } // 現在の防御状態
EatAction CurrentEatAction { get; }       // 現在の摂食状態
MoveToAction CurrentMoveToAction { get; } // 現在の移動状態
Boolean IsAttacking { get; } // 攻撃中か、そうでないか
Boolean IsDefending { get; } // 防御中か、そうでないか
Boolean IsEating { get; }    // 摂食中か、そうでないか
Boolean IsMoving { get; }    // 移動中か、そうでないか
IAnimalSpecies Species { get; } // 生物の種情報を得る
AnimalState State { get; }      // 生物の現在の状態を得る
Int32 WorldHeight { get; }      // 世界の縦幅を得る
Int32 WorldWidth { get; }       // 世界の横幅を得る
あ゛〜。いっぱいですわ。

プロパティ

テラリウム上の生物をクリックしてフォーカスをあてたままプロパティシートを開くと、その生物のプロパティがリアルタイムに見せてくれます。このプロパティシートに出てくる変数を列挙してみましょう。1グリッド=8ピクセル
[OrgStt].ActualDirection           // 移動の方向
[AniStt].AnimalSpecies             // 生物の種に関する情報
[??????].BitmapRadius
[??????].BitmapWidth
[OrgStt].CellRadius                // 半径(グリッド数)
[Animal].CurrentMobeToAction       // 移動の状況:方向と速度
[Organi].CurrentReproduceAction    // 生殖の状況
[AniStt].Damage                    // ダメージの絶対量
[OrgStt].DeathReason               // 死んだ理由
[OrgStt].EnergyState               // 現在のエネルギー状態
[OrgStt].FoodChunks                // 食料値
[OrgStt].Generation                // 世代番号
[??????].GridX                     // 現在位置 X (グリッド単位)
[??????].GridY                     // 現在位置 Y (グリッド単位)
[ISpeci].GrowthWait                // 成長するまでの待ちティック数
[OrgStt].ID                        // EcoSystem 内でのこの生物の ID
[OrgStt].IncubationTicks           // 生殖を終了するまでの待ちティック数
[OrgStt].IsAlive                   // 生きているのか死んでいるのか
[??????].IsImmutable
[OrgStt].IsIncubating              // 生殖の過程にあるのかどうか
[OrgStt].IsMature                  // 成熟しているかどうか
[OrgStt].IsStopped                 // 停止しているかどうか
[??????].OrganismEvents
[OrgStt].PercentEnergy             // 現在持っているエネルギーの%
[AniStt].PercentInjured            // これまでに受けたダメージの%
[OrgStt].PercentLifespanRemaining  // 寿命%
[OrgStt].Position                  // 現在位置
[OrgStt].Position.X                // 現在位置 X
[OrgStt].Position.Y                // 現在位置 Y
[??????].PreviousDisplayAction
[OrgStt].Radius                    // 生物の半径
[OrgStt].ReadyToReproduce          // 生殖を行う準備ができているかどうか
[??????].RenderInfo
[ISpeci].ReproductionWait          // 生殖するための待ちティック数
[AniStt].RotTicks                  // 死んでから経過したティック数
[Animal].Species                   // 生物の不変特性
[IAnisp].Species.EatingSpeedPerUnitRadius // 単位半径当たり1ティックで食べれる食料
[IAnisp].Species.EyesightRadius    // 視界。見ることができる距離
[ISpeci].Species.GrowthWait        // 成長するまでの待ちティック数
[??????].Species.IntialRadius      // ??? 初期半径?みんな 11 だ。
[IAnisp].Species.InvisibleOdds     // Scan メソッドを使用する他の生物から見えずにいられる確率
[IAnisp].Species.IsCarnivore       // 肉食動物であるか?
[ISpeci].Species.LifeSpan          // 寿命ティック数
[??????].Species.MarkingColor
[ISpeci].Species.MatureRadius      // 完全に成長したときの半径
[IAnisp].Species.MaximumAttackDamagePerUnitRadius // 単位半径当たり1ティックで与えうる最大ダメージ
[IAnisp].Species.MaximumDefendDamagePerUnitRadius // 単位半径当たり1ティックで吸収しうる最大ダメージ
[ISpeci].Species.MaximumEnergyPerUnitRadius // 単位半径当たりに貯蔵できる最大エネルギー
[IAnisp].Species.MaximumSpeed      // 最大移動速度
[??????].Species.Name
[ISpeci].Species.ReproductionWait  // 生殖するための待ちティック数。
[ISpeci].Species.Skin              // スキンを識別する文字列
[IAnisp].Species.SkinFamily        // スキン ファミリーを識別する文字列
[MovementVector].Speed             // 移動速度
[OrgStt].StoredEnergy              // 貯蔵エネルギーの量
[OrgStt].TickAge                   // 年齢ティック数

サフィックスの説明
対応するクラスから、アクセッサーを通じて値を読むことができる。
[Organi].   // Organism クラス
[Animal].   // Animal クラス
[OrgStt].   // OrganismState クラス
[AniStt].   // AnimalState クラス
[ISpeci].   // ISpecies クラス
[IAnisp].   // IAnimalSpecies クラス
ふ〜。植物の場合は、また微妙に違いますよん。

foreach みーっけ!

ScanForTargetPlant() メソッドの中に、見慣れた制御文 foreach を見つけました。
ArrayList foundCreatures = Scan(); // 生物を探して情報を配列に格納
if(foundCreatures.Count > 0) // 発見された生物は0より大きい: はい 
{
	// 配列内を走査
	foreach(OrganismState organismState in foundCreatures)
	{ 
		if(organismState is PlantState) // その中に植物はあるか?
		{ 
			targetPlant = (PlantState) organismState; 
			// 植物の場所に移動開始
			BeginMoving(new MovementVector(organismState.Position, 2)); 
			return true; 
		} 
	} 
}
へえ〜、C#って foreach があるんですね。うほほ。foreach(変数 in 配列) という文法で、配列の中身が順番に値渡しされて、処理できるようです。値渡しっていうのがポイントで、C#の foreach は配列の中身までは書き換わりません。

サンプルを野に放つ

草食サンプルをビルドして野に放ちました。これだけだと何にも食べられないので、植物も豊富に与えました。するとどんどん植物を食べては、増えていきます。

次に、テラリウムをリセット後、肉食動物のサンプルコードをビルドして野に放ったら、ワーニングが出ました。「Points applied to 'DefendDamagePointsAttribute' should be in increments of 4. Anything else is wasted.」ん〜? DefendDamagePoints は4おきに設定しないと、無駄が出るよ、ってことでしょうか。サンプルコードのくせに〜〜〜。

ワーニングは出ても、ちゃんと10匹が野に放たれました。このままぼーっと見てると、やがてともぐいをはじめました。勝った奴はまるまると太りましたが、他にえさもないので餓死です。気づいたこと

まずは移動速度からいじってみることにします。どういうアルゴリズムにしましょうか。
以下、執筆中のまま挫折・・・(−−;