If you think this belongs in code samples rather than tutorials, I disagree. The large code sample below is simply to get you started. There are step by step directions, explanations, and comments along the way, making it a tutorial. This is also my first tutorial, please be merciful when rating it. Thanks!
Abstract
This tutorial summarizes so you don't have to read through many pages before you can start coding. It will go through all the steps required to run Terrarium on your computer and the first steps of Terrarium creature development using C#.
Introduction: What is Terrarium?
Terrarium is a multiplayer game developed by Microsoft using the .NET Framework. It is a simulation of an ecosystem, run concurrently on multiple computers (clients). The three basic components of the ecosystem open to the imagination of software developers are plants, herbivores, and carnivores. The goal of the game is to create a component that would outlive others and have the largest population.
The game was developed to demonstrate the capabilities of the .NET Framework and test it at the same time. Since the code you develop runs on many client machines, there is a potential for malicious actions. The security built into the Terrarium engine and the software license agreement prevent users from these actions.
Installation and Setup
In order to run Terrarium on your computer, follow these steps:
- Install either the or the
- Download and install the
- The installation would place a shortcut to .Net Terrarium in the Start Menu->Programs. Click on the shortcut to start the program.
- Familiarize yourself with the interface. Read the for more information.
When Terrarium starts up, you will be connected to the www.terrariumgame.net server by default. The server keeps statistics for all the introduced plants and animals. You can also run your own Terrarium server, which requires SQL 2000 server (or newer) besides the .NET Framework.
The first thing you will notice is the blue ball roaming about on your screen. This is the teleporter. It picks up whatever creature is in its way and teleports it to a random computer connected to the same Terrarium server. All the computers connected to the server are known as peers, and their number can be seen on the top toolbar of the program.
Your screen will begin populating with various plants and animals. Those plants and animals were developed by other programmers, introduced to the Ecosystem, and sent to your computer by their teleporters.
Prototyping
Although you are not required to have Visual Studio .NET to program the creatures, it would make development much easier. This tutorial assumes most of the readers got their hands on a copy of Visual Studio.
Your assignment is to appropriately distribute essential attribute values and to program the event handlers. There are 7 attributes and 10 event handlers, all of which are shown in the code below. More information (not necessary, but just in case): ,
Let us make a simple carnivore. Please do the following:
- Open Visual Studio .NET
- Click File -> New -> Project
- Click on Visual C# Projects in the Project Types pane
- Select Class Library in the Templates pane
- Type the desired name for your carnivore in the Name field and specify its location.
Visual Studio will display the Class1.cs file. Delete everything in the file, and replace it with the following code (comments should be pretty helpful if you've never created a creature before):
using System;
using System.Drawing;
using System.Collections;
using System.IO;
public class MyCreature : Animal {
Point parentLocation = new Point(-1,-1);
protected override void Initialize() {
huntingSpeed = Species.MaximumSpeed;
cruisingSpeed = (int) (Species.MaximumSpeed * .4);
Born += new BornEventHandler(BornEvent);
Load += new LoadEventHandler(LoadEvent);
Idle += new IdleEventHandler(IdleEvent);
Teleported += new TeleportedEventHandler(TeleportedEvent);
DefendCompleted += new DefendCompletedEventHandler(DefendCompletedEvent);
Attacked += new AttackedEventHandler(AttackedEvent);
MoveCompleted += new MoveCompletedEventHandler(MoveCompletedEvent); }
bool CanMove(OrganismState target, int speed) {
return (State.EnergyRequiredToMove(DistanceTo(target), speed) < State.StoredEnergy);
}
void MoveToTarget(Point location, int speed) {
BeginMoving(new MovementVector(location, speed));
}
void ScanForTargetAnimal() {
ArrayList foundAnimals = Scan();
if (foundAnimals.Count > 0)
foreach (OrganismState organismState in foundAnimals)
if (organismState is AnimalState)
if (!IsMySpecies(organismState)) {
targetAnimal = (AnimalState) organismState;
MoveToTarget(targetAnimal.Position, huntingSpeed);
return;
}
}
void BornEvent(object sender, BornEventArgs e) {
byte[] dna = e.Dna;
if (dna != null) {
MemoryStream m = new MemoryStream(dna);
BinaryReader b = new BinaryReader(m);
parentLocation = new Point(b.ReadInt32(), b.ReadInt32());
b.Close();
MoveToTarget(parentLocation, cruisingSpeed);
}
}
void LoadEvent(object sender, LoadEventArgs e) {
if(targetAnimal != null)
targetAnimal = (AnimalState) LookFor(targetAnimal);
if(targetAnimal == null) {
this.WriteTrace("Target animal disappeared.");
ScanForTargetAnimal();
}
}
void IdleEvent(object sender, IdleEventArgs e) {
if(CanReproduce) {
MemoryStream m = new MemoryStream();
BinaryWriter b = new BinaryWriter(m);
b.Write(State.Position.X);
b.Write(State.Position.Y);
BeginReproduction(m.ToArray());
b.Close();
}
if(IsAttacking || IsDefending || IsEating)
return;
if(IsMoving && targetAnimal != null)
return;
if (CanEat) {
this.WriteTrace("Hungry.");
if (targetAnimal != null) {
if (targetAnimal.IsAlive) {
if (WithinAttackingRange(targetAnimal)) {
this.WriteTrace("Live target within attack range. Attacking.");
BeginAttacking(targetAnimal);
}
else {
this.WriteTrace("Live target out of attack range. Moving closer.");
MoveToTarget(targetAnimal.Position, huntingSpeed);
}
}
else {
if(WithinEatingRange(targetAnimal)) {
this.WriteTrace("Dead target within eating range. Eating.");
if(IsMoving)
StopMoving();
BeginEating(targetAnimal);
}
else {
this.WriteTrace("Dead target out of eathing range. Moving closer.");
MoveToTarget(targetAnimal.Position, huntingSpeed);
}
}
}
else {
if(!IsMoving) {
this.WriteTrace("No target. Start Searching.");
BeginMoving(new MovementVector(new Point(OrganismRandom.Next(0, WorldWidth - 1), OrganismRandom.Next(0, WorldHeight - 1)), cruisingSpeed));
}
else {
this.WriteTrace("No target. Already Searching.");
}
}
}
else {
this.WriteTrace("Full. Lazy.");
if(IsMoving)
StopMoving();
}
}
void TeleportedEvent(object sender, TeleportedEventArgs e) {
targetAnimal = null;
}
void ReproduceCompletedEvent(object sender, ReproduceCompletedEventArgs e) {
}
void EatCompletedEvent(object sender, EatCompletedEventArgs e) {
}
void DefendCompletedEvent(object sender, DefendCompletedEventArgs e) {
}
void AttackedEvent(object sender, AttackedEventArgs e) {
AnimalState theAttacker = e.Attacker;
try {
if (theAttacker.IsAlive) {
this.WriteTrace("We're under attack!");
if (!IsDefending)
BeginDefending(theAttacker);
if (!IsAttacking)
BeginAttacking(theAttacker);
}
}
catch (Exception ex) {
this.WriteTrace("AttackedEvent Exception: " + ex.ToString());
}
}
void AttackCompletedEvent(object sender, AttackCompletedEventArgs e) {
}
void MoveCompletedEvent(object sender, MoveCompletedEventArgs e) {
if (e.Reason == ReasonForStop.Blocked) {
if (e.BlockingOrganism is AnimalState) {
AnimalState blockingAnimal = (AnimalState) e.BlockingOrganism;
if (blockingAnimal.IsAlive) {
if (!IsMySpecies(blockingAnimal)) {
if (WithinAttackingRange(blockingAnimal)) {
this.WriteTrace("Blocking animal within attacking range. Attacking.");
targetAnimal = blockingAnimal;
BeginDefending(targetAnimal);
BeginAttacking(targetAnimal);
}
else {
this.WriteTrace("Blocking animal not in attacking range.");
}
}
else {
this.WriteTrace("MySpecies in the way.");
}
}
else {
this.WriteTrace("Blocking animal is dead. Free food!");
if (WithinEatingRange(blockingAnimal)) {
if ((State.EnergyState < EnergyState.Full)) {
if (IsMoving) StopMoving(); targetAnimal = blockingAnimal; BeginEating(targetAnimal);
}
else {
if (IsMoving) StopMoving();
}
}
else {
MoveToTarget(targetAnimal.Position, cruisingSpeed);
}
}
}
else {
this.WriteTrace("Plant in the way.");
}
}
}
public override void SerializeAnimal(MemoryStream m) { BinaryWriter b = new BinaryWriter(m);
if (targetAnimal != null) {
b.Write(true); b.Write(targetAnimal.ID);
}
else
b.Write(false);
if (parentLocation.X > -1) {
b.Write(true);
b.Write(parentLocation.X);
b.Write(parentLocation.Y);
}
else
b.Write(false);
}
public override void DeserializeAnimal(MemoryStream m) {
BinaryReader b = new BinaryReader(m);
if (((bool) b.ReadBoolean()))
targetAnimal = (AnimalState) RefreshState(b.ReadString());
if (((bool) b.ReadBoolean())) parentLocation = new Point(b.ReadInt32(), b.ReadInt32());
}
}
|
Now you have to actually tell the compiler what classes you just overloaded:
- Click on Project -> Add Reference...
- Select System.Drawing.dll in the .NET tab and click the "Select" button
- Click the "Browse..." button, find your Terrarium installation folder (C:\Program Files\Terrarium by default), browse to the Bin directory and double-click on organismbase.dll
-
OK all the dialogs, click Build -> Build Solution
Hopefully these instructions are clear and you get no compilation errors. Read the next section to learn how to test your carnivore.
First Steps
Now that you have compiled a dll, you can test your animal. To do so, start the Terrarium client, click on the small aquarium icon in the bottom left corner of the window, and click on Create New Terrarium button. Call it whatever you want. You now have your own playground.
You can add creatures to your Terrarium by clicking the Introduce Animal... button. This will display the database of all animals stored on the server you are connected to. Start by adding a few plants (food for herbivores) and a few herbivores (food for your carnivore). Then introduce your creature by clicking the Browse... button on the Reintroduce Species screen, and find the dll you just compiled (bin/Debug in your project directory).
To see the trace messages printed using this.WriteTrace() function above, select an instance (or multiple instances) of your carnivore, and click the Show Trace Window button
The Real Deal
Once you feel confident about your bug, you can introduce it to the Ecosystem:
- Click on the Globe icon, then click Join Ecosystem. This will connect you to the server specified in your preferences. In case you don't know how to change those preferences, right click anywhere on the screen and click Game Settings... menu item, then Server tab
- Click on Introduce Animal..., Browse..., and select your creature's dll
Remember that once you introduce a bug into the Ecosystem, you cannot reintroduce it until all the instances of your animal die off. If you want to "upgrade" it and add it to the Ecosystem, you have to modify the name of your creature like so:
[cs]
[assembly: OrganismClass("MyCreature01")]
...
public class MyCreature01 : Animal {
...
}
[/cs]
The matching keywords above must always be identical.
Conclusion
You should now be on your way to programming the best AI on Terrarium. The sample code above will not produce a smart carnivore, since advanced features like group behavior or even walking around obstacles are not provided. I will leave that to your imagination.
References and Additional Resources
You can find more code samples and full documentation on the Terrarium homepage at . Please feel free to email me if you have questions or suggestions.
There are also active threads on the forums:
Messages > C# > Terrarium
Messages > General Technical > .NET Terrarium Project
Thanks to Joseph for his input and suggestions on writing the tutorial.