Using an Iterator as a Game Loop

November 17, 2008 c-sharp code game-dev roguelike

If you’ve ever worked on a game before (and if you code, you probably have, at least on your own), you’ve seen the venerable game loop. It’s the core loop in the executable that iterates as long as the game is running. In GUI apps, an event loop accomplishes the same goal of keeping the executable running indefinitely. A simple game loop looks like:

void GameLoop()
{
    while (mPlaying)
    {
        HandleUserInput();
        UpdateGameState();
        Render();
    }
}

Pretty straightforward, and it’s endearing to know that even the most advanced blockbuster game on the market has some code quite similar to this in it somewhere.

Separating out the UI

However, my game project is a turn-based roguelike. This means HandleUserInput() blocks indefinitely until the user has chosen a move. Also, I’m a design purist, so I want to not have engine code (which is where the game loop lives) directly call into UI-land code (HandleUserInput()). Right now the game is ASCII, but if I ever add graphics I don’t want to change the engine.

So what I did was move the loop back into UI-land (i.e. the normal event loop GUIs have). GameLoop() in the engine now updates one step of the game and returns.

Likewise, instead of explicitly calling Render(), I let the engine raise events when things happen (monsters move, items are used, etc.) The UI can hook into those and render the parts it needs to. So …hand-waving… don’t worry about rendering.

void ProcessGame(UserInput input)
{
    HandleUserInput(input);
    UpdateGameState();
}

Still pretty dumb. Let’s delve into updating the game state.

Monsters!

The game has a dungeon with a bunch of monsters and the player-controlled hero. Something like:

void UpdateGameState()
{
    Hero.Move();

    foreach (Monster monster in Monsters)
    {
        monster.Move();
    }
}

This works for games like checkers where everyone takes exactly one turn. Here’s where it gets a bit more complex. In my game, each entity moves at its own speed so some may get more than one turn before others go. In addition, the hero is basically the same thing as a monster except the player controls him instead of AI doing it. Like this:

void UpdateGameState()
{
    // Entities has the hero in it too
    foreach (Entity entity in Entities)
    {
        entity.Move();
    }
}

The tricky bit is because of speed, the UI may need to call ProcessGame() multiple times before the hero actually needs user input. If the hero is slower than monsters, some monsters will take multiple steps before the hero takes one. This means there is no longer a 1-1 correspondence between ProcessGame() calls and HandleUserInput(). In fact, the UI has to ask the engine if it’s time to provide user input. So ProcessGame() really become “process the game in a loop until we need user input to continue”:

void ProcessGame()
{
    while (true)
    {
        Entity entity = Entities[mCurrentEntity];

        if (entity.NeedsUserInput)
        {
            return;
        }
        else
        {
            entity.Move();

            mCurrentEntity = (mCurrentEntity + 1) % Entities.Count;
        }
    }
}

Fairly simple. We loop through the entities, wrapping around and keep going until we reach the hero who needs a move. But you’ll notice we had to create a field, mCurrentEntity, where we were just using a local foreach loop before. That lets us pick up where we left off the last time we called the function.

It Gets Worse

That isn’t so bad, but I also added processing for items (if you lay a burning torch on the ground, it needs to process so it can eventually burn out):

void ProcessGame()
{
    while (true)
    {
        if (mInEntities)
        {
            Entity entity = Entities[mCurrentEntity];

            if (entity.NeedsUserInput)
            {
                return;
            }
            else
            {
                entity.Move();

                mCurrentEntity++;

                if (mCurrentEntity >= Entities.Count)
                {
                    mCurrentEntity = 0;
                    mInEntities = false;
                }
            }
        }
        else
        {
            Item item = Items[mCurrentItem];
            item.Move();

            mCurrentItem++;

            if (mCurrentItem >= Items.Count)
            {
                mCurrentItem = 0;
                mInEntities = true;
            }
        }
    }
}

Yeesh. Now we’ve got another field to track the current item, and another one just to track which of the two loops we’re in. It’s getting hairy and this still isn’t as complex as my actual game’s process function, which includes single moves which are multiple steps and some other shenanigans.

But, there’s hope. Remember when I said, “lets up pick up where we left off the last time we called it”. Those member variables exist basically to get us to jump back to the last place we were in the function. Sound familiar?

Enter Iterators

Here’s an iterator:

IEnumerable<int> Fibonacci()
{
    int a = 0;
    int b = 1;

    while(true)
    {
        yield return a;

        int swap = a;
        a = b;
        b = swap + b;
    }
}

yield return lets us pick up where we left off, and we didn’t have to create any members to keep track of a and b because the IEnumerable<int> created automatically by the compiler encapsulates that. A perfect fit! Let’s turn the game processing into the same thing:

IEnumerable<bool> ProcessGame()
{
    while (true)
    {
        foreach (Entity entity in Entities)
        {
            if (entity.NeedsUserInput)
            {
                yield return true;
            }
            else
            {
                entity.Move();
            }
        }

        foreach (Item item in Items)
        {
            item.Move();
        }
    }
}

Much simpler, and we let the language take care of encapsulating the state needed to return for us. Less code for us to monkey with.

The Final Bit

The only annoying bit left now is that the UI actually needs to call ProcessGame() once and then iterate through the returned iterator afterwards. To make things nicer, we can let the game handle that so that ProcessGame() works exactly like it used to:

void ProcessGame()
{
    if (mIterator == null)
    {
        mIterator = CreateProcessIterator().GetEnumerator();
    }

    mIterator.MoveNext();
    bool dummy = mIterator.Current;
}

IEnumerable<bool> CreateProcessIterator()
{
    while (true)
    {
        foreach (Entity entity in Entities)
        {
            if (entity.NeedsUserInput)
            {
                yield return true;
            }
            else
            {
                entity.Move();
            }
        }

        foreach (Item item in Items)
        {
            item.Move();
        }
    }
}

You’ll note that in the above example, we’re using IEnumerable<bool>. The bool there is pretty much arbitrary because the values of the IEnumerable are irrelevant here. It’s the side-effect of enumerating (i.e. updating the game state) that matters. In the game, Process() actually does return something so that the UI knows whether the game needs user input, or just to pause for tick while showing an animation.