I was inspired by Number Wizard game question and I decided to write my own version of the game. My goals were to follow the SOLID principles and make the logic UI independent. I'm a bit confused about the outcome, though.
I think my solutions reads much easier than the version in original question, but on the other hand it's also much more complex on the design side. Have I over complicated things?
Interfaces:
namespace NumberWizard.Api
{
public interface IMessenger
{
IEnumerable<string> BeforeStateEnterMessages();
IEnumerable<string> InStateMessages();
}
}
namespace NumberWizard.Api
{
public interface IState
{
void Increase();
void Decrease();
int Value { get; }
}
}
Useful Abstractions:
using NumberWizard.Api;
using System;
namespace NumberWizard
{
public class SteppedValueChanger
{
private int _value = 0;
private readonly int _step;
public SteppedValueChanger(int step)
{
_step = step;
}
public void Increase()
{
_value += _step;
}
public void Decrease()
{
_value = Math.Max(0, _value - _step);
}
public int Value
{
get { return _value; }
}
}
}
namespace NumberWizard
{
public class Range
{
public int Min { get; private set; }
public int Max {get; private set;}
public Range() : this(0, 0)
{
}
public Range(int min, int max)
{
Min = min;
Max = max;
}
internal void SetMin(int min)
{
Min = min;
}
internal void SetMax(int max)
{
Max = max;
}
}
}
States:
using NumberWizard.Api;
using System.Collections.Generic;
namespace NumberWizard.State
{
class SetupMax : IState, IMessenger
{
private readonly SteppedValueChanger _valueModifier = new SteppedValueChanger(100);
public IEnumerable<string> BeforeStateEnterMessages()
{
return new[] { "Welcome to Number Wizard", "Set max number!" };
}
public IEnumerable<string> InStateMessages()
{
return new[] { string.Format("Max: {0}", Value) };
}
public void Increase()
{
_valueModifier.Increase();
Value = _valueModifier.Value;
}
public void Decrease()
{
_valueModifier.Decrease();
Value = _valueModifier.Value;
}
public int Value { get; private set; }
}
}
using NumberWizard.Api;
using System.Collections.Generic;
namespace NumberWizard.State
{
class SetupMin : IState, IMessenger
{
private readonly SteppedValueChanger _valueModifier = new SteppedValueChanger(1);
public IEnumerable<string> BeforeStateEnterMessages()
{
return new[] { "Set min number!" };
}
public IEnumerable<string> InStateMessages()
{
return new[] { string.Format("Min: {0}", Value) };
}
public void Increase()
{
_valueModifier.Increase();
Value = _valueModifier.Value;
}
public void Decrease()
{
_valueModifier.Decrease();
Value = _valueModifier.Value;
}
public int Value { get; private set; }
}
}
using NumberWizard.Api;
using System.Collections.Generic;
namespace NumberWizard.State
{
class Guess : IState, IMessenger
{
private Range _guessingRange;
private int _guess;
public Guess(Range range)
{
_guessingRange = range;
_guess = GuessNext();
}
public void Increase()
{
_guessingRange = new Range(_guess, _guessingRange.Max);
_guess = GuessNext();
}
public void Decrease()
{
_guessingRange = new Range(_guessingRange.Min, _guess);
_guess = GuessNext();
}
private int GuessNext()
{
return (_guessingRange.Max + _guessingRange.Min) / 2;
}
public IEnumerable<string> BeforeStateEnterMessages()
{
return new[]{
"Pick a number in your head, but don't tell me!",
string.Format("The highest number you can pick is {0}", _guessingRange.Max),
string.Format("The lowest number you can pick is {0}", _guessingRange.Min),
string.Format("Is the number higher or lower than {0}?", Value)
};
}
public IEnumerable<string> InStateMessages()
{
return new[] {
string.Format("Is the number higher or lower than {0}?", Value) };
}
public int Value { get { return _guess; } }
}
}
'State Machine':
using NumberWizard.Api;
using NumberWizard.State;
using System.Collections.Generic;
using System.Linq;
namespace NumberWizard
{
public class Game
{
private Range guessingRange = new Range();
private IState _gameState;
public IEnumerable<string> Decrease()
{
_gameState.Decrease();
return GetInStateMessages(_gameState);
}
public IEnumerable<string> Increase()
{
_gameState.Increase();
return GetInStateMessages(_gameState);
}
private IEnumerable<string> GetInStateMessages(IState gameState)
{
var messenger = gameState as IMessenger;
if (messenger == null) { return Enumerable.Empty<string>(); }
return messenger.InStateMessages();
}
private IEnumerable<string> GetBeforeStateEnterMessages(IState gameState)
{
var messenger = gameState as IMessenger;
if (messenger == null) { return Enumerable.Empty<string>(); }
return messenger.BeforeStateEnterMessages();
}
public IEnumerable<string> ChangeState()
{
if (_gameState == null)
{
_gameState = new SetupMax();
return GetBeforeStateEnterMessages(_gameState);
}
if (_gameState is SetupMax)
{
guessingRange.SetMax(_gameState.Value);
_gameState = new SetupMin();
return GetBeforeStateEnterMessages(_gameState);
}
if (_gameState is SetupMin)
{
guessingRange.SetMin(_gameState.Value);
_gameState = new Guess(guessingRange);
return GetBeforeStateEnterMessages(_gameState);
}
if (_gameState is Guess)
{
_gameState = null;
return new[] { "I guessed Correctly!", "Press 'c' to start over or 'q' to quit" };
}
return Enumerable.Empty<string>();
}
}
}
Main:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NumberWizard
{
class Program
{
static void Main(string[] args)
{
var game = new Game();
IEnumerable<string> messages = game.ChangeState();
PrintMessages(messages);
var key = Console.ReadKey(true);
while (true)
{
switch (key.KeyChar)
{
case 'u':
messages = game.Increase();
break;
case 'd':
messages = game.Decrease();
break;
case 'c':
messages = game.ChangeState();
break;
case 'q':
Environment.Exit(0);
break;
}
PrintMessages(messages);
key = Console.ReadKey(true);
}
}
private static void PrintMessages(IEnumerable<string> messages)
{
foreach (var m in messages)
{
Console.WriteLine(m);
}
}
}
}