Tag Archives: RLNET

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 – Player Input

Next Tutorial Post – Simple Room Generation
Previous Tutorial Post – Player

Goal

During this tutorial we’ll handle keyboard input and allow moving the Player around the screen. We’ll update the field-of-view as the Player moves and make sure that walls cannot be walked through.

PlayerMove

Player Moving

Small Refactoring

Before starting on new code I noticed two things that should have been cleaned up in the last tutorial. The first was that in our OnRootConsoleUpdate() method in Game.cs we were still setting the background color and printing a message to _mapConsole. Immediately after doing this we overwrote all that work and drew our real map over it. Basically we were doing extra work that we didn’t need to do so we can delete those lines.

The second thing is that all of the code from OnRootConsoleUpdate() for setting the background color and printing a message to each of the other three consoles is being called on each update. This is also a waste as currently the contents of those consoles never changes. All of that code can be moved to Main() so it will only be executed once when we first start our game.

Although these refactorings are optional and the code will still work without doing them, I find that it is nice to continuously look at what you have written and try to clean it up. Some people call this the “boy scout rule” which is to always leave the code cleaner than you found it.

Creating a Direction Enumeration

Before we start handling the keyboard input it could be nice to create an enumeration of all the possible directions that a player could move. Create a new Enum called Direction.cs in the Core folder of the project. Direction.cs should have the following code in it:

namespace RogueSharpV3Tutorial.Core
{
  // Direction values correspond to numpad numbers
  public enum Direction
  {
    None = 0,
    DownLeft = 1,
    Down = 2,
    DownRight = 3,
    Left = 4,
    Center = 5,
    Right = 6,
    UpLeft = 7,
    Up = 8,
    UpRight = 9
  }
}

You’ll notice that the enum supports diagonals however in this tutorial game we will only allow the player to move up, down, left and right. At this time RogueSharp is missing diagonal support in it’s pathfinder but that is something that we’re looking into adding.

Setting Actor Positions

Since our DungeonMap class is responsible for knowing which Cells are walkable and it will also keep track of the positions of all of our Actors it seems like a good place to put a method that will move an Actor. Note that currently the Player is defined in the Game class and it is an exception as we want the Player to exist outside of the context of the DungeonMap so that we can regenerate the DungeonMap when moving down into deeper levels

Open DungeonMap.cs and add the following methods to it:

// Returns true when able to place the Actor on the cell or false otherwise
public bool SetActorPosition( Actor actor, int x, int y )
{
  // Only allow actor placement if the cell is walkable
  if ( GetCell( x, y ).IsWalkable )
  {
    // The cell the actor was previously on is now walkable
    SetIsWalkable( actor.X, actor.Y, true );
    // Update the actor's position
    actor.X = x;
    actor.Y = y;
    // The new cell the actor is on is now not walkable
    SetIsWalkable( actor.X, actor.Y, false );
    // Don't forget to update the field of view if we just repositioned the player
    if ( actor is Player )
    {
      UpdatePlayerFieldOfView();
    }
    return true;
  }
  return false;
}

// A helper method for setting the IsWalkable property on a Cell
public void SetIsWalkable( int x, int y, bool isWalkable )
{
  Cell cell = GetCell( x, y );
  SetCellProperties( cell.X, cell.Y, cell.IsTransparent, isWalkable, cell.IsExplored );
}

The SetActorPosition() method runs through a set of steps to make sure that the old cell that the actor was previously on is now walkable, and the new cell the actor is moved to is not walkable. It also updates the player field-of-view if the actor that was moved was the player. The return status here is also important. It returns false if the actor could not be moved. This is necessary when moving the player in case we try to move into a wall or other impassible cell.

The SetIsWalkable() method is really just a helper function so that we don’t have to call SetCellProperties() with so many parameters. In the future I think these helpers might be added directly to the RogueSharp Map class.

Command System

Make a new class in the Systems folder called CommandSystem.cs. The responsibility of this class will be to carry out the commands that the Player executes.

Add the following code to CommandSystem.cs:

public class CommandSystem
{
  // Return value is true if the player was able to move
  // false when the player couldn't move, such as trying to move into a wall
  public bool MovePlayer( Direction direction )
  {
    int x = Game.Player.X;
    int y = Game.Player.Y;

    switch ( direction )
    {
    case Direction.Up:
      {
        y = Game.Player.Y - 1;
        break;
      }
    case Direction.Down:
      {
        y = Game.Player.Y + 1;
        break;
      }
    case Direction.Left:
      {
        x = Game.Player.X - 1;
        break;
      }
    case Direction.Right:
      {
        x = Game.Player.X + 1;
        break;
      }
    default:
      {
        return false;
      }
    }

    if ( Game.DungeonMap.SetActorPosition( Game.Player, x, y ) )
    {
      return true;
    }

    return false;
  }
}

The only method in this class for now is the MovePlayer() method which takes a Direction as a parameter. Based on the direction that is input we add or subtract to the X or Y position of the player. Then we call SetActorPosition() on the player to move them to the new position and return the result. The result will be true if the player is able to move and false otherwise.

Capturing Key Presses

The last class that we’ll need to modify is Game.cs to get everything hooked up.

First of all we need to add a new static member _renderRequired and a new property CommandSystem to the top of the class.

private static bool _renderRequired = true;

public static CommandSystem CommandSystem { get; private set; }

Then in our Main() method we need to instantiate a new CommandSystem

CommandSystem = new CommandSystem();

Next we need to capture the last key pressed and handle it in OnRootConsoleUpdate()

