4
\$\begingroup\$

Items.cs, Creates dictionaries, enumerators, and classes for every type of item.

using System;
using System.Collections.Generic;

public class Item {
    // Medical
    public static Dictionary < Medical, ItemMedical > Medicals = new Dictionary<Medical, ItemMedical>() {
    { Medical.bandage, new ItemMedical() { name = "bandage", healing = 15, weight = 0.2f, value = 75, description =
        "Simple bandage to dress minor injuries. For patching up boo boos when you fall off your bicycle, won't save you from a gunshot." }},
    { Medical.tourniquet, new ItemMedical() { name = "tourniquet", healing = 25, weight = 0.7f, value = 225, description =
        "A device that tightly wraps around a limb near the wound to stop the flow of blood, more importantly, it stops blood from flowing OUT." }}
    };
    // Weapon
    public static Dictionary < Weapon, ItemWeapon > Weapons = new Dictionary < Weapon, ItemWeapon > () {
    { Weapon.shortsword, new ItemWeapon() { name = "shortsword", Damage = 25, weight = 4, value = 750, description =
        "A relatively short sword, hence the very creative name \"Shortsword\"." }}, 
    { Weapon.longsword, new ItemWeapon() { name = "longsword", Damage = 40, weight = 6, value = 950, description = 
        "A relatively long sword, hence the very creative name \"Longsword\"." }}
    };
    // Armor
    public static Dictionary < Armor, ItemArmor > Armors = new Dictionary < Armor, ItemArmor > () {
    { Armor.police_vest, new ItemArmor() { name = "police vest", Resistance = 25, Durability = 15, weight = 5, value = 1200, description =
        "A pistol grade body armor used by police forces." }},
    { Armor.military_vest, new ItemArmor() { name = "military vest", Resistance = 60, Durability = 40, weight = 8, value = 2400, description =
        "A heavy military vest capable of withstanding some rifle rounds." }}
    };
  public static ItemMedical Get(Medical key) => Medicals[key];
  public static ItemWeapon Get(Weapon key) => Weapons[key];
  public static ItemArmor Get(Armor key) => Armors[key];
}
public enum Medical {
  bandage,
  tourniquet
}
public enum Weapon {
  shortsword,
  longsword
}
public enum Armor {
  police_vest,
  military_vest
}

public class ItemMedical: ItemBase {
  public float healing = 0;
}

public class ItemWeapon: ItemBase {
  private float damage = 0;
  public float Damage {
    get => damage;
    set => damage = value;
  }
}

public class ItemArmor: ItemBase {
  // Damage required to penetrate armor
  public float Resistance = 0;
  // "health" of the armor, damaged much more if penetrated
  private float durability = 0;
  public float Durability {
    get => durability;
    set => durability = Math.Clamp(value, 0, value);
  }
}

public class ItemBase {
  public string name = "No name";
  public string description = "No description";
  public float weight = 0;
  public float value = 0;
}

Inventory.cs, equipment is an array because the slots never change, but backpack is a list because i dont know how many items will be in there.

  public static ItemBase[] equipment = new ItemBase[4] {
    // Primary weapon
    Item.Get(Weapon.longsword),
    // Secondary weapon
    Item.Get(Weapon.shortsword),
    // Body armor
    Item.Get(Armor.police_vest),
    // Rig
    null
  };
  public static List<ItemBase> backpack = new List<ItemBase>() {
      Item.Get(Medical.bandage),
      Item.Get(Medical.bandage),
  };
}

Usage

class Program {
  static void Main(string[] args) {
    Console.WriteLine("Items in inventory:");
    foreach(ItemBase item in Inventory.equipment) {
      if (item != null) Console.WriteLine(item.name);
      else Console.WriteLine("Nothing");
    }
    Console.WriteLine("\nItems in backpack:");
    foreach(ItemBase item in Inventory.backpack) {
      Console.WriteLine(item.name);
    }
  }
}

Output:

Items in inventory: longsword shortsword police vest Nothing

Items in backpack: bandage bandage

My first actually competant looking piece of code as a beginner. Really proud of it.

\$\endgroup\$
0

1 Answer 1

5
\$\begingroup\$

First of all, congratulation it is a great first attempt. Even though it does not contain too much functionality rather structure and data.

Most of my recommendation will be related to C# coding conventions. Some of my suggestions are taking advantage of C# 9's new features so I'll share some links about them.

Enums

public enum Medical { Bandage, Tourniquet }
public enum Weapon { ShortSword, LongSword }
public enum Armor { PoliceVest, MilitaryVest }
  • In C# we usually use PascalCasing for enum members
    • In your Weapon enum you have used lower casing
    • whereas in your Armor enum you have used snake_casing
    • Please try to chase consistency across your domain model
  • Most of the time when the enum contains less than 5 members (and they are not overwriting default values) C# developers tend to define the enum in a single line

