Virtual Spelunking: Exits and Treasure

I’ve spent several posts detailing how to procedurally generate a cave for a player to explore. Now, it’s about time for me to fill that cave with things for the player to do. The most basic goal for any player dropped into the middle of a cave is to find a way out, so the first thing I’ll do is drop a starting point and an exit point into the cave. Just trying to escape a cave by itself is a little too sparse though, so I’ll also add treasure for the player to find and collect while they’re looking for the exit. Now, it’s starting to sound like a real game.

Placing the player starting point and exit is a straight-forward process. The primary case I want to avoid is placing the starting point and exit too close together. After all, I spent all this time generating a cave, I want the player to have to go through it!

In order to accomplish this, I simply place the player starting point and the dungeon exit on opposite ends of the map. For the starting point, I scan each column from the left and randomly pick a floor space from the first column that isn’t all wall onto which to drop the player start point. I then repeat the process from the right side of the map scanning left and drop the exit on a random floor space in the first available column. For maps that are taller than they are wide, I do the same thing only scanning from the top for starting point placement and from the bottom for exit placement.

For treasure placement, I’m still looking for a solution that I like. For now though, I use the Togetherness algorithm I used for floor decoration. This time around, I use just two states for togetherness (0 – no treasure, 1 – treasure) and a significantly lower chance of any given square being non-empty (5% as opposed to the 40% chance for any-given floor space to be something other than gray tile.) After a bit of parameter tweaking, I get a satisfactory dispersal of treasure. (In the picture below, ‘T’ marks the spot.)

Now, with a bit of simple collision code and those nice treasure chest graphics from the PlanetCute tileset I can make my cave a more lucrative place to explore.

You can find my (decidedly unsophisticated) code for placing starting point and exit points below:

void _PlaceStartAndEndPoints(Random rng)
{
    int maxColumn = _mapToPopulate[0].Count;
    int maxRow = _mapToPopulate.Count;
    bool bVerticalMap = maxRow > maxColumn;

    //Add player start to space on furthest left column / top row
    if (!bVerticalMap)
    {
        for (int currColumn = 0; currColumn < maxColumn; ++currColumn)
        {
            List&lt;int&gt; floorsInCol = new List&lt;int&gt;();
            for (int currRow = 0; currRow < maxRow; ++currRow)
            {
                if (_mapToPopulate[currRow][currColumn].SpaceType == CaveSpaceType.Floor)
                {
                    floorsInCol.Add(currRow);
                }
            }

            if (floorsInCol.Count > 0)
            {
                _mapInhabitants.Add(new MapInhabitant(InhabitantType.StartingPoint, currColumn, floorsInCol[rng.Next(floorsInCol.Count)]));
                break;
            }
        }
    }
    else
    {
        for (int currRow = 0; currRow < maxRow; ++currRow)
        {
            List&lt;int&gt; floorsInRow = new List&lt;int&gt;();
            for (int currColumn = 0; currColumn < maxColumn; ++currColumn)
            {
                if (_mapToPopulate[currRow][currColumn].SpaceType == CaveSpaceType.Floor)
                {
                    floorsInRow.Add(currColumn);
                }
            }

            if (floorsInRow.Count > 0)
            {
                _mapInhabitants.Add(new MapInhabitant(InhabitantType.StartingPoint, floorsInRow[rng.Next(floorsInRow.Count)], currRow));
                break;
            }
        }
    }

    //Add end point to space on furthest right column / bottom row
    if (!bVerticalMap)
    {
        for (int currColumn = maxColumn - 1; currColumn >= 0; --currColumn)
        {
            List&lt;int&gt; floorsInCol = new List&lt;int&gt;();
            for (int currRow = 0; currRow < maxRow; ++currRow)
            {
                if (_mapToPopulate[currRow][currColumn].SpaceType == CaveSpaceType.Floor)
                {
                    floorsInCol.Add(currRow);
                }
            }

            if (floorsInCol.Count > 0)
            {
                _mapInhabitants.Add(new MapInhabitant(InhabitantType.EndingPoint, currColumn, floorsInCol[rng.Next(floorsInCol.Count)]));
                break;
            }
        }
    }
    else
    {
        for (int currRow = maxRow - 1; currRow >= 0; --currRow)
        {
            List&lt;int&gt; floorsInRow = new List&lt;int&gt;();
            for (int currColumn = 0; currColumn < maxColumn; ++currColumn)
            {
                if (_mapToPopulate[currRow][currColumn].SpaceType == CaveSpaceType.Floor)
                {
                    floorsInRow.Add(currColumn);
                }
            }

            if (floorsInRow.Count > 0)
            {
                _mapInhabitants.Add(new MapInhabitant(InhabitantType.EndingPoint, floorsInRow[rng.Next(floorsInRow.Count)], currRow));
                break;
            }
        }
    }
}

And the treasure placement code:
(You can find my Togetherness implementation here)

void _PlaceTreasure(Random rng)
{
    //Working but not satisfactory, trying Browninan walk?

    TogethernessAutomoton togetherArray = new TogethernessAutomoton(_randomSeed, _mapToPopulate[0].Count, _mapToPopulate.Count, 1, _treasureDensity, 1000, 6, 12);
    int[] resultArray = new int[togetherArray.DesiredArraySize];
    togetherArray.FillArray(ref resultArray);

    int startPointCol = -1, startPointRow = -1;
    int endPointCol = -1, endPointRow = -1;

    foreach (MapInhabitant inhabitant in _mapInhabitants)
    {
        switch (inhabitant.InhabitantType)
        {
            case InhabitantType.StartingPoint:
                startPointCol = inhabitant.Column;
                startPointRow = inhabitant.Row;
                break;
            case InhabitantType.EndingPoint:
                endPointCol = inhabitant.Column;
                endPointRow = inhabitant.Row;
                break;
        }
    }

    for (int rowIdx = 0; rowIdx < _mapToPopulate.Count; ++rowIdx)
    {
        for (int colIdx = 0; colIdx < _mapToPopulate[rowIdx].Count; ++colIdx)
        {
            if (_mapToPopulate[rowIdx][colIdx].SpaceType == CaveSpaceType.Floor)
            {
                if ( (colIdx == startPointCol && rowIdx == startPointRow) ||
                    (colIdx == endPointCol && rowIdx == endPointRow) )
                {
                    continue;
                }

                if (resultArray[colIdx * _mapToPopulate.Count + rowIdx] == 1)
                {
                    _mapInhabitants.Add(new MapInhabitant(InhabitantType.Treasure, colIdx, rowIdx));
                }
            }
        }
    }
}
Share this Article:
  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Yahoo! Buzz
  • Twitter
  • Google Bookmarks
  • Print