The status of a game can be:
- running, when it is not over yet,
- tie, when over but no player won,
- win, when over and a certain player won.
Being able to check equality of statuses is desired.
With these in mind, I coded a GameStatus
class which implements IEquatable
:
class GameStatus : IEquatable<GameStatus> {
public static GameStatus Running() => new GameStatus() { IsOver = false };
public static GameStatus Tie() => new GameStatus() { IsOver = true, IsTie = true };
public static GameStatus Win(Player winner) =>
new GameStatus() { IsOver = true, IsTie = false, Winner = winner };
public bool IsOver { get; private set; }
public bool IsTie { get; private set; }
public Player Winner { get; private set; }
public override bool Equals(object obj) => Equals(obj as GameStatus);
public bool Equals(GameStatus gs) {
return gs != null ?
(IsOver == gs.IsOver) && (IsTie == gs.IsTie) && (Winner == gs.Winner) :
false;
}
public override int GetHashCode() => HashHelper.GetHashCode(IsOver, IsTie, Winner);
public static bool operator ==(GameStatus arg1, GameStatus arg2) =>
ReferenceEquals(arg1, null) ? ReferenceEquals(arg2, null) : arg1.Equals(arg2);
public static bool operator !=(GameStatus arg1, GameStatus arg2) => !(arg1 == arg2);
private GameStatus() { }
}
using the Player
enumeration and the HashHelper
class:
enum Player {
Red,
Black
};
static class HashHelper {
public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3) {
unchecked {
return 103 * arg1.GetHashCode() + 31 * arg2.GetHashCode() + arg3.GetHashCode();
}
}
}
A GameStatus can be compared to the Running
, Tie
, Win(winner)
immutables, or with other statuses. Overriding the Equals()
, GetHashCode()
and the ==
!=
operators assures that every comparison should be correct.
However, there was something missing.
According to the business logic, there is no point of asking whether status is tie, when the game is running. Similarly there is no point asking for the winner when the game is running or a tie. So the IsTie
and Winner
properties had to be modified:
class GameStatusStrict : IEquatable<GameStatusStrict> {
public static GameStatusStrict Running() => new GameStatusStrict() { _isOver = false };
public static GameStatusStrict Tie() => new GameStatusStrict() { _isOver = true, _isTie = true };
public static GameStatusStrict Win(Player winner) =>
new GameStatusStrict() { _isOver = true, _isTie = false, _winner = winner };
public bool IsOver => _isOver;
public bool IsTie {
get {
Checker.Check(nameof(IsOver), IsOver, true);
return _isTie;
}
}
public Player Winner {
get {
Checker.Check(nameof(IsOver), IsOver, true);
Checker.Check(nameof(IsTie), IsTie, false);
return _winner;
}
}
public override bool Equals(object obj) => Equals(obj as GameStatusStrict);
public bool Equals(GameStatusStrict gs) {
return gs != null ?
(_isOver == gs._isOver) && (_isTie == gs._isTie) && (_winner == gs._winner) :
false;
}
public override int GetHashCode() => HashHelper.GetHashCode(_isTie, _isTie, _winner);
public static bool operator ==(GameStatusStrict arg1, GameStatusStrict arg2) =>
ReferenceEquals(arg1, null) ? ReferenceEquals(arg2, null) : arg1.Equals(arg2);
public static bool operator !=(GameStatusStrict arg1, GameStatusStrict arg2) => !(arg1 == arg2);
private bool _isOver;
private bool _isTie;
private Player _winner;
private GameStatusStrict() { }
}
The Checker class:
static class Checker {
public static void Check(string varName, bool value, bool expectedValue) {
if (value != expectedValue)
throw new InvalidOperationException(string.Format("{0} should be {1}.", varName, expectedValue));
}
}
My own remarks:
- Is
GameStatusStrict
doing too much (i.e. over-engineering) by checking on properties? - Any suggestions for the names of
Checker
class andCheck()
? - The methods
Equals(object)
,Equals(GameStatus)
,GetHashCode()
,operator ==()
andoperator !=()
seem to be pretty much boilerplate that will appear in many classes. The only changing code is the logic "use all three attributesIsOver
,IsTie
,Winner
and my class type". GameStatus
andGameStatusStrict
classes seem to be simple enough so as not to need unit testing. Do you agree?