4

I am designing a very simple inventory system for a game. I have run into an obstacle where I have the inventory (an array of a specific type) that would need to accept multiple types of objects. My code:

IWeapon[] inventory = new IWeapon[5];

public void initialiseInventory(IWeapon weapon, IArmour armour)
{
    inventory[0] = weapon; // Index 0 always contains the equipped weapon
    inventory[1] = armour; // Index 1 always contains the equipped armour
}

I would get an error stating that the array can't convert the armour object to a weapon object (which is the array type). I then thought I might make a superclass (well, interface to be precise) that IWeapon and IArmour would inherit from. But then I run into another error...

IItem[] inventory = new IItem[5];

public void initialiseInventory(IWeapon weapon, IArmour armour)
{
    inventory[0] = weapon; // Index 0 always contains the equipped weapon
    inventory[1] = armour; // Index 1 always contains the equipped armour

    Console.WriteLine("The weapon name is: " + inventory[0].Name) // Problem!
}

Since the array type is IItem, it would only contain properties and methods from IItem, and not from IWeapon or IArmour. Thus the problem came in that I could not access the name of the weapon located in the subclass (subinterface) IWeapon. Is there a way I could redirect it somehow to look for properties in a subinterface (IWeapon or IArmour) rather than the superinterface (IItem)? Am I even on the right path?

Frapie
  • 225
  • 1
  • 6
  • 14
  • Iweapon and IArmor would need to derive from IItem in order for your second example to work. – Lee Harrison Feb 08 '13 at 17:52
  • You could use `List Generics`. It will allow you to store all `Object` related information. – Greg Feb 08 '13 at 17:53
  • [[This question](http://stackoverflow.com/questions/14562047/generic-type-parameter-covariance-and-multiple-interface-implementations)] might brings you some inspiration to define your interfaces. – Ken Kin Feb 08 '13 at 18:24
  • @KenKin Sure you link the correct question? I can't see the relevance. – Jeppe Stig Nielsen Feb 11 '13 at 10:07
  • @Jeppe Stig Nielsen: Yes, I'm sure. I think the covariant intefrace definition might brings you some inspiration. – Ken Kin Feb 13 '13 at 15:19

9 Answers9

11

Since the first item will always be a weapon, and the second will always be armor, you shouldn't use an array (or any data structure) at all. Just have two separate fields, one that holds a weapon and another an armor instance.

private IWeapon weapon;
private IArmour armor;

public void initialiseInventory(IWeapon weapon, IArmour armour)
{
    this.weapon = weapon;
    this.armor = armor;
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • 4
    I don't disagree with what you've put here if he truly has just one weapon and one bit of armour, but the questions heart seems to be about polymorphism of items... – Immortal Blue Feb 08 '13 at 17:58
  • 1
    @ImmortalBlue It appears to be a problem in which polymorphism is being used to solve a problem not suited for it. If you want to demonstrate uses of polymorphism, find a problem to solve that it effectively addresses. – Servy Feb 08 '13 at 19:47
2

This is an interesting (and common) puzzle. You have figured out its first part correctly: in order to store the elements in a single array, the array type must match the common ancestor of all elements that go into the array. Of course, this limits the functionality to only what's offered by that common ancestor, which apparently is not enough in your circumstances.

The second part (namely, what to do with elements once you have them all in the array) is a bit harder. You need either a type cast, or a multiple dispatch. The type cast is easy: just add (IWeapon) in front of the element:

((IWeapon)inventory[0]).Name

For multiple items, you can use LINQ:

foreach (IWeapon w in inventory.OfType<IWeapon>()) {
    Console.WriteLine("The weapon name is: " + w.Name);
}

Multiple dispatch is a lot more complex. It lets you make methods virtual with respect to more than one object. In return you must sacrifice the simplicity offered by the language: calling methods would require making special objects, rather than calling methods directly. Take a look at the Visitor Pattern for some ideas of how to deal with multiple dispatch.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
2

You can use the is operator to determine if a variable implements a specific interface and then cast that variable to an instance of that interface.

        if (inventory[0] is IWeapon)
        {
            IWeapon myWeapon = (IWeapon)inventory[0];
            Console.WriteLine("The weapon name is: " + myWeapon.Name);
        }
jeconner
  • 398
  • 1
  • 2
  • 10
1

In the parent class/ interface, you need to decide what common operations/ attributes are truly meant to go here.

It might be worth having the interface something like this:

Interface IItem  
{
    string name {get};
    string itemType {get};

}

then you can just go

foreach(Iitem anItem in itemArray)
{
    Console.WriteLine("The " + anItem.itemType + " is: " + anItem.Name);
}

It's not perfect, and raises questions about your model, but it is just something to think about.

Immortal Blue
  • 1,691
  • 13
  • 27
  • The problem is that items and weapons won't be truly interchangeable, most likely. They would need to be (in this context) for it to work. – Servy Feb 08 '13 at 17:52
  • 2
    it's an answer to the OPs question tho – bas Feb 08 '13 at 17:56
  • 1
    Well, that's based on an assumption, the question seems to be about mixed lists. I figured the comments as being temporary whilst he tries to figure out how to use the mixed list... (I'm smelling inventory for an adventure game ;) ) – Immortal Blue Feb 08 '13 at 17:56
0

Although I totally agree with the answer Servy gave:

If you really really want to use an array (or a List<IITem> ?) you only need to add the Name property to the IItem interface.

interface IItem
{
    string Name {get;set;}
}

I doubt if it will help you on the long run, so I'd go for Servy's answer.

bas
  • 13,550
  • 20
  • 69
  • 146
0

Both weapons and armour have names, so that's a property that should go in the IItem interface.

Also, what Servy says makes sense, having an array for different kind of items doesn't make much sense as the specific positions always have the same type of item.

If you want to access them as an array, you can create a class that both has an array, and lets you access the equipped items with the specific type:

public class Inventory {

  public IWeapon CurrentWeapon { get; }
  public IArmour CurrentArmour { get; }

  private IItem[] _items = new IItem[8];

  public IItem[int idx] {
    get {
      return
        idx == 0 ? CurrentWeapon :
        idx == 1 ? CurrentArmour :
        idx_items[idx - 2];
    }
  }

}
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
0

I detect a little confusion between interfaces and classes, but nevertheless, you should very simply make sure that IItem has the Name property on it (which, if its an interface, IWeapon and IArmour would need to implement), rather than putting the Name property on each subclass (no such thing as a "subinterface" :) ) Perhaps you should post the code of your interfaces/classes though....