// Event handler for RLNET's Update event
private static void OnRootConsoleUpdate( object sender, UpdateEventArgs e )
{
  bool didPlayerAct = false;
  RLKeyPress keyPress = _rootConsole.Keyboard.GetKeyPress();

  if ( keyPress != null )
  {
    if ( keyPress.Key == RLKey.Up )
    {
      didPlayerAct = CommandSystem.MovePlayer( Direction.Up );
    }
    else if ( keyPress.Key == RLKey.Down )
    {
      didPlayerAct = CommandSystem.MovePlayer( Direction.Down );
    }
    else if ( keyPress.Key == RLKey.Left )
    {
      didPlayerAct = CommandSystem.MovePlayer( Direction.Left );
    }
    else if ( keyPress.Key == RLKey.Right )
    {
      didPlayerAct = CommandSystem.MovePlayer( Direction.Right );
    }
    else if ( keyPress.Key == RLKey.Escape )
    {
      _rootConsole.Close();
    }
  }

  if ( didPlayerAct )
  {
    _renderRequired = true;
  }
}

RLNET allows us to get the last key pressed by calling Keyboard.GetKeyPress() on the root console. The _renderRequired private member is a bit of an optimization. There is no need to redraw our map if no action was performed and the player didn’t move. Other than that all we are doing is checking if one of the direction keys was pressed and handing it off to the CommandSystem.MovePlayer() method to handle the movement. We also hooked up the Escape key to close the game.

The last step is to wrap our drawing code in OnRootConsoleRender() in an if statement checking _renderRequired so that we don’t redraw all the consoles when nothing has changed.

// Event handler for RLNET's Render event
private static void OnRootConsoleRender( object sender, UpdateEventArgs e )
{
  // Don't bother redrawing all of the consoles if nothing has changed.
  if ( _renderRequired )
  {
    // ... previous drawing code remains here

    _renderRequired = false;
  }
}

As always the complete code can be found on bitbucket:
https://bitbucket.org/FaronBracy/roguesharpv3tutorial/commits/tag/05PlayerInput

RogueSharp V3 Tutorial – Player

Next Tutorial Post – Player Input
Previous Tutorial Post – Simple Map Drawing

Goal

During this tutorial we’ll create interfaces for Actors and for Drawing things. We’ll then create a simple Player class that implements both of our new interfaces. We’ll draw the Player and his field of view to the screen but we will not get into accepting keyboard input and moving the Player until next time.

Creating the Interfaces

There are two new interfaces that we want to create. In C# interfaces are used to define a set of functionality or a group of related behaviors. You can think of them as a contract that classes must follow. By implementing the interface a Class is agreeing to the contract and must implement those methods and behaviors. We will always prefix our interfaces with the letter ‘I’ as is common in C#.

IActor Interface

The first interface we will create will be IActor. For now it will only have two properties, Name and Awareness. The Awareness property will be used when calculating field-of-view to determine the maximum distance the Actor is aware of, can see or sense.

Create a new folder called Interfaces and then create a new interface in the folder called IActor.cs. Place the following code inside the new file.

namespace RogueSharpV3Tutorial.Interfaces
{
  public interface IActor
  {
    string Name { get; set; }
    int Awareness { get; set; }
  }
}

IDrawable Interface

The next interface we will define will be IDrawable. The properties that we need to draw a cell in the console are a Color, Symbol, and the X and Y coordinates of the cell. We also want to have a Draw method that will take the RLConsole to draw to as well an IMap. Notice that we don’t require a concrete Map class but we will accept any class that follows the IMap contract. The IMap is important because that will allow us to calculate field-of-view.

namespace RogueSharpV3Tutorial.Interfaces
{
  public interface IDrawable
  {
    RLColor Color { get; set; }
    char Symbol { get; set; }
    int X { get; set; }
    int Y { get; set; }

    void Draw( RLConsole console, IMap map );
  }
}

Actor Base Class

Now that we have defined our interfaces we’ll make a base class called Actor that implements both of our interfaces. In C# a class can only inherit from a single class. It can however implement as many interfaces as you want.

Start by creating a new class in the Core folder called Actor.cs. It should implement both IActor and IDrawable. Place the following code in Actor.cs:

public class Actor : IActor, IDrawable
{
  // IActor
  public string Name { get; set; }
  public int Awareness { get; set; }

  // IDrawable
  public RLColor Color { get; set; }
  public char Symbol { get; set; }
  public int X { get; set; }
  public int Y { get; set; }
  public void Draw( RLConsole console, IMap map )
  {
    // Don't draw actors in cells that haven't been explored
    if ( !map.GetCell( X, Y ).IsExplored )
    {
      return;
    }

    // Only draw the actor with the color and symbol when they are in field-of-view
    if ( map.IsInFov( X, Y ) )
    {
      console.Set( X, Y, Color, Colors.FloorBackgroundFov, Symbol );
    }
    else
    {
      // When not in field-of-view just draw a normal floor
      console.Set( X, Y, Colors.Floor, Colors.FloorBackground, '.' );
    }
  }
}

Implementing the properties should be straightforward. They just have public getters and setters. The most interesting part will be the Draw() method because it needs to inspect the IMap for explored cells and cells in field-of-view so that it can draw the appropriate symbols.

Player Color

We need to establish a Color for our Player. Open Colors.cs and add the following line:

public static RLColor Player = Swatch.DbLight;

Player Class

It’s time to create the Player class. In the Core folder make a new class called Player.cs. We want it to have a base class of Actor so that it will automatically get the Draw() method and all the necessary properties. The code should look like this.

namespace RogueSharpV3Tutorial.Core
{
  public class Player : Actor
  {
    public Player()
    {
      Awareness = 15;
      Name = "Rogue";
      Color = Colors.Player;
      Symbol = '@';
      X = 10;
      Y = 10;
    }
  }
}

You’ll see that all we have to do to make the Player unique from other Actors is to define unique values for our properties. In this case the Player can see 15 cells so the Awareness is 15. We’re also setting the starting position to 10, 10 and the Color to the one we defined earlier. The Symbol is the standard ‘@’ character from the original Rogue. Feel free to adjust any of these values to anything you want.

