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.
1 Answer 1
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
- In your
- 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 ofset
, 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
)
- The only exception is whenever you have custom logic either inside the getter or inside the setter (like
- 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 isstatic
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
- Here I've implemented this pattern with a
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],
};