連載:世界のWebサービス
第10回 モバイルWebアプリケーション

3.サンプル肉食動物

田口 景介
2001/12/01

 最後に筆者が試作した肉食動物を紹介しておこう。

  1: using System;
  2: using System.Drawing;
  3: using System.Collections;
  4: using System.Reflection;
  5: using System.IO;
  6:
  7: [assembly: OrganismClass("KSK")]
  8: [assembly: AuthorInformation("keisuke", "mail@tmepuri.org")]
  9: // 動物のサイズは小さめに
 10: [MatureSize(30)]
 11: // trueならば肉食動物
 12: [CarnivoreAttribute(true)]
 13:
 14: // 動物の外見はサソリを選択
 15: [AnimalSkin(AnimalSkinFamilyEnum.Scorpion)]
 16: // 全体図に表示される色(未実装?)
 17: [MarkingColor(KnownColor.Yellow)]
 18:
 19: // 移動距離が長くなることを想定して体力は多めに
 20: [MaximumEnergyPoints(10)]
 21: // 襲われる可能性は無視してゆっくり食べる
 22: [EatingSpeedPoints(0)]
 23: // 手早くしとめないと逃げられることがあるので
 24: // 攻撃力は高めに
 25: [AttackDamagePoints(40)]
 26: // 防御力0。やられる前にやれ
 27: [DefendDamagePoints(0)]
 28: // フィールドが混み合っていれば、簡単に獲物を追い
 29: // つめられるので、移動速度は低めに
 30: [MaximumSpeedPoints(20)]
 31: // 偽装能力0。丸見え
 32: [CamouflagePoints(0)]
 33: // 視界が狭いとあてずっぽうで獲物を探さなければ
 34: // ならないので、少し多めに
 35: [EyesightPoints(30)]
 36:
 37: // 草食動物と肉食動物はどちらもAnimalクラスの
 38: // サブクラスとして実装する
 39: public class KSK : Animal {
 40:   AnimalState targetAnimal = null;
 41:   int wanderingCount = 0;
 42:   Vector wanderingVector = null;
 43:
 44:   //イベントハンドラの登録
 45:   protected override void Initialize() {
 46:     Idle += new IdleEventHandler(IdleEvent);
 47:     MoveCompleted += new
 48:       MoveCompletedEventHandler(MoveCompletedEvent);
 49:   }
 50:
 51:   // Idleイベントハンドラ。定期的に呼び出される
 52:   void IdleEvent(object sender, IdleEventArgs e) {
 53:     try {
 54:       // 可能ならば出産する。出産する条件は成人して
 55:       // いること、体力が十分あること、以前出産して
 56:       // から十分時間が経過していること
 57:       if (CanReproduce) {
 58:         BeginReproduction(null);
 59:         WriteTrace("Reproduction");
 60:       }
 61:       // 行動中ならば何もしない
 62:       if (IsAttacking || IsEating || IsMoving
 63:             || State.EnergyState > EnergyState.Normal) {
 64:         return;
 65:       }
 66:       // 獲物がいなければ探す。すでにターゲットを見つ
 67:       // けていれば、最新の情報(座標など)を取得する
 68:       if (targetAnimal == null) {
 69:         targetAnimal
 70:           = FindNewTarget(false, double.MaxValue);
 71:       } else {
 72:         targetAnimal
 73:           = (AnimalState) LookFor(targetAnimal);
 74:       }
 75:       // ターゲットがいれば攻撃するか、食べるか、隣接する
 76:       // まで移動する。いなければ、ランダムに移動する
 77:       if (targetAnimal != null ) {
 78:         if (!AttackOrEat()) {
 79:           if (!IsMoving) MoveToTarget();
 80:         }
 81:       } else {
 82:         if (!IsMoving) BeginWandering();
 83:       }
 84:     } catch(Exception ex) {
 85:       WriteTrace(ex.ToString());
 86:     }
 87:   }
 88:
 89:   // ターゲットに関する最新の情報を取得する
 90:   void RefreshTarget() {
 91:     if ( targetAnimal == null ) return;
 92:     targetAnimal = (AnimalState) LookFor(targetAnimal);
 93:   }
 94:
 95:   // 視界内でもっとも近くにいる獲物をターゲットにする
 96:   AnimalState FindNewTarget(bool mySpecies, double min) {
 97:     ArrayList foundAnimals = Scan();
 98:
 99:     double minDistance = min;
100:     AnimalState possibleTarget = null;
101:     int count = 0;
102:
103:     if (foundAnimals.Count > 0) {
104:       foreach (OrganismState organismState in foundAnimals) {
105:         if (organismState is AnimalState &&
106:             IsMySpecies(organismState) == mySpecies) {
107:           count++;
108:           if (DistanceTo(organismState) < minDistance) {
109:             possibleTarget = (AnimalState) organismState;
110:             minDistance = DistanceTo(organismState);
111:           }
112:         }
113:       }
114:     }
115:     // トレースウィンドウにデバッグメッセージを出力する
116:     WriteTrace("FindNewTarget: " + count.ToString());
117:     return possibleTarget;
118:   }
119:
120:   // 指定した動物のそばへ移動する
121:   void MoveToTarget() {
122:     if ( targetAnimal == null ) {
123:         return;
124:     }
125:     // 残り体力を考慮して移動速度を決める
126:     int possibleSpeed = Species.MaximumSpeed;
127:     while(possibleSpeed > 2 &&
128:       State.EnergyRequiredToMove(possibleSpeed, possibleSpeed)
129:         > (State.StoredEnergy / 2)) {
130:         possibleSpeed /= 2;
131:     }
132:     if (possibleSpeed < 2)
133:       possibleSpeed = 2;
134:
135:     BeginMoving(new
136:       MovementVector(targetAnimal.Position, possibleSpeed));
137:   }
138:
139:   // MoveCompletedイベント。移動終了すると呼び出される。
140:   void MoveCompletedEvent(object sender, MoveCompletedEventArgs e) {
141:     WriteTrace("Move Completed");
142:
143:     try {
144:       RefreshTarget();
145:
146:       // 障害物にあたって止まったのか、目的地に到着したのか
147:       if (e.Reason == ReasonForStop.Blocked) {
148:         if (targetAnimal != null) {
149:           if (!AttackOrEat()) {
150:             MoveToAvoid(e);
151:           }
152:         } else {
153:           wanderingCount = 0;
154:           BeginWandering();
155:         }
156:       } else {
157:         if (targetAnimal != null) {
158:           if (!AttackOrEat()) {
159:             MoveToTarget();
160:           }
161:         } else {
162:           targetAnimal = FindNewTarget(false, double.MaxValue);
163:           if (targetAnimal == null) {
164:             BeginWandering();
165:           } else {
166:             MoveToTarget();
167:           }
168:         }
169:       }
170:     }
171:     catch (Exception ex) {
172:       WriteTrace(ex.ToString());
173:     }
174:   }
175:
176:   // 近くに獲物がいないときは、ランダムに移動する
177:   void BeginWandering() {
178:     if (wanderingCount < 1 || wanderingVector == null) {
179:       WriteTrace("New Direction");
180:       wanderingCount = 10;
181:       wanderingVector = new Vector(
182:         20 * (OrganismRandom.Next(0, 3) - 1),
183:         20 * (OrganismRandom.Next(0, 3) - 1));
184:     } else {
185:       wanderingCount--;
186:     }
187:     Point newDest = Vector.Add(Position, wanderingVector);
188:     BeginMoving(new MovementVector(newDest, 3));
189:   }
190:
191:   // 近くに獲物がいれば、生きていれば攻撃し、
192:   // 死んでいれば食べる
193:   bool AttackOrEat() {
194:     bool action = false;
195:     if (!IsEating && !IsAttacking ) {
196:       if (targetAnimal.IsAlive
197:             && WithinAttackingRange(targetAnimal)) {
198:         action = true;
199:         if (State.EnergyState < EnergyState.Normal) {
200:           WriteTrace("Attack");
201:           BeginAttacking(targetAnimal);
202:         }
203:       } else if (!targetAnimal.IsAlive
204:                     && WithinEatingRange(targetAnimal)
205:                     && State.EnergyState < EnergyState.Full) {
206:         WriteTrace("Eat");
207:         BeginEating(targetAnimal);
208:         action = true;
209:       }
210:     }
211:     return action;
212:   }
213:
214:   // 障害物を回避するアルゴリズム。いろいろ試したあげく
215:   // ランダムに移動して距離を置くシンプルな解法で様子見
216:   void MoveToAvoid(MoveCompletedEventArgs e) {
217:     try {
218:       WriteTrace("Avoid");
219:       targetAnimal = null;
220:       wanderingCount = 2;
221:       BeginWandering();
222:     }
223:     catch (Exception ex) {
224:       WriteTrace(ex.ToString());
225:     }
226:   }
227:
228:   // Terrariumを保存、再開するときに呼び出されるメソッド
229:   // 保存すべき状態情報がなければ、空でかまわない
230:   public override void SerializeAnimal(MemoryStream m) {
231:   }
232:
233:   public override void DeserializeAnimal(MemoryStream m) {
234:   }
235: }
筆者が試作した肉食動物のサンプル・プログラム

 この肉食動物は次の行動パターンに従って活動するようになっている。

