Tag Archives: RogueLike

RogueSharp V3 Tutorial – Player Stats

Next Tutorial Post – Monster Generation
Previous Tutorial Post – Message Log

Goal

During this tutorial we’ll add all of the necessary stats that the player and other actors will require to perform combat actions. We will also be rendering the player stats to the console that was previously just a placeholder on the right of the map.

DrawStats

Draw Player Stats

Combat Overview

The combat system that I’ve decided upon for the tutorial is going to be a percentage based opposing roll system. First the attacker will roll a number of 100 sided dice equal to their attack value. Each die in the roll will have a percentage chance to be a success. Each success has the potential to inflict 1 point of damage. Thus the maximum amount of damage an actor can do is their Attack value, if they get all successes and their is no defense.

So how about defense? It will work the same way. The defender rolls a number of 100 sided dice equal to their defense value. Each die has a percentage chance to be a success. The number of defender successes is subtracted from the number of attacker successes and if the remaining number is positive then that much damage is applied to the defender.

Whew! Hopefully that’s not too confusing. We’ll go through the stats one-by-one in the next section.

Defining the Stats

  • Attack – int – Number of dice to roll when performing an attack. Also represents the maximum amount of damage that can be inflicted in a single attack.
  • AttackChance – int – Percentage chance that each die rolled is a success. A success for a die means that 1 point of damage was inflicted.
  • Awareness – int – For the player this determines how far their field-of-view extends. For other actors or monsters this is the distance that they can see and hear. If the player gets within that distance then it is likely the monster will be alerted to their presence.
  • Defense– int – Number of dice to roll when defending against an attack. Also represents the maximum amount of damage that can blocked or dodged from an incoming attack.
  • DefenseChance – int – Percentage chance that each die rolled is a success. A success for a die means that 1 point of damage was blocked.
  • Gold – int – How much money the actor has. Most monsters will drop gold upon death.
  • Health – int – The current health total of the actor. If this value is 0 or less then the actor is killed.
  • MaxHealth – int – How much health the actor has when fully healed.
  • Name – string – Name of the actor
  • Speed – int – How fast the actor is. This determines the rate at which they perform actions. A lower number is faster. An actor with a speed of 10 will perform twice as many actions in the same time as an actor with a speed of 20.

Adding Stats to Actors

Before we add the stats directly to our Actor class we should first add them to the IActor interface. Open IActor.cs and update the code to look like the following.

public interface IActor
{
  int Attack { get; set; }
  int AttackChance { get; set; }
  int Awareness { get; set; }
  int Defense { get; set; }
  int DefenseChance { get; set; }
  int Gold { get; set; }
  int Health { get; set; }
  int MaxHealth { get; set; }
  string Name { get; set; }
  int Speed { get; set; }
}

Next open Actor.cs and make sure that each of these properties is implemented. Note that we won’t be doing auto-properties this time because later we’ll want to update the getters on the each property to account for additional stats from items that we equip. For instance the Defense stat might have a base value of 2, but then have an additional +2 bonus from armor that is being worn. So although it is more typing, we’ll implement these properties with their own private backing fields to allow us to add that additional functionality in later.

public class Actor : IActor, IDrawable
{
  // IActor
  private int _attack;
  private int _attackChance;
  private int _awareness;
  private int _defense;
  private int _defenseChance;
  private int _gold;
  private int _health;
  private int _maxHealth;
  private string _name;
  private int _speed;

  public int Attack
  {
    get
    {
      return _attack;
    }
    set
    {
      _attack = value;
    }
  }

  public int AttackChance
  {
    get
    {
      return _attackChance;
    }
    set
    {
      _attackChance = value;
    }
  }

  public int Awareness
  {
    get
    {
      return _awareness;
    }
    set
    {
      _awareness = value;
    }
  }

  public int Defense
  {
    get
    {
      return _defense;
    }
    set
    {
      _defense = value;
    }
  }

  public int DefenseChance
  {
    get
    {
      return _defenseChance;
    }
    set
    {
      _defenseChance = value;
    }
  }

  public int Gold
  {
    get
    {
      return _gold;
    }
    set
    {
      _gold = value;
    }
  }

  public int Health
  {
    get
    {
      return _health;
    }
    set
    {
      _health = value;
    }
  }

  public int MaxHealth
  {
    get
    {
      return _maxHealth;
    }
    set
    {
      _maxHealth = value;
    }
  }

  public string Name
  {
    get
    {
      return _name;
    }
    set
    {
      _name = value;
    } 
  }

  public int Speed
  {
    get
    {
      return _speed;
    }
    set
    {
      _speed = value;
    }
  }
}

Drawing Player Stats

Now that we have the stats out of the way, we want to be able to draw the Player stats in the upper right corner of the statConsole. Open Player.cs and set all of the starting stats for the Player in the constructor. Also add the DrawStats() method to look like the following:

public class Player : Actor
{
  public Player()
  {
    Attack = 2;
    AttackChance = 50;
    Awareness = 15;
    Color = Colors.Player;
    Defense = 2;
    DefenseChance = 40;
    Gold = 0;
    Health = 100;
    MaxHealth = 100;
    Name = "Rogue";
    Speed = 10;
    Symbol = '@';
  }

