Execution Context - wwestlake/Labyrinth GitHub Wiki
To design a context system that interacts with the Web API objects through interfaces, we need to define a set of interfaces that represent the key entities (ApplicationUser, Character, Room, etc.) and the actions that can be performed on them. These interfaces will be used by the context system to interact with the underlying data while adhering to the SOLID principles, especially dependency inversion.
Start by defining the interfaces for your key entities in the Labyrinth.Common.Interfaces namespace. These interfaces will include the necessary properties and methods that the context system will need to interact with.
namespace Labyrinth.Common.Interfaces
{
public interface IApplicationUserContext
{
string Id { get; }
string Name { get; }
int Health { get; set; }
IRoom CurrentRoom { get; set; }
bool HasPermission(string permission);
void Heal(int amount);
// Additional properties and methods as needed
}
public interface ICharacterContext
{
string Name { get; }
int Health { get; set; }
bool IsEnemy { get; }
void Attack(ICharacter target);
void TakeDamage(int amount);
// Additional properties and methods as needed
}
public interface IRoomContext
{
string Id { get; }
string Name { get; }
List<ICharacter> Enemies { get; }
List<IExit> Exits { get; }
ICharacterContext FindCharacter(string name);
void AddCharacter(ICharacter character);
void RemoveCharacter(ICharacter character);
// Additional properties and methods as needed
}
public interface IExitContext
{
string Id { get; }
string Direction { get; }
IRoomContext LeadsTo { get; }
// Additional properties and methods as needed
}
// Additional interfaces for other entities like items, inventory, etc.
}
In the Labyrinth.Common namespace, define the context classes that will use these interfaces. These classes will manage the state, variables, and history, interacting with the entities through the interfaces.
using Labyrinth.Common.Interfaces;
using System.Collections.Generic;
namespace Labyrinth.Common
{
public abstract class CommandContext
{
public IApplicationUserContext User { get; protected set; }
public bool IsElevated { get; protected set; }
protected Dictionary<string, object> Variables { get; } = new Dictionary<string, object>();
protected Stack<ContextHistory> History { get; } = new Stack<ContextHistory>();
public abstract bool HasPermission(string command);
public void SetVariable(string name, object value)
{
Variables[name] = value;
}
public object GetVariable(string name)
{
return Variables.ContainsKey(name) ? Variables[name] : null;
}
public void SaveState()
{
// Save the current state to the history stack
History.Push(new ContextHistory(User, Variables));
}
public void Undo()
{
if (History.Count > 0)
{
var lastState = History.Pop();
User = lastState.User;
Variables.Clear();
foreach (var kvp in lastState.Variables)
{
Variables[kvp.Key] = kvp.Value;
}
}
}
// Define any additional methods that are common to all contexts
}
public class UserContext : CommandContext
{
public UserContext(IApplicationUser user)
{
User = user;
IsElevated = false;
}
public override bool HasPermission(string command)
{
// Check user-specific permissions
return User.HasPermission(command);
}
// Additional methods specific to user context
}
public class AdminContext : CommandContext
{
public AdminContext(IApplicationUserContext adminUser)
{
User = adminUser;
IsElevated = true;
}
public override bool HasPermission(string command)
{
// Check admin-specific permissions
return User.HasPermission(command);
}
public void SetContextForUser(IApplicationUserContext targetUser)
{
SaveState();
User = targetUser;
}
// Additional methods specific to admin context
}
public class SystemContext : CommandContext
{
public SystemContext()
{
IsElevated = true;
}
public override bool HasPermission(string command)
{
// System context typically has permission for all commands
return true;
}
// Additional methods specific to system context
}
// Utility class to save context state
public class ContextHistory
{
public IApplicationUser User { get; }
public Dictionary<string, object> Variables { get; }
public ContextHistory(IApplicationUserContext user, Dictionary<string, object> variables)
{
User = user;
Variables = new Dictionary<string, object>(variables);
}
}
}
On the Web API side, you will implement these interfaces in your actual entity classes. This way, the context system interacts only with the interfaces, making it decoupled from the concrete implementations.
namespace Labyrinth.API.Entities
{
public class ApplicationUser : IApplicationUserContext
{
public string Id { get; set; }
public string Name { get; set; }
public int Health { get; set; }
public IRoom CurrentRoom { get; set; }
public bool HasPermission(string permission)
{
// Implement permission checking logic
}
public void Heal(int amount)
{
Health += amount;
}
// Implement additional methods and properties as needed
}
public class Character : ICharacterContext
{
public string Name { get; set; }
public int Health { get; set; }
public bool IsEnemy { get; set; }
public void Attack(ICharacter target)
{
// Implement attack logic
}
public void TakeDamage(int amount)
{
Health -= amount;
}
// Implement additional methods and properties as needed
}
public class Room : IRoomContext
{
public string Id { get; set; }
public string Name { get; set; }
public List<ICharacter> Enemies { get; set; }
public List<IExit> Exits { get; set; }
public ICharacter FindCharacter(string name)
{
// Implement character search logic
}
public void AddCharacter(ICharacter character)
{
// Implement logic to add a character to the room
}
public void RemoveCharacter(ICharacter character)
{
// Implement logic to remove a character from the room
}
// Implement additional methods and properties as needed
}
public class Exit : IExitContext
{
public string Id { get; set; }
public string Direction { get; set; }
public IRoom LeadsTo { get; set; }
// Implement additional methods and properties as needed
}
}
#Testing: Write unit tests to ensure that the context system interacts correctly with the interfaces and handles scenarios like variable management, state saving, undo operations, and context switching.
Integration: Inject the appropriate implementations of these interfaces into the context system using dependency injection in the Web API. Ensure that the system is able to dynamically interact with these interfaces during command execution.
By designing the context system to depend on interfaces rather than concrete implementations, you achieve a more flexible, decoupled architecture. This approach allows you to limit dependencies, adhere to SOLID principles, and simplify testing and maintenance. The interfaces serve as contracts between the context system and the underlying data entities, ensuring that your MUD game remains scalable and maintainable.