Jeremy Davis
Jeremy Davis
Sitecore, C# and web development
Article printed from: https://blog.jermdavis.dev/posts/2025/textadventure-commands

Text adventures: The player's commands

How they tell the game what they want to do

Published 01 December 2025
Text Games C# ~4 min. read
This is post 2 in an ongoing series titled Text adventures

The world model from the previous post is all well and good, but the player needs to be able to interact with it. What do we need to add to allow that? Well we need an abstraction for the things the game is going to allow the player to do.

Step forward: Commands url copied!

Generally these sorts of games use a "verb noun" structure for doing things. Some of these will be simple verbs like "move" or "look", but your puzzles might need to respond to more arbitrary things like "catch seagull" or "press button". So we need a mechanism to implement how these verbs get processed, and that mechanism is the Command.

We need to give these things a name (the verb the player will type to trigger a Command) and an implementation of some logic that affects or reports on the game world. The player may also need some clues about the verbs they have access to and how they can work, so there needs to be a place to put that too.

There's a base script type here, which provides the name property. This will also be used in the next instalment of this series when we get onto Behaviour classes, but for the moment we'll just focus on the Command:

public abstract class Command : Script
{
    public string Help { get; }

    public Command(string name, string help) : base(name)
    {
        Help = help;
    }

    public abstract void Execute(World world, string[] parameters);
}

					

So the beginnings of a command for "look" might be:

public class LookCommand : Command
{
    public LookCommand() : base("look", "look - describe the world around you")
    {
    }

    public override void Execute(World world, string[] parameters)
    {
        var r = world.Player.Room;

        Console.WriteLine($"{r.Article} {r.Name}");
        if (!string.IsNullOrWhiteSpace(r.Description))
        {
            Console.WriteLine(r.Description);
        }
        
        if (r.Exits.Any())
        {
            Console.WriteLine("Exits:");
            foreach (var exit in r.Exits)
            {
                Console.WriteLine($"  To the {exit.Key} is {exit.Value.Article} {exit.Value.Name}");
            }
        }

        if (r.Characters.Any())
        {
            Console.WriteLine("Characters:");
            foreach (var character in r.Characters)
            {
                if (character != world.Player)
                {
                    Console.WriteLine($"  {character.Article} {character.Name}");
                }
            }
        }

        if (r.Items.Any())
        {
            Console.WriteLine("Items:");
            foreach(var item in r.Items)
            {
                Console.WriteLine($"  {item.Article} {item.Name}");
            }
        }
    }
}

					

In this example it looks through the current room, its exits, other characters and items present. And for each of those things it displays them to the player. Other commands might try to change the state of the world as well - like moving the player - or show the help for all the verbs player can use at this point in the game.

But for the player to use them we need a way to attach the currently valid commands to the player's character. That gives the system a way to know what they can do:

public class Player : Character
{
    public List<string> Commands { get; } = new();
}

					

So when the world is being set up, some extra helper methods can create the player and give them their base set of commands:

var playerCharacter = world
    .CreatePlayer("The", "Hero", "It's you")
    .MoveCharacterToRoom(beach)
    .AddCommand(
        "move", "look",
        "quit", "examine"
    );

					

That's the simplest scenario for giving the player a verb they can use, but later in the series we'll look at scenarios where the player might have a Commands added or removed in reaction to the state of the game. Because of their current location, or something in their inventory for example.

The game loop url copied!

The next thing to consider is how the game gathers input and runs these commands in response to the user's input? The beginnings of main game code might look like:

public class Game
{
    public bool Running { get; set; } = true;
    public World World { get; }

    public Game(World world)
    {
        World = world;
    }
    
    public void Update(string[] input)
    {
        var verb = input[0].ToLower();
        var parameters = input[1..];

        var commandName = World.Player.Commands.Where(c => c.Name == verb).FirstOrDefault();
        if (commandName == null)
        {
            World.Display($"You can't '{verb}' right now.");
            return;
        }

        command.Execute(World, parameters);
    }
}

					

Each time Update() is called it tries to match the first word entered (which should be the command verb) with the commands currently attached to the player. If none matches then we can't process this input and it displays an error. But if there is a match then the Command can be run, passing in the current state of the world and any other words the user entered.

That input being processed here is gathered by this outer loop:

var world = CreateYourGameWorld();
var game = new Game(world);
while (game.Running)
{
    Console.Write("> ");
    var command = Console.ReadLine() ?? string.Empty;
    var input = parser.Parse(command);

    if (input.Length > 0)
    {
        game.Update(input);
    }
}

					

Now in reality making the player type the whole command each time can be a bit tedious, so after testing for exact matches, you can also test for partial matches so that lo can resolve to look or similar. And with that complexity, it can be helpful to pull that logic out to a helper to replace the Where() query above:

public static string? MatchVerb(IEnumerable<string> playerCommands, string verb)
{
    var command = playerCommands
        .Where(c => string.Compare(c, verb, ignoreCase: true) == 0)
        .FirstOrDefault();

    if(command != null)
    {
        return command; 
    }

    var partialCommand = playerCommands
        .Where(c => c.StartsWith(verb, StringComparison.InvariantCultureIgnoreCase))
        .FirstOrDefault();

    if (partialCommand != null)
    {
        return partialCommand;
    }

    return null;
}

					

A game might also look at synonyms for some verbs. Having "talk" and "speak" achieve the same thing, or "drop" and "put" for example. The example code here doesn't do that, but it is something that famous games have done for both verbs and items - you can see synonym definitions in the source for the Hitchhiker's Guide to the Galaxy, for example.

With this framework in place, it's easy to add classes for the other core commands like "examine" (which finds an item in the player's inventory or current room and displays it) and "move" (transfers the player via an exit to the correct target room) and we get a very basic game. Though the way the state-altering behaviour of this command works will make more sense after the next post in this series.

As before, the example code for all the stuff coming in this series, and an example game is available on Github.

Next up: How can we make the behaviour of the world more interesting than just moving and looking, by letting the game respond and change state when the user is issuing commands?

↑ Back to top