  public void DrawStats( RLConsole statConsole )
  {
    statConsole.Print( 1, 1, $"Name:    {Name}", Colors.Text );
    statConsole.Print( 1, 3, $"Health:  {Health}/{MaxHealth}", Colors.Text );
    statConsole.Print( 1, 5, $"Attack:  {Attack} ({AttackChance}%)", Colors.Text );
    statConsole.Print( 1, 7, $"Defense: {Defense} ({DefenseChance}%)", Colors.Text );
    statConsole.Print( 1, 9, $"Gold:    {Gold}", Colors.Gold );
  }
}

Make sure to also add the colors to Colors.cs. Pick any colors you like. I used these:

public static RLColor TextHeading = RLColor.White;
public static RLColor Text = Swatch.DbLight;
public static RLColor Gold = Swatch.DbSun;

Updating the Game

The final thing left to do is to update Game.cs to make sure that Player.DrawStats() is called. During the last tutorial we also added some code to test out our the message log by printing a line every time the player took a step. I’ll be deleting that code now too.

// DELETE all of the following lines
private static int _steps = 0;

MessageLog.Add( $"Step # {++_steps}" );  

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

Then update OnRootConsoleRender() to call Player.DrawStats()

if ( _renderRequired )
{
  // Old code to keep
  DungeonMap.Draw( _mapConsole );
  Player.Draw( _mapConsole, DungeonMap );

  // New code after Player.Draw()
  Player.DrawStats( _statConsole ); 
}

If you run the program now you should get something similar to the screenshot at the top of this post.

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

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

RogueSharp V3 Tutorial – Message Log

Next Tutorial Post – Player Stats
Previous Tutorial Post – Connecting Rooms with Hallways

Goal

During this tutorial we’ll create a new MessageLog class that will allow us to add messages and draw them to a console. We’ll then add a bunch of messages to the log to prove that it works

MessageLog

Message Log

Designing the Message Log Class

How do we want to handle messages? We want a class that allows us to add messages to some sort of list. We also want to display some number of previous messages and have new messages scroll into view when they are added. Old messages after a certain limit should scroll out of view.

A decent structure for handling this is a Queue. You can read more about them from the link but basically you can think of it as a line at the grocery store. The first person to get in line is the first person to check out. In terms of our message log the oldest message will be the first one removed from the history.

Creating the Message Log Class

Create a new class in the Systems folder called MessageLog.cs. Add the following code to it:

// Represents a queue of messages that can be added to
// Has a method for and drawing to an RLConsole
public class MessageLog
{
  // Define the maximum number of lines to store
  private static readonly int _maxLines = 9;

  // Use a Queue to keep track of the lines of text
  // The first line added to the log will also be the first removed
  private readonly Queue<string> _lines;

  public MessageLog()
  {
    _lines = new Queue<string>();
  }

  // Add a line to the MessageLog queue
  public void Add( string message )
  {
    _lines.Enqueue( message );
    
    // When exceeding the maximum number of lines remove the oldest one.
    if ( _lines.Count > _maxLines )
    {
      _lines.Dequeue();
    }
  }

  // Draw each line of the MessageLog queue to the console
  public void Draw( RLConsole console )
  {
    console.Clear();
    string[] lines = _lines.ToArray();
    for ( int i = 0; i < lines.Length; i++ )
    {
      console.Print( 1, i + 1, lines[i], RLColor.White );
    }
  }
}

Hooking up the Message Log

All of the remaining work will be handled in Game.cs.

First in the section at the top of the file where we define Player, DungeonMap and CommandSystem add the following:

public static MessageLog MessageLog { get; private set; }

Next in the Main() method of Game.cs add some code to instantiate a new MessageLog and add a couple of messages to it. Don’t forget to also remove the old code that was setting the _messageConsole to a blue color and printing “Messages” on it.

// Create a new MessageLog and print the random seed used to generate the level
MessageLog = new MessageLog();
MessageLog.Add( "The rogue arrives on level 1" );
MessageLog.Add( $"Level created with seed '{seed}'" );

// Remove these lines:
_messageConsole.SetBackColor( 0, 0, _messageWidth, _messageHeight, Swatch.DbDeepWater );
_messageConsole.Print( 1, 1, "Messages", Colors.TextHeading );

The last thing we need to do is call MessageLog.Draw() in our OnRootConsoleRender() method near where we call DungeonMap.Draw() and Player.Draw().

MessageLog.Draw( _messageConsole );

Temporary Code for Generating Lots of Messages

Just to prove that our messages work the way we expect and remove old messages once we reach the limit we set of “9” lets log a bunch of messages. We’ll do this by adding a private member variable _steps and increment it each time the player acts.

// Temporary member variable just to show our MessageLog is working
private static int _steps = 0;

// In OnRootConsoleUpdate() replace the if ( didPlayerAct ) block
if ( didPlayerAct )
{
  // Every time the player acts increment the steps and log it
  MessageLog.Add( $"Step # {++_steps}" );  
  _renderRequired = true;
}

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

Closing Thoughts

I’ve been spending a bunch of time messing with RexPaint. It’s a fantastic ASCII art editor and can be used to design maps. I highly recommend you take a look at it.

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