・Idleイベント
  可能ならば出産
  ターゲットに向けて移動中
    可能ならば攻撃または捕食
    ターゲットに隣接していなければ継続して移動
  ターゲットを定めていない
    索敵、見つからなければランダムに移動(新規方向または継続)

・MoveCompletedイベント
  何かにぶつかって移動終了
    ターゲットに向けて移動中
      可能ならば攻撃、または捕食
      障害物ならば迂回
    ターゲットを定めていない
      ランダムに移動(新規方向)
  目的地に到着
    ターゲットに向けて移動中
      可能ならば攻撃、または捕食
      隣接していなければ(逃げられたら)、継続して移動
    ターゲットを定めていない
      索敵、見つからなければランダムに移動(新規方向または継続)
肉食動物の行動パターン

 ほとんど獲物を探して食べるだけの頭の悪い動物だが、それすらも苦労するのが肉食動物の宿命だ。植物を枯らさないように1カ所で食べ続ければ基本的には問題ない草食動物に比べて、肉食動物は次から次へと獲物を探して動き回らなければならないので、体力の消耗が激しくなりがちだ。体力を消耗すれば餓死するばかりか、子供を産むこともできなくなるため、種族全体の危機を招いてしまう。Terrariumの動物はすべて単性生殖が可能ではあるが、それでも肉食動物が一生に2回出産するのは至難の業といえそうだ。できのいい動物ができたら、ぜひEcoSystemへチャレンジしてみてほしい。End of Article

 

 INDEX
  [連載]世界のWebサービス―― 究極のWebサービスを求めて ――
  第10回 マルチプレイヤー・ネットワーク・ゲームTerrarium
     1.EcoSystemモード(マルチ・プレイヤー・モード)
     2.生存競争に勝ち抜くには
   3.サンプル肉食動物

更新履歴
【2002/04/23】 Terrariumおよび.NET Frameworkの正式版リリースに伴い、記事中のプログラムを一部修正しました。

「世界のWebサービス」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間