If you’ve been following along with the tutorial your project structure should now look like this:

InterfacesFolder

Project Layout

Player Field-of-View Method

Open DungeonMap.cs and create a new method called UpdatePlayerFieldOfView() which will be called when we position the Player

// This method will be called any time we move the player to update field-of-view
public void UpdatePlayerFieldOfView()
{
  Player player = Game.Player;
  // Compute the field-of-view based on the player's location and awareness
  ComputeFov( player.X, player.Y, player.Awareness, true );
  // Mark all cells in field-of-view as having been explored
  foreach ( Cell cell in GetAllCells() )
  {
    if ( IsInFov( cell.X, cell.Y ) )
    {
      SetCellProperties( cell.X, cell.Y, cell.IsTransparent, cell.IsWalkable, true );
    }
  }
}

Adding the Player to the Game

The last thing that we have to do in this tutorial is add the Player to the Game. Open Game.cs and add a Player property just above our existing DungeonMap property.

public static Player Player { get; private set; }

Next we need to construct a new Player in our Main() method. Don’t forget to update the field-of-view by calling DungeonMap.UpdatePlayerFieldOfView().

Player = new Player();
// The next two lines already existed
MapGenerator mapGenerator = new MapGenerator( _mapWidth, _mapHeight );
DungeonMap = mapGenerator.CreateMap();
// End of existing code
DungeonMap.UpdatePlayerFieldOfView();

We’re almost done. The last thing we need to do is call Player.Draw() in our OnRootConsoleRender() method.

Player.Draw( _mapConsole, DungeonMap );

If all went well when you start your game you should see this:

MapWithPlayer

Map with Player

The code after all of the changes so far can be found here:
https://bitbucket.org/FaronBracy/roguesharpv3tutorial/commits/tag/04PlayerActor

Closing Thoughts

For the near future expect 1-2 posts per week. This is a large series and I expect there to be 30 or more posts to complete the tutorial. I appreciate all the comments and feedback. For those of you that private messaged me in BitBucket I’m embarrassed to know that there was an inbox there until recently so I apologize for not answering your messages.

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 V3 Tutorial – Color Palette

Next Tutorial Post – Simple Map Drawing
Previous Tutorial Post – Multiple Consoles

Goal

In this tutorial we’ll look defining a color palette or swatch that we’ll use for the elements in our game.

Paletton

There are lots of websites that will help you choose colors. One that I like is paletton. After some fiddling around I went with a Tetrad 4-color scheme that you can see here:

http://paletton.com/#uid=73d0u0k5qgb2NnT41jT74c8bJ8X

Paletton

Sample Colors from Paletton.com

DawnBringer’s 16 Color Palette

Another palette that I ran across on the pixeljoint forums is DawnBringer’s 16 Color Palette. Read more about it here:

http://pixeljoint.com/forum/forum_posts.asp?TID=12795

DawnBringer16Color

DawnBringer’s 16 Color Palette

Creating a Folder with Color and Swatch Classes

We are going to make two new classes called Colors.cs and Swatch.cs and they should be categorized under their own folder called Core.

You can accomplish this by right clicking on the project and choosing Add -> New Folder. Then right click on the folder and choose Add -> Class… 

Do this for both Color.cs and Swatch.cs and your project should look something like the one below.

CoreFolder

Core Folder

Swatch Class

RLNET lets you define colors with the RLColor constructor taking the RGB values of the color as parameters as in RLColor myColor = new RLColor( red, green, blue ); 

In the Swatch class we’ll define friendly names for each of the colors from the palettes that we choose so that we don’t have to recall those RGB values.

In hindsight it might have made more sense to call this class Palette. Feel free to rename yours to whatever makes sense to you.

Add the following code to Swatch.cs

using RLNET;

namespace RogueSharpV3Tutorial.Core
{
  public class Swatch
  {
    // http://paletton.com/#uid=73d0u0k5qgb2NnT41jT74c8bJ8X

    public static RLColor PrimaryLightest = new RLColor( 110, 121, 119 );
    public static RLColor PrimaryLighter = new RLColor( 88, 100, 98 );
    public static RLColor Primary = new RLColor( 68, 82, 79 );
    public static RLColor PrimaryDarker = new RLColor( 48, 61, 59 );
    public static RLColor PrimaryDarkest = new RLColor( 29, 45, 42 );

    public static RLColor SecondaryLightest = new RLColor( 116, 120, 126 );
    public static RLColor SecondaryLighter = new RLColor( 93, 97, 105 );
    public static RLColor Secondary = new RLColor( 72, 77, 85 );
    public static RLColor SecondaryDarker = new RLColor( 51, 56, 64 );
    public static RLColor SecondaryDarkest = new RLColor( 31, 38, 47 );

    public static RLColor AlternateLightest = new RLColor( 190, 184, 174 );
    public static RLColor AlternateLighter = new RLColor( 158, 151, 138 );
    public static RLColor Alternate = new RLColor( 129, 121, 107 );
    public static RLColor AlternateDarker = new RLColor( 97, 89, 75 );
    public static RLColor AlternateDarkest = new RLColor( 71, 62, 45 );

    public static RLColor ComplimentLightest = new RLColor( 190, 180, 174 );
    public static RLColor ComplimentLighter = new RLColor( 158, 147, 138 );
    public static RLColor Compliment = new RLColor( 129, 116, 107 );
    public static RLColor ComplimentDarker = new RLColor( 97, 84, 75 );
    public static RLColor ComplimentDarkest = new RLColor( 71, 56, 45 );

    // http://pixeljoint.com/forum/forum_posts.asp?TID=12795

