Part 2: Attack of the critters!Well we have the most basic framework for our AI going, so let's whip up a critter for our AI to manipulate
namespace Blah
{
class Critter
{
private int currentHP;
private int maxHP;
private List<AIType> aIStack;
public Critter()
{
this.maxHP = 10;
this.currentHP = maxHP;
this.aIStack = new List<AIType>();
}
public int MaxHP
{
get
{
return maxHP;
}
}
public int CurrentHP
{
get
{
return currentHP;
}
set
{
if (value < 0)
{
currentHP = 0;
}
else if (value > MaxHP)
{
currentHP = maxHP;
}
else
{
currentHP = value;
}
}
}
public void Flee()
{
Console.WriteLine("I'm running! IIII'M RUNNING!!!!! WEEEEEEEEEEEEEEEeeeeeeeeeee");
CurrentHP += 1;
}
public void Fight()
{
Console.WriteLine("I'm killing mighty dagons!");
CurrentHP -= 2;
}
}
}
As you can see, it can run, and it can fight. Fighting will deplete it's health, while running will slowly restore it. Now, every Critter has a list of AITypes, but in the same way every AIType needs a critter to manipulate! Let's add that now
namespace Blah
{
abstract class AIType
{
protected Critter critter;
public abstract bool Update();
}
}
You will notice that the critter is protected,
not private. If it were private then subtypes of AIType, like FleeType, wouldn't have access to it, but with the 'protected' keyword AIType can access it's critter, and FleeAI can too, but nothing else, just like nature intended!
While we are here, we should add a constructor to our AIType. Yes, just because it can't be created doesn't mean it can't have a constructor!
namespace Blah
{
abstract class AIType
{
protected Critter critter;
protected AIType(Critter critter)
{
this.critter = critter;
}
public abstract bool Update();
}
}
Once again, protected, as nothing else needs to see it, but out subclasses
must be able to see it. This is because to make an object with a constructor, we need to call it's constructor, and we
are making AITypes, but we call this in a different way to how we normally do.
You remember the 'this' keyword? Well there is a similar one called 'base' that works a lot like 'this', but instead of looking at itself, it will look at its super class, and the first time we are going to use it is in calling it's bases constructor.
namespace Blah
{
class FleeAI : AIType
{
public FleeAI(Critter c)
: base(c)
{
}
public override bool Update()
{
Console.WriteLine("I'm running!!!");
return true;
}
}
}
This time public. We want other things to be able to make this class. Now, you will notice no main body to the constructor. We don't need it to do anything, so leave it empty, it's fine, all the cool kids do it and leaves room to add stuff later on when things get more complex. Now let's beef up out FleeAI!
namespace Blah
{
class FleeAI : AIType
{
public FleeAI(Critter c)
: base(c)
{
}
public override bool Update()
{
if (critter.CurrentHP < critter.MaxHP / 2) //If the critters health is less than half
{
critter.Flee(); //then run away
return true; //and yes, I updated
}
return false; //Otherwise no, nothing happened here
}
}
}
Now when it is called to update, it will figure out if it needs to flee, and then tell the critter if it is done thinking or not. Pretty snappy! How about for diversity, we add another type of AI, called FightAI?
namespace Blah
{
class FightAI : AIType
{
public FightAI(Critter c)
: base(c)
{
}
public override bool Update()
{
critter.Fight();
return true;
}
}
}
As you can see, fighting isn't as smart as fleeing, but that is ok. It will take a low preference on our critter, meaning it will only fight if it has nothing else to do. We now have two fully functional AITypes, but our critter needs a bit of work. It needs to be able to update, and go through each of it's AITypes until one of the does something. It could also use a few instances of AITypes on in it's list.
namespace Blah
{
class Critter
{
private int currentHP;
private int maxHP;
private List<AIType> aIStack;
public Critter()
{
this.maxHP = 10;
this.currentHP = maxHP;
this.aIStack = new List<AIType>();
aIStack.Add(new FleeAI(this)); //adding AI types. It will check through them in the same order it is given them
aIStack.Add(new FightAI(this)); //If fight was added before flee, it would never stop fighting, and die quickly
}
public void Update()
{
foreach (AIType ai in aIStack)
{
if (ai.Update())
break;
}
}
public int MaxHP
{
get
{
return maxHP;
}
}
public int CurrentHP
{
get
{
return currentHP;
}
set
{
if (value < 0)
{
currentHP = 0;
}
else if (value > MaxHP)
{
currentHP = maxHP;
}
else
{
currentHP = value;
}
}
}
public void Flee()
{
Console.WriteLine("I'm running! IIII'M RUNNING!!!!! WEEEEEEEEEEEEEEEeeeeeeeeeee");
CurrentHP += 1;
}
public void Fight()
{
Console.WriteLine("I'm killing mighty dagons!");
CurrentHP -= 2;
}
}
}
There, we now have a critter class that will do as it's AI asks it to, so let's test it with this.
namespace Blah
{
class Program
{
static void Main(string[] args)
{
Critter critter = new Critter();
for (int i = 0; i < 10; i++)
{
critter.Update();
}
Console.ReadKey();
}
}
}
That will make a new critter, and make it run over 10 ticks. Run that and see what happens!
This is all a very simplified version of what will need to be more complex in a working roguelike, but you get the idea
-The critter runs over it's AITypes until one of them says to stop
-The AITypes all inherit a single class, and all override the same method
-In that method is where the thinking is done
-The AITypes get their information from the critter, decide what to do with it, and then command the critter
EDIT: Also, there
is a part three, and with it, a level of power so great that you will feel static sparks come from your finger tips. Tell me if anybody feels good with this so far and wants me to write it up.