Base class

public abstract class ItemBase
{
    public string Name { get; init; } = "No name";
    public string Description { get; init; } = "No description";
    public float Weight { get; init; }
    public float Value { get; init; }
}
  • This class is used to define common properties that's why it is advisable to mark it as abstract
    • That prevents the consumer of this class to instantiate an object from it
    • You want to allow to be able to create only derived classes
  • C# developers are preferring properties over fields whenever they are public
    • That's why I've changed all the base class members to properties
    • I've also changed their name since in C# we normally use Pascal Casing for properties
    • I've used init instead of set, because it only allows initialization (via constructor or via object initializer)
      • So, after an item is created with a specified values it can't be changed later on
    • Since C# 6 you can define default value for auto generated fields

Derived classes

public class ItemMedical : ItemBase
{
    public float Healing { get; init; }
}

public class ItemWeapon : ItemBase
{
    public float Damage { get; init; }
}

public class ItemArmor : ItemBase
{
    public float Resistance { get; init; }
    private float durability;
    public float Durability
    {
        get => durability;
        set => durability = Math.Clamp(value, 0, value);
    }
}
  • Please prefer auto-generated properties over manually creating backing fields and defining getter and setter methods
    • The only exception is whenever you have custom logic either inside the getter or inside the setter (like Durability)
  • I've get rid of the = 0 initial value assignments since these are their default values

The Item class #1

This constant data class can be implemented in several ways. One way to achieve it as you have done it.

  • In this case you can mark the class itself as static since all of its member is static as well
  • If you expose the dictionaries (public) then you should consider to make them immutable to prevent further element removal or addition after intialization
public static class Item
{
    public static readonly ImmutableDictionary<Medical, ItemMedical> Medicals = new Dictionary<Medical, ItemMedical>
    {
        {
            Medical.Bandage,
            new()
            {
                Name = "bandage",
                Healing = 15,
                Weight = 0.2f,
                Value = 75,
                Description = "Simple bandage to dress minor injuries. For patching up boo boos when you fall off your bicycle, won't save you from a gunshot."
            }
        },
        {
            Medical.Tourniquet,
            new()
            {
                Name = "tourniquet",
                Healing = 25,
                Weight = 0.7f,
                Value = 225,
                Description = "A device that tightly wraps around a limb near the wound to stop the flow of blood, more importantly, it stops blood from flowing OUT."
            }
        }
    }.ToImmutableDictionary();

    ...

    public static ItemMedical Get(Medical key) => Medicals[key];
    public static ItemWeapon Get(Weapon key) => Weapons[key];
    public static ItemArmor Get(Armor key) => Armors[key];
}
  • I've marked the collections as readonly to prevent overwriting with another collection or null by the consumer of the class
  • I've used new() (target typed new expression) to avoid repeating the class names over and over again
    • It can be inferred from the Dictionary type parameter

The Item class #2

Let me show you an alternative approach:

public class Item
{
    private static readonly Lazy<Item> singleton = new Lazy<Item>(new Item());
    public static Item Instance => singleton.Value;

    private Item() { }

    private readonly Dictionary<Medical, ItemMedical> Medicals = new ()
    {
        {
            Medical.Bandage,
            new ()
            {
                Name = "bandage",
                Healing = 15,
                Weight = 0.2f,
                Value = 75,
                Description = "Simple bandage to dress minor injuries. For patching up boo boos when you fall off your bicycle, won't save you from a gunshot."
            }
        },
        {
            Medical.Tourniquet,
            new ()
            {
                Name = "tourniquet",
                Healing = 25,
                Weight = 0.7f,
                Value = 225,
                Description = "A device that tightly wraps around a limb near the wound to stop the flow of blood, more importantly, it stops blood from flowing OUT."
            }
        }
    };

    ...

    public ItemMedical this[Medical key] => Medicals[key];
    public ItemWeapon this[Weapon key] => Weapons[key];
    public ItemArmor this[Armor key] => Armors[key];
}
  • Here I've made the dictionaries private so they became implementation details
  • I've replaced the Get methods to index operators to make the item retrieval a bit more convenient
    • See next section for usage
    • The index operators can't be defined as static so we have to make a little trick here to make the usage easy
  • We use the singleton pattern to expose a single instance to the consumers of the class
    • Here I've implemented this pattern with a Lazy to make sure that the instance is created only when it is first accessed but in a thread safe manner

These changes allow us to define the Equipments and Backpack like this:

public ItemBase[] Equipments = new []
{
    Item.Instance[Weapon.LongSword],
    Item.Instance[Weapon.ShortSword],
    Item.Instance[Armor.PoliceVest],
    (ItemBase)null
};

public List<ItemBase> Backpack = new ()
{
    Item.Instance[Medical.Bandage],
    Item.Instance[Medical.Bandage],
};
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.