    public static RLColor DbDark = new RLColor( 20, 12, 28 );
    public static RLColor DbOldBlood = new RLColor( 68, 36, 52 );
    public static RLColor DbDeepWater = new RLColor( 48, 52, 109 );
    public static RLColor DbOldStone = new RLColor( 78, 74, 78 );
    public static RLColor DbWood = new RLColor( 133, 76, 48 );
    public static RLColor DbVegetation = new RLColor( 52, 101, 36 );
    public static RLColor DbBlood = new RLColor( 208, 70, 72 );
    public static RLColor DbStone = new RLColor( 117, 113, 97 );
    public static RLColor DbWater = new RLColor( 89, 125, 206 );
    public static RLColor DbBrightWood = new RLColor( 210, 125, 44 );
    public static RLColor DbMetal = new RLColor( 133, 149, 161 );
    public static RLColor DbGrass = new RLColor( 109, 170, 44 );
    public static RLColor DbSkin = new RLColor( 210, 170, 153 );
    public static RLColor DbSky = new RLColor( 109, 194, 202 );
    public static RLColor DbSun = new RLColor( 218, 212, 94 );
    public static RLColor DbLight = new RLColor( 222, 238, 214 );
  }
}

Colors Class

Why do we have two classes for colors? What is the difference between the Swatch class and the Colors class? I thought that the Swatch would make sense to just define the possible colors that we have to work with. Then when we define the color of something specific to our game’s domain like the Player’s symbol we can put that definition in the Colors class.  It might be that both the Player’s ‘@’ symbol and the text for the inventory both use a color from the swatch such as Swatch.DbLight.

A nice benefit of keeping all of our Colors together is that if we want to change colors in the future we can come to this one place to change them and don’t have to dig all over in code to find colors spread throughout. We could also load different colors if we wanted a special colorblind mode.

Add the following code to Colors.cs

using RLNET;

namespace RogueSharpV3Tutorial.Core
{
  public class Colors
  {
    public static RLColor FloorBackground = RLColor.Black;
    public static RLColor Floor = Swatch.AlternateDarkest;
    public static RLColor FloorBackgroundFov = Swatch.DbDark;
    public static RLColor FloorFov = Swatch.Alternate;

    public static RLColor WallBackground = Swatch.SecondaryDarkest;
    public static RLColor Wall = Swatch.Secondary;
    public static RLColor WallBackgroundFov = Swatch.SecondaryDarker;
    public static RLColor WallFov = Swatch.SecondaryLighter;

    public static RLColor TextHeading = Swatch.DbLight;
  }
}

You’ll notice that the only colors that we have defined so far are for the Floor and Walls both when they are in Field-of-View and when they are not. We also defined a color for text headings.

Testing our Colors

To see if our colors are working as we expect lets go back to Game.cs  and find the OnRootConsoleUpdate() method and substitute in our own colors.

// Remember to add the using for our new namespace to the top of the file
using RogueSharpV3Tutorial.Core;

private static void OnRootConsoleUpdate( object sender, UpdateEventArgs e )
{
  _mapConsole.SetBackColor( 0, 0, _mapWidth, _mapHeight, Colors.FloorBackground );
  _mapConsole.Print( 1, 1, "Map", Colors.TextHeading );

  _messageConsole.SetBackColor( 0, 0, _messageWidth, _messageHeight, Swatch.DbDeepWater );
  _messageConsole.Print( 1, 1, "Messages", Colors.TextHeading );

  _statConsole.SetBackColor( 0, 0, _statWidth, _statHeight, Swatch.DbOldStone );
  _statConsole.Print( 1, 1, "Stats", Colors.TextHeading );

  _inventoryConsole.SetBackColor( 0, 0, _inventoryWidth, _inventoryHeight, Swatch.DbWood );
  _inventoryConsole.Print( 1, 1, "Inventory", Colors.TextHeading );
}

If you run the game you should now see something like this:

FourColorConsoles

Updated Colors

This should look very familiar. That was quite a bit of work for only slightly different colors, but we’ll be using these colors for the rest of our game.

Here is the complete code as of this post on Bitbucket:
https://bitbucket.org/FaronBracy/roguesharpv3tutorial/commits/tag/02ChooseColors

Closing Thoughts

I’m a little disappointed that throughout this post I refer to our color palettes but then when it comes to code I put the palettes in a class called swatch. I’m basically using two terms for the same thing.

When starting a new project it could be beneficial before coding to come up with a glossary for all of the objects of your game. Do you call them tiles or cells? Is it a grid or a map? Characters, Entities, Actors, Tokens, Figures… if you ever read any books on Domain Driven Design they recommend that you come up with a ubiquitous language that you use for both describing your domain (in this case our game) in both code and when speaking to others about it and then stick to it.

RogueSharp V3 Tutorial – Multiple Consoles

Next Tutorial Post – Color Palette
Previous Tutorial Post – Creating the Project

Goal

In this tutorial we’ll create sub consoles for each part of our game window and Blit them to the root console.

FourConsoles

Four Sub Consoles

Sub Consoles

Our game is more than a map. It will also have areas for the inventory, player statistics, and a message log. We could just draw all these directly to the root console but it will be better to draw each to their own console and Blit them to the root in their correct positions.

Defining Console Dimensions

First we need to define the width and height of each of our sub-consoles and declare each RLConsole. At the top of Game.cs add the following code:

public static class Game
{
  // The screen height and width are in number of tiles
  private static readonly int _screenWidth = 100;
  private static readonly int _screenHeight = 70;
  private static RLRootConsole _rootConsole;

  // The map console takes up most of the screen and is where the map will be drawn
  private static readonly int _mapWidth = 80;
  private static readonly int _mapHeight = 48;
  private static RLConsole _mapConsole;

  // Below the map console is the message console which displays attack rolls and other information
  private static readonly int _messageWidth = 80;
  private static readonly int _messageHeight = 11;
  private static RLConsole _messageConsole;

  // The stat console is to the right of the map and display player and monster stats
  private static readonly int _statWidth = 20;
  private static readonly int _statHeight = 70;
  private static RLConsole _statConsole;

  // Above the map is the inventory console which shows the players equipment, abilities, and items
  private static readonly int _inventoryWidth = 80;
  private static readonly int _inventoryHeight = 11;
  private static RLConsole _inventoryConsole;
  
  // ... additional code omitted
}

Instantiate the Consoles

Now we need to new up our four consoles with the widths and heights that we defined. In our Main() just after the line where we make a new RLRootConsole add the following code:

public static void Main()
{
  // ... previous code omitted
  // After the line that starts _rootConsole = new RLRootConsole( ...  

  // Initialize the sub consoles that we will Blit to the root console
  _mapConsole = new RLConsole( _mapWidth, _mapHeight );
  _messageConsole = new RLConsole( _messageWidth, _messageHeight );
  _statConsole = new RLConsole( _statWidth, _statHeight );
  _inventoryConsole = new RLConsole( _inventoryWidth, _inventoryHeight );
  
  // ... additional code omitted
}

Set Background Color and Label

Next we’ll want to set the background color of each console and put a label on them so that we can verify that they are in the correct positions when we Blit them to the Root console. In OnRootConsoleUpdate()  replace _rootConsole.Print( 10, 10, “It worked!”, RLColor.White ); with the following code:

private static void OnRootConsoleUpdate( object sender, UpdateEventArgs e )
{
  // Set background color and text for each console 
  // so that we can verify they are in the correct positions
  _mapConsole.SetBackColor( 0, 0, _mapWidth, _mapHeight, RLColor.Black );
  _mapConsole.Print( 1, 1, "Map", RLColor.White );  

  _messageConsole.SetBackColor( 0, 0, _messageWidth, _messageHeight, RLColor.Gray );
  _messageConsole.Print( 1, 1, "Messages", RLColor.White );

  _statConsole.SetBackColor( 0, 0, _statWidth, _statHeight, RLColor.Brown );
  _statConsole.Print( 1, 1, "Stats", RLColor.White );

  _inventoryConsole.SetBackColor( 0, 0, _inventoryWidth, _inventoryHeight, RLColor.Cyan );
  _inventoryConsole.Print( 1, 1, "Inventory", RLColor.White );
}

Blit and Render

The final step we’ll need to take is to update OnRootConsoleRender() to Blit each of our consoles to the root console in the correct positions. The RLNET method we will use is RLConsole.Blit which takes several parameters.

  • A source console
  • The X and Y position of the top left corner of a rectangular area from the source
  • The width and height of the rectangular area from the source that we will be Blitting
  • A destination console to Blit to
  • The X and Y position of the top left corner of where we will Blit to in the destination console
private static void OnRootConsoleRender( object sender, UpdateEventArgs e )
{
  // Blit the sub consoles to the root console in the correct locations
  RLConsole.Blit( _mapConsole, 0, 0, _mapWidth, _mapHeight, 
    _rootConsole, 0, _inventoryHeight );
  RLConsole.Blit( _statConsole, 0, 0, _statWidth, _statHeight, 
    _rootConsole, _mapWidth, 0 );
  RLConsole.Blit( _messageConsole, 0, 0, _messageWidth, _messageHeight, 
    _rootConsole, 0, _screenHeight - _messageHeight );
  RLConsole.Blit( _inventoryConsole, 0, 0, _inventoryWidth, _inventoryHeight, 
    _rootConsole, 0, 0 );

  // Tell RLNET to draw the console that we set
  _rootConsole.Draw();
}

And that’s it! If you run the program now you should see output similar to the screenshot at the beginning of this post.

Complete code for this post – https://bitbucket.org/FaronBracy/roguesharpv3tutorial/commits/tag/01SetupConsoles

Closing thoughts

So far we haven’t used RogueSharp at all. I think it is necessary to do some of these basic setup tasks to pave the way for the future. Don’t worry though we’ll be getting into RogueSharp functionality shortly.

RogueSharp V3 Tutorial – Creating the Project

Next tutorial post – Multiple Consoles
Previous tutorial post – Introduction and Goals

Goal

In this tutorial we’ll get the RogueSharp and RLNET NuGet packages added to a brand new project.

Creating a new Console Project

I’ll be using Visual Studio 2015 for this project. The community edition can be found here: https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx

  1. Go to the File menu and choose New -> Project…
  2. From Templates choose Visual C# -> Windows -> Console Application
  3. Give it any name and location that want.
Console Application

Console Application

Adding NuGet Packages

Both RogueSharp and RLNET are available via NuGet and are easy to add to our project.

  1. Right click on the project (the name of your project will be whatever you named it in step 3 above)
  2. Choose Manage NuGet Packages… from the context menu.
Manage NuGet Packages

Manage NuGet Packages

Now you should be able to choose Browse and search for RogueSharp and RLNET. Make sure that the Package Source dropdown is set to nuget.org Click Install for both packages. As of this writing the latest version of RogueSharp is 3.0.0 and RLNET is 1.0.6. Note RLNet has a dependency on OpenTK so it will also install v1.1.225 of that library.

Adding a Font File

RLNET needs a special font file like libtcod uses. If you aren’t familiar with this and want to know more about it there is good information here.

But for our purposes just right click and download this image and place it in your project directory.

Save to project directory

Save to project directory

Once the file is in your project directory, you should be able to Right Click on your project in Visual Studio and choose Add -> Existing Item… Choose the image that you saved and it should show up in the project. Make sure to set the Properties of this file to Copy always.

Set Copy Always

Set Copy Always

The Code

A console application always includes a file called Program.cs which has a single Main() function which serves as the entry point.

Rename Program.cs to Game.cs

Now we’re going to add a bit of code to render a RLRootConsole with the text “It worked!” just to prove that we hooked up the NuGet packages correctly. I won’t go into a lot of detail here but if you want more depth check out these other tutorials.

using RLNET;

namespace RogueSharpV3Tutorial
{
  public class Game
  {
    // The screen height and width are in number of tiles
    private static readonly int _screenWidth = 100;
    private static readonly int _screenHeight = 70;

    private static RLRootConsole _rootConsole;

    public static void Main()
    {
      // This must be the exact name of the bitmap font file we are using or it will error.
      string fontFileName = "terminal8x8.png";
      // The title will appear at the top of the console window
      string consoleTitle = "RougeSharp V3 Tutorial - Level 1";
      // Tell RLNet to use the bitmap font that we specified and that each tile is 8 x 8 pixels
      _rootConsole = new RLRootConsole( fontFileName, _screenWidth, _screenHeight, 
        8, 8, 1f, consoleTitle );
      // Set up a handler for RLNET's Update event
      _rootConsole.Update += OnRootConsoleUpdate;
      // Set up a handler for RLNET's Render event
      _rootConsole.Render += OnRootConsoleRender;
      // Begin RLNET's game loop
      _rootConsole.Run();
    }

    // Event handler for RLNET's Update event
    private static void OnRootConsoleUpdate( object sender, UpdateEventArgs e )
    {
      _rootConsole.Print( 10, 10, "It worked!", RLColor.White );
    }

    // Event handler for RLNET's Render event
    private static void OnRootConsoleRender( object sender, UpdateEventArgs e )
    {
      // Tell RLNET to draw the console that we set
      _rootConsole.Draw();
    }
  }
}

If you run your project now you should see something like this:

RootConsoleRender

It Worked!

Here is the link to the final code on BitBucket

Using GoalMaps in RogueSharp

RogueSharp has had GoalMaps since version 1 however they never performed as well as I had hoped so I didn’t promote the feature. As of version 3.0.0-pre the feature has been cleaned up and I’m now ready to present a sample application of their use. The GoalMap feature was originally inspired by The Incredible Power of Dijkstra Maps post on RogueBasin.

GoalMapSample

GoalMap Sample

Sample GoalMap Application

The code for the Sample GoalMap application is on Bitbucket:

https://bitbucket.org/FaronBracy/roguesharprlnetsamples/src/0cbcd866875e64f6456d5fe08c86602d5f9591b9/?at=GoalMap

Commands

Left Click – Set the start location for the Goal Map Calculation
Right Click – Toggle a Goal on/off
O – Toggle obstacle on/off
A – Toggle between Goal avoidance and Goal seeking
C – Clear all Goals, Paths and Obstacles

Definitions

GoalMap – The GoalMap class is used in RogueSharp for assigning weights to Cells on the Map which can then be used for finding paths or building desire-driven AI

Goal – The Goals in a GoalMap represent weighted locations on the Map that will be used when calculating paths for either seeking or avoiding Goals. The smaller the weight the more desirable the location will be to either seek or avoid. When finding paths both the distance to the Goal and the weight of the Goal will be taken into account.

Obstacle – An Obstacle is a location that a Path must not pass through when calculating paths with GoalMaps. Examples of possible obstacles could be an ally, an enemy, water, or anything that we don’t want to pass through. By default any Map Cells that are not marked as walkable will be Obstalces. For instance, there will be no need to set Obstacles for each wall in the Map.

GoalMapSampleApp

GoalMap Demo Application

Code Sample

// Create a new map that only has a wall around the border
var map = Map.Create( new BorderOnlyMapCreationStrategy
<Map>( 50, 50 ) );

// Create a new GoalMap for the map
var goalMap = new GoalMap( map );

// Add a Goal at X=10, Y=25 with a weight of 10
// Lower weights are more desirable
goalMap.AddGoal( 10, 25, 10 );

// Add a second Goal at X=40, Y=15 with a weight of 10
goalMap.AddGoal( 40, 15, 10 );

// Add an obstacle at X=1, Y=2
// An obstacle is something that a Path must not go through
// Examples: an ally, an enemy, water, etc.
goalMap.AddObstacle( 1, 2 );  

// Find a "best" path to a Goal based on Goal weight and distance to the Goal
// Start the path from X=1, Y=1
// The path will not pass through obstacles
Path path = goalMap.FindPath( 1, 1 );

// Find a "best" path away from or avoiding Goals based on Goal weight and distance
// Start the path from X=20, Y=20
// The path will not pass through obstacles
Path pathAvoiding = goalMap.FindPathAvoidingGoals( 20, 20 );

Tile and Cell Selection with RogueSharp

One of the features of RogueSharp is that it contains multiple methods for selecting Cells in different areas and patterns.

Cell Selection

Cell Selection

Note: Some people call these Tiles instead of Cells. I believe the terms Cell and Tile are interchangeable but I’ll be using the term Cell for the remainder of this tutorial.

Sample Selection Application

The sample code for the application pictured in the above image is on Bitbucket:
https://bitbucket.org/FaronBracy/roguesharprlnetsamples/branch/AreaSelection

When using the sample the Cell selection will be centered on the mouse cursor.

Commands:

  • Left Click – Cycle through selection types
  • W Key – Toggle highlighting of walls on and off
  • Q Key – Decrease the selection size
  • E Key – Increase the selection size

Selection Types

All of the different selection types are methods on the Map class that return an IEnumerable<Cell>. It is then easy to write a foreach loop that will iterate through each of the selected Cells and do something with it. In the example code we just set the background color of the Cell to yellow. There are lots of other applications though. Perhaps you have a wizard in your game that can cast fireballs that explode and damage all enemies in a given radius. This would be perfect for that.

// Define the point of impact and the size of our explosion
int xOriginOfExplosion;
int yOriginOfExplosion;
int radiusOfExplosion;

// Call GetCellsInRadius on our IMap. This assumes _map is our game map of type IMap.
IEnumerable<Cell> cellsInExplosion = 
  _map.GetCellsInRadius( xOriginOfExplosion, yOriginOfExplosion, radiusOfExplosion );
         
// Enumerate through each cell in the explosion
foreach ( Cell cell in cellsInExplosion )
{
  // Assume that we have a function that will
  // retrieve an enemy at a given position or null if there isn't one.
  Enemy enemy = GetEnemyAtPosition( cell.X, cell.Y );
  if ( enemy != null)
  {
    // Assume that we have a method on our enemy to cause him to take damage.
    enemy.TakeDamage( 6 );
  }
}

 

GetCellsInRadius

GetCellsInRadius

GetCellsInRadius

/// <summary>
/// Get an IEnumerable of Cells in a circular radius around the Origin Cell
/// </summary>
/// <param name="xOrigin">X location of the Origin Cell with 0 as the farthest left</param>
/// <param name="yOrigin">Y location of the Origin Cell with 0 as the top</param>
/// <param name="radius">The number of Cells to get in a radius from the Origin Cell</param>
/// <returns>IEnumerable of Cells in a circular radius around the Origin Cell</returns>
public IEnumerable<Cell> GetCellsInRadius( int xOrigin, int yOrigin, int radius )

GetBorderCellsInRadius

GetBorderCellsInRadius

GetBorderCellsInRadius

/// <summary>
/// Get an IEnumerable of the outermost border Cells in a circular Radius around the Origin Cell
/// </summary>
/// <param name="xOrigin">X location of the Origin Cell with 0 as the farthest left</param>
/// <param name="yOrigin">Y location of the Origin Cell with 0 as the top</param>
/// <param name="radius">The radius from the Origin Cell in which the border Cells lie</param>
/// <returns>IEnumerable of the outermost border Cells in a circular radius around the Origin Cell</returns>
public IEnumerable<Cell> GetBorderCellsInRadius( int xOrigin, int yOrigin, int radius )

GetCellsInArea

GetCellsInArea

GetCellsInArea

/// <summary>
/// Get an IEnumerable of Cells in a square area around the Origin Cell
/// </summary>
/// <param name="xOrigin">X location of the Origin Cell with 0 as the farthest left</param>
/// <param name="yOrigin">Y location of the Origin Cell with 0 as the top</param>
/// <param name="distance">The number of Cells to get in each direction from the Origin Cell</param>
/// <returns>IEnumerable of Cells in a square area around the Origin Cell</returns>
public IEnumerable<Cell> GetCellsInArea( int xOrigin, int yOrigin, int distance )

GetBorderCellsInArea

GetBorderCellsInArea

GetBorderCellsInArea

/// <summary>
/// Get an IEnumerable of the outermost border Cells in a square around the Origin Cell
/// </summary>
/// <param name="xOrigin">X location of the Origin Cell with 0 as the farthest left</param>
/// <param name="yOrigin">Y location of the Origin Cell with 0 as the top</param>
/// <param name="distance">The distance from the Origin Cell in which the border Cells lie</param>
/// <returns> IEnumerable of the outermost border Cells in a square around the Origin Cell</returns>
public IEnumerable<Cell> GetBorderCellsInArea( int xOrigin, int yOrigin, int distance )

GetCellsInColumns

GetCellsInColumns

GetCellsInColumns

/// <summary>
/// Get an IEnumerable of all the Cells in the specified column numbers
/// </summary>
/// <param name="columnNumbers">Integer array of column numbers with 0 as the farthest left</param>
/// <returns>IEnumerable of all the Cells in the specified column numbers</returns>
public IEnumerable<Cell> GetCellsInColumns( params int[] columnNumbers )

GetCellsInRows

GetCellsInRows

GetCellsInRows

/// <summary>
/// Get an IEnumerable of all the Cells in the specified row numbers
/// </summary>
/// <param name="rowNumbers">Integer array of row numbers with 0 as the top</param>
/// <returns>IEnumerable of all the Cells in the specified row numbers</returns>
public IEnumerable<Cell> GetCellsInRows( params int[] rowNumbers )

Toggle Wall Selection

Wall Selection Off

Wall Selection Off

WallSelectionOn

Wall Selection On

By default all of the selection methods will return every Cell in the area regardless of type. Because the selections all return IEnumerable it means that we can use Linq to filter them. So if we wanted to only select Cells that were walkable we could do something like this.

// Assume that map, x, y, and selectionSize are previously defined
IEnumerable<Cell> walkableCells = map.GetCellsInRadius( x, y, selectionSize )
// We can use a Linq .Where clause to filter only the walkable Cells.
  .Where( c => c.IsWalkable );

I hope that this helps clear up how some of the different map selection methods works. Thank you for reading!

Using RogueSharp with RLNET Console

People have asked me if it is possible to use RogueSharp without using MonoGame. They’ve also asked how to use RogueSharp to make a classic ASCII game. Though MonoGame is a great framework there are plenty of other ways to create games in C#. Today I’m going to show how to use RogueSharp with RLNET which is a lightweight API to help quickly create tile-based games for .NET.

Large Cavern

Large Cavern

Goal

In this tutorial we’ll get RogueSharp and RLNET added to a brand new project. We’ll render the console which will be a cave-like map and intercept keyboard commands to be able to move the player represented by an ‘@’ through the ASCII cave. We’ll calculate field-of-view and render what the player can see as well as what they have previously explored. Best of all we’ll do this in just a few lines of code.

Creating a new Console Project

I’ll be using Visual Studio for this Project.

  1. Go to the File menu and choose New -> Project…
  2. From Templates choose Visual C# -> Windows Desktop -> Console Application
  3. Give it any name and location that want.
Console Application

Console Application

Adding NuGet Packages

Both RogueSharp and RLNET are available via NuGet and are easy to add to our project.

  1. Right click on the project (the name of your project will be whatever you named it in step 3 above)
  2. Choose Manage NuGet Packages… from the context menu.
Manage NuGet Packages

Manage NuGet Packages

Now you should be able to choose Online -> nuget.org and search for RogueSharp and RLNET. Click Install for both packages. As of this writing the latest version of RogueSharp is 2.0.0.0

Adding Nuget Packages

Adding Nuget Packages

Adding a Font File

RLNET needs a special font file like libtcod uses. If you aren’t familiar with this and want to know more about it there is good information here.

But for our purposes just right click and download this image and place it in your project directory.

Save to project directory

Save to project directory

Once the file is in your project directory, you should be able to Right Click on your project in Visual Studio and choose Add -> Existing Item… Choose the image that you saved and it should show up in the project. Make sure to set the Properties of this file to Copy always.

Set Copy Always

Set Copy Always

The Code

Because I’ve already produced a bunch of tutorials for RogueSharp and RLNET has it’s own set of tutorials I’m not going to explain each step. The code is only about 100 lines with comments and extra spacing and hopefully my comments are sufficient to help you out. Just copy and paste the code block below into Program.cs

using RLNET;
using RogueSharp;

namespace RogueSharpRLNetSamples
{
  public class Program
  {
    // The screen height and width are in number of tiles
    private static readonly int _screenWidth = 50;
    private static readonly int _screenHeight = 50;
    // The starting position for the player
    private static int _playerX = 25;
    private static int _playerY = 25;
    private static RLRootConsole _rootConsole;
    private static IMap _map;

    public static void Main()
    {
      // Use RogueSharp to create a new cave map the same size as the screen.
      _map = Map.Create( new CaveMapCreationStrategy<Map>( _screenWidth, _screenHeight, 45, 4, 3 ) );
      // This must be the exact name of the bitmap font file we are using or it will error.
      string fontFileName = "terminal8x8.png";
      // The title will appear at the top of the console window
      string consoleTitle = "RougeSharp RLNet Tutorial" ;
      // Tell RLNet to use the bitmap font that we specified and that each tile is 8 x 8 pixels
      _rootConsole = new RLRootConsole( fontFileName, _screenWidth, _screenHeight, 8, 8, 1f, consoleTitle );
      // Set up a handler for RLNET's Update event
      _rootConsole.Update += OnRootConsoleUpdate;
      // Set up a handler for RLNET's Render event
      _rootConsole.Render += OnRootConsoleRender;
      // Begin RLNET's game loop
      _rootConsole.Run();
    }

    // Event handler for RLNET's Update event
    private static void OnRootConsoleUpdate( object sender, UpdateEventArgs e )
    {
      RLKeyPress keyPress = _rootConsole.Keyboard.GetKeyPress();
      if ( keyPress != null )
      {
        // Check for the Up key press
        if ( keyPress.Key == RLKey.Up )
        {
          // Check the RogueSharp map to make sure the Cell is walkable before moving
          if ( _map.GetCell( _playerX, _playerY - 1 ).IsWalkable )
          {
            // Update the player position
            _playerY--;
          }
        }
        // Repeat for the other directions
        else if ( keyPress.Key == RLKey.Down )
        {
          if ( _map.GetCell( _playerX, _playerY + 1 ).IsWalkable )
          {
            _playerY++;
          }
        }
        else if ( keyPress.Key == RLKey.Left )
        {
          if ( _map.GetCell( _playerX - 1, _playerY ).IsWalkable )
          {
            _playerX--;
          }
        }
        else if ( keyPress.Key == RLKey.Right )
        {
          if ( _map.GetCell( _playerX + 1, _playerY ).IsWalkable )
          {
            _playerX++;
          }
        }
      }
    }

    // Event handler for RLNET's Render event
    private static void OnRootConsoleRender( object sender, UpdateEventArgs e )
    {
      _rootConsole.Clear();

      // Use RogueSharp to calculate the current field-of-view for the player
      _map.ComputeFov( _playerX, _playerY, 50, true );

      foreach ( var cell in _map.GetAllCells() )
      {
        // When a Cell is in the field-of-view set it to a brighter color
        if ( cell.IsInFov )
        {
          _map.SetCellProperties( cell.X, cell.Y, cell.IsTransparent, cell.IsWalkable, true );
          if ( cell.IsWalkable )
          {
            _rootConsole.Set( cell.X, cell.Y, RLColor.Gray, null, '.' );
          }
          else
          {
            _rootConsole.Set( cell.X, cell.Y, RLColor.LightGray, null, '#' );
          }
        }
        // If the Cell is not in the field-of-view but has been explored set it darker
        else if ( cell.IsExplored )
        {
          if ( cell.IsWalkable )
          {
            _rootConsole.Set( cell.X, cell.Y, new RLColor( 30, 30, 30 ), null, '.' );
          }
          else
          {
            _rootConsole.Set( cell.X, cell.Y, RLColor.Gray, null, '#' );
          }
        }
      }

      // Set the player's symbol after the map symbol to make sure it is draw
      _rootConsole.Set( _playerX, _playerY, RLColor.LightGreen, null, '@' );

      // Tell RLNET to draw the console that we set
      _rootConsole.Draw();
    }
  }
}

If you run your project now you should see something like this:

RougeSharp and RLNET Starter Project

RougeSharp and RLNET Starter Project

Here is the link to the final code on BitBucket

Additional Fun

The eleventh Seven Day Roguelike Challenge is nearly upon us! I urge you to participate. If you don’t like RogueSharp or C# there are plenty of other RogueLike libraries out there for most languages.

There are also more .NET console libraries that should work well with RougeSharp. Why don’t you give them a try, or go further with RLNET. If you haven’t used C# before maybe try it for your 7DRL this year.

  • SadConsole – Works with MonoGame! I haven’t tried it yet but want to soon.
  • Malison – I have not tried this one yet either but it looks promising.
  • RLNET – What we used in this tutorial.