I just spent the last few days constructing the currency system for my game, and was wondering if you guys had any suggestions on how—if at all—I could improve it. Before I show the code, let me explain the system as it currently stands. It works a lot like World of Warcraft, in that Copper is automatically converted to Silver when you gain 100 of it. Silver is then converted into Gold, and Gold into Platinum. For those of you who have played Everquest, this system would seem even more familiar.
I have already made a few optimizations to the root structure of it all, like how I keep all denominators of currency within a single long. Using this long I can do computations to "fake" the other denominations. Essentially, everything is internally kept as Copper.
I have also implied a maximum base denominator value of "999999999". Using this somewhat arbitrary number allows me to cap the total currency out at 999 Platinum, 99 Gold, 99 Silver, and 99 Copper.
I was just wondering if you guys had any suggestions(besides ones like "hey you should totally use var right there!") that could help to improve my current implementation.
Here is the code as it currently stands:
using System;
namespace Some.Arbitrary.Framework
{
public enum Coins
{
/// <summary>
/// Copper is the lowest denominator of currency.
/// It requires 100 Copper to make 1 Silver.
/// </summary>
Copper = 1,
/// <summary>
/// Silver is the second most common form of currency.
/// It requires 100 Silver to Make 1 Gold.
/// </summary>
Silver = 2,
/// <summary>
/// Gold is the most common form of currency. It takes
/// part in most expensive transactions.
/// It requires 100 Gold to make 1 Platinum.
/// </summary>
Gold = 3,
/// <summary>
/// Platinum is a coin which most people never see. A single
/// Platinum coin can purchase almost anything.
/// 1 Platinum Coin = 100 Gold.
/// 1 Platinum Coin = 10,000 Silver.
/// 1 Platinum Coin = 1,000,000 Copper.
/// </summary>
Platinum = 4
}
public class MoneyBag : IEquatable<MoneyBag>, IComparable<MoneyBag>
{
private long _baseDenomination;
public const string CopperName = "Copper";
public const string SilverName = "Silver";
public const string GoldName = "Gold";
public const string PlatinumName = "Platinum";
public const char CopperAbbreviation = 'c';
public const char SilverAbbreviation = 's';
public const char GoldAbbreviation = 'g';
public const char PlatinumAbbreviation = 'p';
public const long MaximumBaseDenomination = 999999999;
public static readonly MoneyBag FilledBag = new MoneyBag(MaximumBaseDenomination);
public static readonly MoneyBag EmptyBag = new MoneyBag();
public long BaseDenomination
{
get
{
return _baseDenomination;
}
set
{
_baseDenomination = value;
// Clamp if required.
if (_baseDenomination > MaximumBaseDenomination)
{
_baseDenomination = MaximumBaseDenomination;
}
if (_baseDenomination < 0)
{
_baseDenomination = 0;
}
}
}
/// <summary>
/// The total amount of Copper.
/// </summary>
public int Copper
{
get
{
return ComputeCopper(_baseDenomination);
}
}
/// <summary>
/// The total amount of Silver.
/// </summary>
public int Silver
{
get
{
return ComputeSilver(_baseDenomination);
}
}
/// <summary>
/// The total amount of Gold.
/// </summary>
public int Gold
{
get
{
return ComputeGold(_baseDenomination);
}
}
/// <summary>
/// The total amount of Platinum.
/// </summary>
public int Platinum
{
get
{
return ComputePlatinum(_baseDenomination);
}
}
public bool IsFull
{
get
{
return _baseDenomination == MaximumBaseDenomination;
}
}
public bool IsEmpty
{
get
{
return _baseDenomination == 0;
}
}
public bool HasPlatinum
{
get
{
return Platinum > 0;
}
}
public bool HasGold
{
get
{
return Gold > 0 || Platinum > 0;
}
}
public bool HasSilver
{
get
{
return Silver > 0 || Gold > 0 || Platinum > 0;
}
}
public bool HasCopper
{
get
{
return Copper > 0 || Silver > 0 || Gold > 0 || Platinum > 0;
}
}
public MoneyBag()
{
}
public MoneyBag(int platinum, int gold, int silver, int copper)
{
Add(platinum, gold, silver, copper);
}
public MoneyBag(long baseDenomination)
{
BaseDenomination = baseDenomination;
}
public void Add(int platinum, int gold, int silver, int copper)
{
BaseDenomination += platinum * 1000000;
BaseDenomination += gold * 10000;
BaseDenomination += silver * 100;
BaseDenomination += copper;
}
public void Add(int amount, Coins type)
{
if (amount <= 0) return;
switch (type)
{
case Coins.Copper:
Add(0, 0, 0, amount);
break;
case Coins.Silver:
Add(0, 0, amount, 0);
break;
case Coins.Gold:
Add(0, amount, 0, 0);
break;
case Coins.Platinum:
Add(amount, 0, 0, 0);
break;
}
}
public void Add(MoneyBag other)
{
BaseDenomination += other._baseDenomination;
}
public void Subtract(int platinum, int gold, int silver, int copper)
{
BaseDenomination -= platinum * 1000000;
BaseDenomination -= gold * 10000;
BaseDenomination -= silver * 100;
BaseDenomination -= copper;
}
public void Subtract(int amount, Coins type)
{
if (amount <= 0) return;
switch (type)
{
case Coins.Copper:
Subtract(0, 0, 0, amount);
break;
case Coins.Silver:
Subtract(0, 0, amount, 0);
break;
case Coins.Gold:
Subtract(0, amount, 0, 0);
break;
case Coins.Platinum:
Subtract(amount, 0, 0, 0);
break;
}
}
public void Subtract(MoneyBag other)
{
BaseDenomination -= other._baseDenomination;
}
public void Empty()
{
_baseDenomination = 0;
}
public void Fill()
{
_baseDenomination = MaximumBaseDenomination;
}
public static MoneyBag operator +(MoneyBag b1, MoneyBag b2)
{
return new MoneyBag(b1._baseDenomination + b2._baseDenomination);
}
public static MoneyBag operator -(MoneyBag b1, MoneyBag b2)
{
return new MoneyBag(b1._baseDenomination - b2._baseDenomination);
}
public bool Equals(MoneyBag other)
{
return _baseDenomination == other._baseDenomination;
}
public override bool Equals(object obj)
{
return (obj is MoneyBag) && Equals((MoneyBag)obj);
}
public override int GetHashCode()
{
return _baseDenomination.GetHashCode();
}
public static bool operator ==(MoneyBag a, MoneyBag b)
{
if (ReferenceEquals(a, null)) return false;
if (ReferenceEquals(b, null)) return false;
return a.Equals(b);
}
public static bool operator !=(MoneyBag a, MoneyBag b)
{
if (ReferenceEquals(a, null)) return false;
if (ReferenceEquals(b, null)) return false;
return !a.Equals(b);
}
public static bool operator <(MoneyBag a, MoneyBag b)
{
return a.CompareTo(b) < 0;
}
public static bool operator >(MoneyBag a, MoneyBag b)
{
return a.CompareTo(b) > 0;
}
public int CompareTo(MoneyBag other)
{
// The shit was null, dumbass!
if (other == null) return 0;
if (_baseDenomination > other._baseDenomination)
{
return 1;
}
if (_baseDenomination < other._baseDenomination)
{
return -1;
}
// They were equal.
return 0;
}
public static void ComputeWealth(long baseDenomination, out int platinum, out int gold, out int silver, out int copper)
{
platinum = ComputePlatinum(baseDenomination);
gold = ComputeGold(baseDenomination);
silver = ComputeSilver(baseDenomination);
copper = ComputeCopper(baseDenomination);
}
public static int ComputeCopper(long baseDenomination)
{
return (int)Math.Floor((double)Math.Abs(baseDenomination % 100));
}
public static int ComputeSilver(long baseDenomination)
{
return (int)Math.Floor((double)Math.Abs((baseDenomination / 100) % 100));
}
public static int ComputeGold(long baseDenomination)
{
return (int)Math.Floor((double)Math.Abs((baseDenomination / 10000) % 100));
}
public static int ComputePlatinum(long baseDenomination)
{
return (int)Math.Floor((double)Math.Abs(baseDenomination / 1000000));
}
public override string ToString()
{
return
"" + Platinum + PlatinumAbbreviation + "," +
Gold + GoldAbbreviation + "," +
Silver + SilverAbbreviation + "," +
Copper + CopperAbbreviation;
}
}
}
Succinct usage example(Feel free to use it more yourself):
using Some.Arbitrary.Framework;
namespace SomeGameOrSomething
{
static class Program
{
static void Main()
{
MoneyBag bag = new MoneyBag(1,22,44,55);
bag.Subtract(0,50,22,0);
Console.WriteLine(bag);
Console.Read();
}
}
}
EDIT/Update
In case anyone is interested with what I ended up with after all the great suggestions, the code can be found here: http://pastebin.com/sqVjZYry
HasFoo
, just re-use the higher-order functions.HasGold = Gold > 0 || HasPlatinum
, for example. \$\endgroup\$999999999
is less thanint.MaxValue
so you can store it all in anint
rather than along
. You could also use auint
as you're not allowing negative numbers. \$\endgroup\$1024*128*128*128 - 1
is small enough to fit into even a signedint
, so you could easily fit999*128*128*128
in. This means you can use bitshifting instead of multiplying by powers of 10, and still fit in just as many values. \$\endgroup\$