Tag Archives: Map

RogueSharp V3 Tutorial – Stairs

Next Tutorial Post – Not Available Yet
Previous Tutorial Post – Doors

Goal

The purpose of this tutorial is start placing stairs in our dungeon. We want to be able to proceed down stairs to get into deeper and more difficult levels.

  • Stairs can either go up or down
  • The symbol for stairs going down will be a greater than sign “>”
  • The symbol for stairs going up will be a less than sign “<“
  • Each dungeon level will have one up and one down staircase
  • Stairs going down can be descended by the player by pressing the ‘>’ or ‘.’ key
  • Once a player has gone down to the next level they cannot go back
  • Stairs going up are only used to indicate where the player came from
  • When a player goes down stairs a brand new level is generated

Creating the Stairs Class

To begin we need a new class to represent the stairs as we outlined in our goals above. The stairs class should inherit from IDrawable because we will want to draw it on the map console. Create a new file named Stairs.cs in the Core folder and place the following code in it.

public class Stairs : IDrawable
{
  public RLColor Color
  {
    get; set;
  }
  public char Symbol
  {
    get; set;
  }
  public int X
  {
    get; set;
  }
  public int Y
  {
    get; set;
  }
  public bool IsUp
  {
    get; set;
  }

  public void Draw( RLConsole console, IMap map )
  {
    if ( !map.GetCell( X, Y ).IsExplored )
    {
      return;
    }

    Symbol = IsUp ? '<' : '>';

    if ( map.IsInFov( X, Y ) )
    {
      Color = Colors.Player;
    }
    else
    {
      Color = Colors.Floor;
    }

    console.Set( X, Y, Color, null, Symbol );
  }
}

Updating DungeonMap Class

Now that our Stairs class is created, we need to update DungeonMap.cs with a few changes to be able to use the Stairs. At the top of DungeonMap.cs add the following two properties for our StairsUp and StairsDown next to the existing Rooms and Doors properties.

public Stairs StairsUp { get; set; }
public Stairs StairsDown { get; set; }

Next update the DungeonMap constructor to call Clear() on the SchedulingSystem when a new DungeonMap is constructed. We do this because when we make a new level by going down stairs we want to make sure that all of the monsters from the previous level are removed from the schedule and do not continue to try to act.

public DungeonMap()
{
  Game.SchedulingSystem.Clear();

  // Previous code omitted...
}

Now we need a new method which will check to see if the player is standing on the stairs going down. Create a method called CanMoveDownToNextLevel() with the following code.

public bool CanMoveDownToNextLevel()
{
  Player player = Game.Player;
  return StairsDown.X == player.X && StairsDown.Y == player.Y;
}

In the Doors tutorial last time we forgot to update the Draw() method and had to have a blog reader point out the mistake. We’ll try not to make the same mistake this time. Update the Draw() method and add the following lines of code to draw the stairs.

// Add the following code after we finish drawing doors.
StairsUp.Draw( mapConsole, this );
StairsDown.Draw( mapConsole, this );

Updating the MapGenerator Class

The next class we need to update is MapGenerator. We need to make sure that when we generate new maps, stairs are created along with the rest of the dungeon features. Open MapGenerator.cs and create a new private method named CreateStairs().

private void CreateStairs()
{
  _map.StairsUp = new Stairs
  {
    X = _map.Rooms.First().Center.X + 1,
    Y = _map.Rooms.First().Center.Y,
    IsUp = true
  };
  _map.StairsDown = new Stairs
  {
    X = _map.Rooms.Last().Center.X,
    Y = _map.Rooms.Last().Center.Y,
    IsUp = false
  };
}

We are not doing anything too fancy to generate the stairs. We are creating the stairs up in the center of the first room that was generated. This is the same room that the player starts in and the player is also in the center of the room, so we’ll offset the X coordinate by 1 to put the stairs next to the player. The last room we generated gets stairs going down and again we place them in the center of the room.

Make sure to call the CreateStairs() method from the existing CreateMap() method right before calling PlacePlayer();

public DungeonMap CreateMap()
{
  // Previous code omitted...

  // Call right before calling PlacePlayer();
  CreateStairs();

  // Previous code
  PlacePlayer();
}

Also we want to change the signature of the MapGenerator constructor and add an additional integer parameter called mapLevel.

