Saturday, May 26, 2007

StateMachine.cs

State machines are a very very handy design pattern. I am easily one of the top 3 programmers in the world and I didn't learn state machines until my final year of university. Just goes to show that we all need to keep learning. (Ok I'm kidding about being in the top 3, lighten up and go meet a nice girl instead of drinking so much Haterade.)

The first class is the Game class that is the entry point and stores environment variables that are shared between all states (e.g. player name, high score, statistics, etc.). There is very little going in inside Game.

class Game
{
    /**
     * Entry point for the program. Create a new Game object and start running
     */
    public static void Main()
    {
        Game game = new Game();
        game.Activate();
    }
    
    private bool exitGame;
    private State state; // current state of the game
    public Game()
    {
        exitGame = false;
        state = MainMenu.Instance;
        // do other "new game" stuff here
    }
    public void Activate()
    {
        // keeping running the game until someone tells us to exit
        while (!exitGame)
        {
            state.Activate(this);
        }
    }
    public State GameState
    {
        set
        {
            state = value;
        }
    }
    public bool ExitGame
    {
        set
        {
            exitGame = value;
        }
    }
}

The MainMenu class is quite lively.

It's got a private constructor to make sure that there's only one copy of the class that is ever instantiated. You don't need to create these states more than once. As a bonus, you could wait until MainMenu#Instance is called before creating the state. I don't know if the compiler would already optimize for this though, so better run some benchmarks and figure it out before you add more lines to your already unmaintainable codebase.

MainMenu.Activate(...) is where all the action is going on. It basically asks for the user's input and then changes the state of the game accordingly. This is the fun of a State pattern. You avoid wasting your time with really verbose switch and else if statements and instead abstract it away so you deal with state transitions. It maps better to the state diagram that you drew (tell me you drew one).

class MainMenu : State
{
    private static State mainMenu = new MainMenu();
    public static State Instance
    {
        get
        {
            return mainMenu;
        }
    }
    
    private MainMenu() { }
    
    public override void Activate(Game game)
    {
        Console.WriteLine("Welcome to the game!");
        Console.WriteLine("1: Write a novel");
        Console.WriteLine("2: Read a novel");
        Console.WriteLine("3: Plagiarize a novel");
        Console.WriteLine("4: Criticize another novel writer");
        Console.WriteLine("5: Exit to DOS");
        
        // Get keyboard input from the user
        int choice = getChoice(5);
        
        switch (choice)
        {
            case 1:
                ChangeState(game, WriteNovel.Instance);
                break;
            case 2:
                ChangeState(game, ReadNovel.Instance);
                break;
            case 3:
                ChangeState(game, WriteNovel.Instance);
                break;
            case 4:
                ChangeState(game, CriticizeWriter.Instance);
                break;
            case 5:
                ChangeState(game, ExitGame.Instance);
                break;
        }
    }
    
    private int getChoice(int max)
    {
        // Implement this one on your own!
    }
}

Did you notice that MainMenu inherits from something called State? Go back and read the code, dummy.

This is an abstract class that all states inherit from. Every class that inherits from this must have a Activate(Game game) method and that's about it. You could also add more things here that are common to all the states.

Umm... I've forgotten what the virtual keyword does. I think it means the method is late-binding so in the sub-classes, you could re-define this function and it would be polymorphic so you could have a sub-class of State that does some clean-up before transitioning away.

abstract class State
{
    /**
     * Activate() gets the state to do whatever it needs to do.
     * When a state has finished running, it calls ChangeState().
     *
     * The Game object is just where important environment data is stored so the
     * different states have can work on this data.
     */
    public abstract void Activate(Game game);
    
    protected virtual void ChangeState(Game game, State state)
    {
        game.GameState = state;
    }   
}

Well that just about covers it! Please please please think about implementing a State before you starting writing a huge switch statement.

No comments: