patterncsharpMinor
Logon state management
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:
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
```
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.
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...
The name of the method suggests it should be a property but it looks like an event. Did you mean
I've never seen someone writing conditions backwards ;-)
The name suggests it's a factory but it doesn't really create anything. It has a lot of
Repositories don't create things. They store them. For creating things we use factories.
You have an abstraction for almost everything but the
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
In the next step you can create a data model for each screen. Let's say
You can start the game with the
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 != _sessionInputHandlerI've never seen someone writing conditions backwards ;-)
SessionInputHandlerFactoryThe 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.
PlayerYou 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 != _sessionInputHandlerSessionInputHandlerFactoryvar 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.