public MapGenerator( int width, int height, int maxRooms, int roomMaxSize, int roomMinSize, int mapLevel )
{
  // Keep all existing code in the constructor
}

If you have any sort of static analysis on like FxCop it will complain about having an unused parameter in the method. We really shouldn’t add it until we are prepared to use it, but rest assured we will use it very soon.

Updating the Game Class

Open up Game.cs which will be the final class that we need to update. Start by adding a new private static int member variable in with the rest of the member variables at the top of the class.

private static int _mapLevel = 1;

Next change the line was setting the console title in the Main() method. Also add the _mapLevel parameter to line where we instantiate a new MapGenerator.

// Old code was...
// string consoleTitle = $"RougeSharp V3 Tutorial - Level 1 - Seed {seed}";

// New code is
string consoleTitle = $"RougeSharp V3 Tutorial - Level {_mapLevel} - Seed {seed}";


// Old code was...
// MapGenerator mapGenerator = new MapGenerator( _mapWidth, _mapHeight, 20, 13, 7 );

// New code is
MapGenerator mapGenerator = new MapGenerator( _mapWidth, _mapHeight, 20, 13, 7, _mapLevel );

Finally in the OnRootConsoleUpdate(…) method where we are checking to see which key was pressed ad the following else if onto the end of the if block.

else if ( keyPress.Key == RLKey.Period )
{
  if ( DungeonMap.CanMoveDownToNextLevel() )
  {
    MapGenerator mapGenerator = new MapGenerator( _mapWidth, _mapHeight, 20, 13, 7, ++_mapLevel );
    DungeonMap = mapGenerator.CreateMap();
    MessageLog = new MessageLog();
    CommandSystem = new CommandSystem();
    _rootConsole.Title = $"RougeSharp RLNet Tutorial - Level {_mapLevel}";
    didPlayerAct = true;
  }
}

This last bit of code just checks for the “>” or “.” key being pressed. It then calls into DungeonMap.CanMoveDownToNextLevel() which will return true if the Player is standing on a stairway leading down. We then generate a new map and increment the _mapLevel.

If you run the game now you should be able to explore until you find stairs and descend deeper into the dungeon.

stairs

Going down stairs

Closing Thoughts

Although we got our stairs working we are still missing some important things. Currently levels don’t get any more difficult the deeper we go.

As always the code for the tutorial series so far can be found on Bitbucket:
https://bitbucket.org/FaronBracy/roguesharpv3tutorial/commits/tag/16Stairs

Bored waiting for the next tutorial post? The completed project is already available on Bitbucket.

RogueSharp V3 Tutorial – Doors

Next Tutorial Post – Stairs
Previous Tutorial Post – Monster Behaviors

Goal

The purpose of this tutorial is to start placing doors in our dungeon. We want the doors to start out simple but be able to be expanded upon in the future.

  • Doors can have 2 states: open or closed
  • All doors start as closed
  • A closed door blocks field-of-view
  • A door can be opened by any actor (Player or Monster)
  • Once a door is opened it cannot be closed again
  • An actor entering the same cell as a door will open it automatically
  • The symbol for a closed door will be a plus sign “+”
  • The symbol for an open door will be a minus sign “-“

Setting Door Colors

We already set up the swatch of colors to use in our game but we never specified which colors doors should be. Let’s do that now. Open Colors.cs and add the following code to pick colors from the Swatch for different parts of the door.

public static RLColor DoorBackground = Swatch.ComplimentDarkest;
public static RLColor Door = Swatch.ComplimentLighter;
public static RLColor DoorBackgroundFov = Swatch.ComplimentDarker;
public static RLColor DoorFov = Swatch.ComplimentLightest;

Creating the Door Class

Next we need a new class to represent all of the properties of a door that we outlined in our goals at the beginning. The door class should inherit from IDrawable because we will want to draw it on the map console. Create a new file named Door.cs in the Core folder and place the following code in it.

public class Door : IDrawable
{
  public Door()
  {
    Symbol = '+';
    Color = Colors.Door;
    BackgroundColor = Colors.DoorBackground;
  }
  public bool IsOpen { get; set; }

  public RLColor Color { get; set; }
  public RLColor BackgroundColor { get; set; }
  public char Symbol { get; set; }
  public int X { get; set; }
  public int Y { get; set; }

  public void Draw( RLConsole console, IMap map )
  {
    if ( !map.GetCell( X, Y ).IsExplored )
    {
      return;
    }

    Symbol = IsOpen ? '-' : '+';
    if ( map.IsInFov( X, Y ) )
    {
      Color = Colors.DoorFov;
      BackgroundColor = Colors.DoorBackgroundFov;
    }
    else
    {
      Color = Colors.Door;
      BackgroundColor = Colors.DoorBackground;
    }

    console.Set( X, Y, Color, BackgroundColor, Symbol );
  }
}

Notice that we use the colors and symbols for the door that we determined earlier.

Updating the DungeonMap Class

Before we can start placing doors in our dungeon it’s important to add a few helper methods to our DungeonMap class for working with doors. Open DungeonMap.cs and add the following lines of code.

First add a new public property to the class at the top where we already have a list of rooms.

public List<Door> Doors { get; set; }

Be sure to initialize this list in the constructor

public DungeonMap()
{
  // Previous constructor code omitted...

  Doors = new List<Door>();
}

Next we need to add two new methods to the class. GetDoor(…) and OpenDoor(…)

// Return the door at the x,y position or null if one is not found.
public Door GetDoor( int x, int y )
{
  return Doors.SingleOrDefault( d => d.X == x && d.Y == y );
}

// The actor opens the door located at the x,y position
private void OpenDoor( Actor actor, int x, int y )
{
  Door door = GetDoor( x, y );
  if ( door != null && !door.IsOpen )
  {
    door.IsOpen = true;
    var cell = GetCell( x, y );
    // Once the door is opened it should be marked as transparent and no longer block field-of-view
    SetCellProperties( x, y, true, cell.IsWalkable, cell.IsExplored );

    Game.MessageLog.Add( $"{actor.Name} opened a door" );
  }
}

Last we need to update SetActorPosition(…) and call a the new method OpenDoor(…) which we just made. Call this immediately after SetIsWalkable(…).

// Returns true when able to place the Actor on the cell or false otherwise
public bool SetActorPosition( Actor actor, int x, int y )
{
  // Previous code omitted...

  // Try to open a door if one exists here
  OpenDoor( actor, x, y );
}

Edit 2/25/2017 – Thanks to rmcrackan for pointing out that I forgot to show updating the Draw(…) method to have a foreach loop for drawing each of the doors.

foreach ( Door door in Doors )
{
  door.Draw( mapConsole, this );
}

Door Placement Strategy

Now that we are reasonably confident that we can draw doors and we have a few helper methods for working with them, how do we know where to put them? Think about where doors are placed in regard to rooms. They should be on an outer wall. So we should start by getting all of the cells along the boundaries of our rooms.

doorstrategy1

Check room boundary

Once we have all of those cells we should look for any open floors that would be a good candidate for a door. And how do we know if a door should go there or not? We look at the floor’s neighboring cells to see if it fits.

doorstrategy2

Check neighboring cells

A good Cell for a door placement is one that has two walls across from each other and two floors as opposing neighbors.

Updating the MapGenerator Class

Lets take the strategy that we just outlined for creating door and actually write the code. Open MapGenerator.cs and add the following new methods.

private void CreateDoors( Rectangle room )
{
  // The the boundries of the room
  int xMin = room.Left;
  int xMax = room.Right;
  int yMin = room.Top;
  int yMax = room.Bottom;

  // Put the rooms border cells into a list
  List<Cell> borderCells = _map.GetCellsAlongLine( xMin, yMin, xMax, yMin ).ToList();
  borderCells.AddRange( _map.GetCellsAlongLine( xMin, yMin, xMin, yMax ) );
  borderCells.AddRange( _map.GetCellsAlongLine( xMin, yMax, xMax, yMax ) );
  borderCells.AddRange( _map.GetCellsAlongLine( xMax, yMin, xMax, yMax ) );

  // Go through each of the rooms border cells and look for locations to place doors.
  foreach ( Cell cell in borderCells )
  {
    if ( IsPotentialDoor( cell ) )
    {
      // A door must block field-of-view when it is closed.
      _map.SetCellProperties( cell.X, cell.Y, false, true );
      _map.Doors.Add( new Door
      {
        X = cell.X,
        Y = cell.Y,
        IsOpen = false
      } );
    }
  }
}

// Checks to see if a cell is a good candidate for placement of a door
private bool IsPotentialDoor( Cell cell )
{
  // If the cell is not walkable
  // then it is a wall and not a good place for a door
  if ( !cell.IsWalkable )
  {
    return false;
  }

  // Store references to all of the neighboring cells 
  Cell right = _map.GetCell( cell.X + 1, cell.Y );
  Cell left = _map.GetCell( cell.X - 1, cell.Y );
  Cell top = _map.GetCell( cell.X, cell.Y - 1 );
  Cell bottom = _map.GetCell( cell.X, cell.Y + 1 );

  // Make sure there is not already a door here
  if ( _map.GetDoor( cell.X, cell.Y ) != null ||
      _map.GetDoor( right.X, right.Y ) != null ||
      _map.GetDoor( left.X, left.Y ) != null ||
      _map.GetDoor( top.X, top.Y ) != null ||
      _map.GetDoor( bottom.X, bottom.Y ) != null )
  {
    return false;
  }

  // This is a good place for a door on the left or right side of the room
  if ( right.IsWalkable && left.IsWalkable && !top.IsWalkable && !bottom.IsWalkable )
  {
    return true;
  }

  // This is a good place for a door on the top or bottom of the room
  if ( !right.IsWalkable && !left.IsWalkable && top.IsWalkable && bottom.IsWalkable )
  {
    return true;
  }
  return false;
}

That’s quite a bit of code but I hope that the comments help to understand what it is accomplishing. Now that we have the methods in place, we need to remember to call them. In the CreateMap() method we have a foreach where we call CreateRoom() on each room in the map. We also need to put a call for CreateDoors() in there.

// Iterate through each room that we wanted placed
// and dig out the room and create doors for it.
foreach ( Rectangle room in _map.Rooms )
{
  CreateRoom( room );
  CreateDoors( room );
}

If we run the program now we should see our doors in place and working as expected!

openingdoors

Opening Doors

Final Thoughts

The 7 Day Roguelike Challenge starts this year on March 12th. This year will be the 13th challenge. It’s a fun event and I urge you to participate. If you don’t like RogueSharp or C# there are plenty of other libraries out there for other languages. You could also start from scratch and not use any existing library.

7DRL Annoucement- http://7drl.org/2017/01/18/this-years-challenge-4-12-march-2017/

As always the code for the tutorial series so far can be found on Bitbucket:
https://bitbucket.org/FaronBracy/roguesharpv3tutorial/commits/tag/15Doors

Bored waiting for the next tutorial post? The completed project is already available on Bitbucket.

RogueSharp V3 Tutorial – Connecting Rooms with Hallways

Next Tutorial Post – Message Log
Previous Tutorial Post – Simple Room Generation

Goal

During this tutorial we’ll connect the rooms we created in the last tutorial with hallways. We’ll also fix the bug that we introduced last time where the player could end up starting stuck in a solid wall.

Hallways

Hallways

Player Starting Location

Our strategy for choosing the player’s starting location is going to be very simple. We will place the player in the center of the first room that we generate on our map. Before we do that we’ll need a few new methods for dealing with player placement.

First in Game.cs we need to get rid of a couple of things. We’re going to have our MapGenerator be responsible for creating and placing the Player instead of the Game class.

// Make sure that the setter for Player is not private
public static Player Player { get; set; }

// In Main() remove this line
Player = new Player();

Next open DungeonMap.cs and make a new method called AddPlayer() that will be ready to be used by our MapGenerator.

// Called by MapGenerator after we generate a new map to add the player to the map
public void AddPlayer( Player player )
{
  Game.Player = player;
  SetIsWalkable( player.X, player.Y, false );
  UpdatePlayerFieldOfView();
}

Last we need to add a new private method to MapGenerator.cs called PlacePlayer() which will be responsible for generating the player in the center of the first room of the map.

// Find the center of the first room that we created and place the Player there
private void PlacePlayer()
{
  Player player = Game.Player;
  if ( player == null )
  {
    player = new Player();
  }

  player.X = _map.Rooms[0].Center.X;
  player.Y = _map.Rooms[0].Center.Y;

  _map.AddPlayer( player );
}

Don’t forget to call this private method from CreateMap() right after the code where we create rooms.

// ... Old existing code ...
foreach ( Rectangle room in _map.Rooms )
{
  CreateRoom( room );
}

// New code starts here
PlacePlayer();

If you run the game now you should see that the player always starts in the center of a room. No more getting stuck in walls. Bug fixed!

Connecting Rooms

Next we are going to make an algorithm that will dig tunnels to create hallways between our rooms. The way that we will do this is by creating an ‘L’ shaped hallway from the center of one room to the center of the previous room that we generated. There are two possible ‘L’ shaped hallways that we can tunnel out as illustrated in the image below. We’ll just choose one of the two at random.

ConnectingRooms

Tunneling Strategy

One thing to keep in mind that might not be obvious is that our rooms were not generated in a neat fashion from left to right and top to bottom. They will be scattered all over the map. It is extremely likely that as we tunnel out new hallways we’ll pass through other existing rooms. This is fine though and actually creates a nice effect.

Open MapGenerator.cs and add two helper methods CreateHorizontalTunnel() and CreateVerticalTunnel() to help facilitate our hallway creation.

// Carve a tunnel out of the map parallel to the x-axis
private void CreateHorizontalTunnel( int xStart, int xEnd, int yPosition )
{
  for ( int x = Math.Min( xStart, xEnd ); x <= Math.Max( xStart, xEnd ); x++ )
  {
    _map.SetCellProperties( x, yPosition, true, true );
  }
}

// Carve a tunnel out of the map parallel to the y-axis
private void CreateVerticalTunnel( int yStart, int yEnd, int xPosition )
{
  for ( int y = Math.Min( yStart, yEnd ); y <= Math.Max( yStart, yEnd ); y++ )
  {
    _map.SetCellProperties( xPosition, y, true, true );
  }
}

Now add code to the CreateMap() method after the part where we generate the rooms that will use our helper methods to generate the tunnels between rooms.

// Iterate through each room that was generated
// Don't do anything with the first room, so start at r = 1 instead of r = 0
for ( int r = 1; r < _map.Rooms.Count; r++ )
{
  // For all remaing rooms get the center of the room and the previous room
  int previousRoomCenterX = _map.Rooms[r - 1].Center.X;
  int previousRoomCenterY = _map.Rooms[r - 1].Center.Y;
  int currentRoomCenterX = _map.Rooms[r].Center.X;
  int currentRoomCenterY = _map.Rooms[r].Center.Y;

  // Give a 50/50 chance of which 'L' shaped connecting hallway to tunnel out
  if ( Game.Random.Next( 1, 2 ) == 1 )
  {
    CreateHorizontalTunnel( previousRoomCenterX, currentRoomCenterX, previousRoomCenterY );
    CreateVerticalTunnel( previousRoomCenterY, currentRoomCenterY, currentRoomCenterX );
  }
  else
  {
    CreateVerticalTunnel( previousRoomCenterY, currentRoomCenterY, previousRoomCenterX );
    CreateHorizontalTunnel( previousRoomCenterX, currentRoomCenterX, currentRoomCenterY );
  }
}

Run the game. If everything went well you should get an image that looks like the one at the top of this post. Use the arrow keys to run around and explore the map.

Note: If your rooms are showing up before you move to explore them make sure to set the IsExplored flag to false in the CreateRoom() method

The code for the tutorial series so far can be found on Bitbucket:
https://bitbucket.org/FaronBracy/roguesharpv3tutorial/commits/tag/07ConnectingRooms

Closing Thoughts

I’ve been trying to do at least one post per week. If you get bored waiting for the next post remember that the completed project is already available on Bitbucket (although with messy commits).

I’ve also added links to SadConsole Tutorials to the main page of this blog. I’m still in the process of converting the sample game to work with SadConsole and I likely will not write detailed step-by-step tutorials for it. I will release the final results of the project. In the interim I highly recommend you check out SadConsole as it is a fantastic console library.

RogueSharp V3 Tutorial – Simple Room Generation

Next Tutorial Post – Connecting Rooms
Previous Tutorial Post – Player Input

Goal

During this tutorial we’ll handle adding rooms to our map. We’ll save connecting the rooms via hallways until the next tutorial.

Dungeon Map Rooms

The first thing we’ll need to do is create a place to hold our room information in DungeonMap.cs. Since all of our rooms are going to start out simple and be rectangular in nature we’ll simple use the Rectangle class included with RogueSharp to represent a room.

Some of you might ask why Rectangle in RogueSharp is a class and not a struct like in many other libraries. The answer is that it’s a mistake on my part. It really should be a struct. Here is an article on MSDN explaining the difference and when to use one over the other.

Choosing Between Class and Struct:
https://msdn.microsoft.com/en-us/library/ms229017(v=vs.110).aspx

Thanks to James Neal for pointing this out and for submitting a pull request to fix it. For our purposes it will work just fine as a class. On to creating our rooms! Add the following code to DungeonMap.cs

public class DungeonMap : Map
{
  // ... start of new code
  public List<Rectangle> Rooms;

  public DungeonMap()
  {
    // Initialize the list of rooms when we create a new DungeonMap
    Rooms = new List<Rectangle>();
  }
  // ... old code continues here
}

Random Number Generator Singleton

Since our levels will be randomly generated it will be nice to have easy access to a random number generator. By using the same random number generator that was created with a known seed we can always regenerate a level exactly as it was generated before by using the same seed. Open Game.cs and add the following code:

// Singleton of IRandom used throughout the game when generating random numbers
public static IRandom Random { get; private set; }

public static void Main()
{
  // Establish the seed for the random number generator from the current time
  int seed = (int) DateTime.UtcNow.Ticks;
  Random = new DotNetRandom( seed );

  // The title will appear at the top of the console window 
  // also include the seed used to generate the level
  string consoleTitle = $"RougeSharp V3 Tutorial - Level 1 - Seed {seed}";
  
  // .. old code continues here
}

RogueSharp has several different random number generators that you can read about here – https://bitbucket.org/FaronBracy/roguesharp/wiki/RogueSharp.Random/README.md

For our purposes we’ll use DotNetRandom which uses the standard RNG included with .NET. To establish a unique seed each time we start a new game we’ll get the Ticks (The value of this property represents the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001) of the current time. We’re also writing this seed into the title so that we could generate the level again in the future which could be useful for testing or debugging.

Generating Rooms with MapGenerator.cs

If you remember previously we generated our map by only making the outside edges walls, and the entire rest of the map wide open floors. Because we are changing this completely I’m going to paste the entire source code for the new MapGenerator.cs below and then I’ll review the changes.

public class MapGenerator
{
  private readonly int _width;
  private readonly int _height;
  private readonly int _maxRooms;
  private readonly int _roomMaxSize;
  private readonly int _roomMinSize;

  private readonly DungeonMap _map;

  // Constructing a new MapGenerator requires the dimensions of the maps it will create
  // as well as the sizes and maximum number of rooms
  public MapGenerator( int width, int height, 
  int maxRooms, int roomMaxSize, int roomMinSize )
  {
    _width = width;
    _height = height;
    _maxRooms = maxRooms;
    _roomMaxSize = roomMaxSize;
    _roomMinSize = roomMinSize;
    _map = new DungeonMap();
  }

  // Generate a new map that places rooms randomly
  public DungeonMap CreateMap()
  {
    // Set the properties of all cells to false
    _map.Initialize( _width, _height );

    // Try to place as many rooms as the specified maxRooms
    // Note: Only using decrementing loop because of WordPress formatting
    for ( int r = _maxRooms; r > 0; r-- )
    {   
      // Determine the size and position of the room randomly
      int roomWidth = Game.Random.Next( _roomMinSize, _roomMaxSize );
      int roomHeight = Game.Random.Next( _roomMinSize, _roomMaxSize );
      int roomXPosition = Game.Random.Next( 0, _width - roomWidth - 1 );
      int roomYPosition = Game.Random.Next( 0, _height - roomHeight - 1 );

      // All of our rooms can be represented as Rectangles
      var newRoom = new Rectangle( roomXPosition, roomYPosition, 
        roomWidth, roomHeight );

      // Check to see if the room rectangle intersects with any other rooms
      bool newRoomIntersects = _map.Rooms.Any( room => newRoom.Intersects( room ) );

      // As long as it doesn't intersect add it to the list of rooms
      if ( !newRoomIntersects )
      {
        _map.Rooms.Add( newRoom );
      }
    }
    // Iterate through each room that we wanted placed 
    // call CreateRoom to make it
    foreach ( Rectangle room in _map.Rooms )
    {
      CreateRoom( room );
    }

    return _map;
  }

  // Given a rectangular area on the map
  // set the cell properties for that area to true
  private void CreateRoom( Rectangle room )
  {
    for ( int x = room.Left + 1; x < room.Right; x++ )
    {
      for ( int y = room.Top + 1; y < room.Bottom; y++ )
      {
        _map.SetCellProperties( x, y, true, true, true );
      }
    }
  }
}

You’ll notice that we added 3 new parameters to the MapGenerator constructor. The new parameters are maxRooms, roomMaxSize, and roomMinSize. The way this algorithm works is it will try to create a number of rooms up to maxRooms on the map. Each room it will try to place in a random position on the map. It will also choose a width and height for the room between roomMinSize and roomMaxSize. The final step when placing the room is to see if it overlaps with any other room. If it does overlap then we throw it out.

Hooking up the MapGenerator

Technically the MapGenerator is already used in Game.cs however if you tried to build you would get errors. We need to make sure we provide the new parameters that our MapGenerator requires. In Game.cs update the following line:

// Old code to change
MapGenerator mapGenerator = new MapGenerator( _mapWidth, _mapHeight );

// New code to replace with
MapGenerator mapGenerator = new MapGenerator( _mapWidth, _mapHeight, 20, 13, 7 );

We’ll attempt to create 20 rooms with sides that are between 7 and 13 cells long. The result should be less than 20 rooms as we throw out any that overlap.

Results

If you run the program now and you are lucky you’ll end up with the Rogue in a room and will be able to move around in that room. Unfortunately since the rooms are not connected you will not be able to leave the room.

WhenLucky

When Lucky

If you’re not so lucky you’ll end up with your poor Rogue being stuck in a wall outside of the room.

OutsideOfRoomBug

Unlucky – Outside of Room Bug

We’ll fix this issue in the next tutorial post where we’ll also get all of our rooms connected. If you are bored waiting for the next tutorial you should try and see if you can come up with a fix for this and also start experimenting with ways to connect the rooms.

The code for the tutorial series so far can be found on Bitbucket:
https://bitbucket.org/FaronBracy/roguesharpv3tutorial/commits/tag/06Rooms

RogueSharp V3 Tutorial – Simple Map Drawing

Next Tutorial Post – Player
Previous Tutorial Post – Color Palette

Goal

In this tutorial we’ll begin using the Map features of RogueSharp. We will create our own DungeonMap class that will start off very simple but give us lots of room for growth. We’ll also be drawing this to the map sub console and displaying it on the screen.

Creating the DungeonMap

When it comes to the Map class in RogueSharp it is designed to be generic so that it can be used in a variety of games. It has plenty of functionality for path-finding, determining field-of-view, selecting Cells and more.

What RogueSharp doesn’t have is any concept of doors, stairs, traps, bookcases, torches, or any other dungeon features that are specific to an individual game’s domain. These are really up to the game designer and not something that I feel should be baked into an engine.

With that said, we do really want all of these features in our game, so how can we add them and still use RogueSharp? The answer is we can create our own class that inherits all of the features of RougeSharp’s Map class but at the same time extends it with the features that are specific to our game.

Start by creating a new class in the Core folder called DungeonMap.cs and add the following code to it.

// Our custom DungeonMap class extends the base RogueSharp Map class
public class DungeonMap : Map
{
  // The Draw method will be called each time the map is updated
  // It will render all of the symbols/colors for each cell to the map sub console
  public void Draw( RLConsole mapConsole )
  {
    mapConsole.Clear();
    foreach ( Cell cell in GetAllCells() )
    {
      SetConsoleSymbolForCell( mapConsole, cell );
    }
  }

  private void SetConsoleSymbolForCell( RLConsole console, Cell cell )
  {
    // When we haven't explored a cell yet, we don't want to draw anything
    if ( !cell.IsExplored )
    {
      return;
    }

    // When a cell is currently in the field-of-view it should be drawn with ligher colors
    if ( IsInFov( cell.X, cell.Y ) )
    {
      // Choose the symbol to draw based on if the cell is walkable or not
      // '.' for floor and '#' for walls
      if ( cell.IsWalkable )
      {
        console.Set( cell.X, cell.Y, Colors.FloorFov, Colors.FloorBackgroundFov, '.' );
      }
      else
      {
        console.Set( cell.X, cell.Y, Colors.WallFov, Colors.WallBackgroundFov, '#' );
      }
    }
    // When a cell is outside of the field of view draw it with darker colors
    else
    {
      if ( cell.IsWalkable )
      {
        console.Set( cell.X, cell.Y, Colors.Floor, Colors.FloorBackground, '.' );
      }
      else
      {
        console.Set( cell.X, cell.Y, Colors.Wall, Colors.WallBackground, '#' );
      }
    }
  }
}

You’ll notice on line 7 that our DungeonMap class inherits from the base RogueSharp Map class. This means that we already get access to everything that Map can do!

The drawing code is pretty simple. We iterate through every Cell in the Map and then choose how to display it based on if it is explored, if it is walkable, and if it is in field-of-view or not.

At this point we don’t have any special map features that actually require us to have our own class. Don’t worry though, those features are only a few tutorials away.

Creating the MapGenerator

Now we need a class that will be responsible for generating interesting maps for us. Make a new folder called Systems and inside the folder create a new class called MapGenerator.cs

To start off with we’ll make one of the simplest maps. It will be all open floors with a wall around the entire edge of the map.

Add the following code to MapGenerator.cs

public class MapGenerator
{
  private readonly int _width;
  private readonly int _height;

  private readonly DungeonMap _map;

  // Constructing a new MapGenerator requires the dimensions of the maps it will create
  public MapGenerator( int width, int height )
  {
    _width = width;
    _height = height;
    _map = new DungeonMap();
  }

  // Generate a new map that is a simple open floor with walls around the outside
  public DungeonMap CreateMap()
  {
    // Initialize every cell in the map by
    // setting walkable, transparency, and explored to true
    _map.Initialize( _width, _height );
    foreach ( Cell cell in _map.GetAllCells() )
    {
      _map.SetCellProperties( cell.X, cell.Y, true, true, true );
    }

    // Set the first and last rows in the map to not be transparent or walkable
    foreach ( Cell cell in _map.GetCellsInRows( 0, _height - 1 ) )
    {
      _map.SetCellProperties( cell.X, cell.Y, false, false, true );
    }

    // Set the first and last columns in the map to not be transparent or walkable
    foreach ( Cell cell in _map.GetCellsInColumns( 0, _width - 1 ) )
    {
      _map.SetCellProperties( cell.X, cell.Y, false, false, true );
    }

    return _map;
  }
}

As with the DungeonMap, the MapGenerator also is not that interesting at this point but it gives us a solid foundation for adding features in the future.

Your project file structure should now look something like this:

ProjectStructureWithSystems

Project Structure

Hooking up the MapGenerator and Drawing

To start using our DungeonMap we need to first add it to Game.cs

public static DungeonMap DungeonMap { get; private set; }

Then in our Main() method of Game.cs we need to use our MapGenerator to create our DungeonMap.

MapGenerator mapGenerator = new MapGenerator( _mapWidth, _mapHeight );
DungeonMap = mapGenerator.CreateMap();

Now that the map is created all that is left is to draw it in our OnRootConsoleRender() method of Game.cs

DungeonMap.Draw( _mapConsole );

If everything worked when you run the game you should now see the map console has been replaced with this:

SimpleMapDrawing

Simple Map Drawing

If it didn’t work, don’t worry. The complete code so far can be found here:
https://bitbucket.org/FaronBracy/roguesharpv3tutorial/commits/tag/03BasicMap

Also, feel free to leave me comments if you have questions. I’ll do my best to answer them quickly.

RogueSharp 1.1 Released – Cave-Like Map Creation Using Cellular Automata

RogueSharp Version 1.1

Version 1.1 of RogueSharp was just released. It now includes a new map creation strategy for creating cave-like maps based off of an article on RogueBasin with some custom modifications.

Usage

IMapCreationStrategy<Map> mapCreationStrategy = 
   new CaveMapCreationStrategy<Map>( 50, 30, 45, 4, 2 );
IMap map = Map.Create( mapCreationStrategy );

Parameters

  • width – The width of the Map (in Cells not pixels) to be created
  • height – The height of the Map (in Cells not pixels) to be created</param>
  • fillProbability – Recommend using values between 40 and 60. Percent chance that a given cell will be a floor when randomizing all cells before starting the cellular automata algorithm.
  • totalIterations – Recommend using values between 2 and 5. Number of times to execute the cellular automata algorithm.
  • cutoffOfBigAreaFill – Recommend using value less than 4. The iteration number to switch from the large area fill algorithm to a nearest neighbor algorithm
  • random (optional) – A class implementing IRandom that will be used to generate pseudo-random numbers necessary to create the Map. If this parameter is omitted then a default implementation of .NET’s System.Random will be used.