HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Logon state management

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
managementlogonstate

Problem

I'm in the process of writing a simple text based MUD. First up is making sure that it's possible to create new players / logon with existing players. Since everything is text based, there are several state transitions involved where the user is prompted and then their response is processed. With this in mind, I've created some code for handling these states:

Get User Name -
Correct? |
| | Get Password -
|
Logged On


Once logged on, interactions will take on a more fluid approach.

Approach 1

My first draft split the processing up between two session classes. One for managing the logon transitions (LogonSession) and one for post logon (PlayerSession):

LogonSession

```
public class LogonSession : ISession, IInputLineListener
{
enum ConnectedState { ExpectingPlayerName, ExpectingExistingPassword, Closed, ConfirmingPlayerName, GettingPlayerPassword, ConfirmingPassword, LoggedOn };
ConnectedState _state = ConnectedState.ExpectingPlayerName;
IPlayerConnection _playerConnection;
private readonly IMudConfiguration _configuration;
private readonly IPlayerRepository _playerRepository;
private Player _player;
private string _playerName;
private string _password;
private readonly ISessionManager _sessionManager;

public LogonSession(IPlayerConnection playerConnection, IMudConfiguration configuration, IPlayerRepository playerRepository, ISessionManager sessionManager)
{
_playerConnection = playerConnection;
_playerRepository = playerRepository;
_configuration = configuration;
_sessionManager = sessionManager;

_playerConnection.SetReceivedLineListener(this);

_playerConnection.SendText($"Welcome to {_configuration.Name} Mud.\r\n");
_playerConnection.SendText("What is your name?");
}

public void ReceivedLine(string text)
{
switch (_state)
{
case ConnectedState.

Solution

Scratching the tip of the Approach 2 iceberg...

public void ReceivedLine(string text)
{
  if (null != _sessionInputHandler)
  {
      _sessionInputHandler.ReceivedLine(_playerConnection, text);
  }
}


The name of the method suggests it should be a property but it looks like an event. Did you mean OnLineReceived? If it's a method for raising events then it shouldn't be public.

null != _sessionInputHandler


I've never seen someone writing conditions backwards ;-)

SessionInputHandlerFactory


The name suggests it's a factory but it doesn't really create anything. It has a lot of ActivateX methods. It's not what I would expect from a factory. It should rather be some activator or configurator.

var player = _playerRepository.CreatePlayer(_playerName, _password);


Repositories don't create things. They store them. For creating things we use factories.

Player


You have an abstraction for almost everything but the Player. In the LoggedOnInputHandler you use only its name. I don't think it's necessary to pass the entire object and make the handler depend on it.

Approach 3

I find both approaches are really complex, perhaps even too complex. I'd like to suggest yet another one. This should resemble the MVVM design.

Start by creating views. I call them Screens where each screen is derived from the Screen type.

abstract class Screen
{
    public Screen NextScreen { get; protected set; }

    public abstract void Show();

    protected void WriteLine(string message)
    {
        Console.WriteLine(message);
    }

    protected ConsoleKeyInfo ReadKey()
    {
        return Console.ReadKey();
    }
}

class WelcomeScreen : Screen
{
    public override void Show()
    {
        WriteLine("Welcome to Mud.");
        WriteLine("Press Enter to logon or Escape to exit.");

        do 
        {
            var key = ReadKey().Key;
            if (key == ConsoleKey.Enter)
            {
                NextScreen = new LogonScreen();
                return;
            }
            else if (key == ConsoleKey.Escape)
            {
                NextScreen = null;
                return;
            }
        }
        while(true);
    }
}

class LogonScreen : Screen
{
    public override void Show()
    {
        WriteLine("What is your name?");
        ReadKey();
    }
}

class NewUserScreen : Screen
{
    public override void Show()
    {

    }
}


In the next step you can create a data model for each screen. Let's say LogonScreenModel. This will handle the logon process against e.g. a database.

You can start the game with the Game object:

class Game
{
    public Game()
    {
        // initilize something here if necessary
    }

    public void Start()
    {
        var welcomeScreen = new WelcomeScreen();
        welcomeScreen.Show();
        if (welcomeScreen.NextScreen == null)
        {
            return;
        }
        welcomeScreen.NextScreen.Show();
    }
}

Code Snippets

public void ReceivedLine(string text)
{
  if (null != _sessionInputHandler)
  {
      _sessionInputHandler.ReceivedLine(_playerConnection, text);
  }
}
null != _sessionInputHandler
SessionInputHandlerFactory
var player = _playerRepository.CreatePlayer(_playerName, _password);
abstract class Screen
{
    public Screen NextScreen { get; protected set; }

    public abstract void Show();

    protected void WriteLine(string message)
    {
        Console.WriteLine(message);
    }

    protected ConsoleKeyInfo ReadKey()
    {
        return Console.ReadKey();
    }
}

class WelcomeScreen : Screen
{
    public override void Show()
    {
        WriteLine("Welcome to Mud.");
        WriteLine("Press Enter to logon or Escape to exit.");

        do 
        {
            var key = ReadKey().Key;
            if (key == ConsoleKey.Enter)
            {
                NextScreen = new LogonScreen();
                return;
            }
            else if (key == ConsoleKey.Escape)
            {
                NextScreen = null;
                return;
            }
        }
        while(true);
    }
}

class LogonScreen : Screen
{
    public override void Show()
    {
        WriteLine("What is your name?");
        ReadKey();
    }
}

class NewUserScreen : Screen
{
    public override void Show()
    {

    }
}

Context

StackExchange Code Review Q#147100, answer score: 2

Revisions (0)

No revisions yet.