Question

I have a ecosystem simulation where animals move and collide with each other. Here is how I handle collisions:

public void HandleCollisionBetween(Animal a, Animal b)
    {
        if (a.GetType().Name == b.GetType().Name) { // same type collision
            bool fishCollision = a.GetType().Name == typeof(Fish).Name;
            if (fishCollision) {
                AddAnimal(new Fish());
            }
        }
        else { // bear-fish collision
            Bear bear = a.GetType() == typeof(Bear) ? a as Bear : b as Bear;
            Fish fish = a.GetType() == typeof(Fish) ? a as Fish : b as Fish;

            bear.Eat(fish);
            RemoveAnimal(fish);
        }
    }

This code is a part of my ecosystem class. It was suggested that I take this code into a separate class, implement a interface and use it instead. This way it becomes testable. The problem is: whenever two animals collide, I need to either add or remove a animal from the ecosystem. All I can think of is this:

interface ICollisionHandler {
    void HandleCollisionBetween(Animal a, Animal b, Ecosystem ecosystem);
}

But this reference to ecosystem just doesn't feel right. I would greatly appreciate any help and information that can help me improve on these kind of things.

Was it helpful?

Solution

So right off the bat, I find that when you are cross-comparing a lot of classes, one approach is to back up and ask yourself if the classes you have written should be recomposed as DATA, not as classes, at least for the point of contact in your app where they intersect. So to begin with, I'm going to give you an example that reduces the Bear/Fish classes down to one base class, Animal, with the bear-or-fosh details packed inside as data. However, you could take this example further and have a bear and fish class that inherits from Animal, but for the purpose of determining the collision outcome, I do not feel that having them as classes adds much value. Here's my building blocks.

  • An EcoSystem class that knows what animals are present in the environment and has the public function to be called when a collision is desired
  • An Animal base class that allows instances to understand their species (bear or fish) as well as a unique identifier to help track them in the ecosystem
  • A CollisionResolver class who's sole reason to change is to handle mapping new animal conflicts, if/when new animal types are added.

I'm not a big believer in INTERFACES FOR EVERYTHING, but I will give you this example, which breaks the problem into the above chunks:

public class Animal
{
    public string Identifier { get; } 
    public string SpeciesName { get; } 

    public Animal(string animalName)
    {
        SpeciesName = animalName;
        Identifier = Guid.NewGuid().ToString();
    }
}

public class AnimalCollisionResolver
{
    public ICollisionResult ResolveCollision(Animal a, Animal b)
    {
        // Handle the easy case -> reproduction of same species
        if (a.SpeciesName == b.SpeciesName)
            return new AddNewAnimalCollisionResult { NewAnimalToAdd = new Animal(a.SpeciesName) };

        // Handle handle harder case, how to determine which ones eat the others, this will be as complex as you need it

        var animals = new[] { a, b };
        var names = animals.Select(_ => _.SpeciesName);

        // Bear-eats-Fish mapping here
        if (names.Contains("Bear") && names.Contains("Fish"))
        {
            return new RemoveSpecificAnimalCollisionResult
            {
                IdentifierOfAnimalToRemove = animals.Single(_ => _.SpeciesName == "Fish").Identifier
            };
        }

        throw new NotImplementedException("I need to map out the rest of the collision resolutions here.");
    }
}

// This could be an abstract base class and the code would not change, FYI
public interface ICollisionResult { }

public class AddNewAnimalCollisionResult : ICollisionResult
{
    public Animal NewAnimalToAdd { get; set; }
}

public class RemoveSpecificAnimalCollisionResult : ICollisionResult
{
    public string IdentifierOfAnimalToRemove { get; set; }
}

public class Ecosystem
{
    public List<Animal> CurrentAnimals { get; set; }

    public AnimalCollisionResolver CollisionResolver { get; set; }

    public Ecosystem()
    {
        // seed the ecosystem here
        CurrentAnimals = new List<Animal> {
            new Animal("Bear"),
            new Animal("Bear"),
            new Animal("Fish"),
            new Animal("Fish"),
        };

        CollisionResolver = new AnimalCollisionResolver();
    }

    public void HandleCollision(Animal a, Animal b)
    {
        var result = CollisionResolver.ResolveCollision(a, b);

        if (result is AddNewAnimalCollisionResult)
        {
            var addResult = (AddNewAnimalCollisionResult)result;
            CurrentAnimals.Add(addResult.NewAnimalToAdd);
        }

        else if (result is RemoveSpecificAnimalCollisionResult)
        {
            var removeResult = (RemoveSpecificAnimalCollisionResult)result;
            CurrentAnimals = CurrentAnimals.Where(_ => _.Identifier != removeResult.IdentifierOfAnimalToRemove).ToList();
        }

        else
        {
            throw new Exception("Unknown ICollisionResult here");
        }

        PrintOutTheCurrentAnimals();
    }

    public void PrintOutTheCurrentAnimals()
    {
        foreach (var animal in CurrentAnimals)
            Console.WriteLine($"{animal.SpeciesName} {animal.Identifier}");
        Console.WriteLine("");
    }
}

I'm not sure how you're declaring the animals and deciding who collides with who, but here's an example to show the basics:

var ecoSystem = new Ecosystem();
ecoSystem.PrintOutTheCurrentAnimals();

var allBears = ecoSystem.CurrentAnimals.Where(_ => _.SpeciesName == "Bear");
var bear1 = allBears.Take(1).Single();
var bear2 = allBears.Skip(1).Take(1).Single();
var fish1 = ecoSystem.CurrentAnimals.First(_ => _.SpeciesName == "Fish");

ecoSystem.HandleCollision(bear1, fish1);

ecoSystem.HandleCollision(bear1, bear2);
Licensed under: CC-BY-SA with attribution
scroll top