Kevin Versfeld
  • 710
  • 5
  • 18
0

I have to add my 2 cents, because i found the question quite well asked and it may be a common problem.

I would go with something like this:

interface IItem
{
    //some common properties / methods
}

interface IWEapon : IItem
{
    string Name { get; set; } //maybe this should go to IItem? depends on your actual objects, of course
    //some other wepaon specific properties / methods
}

interface IArmor : IItem
{
    //some properties / methods
}

class Inventory
{
    public Inventory(IWEapon startWeapon, IArmor startArmor)
    {
        CurrentWeapon = startWeapon;
        CurrentArmor = startArmor;

        //optional:
        m_items.Add(startWeapon);
        m_items.Add(startArmor);
    }

    private List<IItem> m_items = new List<IItem>();
    IEnumerable<IItem> InventoryItems
    {
        get { return m_items; }
    }

    void AddItem(IItem item)
    {
        m_items.Add(item);
    }

    IWEapon CurrentWeapon
    {
        get;
        set;
    }

    IArmor CurrentArmor
    {
        get;
        set;
    }
}

Why design a class for the inventory? Because you could add things like a TotalWeight or ItemCount property, etc. way more easily than if you have just an array of IItem.

Jobo
  • 1,084
  • 7
  • 14
0

Assuming you really require an array to store the different inventory items (and not just two separate fields) then it seems you could just use inheritance. I took the liberty of adding different properties to Weapon and Armor to clarify the use cases.

Base Class:

abstract class Item
{
    public string Name { get; set; }
}

Derived Types:

class Weapon : Item
{
    public int Power { get; set; }
}

class Armor : Item
{
    public int Resistance { get; set; }
}

Example usage:

Item[] inventory = new Item[2];
inventory[0] = new Weapon { Name = "Singing Sword", Power = 10 };
inventory[1] = new Armor { Name = "Chainmail Shirt", Resistance = 5 };

// Note below that the Name property is always accessible because it is defined in the base class.

// Traverse only Weapon items
foreach (Weapon weapon in inventory.OfType<Weapon>())
{
    Console.WriteLine(weapon.Power);
}

// Traverse only Armor items
foreach (Armor armor in inventory.OfType<Armor>())
{
    Console.WriteLine(armor.Resistance);
}

// Travers all items
foreach (Item item in inventory)
{
    Console.WriteLine(item.Name);
}
blins
  • 2,515
  • 21
  • 32