///////////////////////////////////////////////////////////////////////////////
// //
// GreenWolf_07d by Tommy Ringstad (ringstad@msn.com) //
// //
// This code, with the exception of this paragraph, is exactly as it was //
// released. It still has several issues, most of which are documented. //
// Any feedback, constructive criticism, or ideas will be greatly //
// appreciated and well recieved. //
// //
// And for the record, I am not a programmer, so take it easy on me. //
// //
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Drawing;
using System.Collections;
using System.IO;
// Every organism assembly must have all the attributes below defined on it.
[assembly: AuthorInformation("Tommy Ringstad", "ringstad@msn.com")] // Author's credentials.
[assembly: OrganismClass("GreenWolf_07d")] // This organism's name.
[CarnivoreAttribute(true)] // I'm a cold-blooded killing (and eating) machine.
[MatureSize(32)] // 32 is the optimal size for larger creatures / cellradius.
// Point Based Attributes... 100 points to distribute
[MaximumEnergyPoints(10)] // Need to survive between meals, but keep low to grow and reproduce fast.
[EatingSpeedPoints(7)] // Bigger numbers means bigger bites, to get our energy over 80%.
[AttackDamagePoints(28)] // In case it doesn't want to be eaten.
[DefendDamagePoints(0)] // In case it wants to eat us.
[MaximumSpeedPoints(15)] // Have to be fast if you want to eat.
[CamouflagePoints(0)] // Hide? Nevah! (besides, it's easily defeated)
[EyesightPoints(40)] // Need to see dinner before dinner sees us, or it won't be dinner.
// The following attributes are optional.
[AnimalSkin(AnimalSkinFamilyEnum.Spider, "Wolf")] // Hey, somebody make me a cool Wolf skin.
[MarkingColor(KnownColor.Green)] // To set apart from same-skinned organisms.
// Organism Classes must be serializable
public class GreenWolf_07d : Animal {
ArrayList creatures = new ArrayList(); // A list of all creatures within view.
ArrayList animals = new ArrayList(); // A list of animals within view.
ArrayList others = new ArrayList(); // A list of everything not an animal.
ArrayList live = new ArrayList(); // A list of live animals.
ArrayList dead = new ArrayList(); // A list of dead animals.
ArrayList threats = new ArrayList(); // A list of stronger animals that might attack us.
AnimalState predator = null; // Current potential enemy.
AnimalState prey = null; // Current potential dinner.
Point destination = new Point(); // Where we ultimately want to get to.
ArrayList wayPoints = new ArrayList(); // Array of waypoints to destination.
int afraid = 0; // Gotta know when to hold em, know when to fold em.
// Unused hooks are left commented so that I can refer to
// the proper order of events in the game.
protected override void Initialize() {
Load += new LoadEventHandler(LoadEvent);
MoveCompleted += new MoveCompletedEventHandler(MoveCompletedEvent);
// AttackCompleted += new AttackCompletedEventHandler(AttackCompletedEvent);
// EatCompleted += new EatCompletedEventHandler(EatCompletedEvent);
Teleported += new TeleportedEventHandler(TeleportedEvent);
// ReproduceCompleted += new ReproduceCompletedEventHandler(ReproduceCompletedEvent);
// Born += new BornEventHandler(BornEvent);
// DefendCompleted += new DefendCompletedEventHandler(DefendCompletedEvent);
// Attacked += new AttackedEventHandler(AttackedEvent);
Idle += new IdleEventHandler(IdleEvent);
}
// First event for an organism. Called every turn.
void LoadEvent(object sender, LoadEventArgs e) {
// Comedy Gold - written backwards (bottom to top)
this.WriteTrace("----------------------------------");
this.WriteTrace("- ");
this.WriteTrace("- Contact : ringstad@msn.com ");
this.WriteTrace("- ");
this.WriteTrace("- This Space for Rent! (Cheap!) ");
this.WriteTrace("- ");
this.WriteTrace("----------------------------------");
return;
}
// Fires whenever we arrived at our destination or get blocked.
void MoveCompletedEvent(object sender, MoveCompletedEventArgs e) {
// Determine why we stopped.
if(e.Reason == ReasonForStop.Blocked) {
// Determine what exactly is in our way.
if(e.BlockingOrganism is AnimalState) {
// It's a living animal, let's see if it's edible
AnimalState blockingAnimal = (AnimalState) e.BlockingOrganism;
if(blockingAnimal.IsAlive) {
if(IsMySpecies(e.BlockingOrganism)) {
// If we get blocked by one of our own, we'll try to get around him.
if (FindPath(this.Position, destination, 3)) {
BeginMoving(new MovementVector((Point) wayPoints[0], BestSpeed()));
} else {
RandomizeDestination();
BeginMoving(new MovementVector(destination, BestSpeed()));
}
} else {
// This should only happen if we have targeted a corpse
// on the other side of a living creature, which we either
// did not see, or did not consider to be an obstacle or a
// threat. So we'll just have to deal with it.
BeginDefending(blockingAnimal);
BeginAttacking(blockingAnimal);
}
}
// Theoretically, this shouldn't happen, but we have to account for it.
// We ran into a plant (or something else neither animal nor plant).
} else {
// Seems to be some sort of problem here, occasionally throws Exception
// errors for destination being null, and speed being greater than 0, which
// should not be possible.. needs more research. I hate using try/catch,
// but it's the only interim solution
try {
if (FindPath(this.Position, destination, 3)) {
BeginMoving(new MovementVector((Point) wayPoints[0], BestSpeed()));
} else {
RandomizeDestination();
BeginMoving(new MovementVector(destination, BestSpeed()));
}
} catch (Exception ex) {
// Dunno what to do really. Hope it figures something out in Idle.
this.WriteTrace(ex);
}
}
// If we've stopped of our own accord, we shouldn't need to do anything at all
// since we'll just re-evaluate anyhow, but there are a few rare cases that we
// need to deal with this, so we do it anyhow.
} else {
// See if we've reached our final destination, or just a waypoint.
if (wayPoints.Count > 1) {
// Update our list of waypoints, and begin moving to the next on the list.
wayPoints.RemoveAt(0);
// Seems to be some sort of problem here, occasionally throws Exception
// errors for destination being null, and speed being greater than 0, which
// should not be possible.. needs more research. I hate using try/catch,
// but it's the only interim solution
try {
BeginMoving(new MovementVector((Point) wayPoints[0], BestSpeed()));
} catch (Exception ex) {
// Dunno what to do really. Hope it figures something out in Idle.
this.WriteTrace(ex);
}
// We've reached our final destination.
} else {
wayPoints.Clear();
}
}
return;
}
// Fires whenever we get teleported.
void TeleportedEvent(object sender, TeleportedEventArgs e) {
// We need to reset some variables to prevent confusion.
wayPoints.Clear();
return;
}
// Last event for an organism. Called every turn.
void IdleEvent(object sender, IdleEventArgs e) {
// Reproduce as often as possible.
if (CanReproduce) {
// We don't pass any information to children... yet.
BeginReproduction(null);
}
// See what's around us.
ScanArea();
// Survey the area for potential threats before we do anything more.
if (CheckForPredators()) {
// Turn on the adrenaline.
afraid = 2;
// Fight like a trapped rat if we're not getting away (hell, even if we are).
BeginDefending(predator);
BeginAttacking(predator);
// What I do if threats are present. FindPath(escapeRoute) basically
// The CheckForPredators function should select the largest threat and assign
// it's AnimalState to predator, create an array of potential threats, and set
// our destination to the best escape point... in theory.
// Temporary instructions.
Vector fleeVector = Vector.Subtract(predator.Position, Position);
fleeVector = fleeVector.GetUnitVector();
fleeVector = fleeVector.Scale(100);
int fleeSpeed = (int) (BestSpeed() * 2) < Species.MaximumSpeed ?
(int) (BestSpeed() * 2) :
Species.MaximumSpeed;
destination = Vector.Add(Position, fleeVector);
// Seems there may be some sort of problem with attempting to run
// out of the world bounds here, but if that were the case, FindPath
// should be returning false. Needs more examination.
if (FindPath(this.Position, destination, 3)) {
BeginMoving(new MovementVector((Point) wayPoints[0], BestSpeed()));
} else {
RandomizeDestination();
BeginMoving(new MovementVector(destination, BestSpeed()));
}
// If we were running away the last turn, and we no longer see any threats,
// we will continue to run this turn, just to be safe.
} else if (afraid > 0) {
afraid -= 1;
// We'll see if we can get some dinner
} else if (CheckForPrey()) {
if (!prey.IsAlive) {
if (CanEat) {
if (WithinEatingRange(prey)) {
if (IsMoving) {
StopMoving();
}
BeginEating(prey);
} else {
destination = prey.Position;
if (FindPath(this.Position, destination, 3)) {
BeginMoving(new MovementVector((Point) wayPoints[0], BestSpeed()));
} else {
RandomizeDestination();
BeginMoving(new MovementVector(destination, BestSpeed()));
}
}
} else {
// We're full and have targeted a corpse. Need to think of something
// more productive to do.
}
} else {
BeginDefending(prey);
BeginAttacking(prey);
if (!WithinAttackingRange(prey)) {
destination = prey.Position;
if (FindPath(this.Position, destination, 3)) {
BeginMoving(new MovementVector((Point) wayPoints[0], BestSpeed()));
} else {
RandomizeDestination();
BeginMoving(new MovementVector(destination, BestSpeed()));
}
}
}
} else {
// There is nothing around here, so we'll go look for something to eat.
if (!IsMoving) {
RandomizeDestination();
if (FindPath(this.Position, destination, 3)) {
BeginMoving(new MovementVector((Point) wayPoints[0], BestSpeed()));
} else {
RandomizeDestination();
BeginMoving(new MovementVector(destination, BestSpeed()));
}
}
}
return;
}
///////////////////////////////////////////////////////////////////////////////
// //
// Non Pathfinding Stuff //
// //
///////////////////////////////////////////////////////////////////////////////
// When we have no target in site, we just want to stroll along looking for one.
void RandomizeDestination() {
destination.X = OrganismRandom.Next(10, WorldWidth - 11);
destination.Y = OrganismRandom.Next(10, WorldHeight - 11);
if (destination == this.Position) {
RandomizeDestination();
}
}
// Select the Best Speed for the situation. Needs MUCH more fixing.
// Preferrably using "double this.State.EnergyRequiredToMove(distance, speed)"
// to select a truly optimal speed based on energy remaining, energy cost, and
// situation at hand. For now,we decide based only on EnergyState, and
// whether or not we have a purpose.
int BestSpeed() {
int bestSpeed = 4;
int goodSpeed1 = 4;
int goodSpeed2 = 4;
switch(State.EnergyState) {
case EnergyState.Full:
goodSpeed1 = Convert.ToInt32(Species.MaximumSpeed * .80);
break;
case EnergyState.Normal:
goodSpeed1 = Convert.ToInt32(Species.MaximumSpeed * .60);
break;
case EnergyState.Hungry:
goodSpeed1 = Convert.ToInt32(Species.MaximumSpeed * .40);
break;
default:
goodSpeed1 = Convert.ToInt32(Species.MaximumSpeed * .20);
break;
}
if (prey == null && threats.Count <= 0) {
bestSpeed = 4;
} else if (threats.Count > 0) {
goodSpeed1 = (int) (goodSpeed1 * 1.5) > Species.MaximumSpeed ?
Species.MaximumSpeed : (int) (goodSpeed1 * 1.5);
goodSpeed2 = (predator.Speed + 3) > Species.MaximumSpeed ?
Species.MaximumSpeed : (predator.Speed + 3);
bestSpeed = goodSpeed1 > goodSpeed2 ? goodSpeed1 : goodSpeed2;
} else if (prey.AnimalSpecies.IsCarnivore) {
goodSpeed2 = (prey.Speed + 3) > Species.MaximumSpeed ?
Species.MaximumSpeed : (prey.Speed + 3);
bestSpeed = goodSpeed1 > goodSpeed2 ? goodSpeed1 : goodSpeed2;
} else {
bestSpeed = goodSpeed1;
}
return bestSpeed;
}
// Scan the area, and sort organisms found into various groups.
void ScanArea() {
// Clear our arrays so we can rebuild them.
animals.Clear();
others.Clear();
live.Clear();
dead.Clear();
// Scan our surroundings for all creatures
creatures = Scan();
// Split list of organisms into animals and "others" (others means plants
// really, but who knows if anything goofy will be added in the future).
foreach(OrganismState orgState in creatures) {
if (orgState is AnimalState) {
animals.Add(orgState);
} else {
others.Add(orgState);
}
}
// Split list of animals into live and dead.
foreach(AnimalState animal in animals) {
if (animal.IsAlive) {
live.Add(animal);
} else {
dead.Add(animal);
}
}
return;
}
// Looks for potential threats, decides which way is necessary to escape,
// and defends against closest predator (or closest living animal if no
// predators are present.
bool CheckForPredators() {
int myDamage = 0;
int myDefense = 0;
int myHealth = 0;
int myBlows = 0;
int predDamage = 0;
int predDefense = 0;
int predHealth = 0;
int predBlows = 0;
double minDistance = double.MaxValue;
bool returnVal = false;
// Clear out threats array and predators so we can rebuild them.
threats.Clear();
predator = null;
// Determine if any of the living creatures is a threat to us.
foreach(AnimalState animal in live) {
if (!IsMySpecies(animal)) {
// Our decision process for threats.
if (animal.AnimalSpecies.IsCarnivore) {
myDamage = (Species.MaximumAttackDamagePerUnitRadius * State.Radius);
myDefense = (Species.MaximumDefendDamagePerUnitRadius * State.Radius);
myHealth = ((EngineSettings.DamageToKillPerUnitOfRadius * State.Radius) - State.Damage);
predDamage = (animal.AnimalSpecies.MaximumAttackDamagePerUnitRadius * animal.Radius);
predDefense = (animal.AnimalSpecies.MaximumDefendDamagePerUnitRadius * animal.Radius);
predHealth = ((EngineSettings.DamageToKillPerUnitOfRadius * animal.Radius) - animal.Damage);
// The average number of blows to kill this opponent.
myBlows = (myDamage > predDefense) ? (predHealth / (myDamage - predDefense)) : int.MaxValue;
predBlows = (predDamage > myDefense) ? (myHealth / (predDamage - myDefense)) : int.MaxValue;
if (predBlows <= myBlows) {
threats.Add(animal);
if (DistanceTo(animal) < minDistance) {
predator = animal;
minDistance = DistanceTo(animal);
}
returnVal = true;
}
}
}
}
return returnVal;
}
// Looks for closest target animal, corpses first, then living.
bool CheckForPrey() {
double minDistance = double.MaxValue;
bool returnVal = false;
prey = null;
// We chase other carnivores first, then if we have any corpses available, target
// them instead of fighting, otherwise, go for the nearest living creature.
if (live.Count > 0) {
foreach(AnimalState animal in live) {
if (animal.AnimalSpecies.IsCarnivore && !IsMySpecies(animal) && (DistanceTo(animal) < minDistance)) {
prey = animal;
minDistance = DistanceTo(animal);
returnVal = true;
}
}
}
if (!returnVal && (dead.Count > 0 || live.Count > 0)) {
minDistance = double.MaxValue;
foreach(AnimalState corpse in dead) {
if (DistanceTo(corpse) < minDistance) {
prey = corpse;
minDistance = DistanceTo(corpse);
returnVal = true;
}
}
// }
// if (!returnVal && live.Count > 0) {
foreach(AnimalState animal in live) {
if (!IsMySpecies(animal) && (DistanceTo(animal) < minDistance)) {
prey = animal;
minDistance = DistanceTo(animal);
returnVal = true;
}
}
}
return returnVal;
}
///////////////////////////////////////////////////////////////////////////////
// //
// Pathfinding Stuff //
// //
// Because the world is split into "cells" of 8x8 pixels each it is easier //
// (and more accurate) to calculate movement in terms of the world's //
// "grid", which we'll call "gridspace". However, to properly calculate //
// what "cells" we move into, we need to draw a line through "pixelspace" //
// to get the "cells" that we could potentially occupy along a path. //
// //
///////////////////////////////////////////////////////////////////////////////
// Attempts to find the shortest path between two points.
// If a path is found, ArrayList will be returned, containing at
// most 'maxDepth' waypoints between 'from' and 'to'.
bool FindPath(Point pixFrom, Point pixTo, int maxDepth) {
Vector pixVector = new Vector((pixTo.X - pixFrom.X), (pixTo.Y - pixFrom.Y));
ArrayList obstacles = ScanForObstacles();
// Clear our list of waypoints. This won't affect our recursion, since we insert
// waypoints on the way out of the loop.
wayPoints.Clear();
// Recursively check and branch paths, decreasing maxDepth with each recursion.
// First, check the straight vector.
if (VectorIsClear(pixFrom, pixVector, obstacles)) {
// If vector is clear, add goal to beggining of wayPoints, and return true.
wayPoints.Insert(0, pixTo);
return true;
// If straight vector is blocked, check recursion depth.
} else if (maxDepth > 0) {
// Because we are dealing with squares on a grid, and the straight path was blocked,
// we snap the vector for checking to the nearest cardinal of PI/8.
double snapTheta = ((int) ((pixVector.Direction + (Math.PI / 16)) / (Math.PI / 8))) * (Math.PI / 8);
Vector snapVector = new Vector(Math.Cos(snapTheta), Math.Sin(snapTheta));
double angleIncr = 0.0f;
// scale snapped waypoint
snapVector = snapVector.GetUnitVector();
snapVector = snapVector.Scale(40.0);
// We check our snapped angle first, to avoid double checking it in the loop below
if (VectorIsClear(pixFrom, snapVector, obstacles)) {
// recurse and test from that point to the goal.
// if child succeeds, add point to beginning of list and return true.
Point nextPoint = new Point((pixFrom.X + snapVector.Point.X), (pixFrom.Y + snapVector.Point.Y));
if (FindPath(nextPoint, pixTo, maxDepth - 1)) {
wayPoints.Insert(0, nextPoint);
return true;
}
}
// For each plausible angle of rotation:
for (int i = 0; i < 6; i++) {
// Increment the angle difference to check in each direction
angleIncr += (Math.PI / 8);
// rotate and scale counterclockwise waypoint
Vector ccwVector = snapVector.Rotate(angleIncr);
ccwVector = ccwVector.GetUnitVector();
ccwVector = ccwVector.Scale(40.0); // There has to be a better way to scale.
// rotate and scale clockwise waypoint
Vector cwVector = snapVector.Rotate(-angleIncr);
cwVector = cwVector.GetUnitVector();
cwVector = cwVector.Scale(40.0); // There has to be a better way to scale.
// Because we always check CCW vector first, we'll have a preference for left
// turns, but it shouldn't be too noticable.
if (VectorIsClear(pixFrom, ccwVector, obstacles)) {
// recurse and test from that point to the goal.
// if child succeeds, add point to beginning of list and return true.
Point nextPoint = new Point((pixFrom.X + ccwVector.Point.X), (pixFrom.Y + ccwVector.Point.Y));
if (FindPath(nextPoint, pixTo, maxDepth - 1)) {
wayPoints.Insert(0, nextPoint);
return true;
}
// If CCW vector doesn't work out, we'll check the CW vector.
} else if (VectorIsClear(pixFrom, cwVector, obstacles)) {
// recurse and test from that point to the goal.
// if child succeeds, add point to beginning of list and return true.
Point nextPoint = new Point((pixFrom.X + cwVector.Point.X), (pixFrom.Y + cwVector.Point.Y));
if (FindPath(nextPoint, pixTo, maxDepth - 1)) {
wayPoints.Insert(0, nextPoint);
return true;
}
}
}
}
return false;
}
// Determine if there are any obstacles along a specified vector.
bool VectorIsClear(Point pixFrom, Vector pixVector, ArrayList obstacles) {
bool returnVal = true;
Rectangle vectorBox = new Rectangle();
// Convert our pixel coords to grid form for checking
Point pixTo = new Point((pixFrom.X + pixVector.Point.X), (pixFrom.Y + pixVector.Point.Y));
Point gridFrom = PixToGrid(pixFrom);
Point gridTo = PixToGrid(pixTo);
// If our vector takes us off of the worldmap, discard it outright.
if (pixTo.X < 0 || pixTo.Y < 0 || pixTo.X >= WorldWidth || pixTo.Y >= WorldHeight) {
returnVal = false;
} else {
// Calculate the coordinates for the vector's bounding box in "gridspace".
vectorBox.X = gridFrom.X < gridTo.X ?
gridFrom.X - State.CellRadius :
gridTo.X - State.CellRadius;
vectorBox.Y = gridFrom.Y < gridTo.Y ?
gridFrom.Y - State.CellRadius :
gridTo.Y - State.CellRadius;
vectorBox.Width = Math.Abs(gridFrom.X - gridTo.X) + (State.CellRadius * 2);
vectorBox.Height = Math.Abs(gridFrom.Y - gridTo.Y) + (State.CellRadius * 2);
// We might be able to discard some plants to make this more
// efficient, but for now we'll check them all.
foreach(OrganismState orgState in obstacles) {
// Get the bounding box for this obstacle in gridspace
Rectangle obstacleBox = GetGridBounds(orgState.Position, orgState.CellRadius);
// Check if the obstacle falls within the vector bounding box,
// if it doesn't, there is no point in checking if it's along our vector.
if (CheckBoxInBoundingBox(obstacleBox, vectorBox)) {
// Check if the obstacle falls along the line we intend to move along.
returnVal = !CheckBoxOnLine(pixFrom, pixTo, obstacleBox);
// If we ran into something, this vector is no good, so break and return false.
if (!returnVal) {
break;
}
}
}
}
return returnVal;
}
// Uses a slightly modified bresenham algorithm to draw a line in pixels from
// one point to another. Anytime that line enters a new "cell" we check the
// bounding box for our potential position against that of the obstacle's.
bool CheckBoxOnLine(Point pixFrom, Point pixTo, Rectangle obstacleBox) {
int dx = (pixTo.X - pixFrom.X); // change in X over the span
int dy = (pixTo.Y - pixFrom.Y); // change in Y over the span
int d; // decision variable
int incrE; // increment of d for East
int incrNE; // increment of d for NE
int x = pixFrom.X; // current "x" coordinate
int y = pixFrom.Y; // current "y" coordinate
int incrX, incrY; // Increment values for x and y
bool flipXY = false; // Flags inversion of x and y for certian cases
int tmp; // Used for swapping variables
Point newCell = new Point(0,0);
Point lastCell = new Point(0,0);
// Horizontal, vertical and 45-degree angles are drawn seperately,
// because they have integer slopes already.
if (Math.Abs(dx) == Math.Abs(dy) || dx == 0 || dy == 0 ) {
if (pixTo.X > pixFrom.X) { // Left, vertical, or right?
incrX = 1;
} else if (pixTo.X == pixFrom.X) {
incrX = 0;
} else {
incrX = -1;
}
if (pixTo.Y > pixFrom.Y) { // Up, horizontal, or down?
incrY = 1;
} else if (pixTo.Y == pixFrom.Y) {
incrY = 0;
} else {
incrY = -1;
}
// Step through each pixel until the endpoint.
while ((incrX < 0 && x > pixTo.X) || (incrX > 0 && x < pixTo.X) || (incrY < 0 && y > pixTo.Y) || (incrY > 0 && y < pixTo.Y)) {
x += incrX;
y += incrY;
// Determine what "cell" this pixel falls in.
newCell = PixToGrid(new Point(x, y));
// Determine if this "cell" is different than the last.
if (newCell != lastCell) {
// Check our bounding box at this point with that of the obstacle.
if (CheckBoxInBoundingBox(GetGridBounds(new Point(x, y), this.State.CellRadius), obstacleBox)) {
return true;
}
// Assign newCell to lastCell to compare against in the next check.
lastCell = newCell;
}
}
}
// For all other lines, we use the following.
else {
// If Math.Abs(slope) > 1 then we flip x & y for these "steep" lines.
if (Math.Abs(dx) < Math.Abs(dy)) {
// Swap "from" X & Y values.
tmp = pixFrom.X;
pixFrom.X = pixFrom.Y;
pixFrom.Y = tmp;
// Swap "to" X & Y values.
tmp = pixTo.X;
pixTo.X = pixTo.Y;
pixTo.Y = tmp;
// We flag the change, so we can unswap X & Y for drawing the actual pixels.
flipXY = true;
}
// Initialize variables.
dx = Math.Abs(pixTo.X - pixFrom.X); // Distance over the span.
dy = Math.Abs(pixTo.Y - pixFrom.Y); // Distance over the span.
d = 2 * dy - dx; // Initial value of decision variable.
x = pixFrom.X;
y = pixFrom.Y;
incrE = 2 * Math.Abs(dy);
incrNE = 2 * (Math.Abs(dy) - Math.Abs(dx));
if (pixTo.X > pixFrom.X) { // Drawing left or right?
incrX = 1;
}
else {
incrX = -1;
}
if (pixTo.Y > pixFrom.Y) { // Drawing up or down?
incrY = 1;
}
else {
incrY = -1;
}
// The main drawing loop.
// Because of the x/y reversal for "steep" lines, x is incremented
// by 1 (or -1) at every step. Thus, when x reaches the final coordinate,
// the line has been completely drawn.
while ((incrX < 0 && x > pixTo.X) || (incrX > 0 && x < pixTo.X)) {
// Choose the "East" point if the line is below the midpoint.
if (d < 0) {
d += incrE;
x += incrX;
}
// Handle exact midpoint decisions, as mentioned in CGPP, page 78.
else if (d == 0) {
if (incrX > 0) { // Left to right scan, choose E.
d += incrE;
x += incrX;
}
else { // Right to left scan, choose NE.
d += incrNE; // (actually NW or SW in this case)
x += incrX;
y += incrY;
}
}
else { // Choose the NE point if none of the
d += incrNE; // above cases apply. (ie, line is in
x += incrX; // the "upper" half.
y += incrY;
}
// If we flipped x and y, we must flip them back before drawing
// this pixel on the line.
if (!flipXY) {
// Determine what "cell" this pixel falls in.
newCell = PixToGrid(new Point(x, y));
// Determine if this "cell" is different than the last.
if (newCell != lastCell) {
// Check our bounding box at this point with that of the plant.
if (CheckBoxInBoundingBox(GetGridBounds(new Point(x, y), this.State.CellRadius), obstacleBox)) {
return true;
}
}
// Our X & Y are normal.
} else {
// Determine what "cell" this pixel falls in.
newCell = PixToGrid(new Point(y, x));
// Determine if this "cell" is different than the last.
if (newCell != lastCell) {
// Check our bounding box at this point with that of the plant.
if (CheckBoxInBoundingBox(GetGridBounds(new Point(y, x), this.State.CellRadius), obstacleBox)) {
return true;
}
}
}
// Assign newCell to lastCell to compare against in the next check.
lastCell = newCell;
}
}
return false;
}
// If one bounding box overlaps or shares an edge with a second box, return true.
bool CheckBoxInBoundingBox(Rectangle gridBox1, Rectangle gridBox2) {
bool returnVal = false;
// If any point in the first box falls on the border of,
// or within the second box, then return true.
returnVal |= CheckPointInBoundingBox(
new Point(gridBox1.X, gridBox1.Y), gridBox2);
returnVal |= CheckPointInBoundingBox(
new Point(gridBox1.X, gridBox1.Y + gridBox1.Height), gridBox2);
returnVal |= CheckPointInBoundingBox(
new Point(gridBox1.X + gridBox1.Width, gridBox1.Y), gridBox2);
returnVal |= CheckPointInBoundingBox(
new Point(gridBox1.X + gridBox1.Width, gridBox1.Y + gridBox1.Height), gridBox2);
// Same is true for the reverse
if (!returnVal) {
returnVal |= CheckPointInBoundingBox(
new Point(gridBox2.X, gridBox2.Y), gridBox1);
returnVal |= CheckPointInBoundingBox(
new Point(gridBox2.X, gridBox2.Y + gridBox2.Height), gridBox1);
returnVal |= CheckPointInBoundingBox(
new Point(gridBox2.X + gridBox2.Width, gridBox2.Y), gridBox1);
returnVal |= CheckPointInBoundingBox(
new Point(gridBox2.X + gridBox2.Width, gridBox2.Y + gridBox2.Height), gridBox1);
}
// Check for overlapping boxes (cross shaped)
if (!returnVal) {
returnVal = gridBox1.X >= gridBox2.X &&
gridBox1.X + gridBox1.Width <= gridBox2.X + gridBox2.Width &&
gridBox1.Y <= gridBox2.Y &&
gridBox1.Y + gridBox1.Height >= gridBox2.Y + gridBox2.Height;
}
// Check for overlapping boxes (reversed)
if (!returnVal) {
returnVal = gridBox2.X >= gridBox1.X &&
gridBox2.X + gridBox2.Width <= gridBox1.X + gridBox1.Width &&
gridBox2.Y <= gridBox1.Y &&
gridBox2.Y + gridBox2.Height >= gridBox1.Y + gridBox1.Height;
}
return returnVal;
}
// If a point falls on the border of or within a bounding box, return true.
bool CheckPointInBoundingBox(Point gridPoint, Rectangle gridBox) {
return (
gridPoint.X >= gridBox.X &&
gridPoint.X <= gridBox.X + gridBox.Width &&
gridPoint.Y >= gridBox.Y &&
gridPoint.Y <= gridBox.Y + gridBox.Height
);
}
// Used to get any organism's bounds in "gridspace".
Rectangle GetGridBounds(Point pixPoint, int gridRadius) {
Rectangle gridBounds = new Rectangle(
((pixPoint.X>>3) - gridRadius), ((pixPoint.Y>>3) - gridRadius),
(gridRadius * 2), (gridRadius * 2)
);
return gridBounds;
}
// Convert "pixel" coordinates to "grid" coordinates
Point PixToGrid(Point pixPoint) {
Point gridPoint = new Point((pixPoint.X>>3), (pixPoint.Y>>3));
return gridPoint;
}
// Get a list of anything in our path that isn't a potential meal.
ArrayList ScanForObstacles() {
ArrayList obstacles = new ArrayList();
// If it isn't what I'm trying to get to, it's an obstacle.
foreach(OrganismState orgState in creatures) {
if (prey != orgState) {
obstacles.Add(orgState);
}
}
return obstacles;
}
///////////////////////////////////////////////////////////////////////////////
// //
// Serialization Stuff //
// //
///////////////////////////////////////////////////////////////////////////////
// This gets called whenever an animal is being saved, either because the
// game is closing or because you are being teleported. Store anything you
// want to remember when you are instantiated again in the stream.
public override void SerializeAnimal(MemoryStream m) {
// I seriously need to learn how to use this so that we can save:
// predator
// prey
// destination
// wayPoints
// Although we should be able to obtain all this info again easily, it
// would be nice to have it immediately available. Every turn counts.
return;
}
// This gets called when you are instantiated again after being saved. You
// get a chance to pull out any information that you put into the stream
// when you were saved.
public override void DeserializeAnimal(MemoryStream m) {
// Same as above
return;
}
}