Tag Archives: RLNET

RogueSharp V3 Tutorial – Stairs

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

Goal

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

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

Creating the Stairs Class

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

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

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

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

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

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

Updating DungeonMap Class

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

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

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

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

  // Previous code omitted...
}

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

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

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

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

Updating the MapGenerator Class

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

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

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

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

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

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

  // Previous code
  PlacePlayer();
}

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

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

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

Updating the Game Class

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

private static int _mapLevel = 1;

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

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

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


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

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

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

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

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

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

stairs

Going down stairs

Closing Thoughts

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

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

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

RogueSharp V3 Tutorial – Doors

Next Tutorial Post – Stairs
Previous Tutorial Post – Monster Behaviors

Goal

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

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

Setting Door Colors

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

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

Creating the Door Class

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

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

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

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

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

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

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

Updating the DungeonMap Class

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

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

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

Be sure to initialize this list in the constructor

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

  Doors = new List<Door>();
}

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

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

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

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

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

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

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

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

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

Door Placement Strategy

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

doorstrategy1

Check room boundary

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

doorstrategy2

Check neighboring cells

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

Updating the MapGenerator Class

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

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

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

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

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

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

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

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

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

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

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

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

openingdoors

Opening Doors

Final Thoughts

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

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

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

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

RogueSharp V3 Tutorial – Monster Behaviors

Next Tutorial Post – Doors
Previous Tutorial Post – Scheduling System

Goal

There is going to be a lot of code in this tutorial. We have to start using the scheduling system we built last time. We’ll also be laying the foundation for creating monster behaviors. Our first behavior will be a simple move and attack, but we can expand on this in the future to create lots of interesting monsters.

simplebehaviors

Simple Behaviors

Defining a Behavior Interface

First we need to define the interface that all of our future Behaviors will use. Create a new file named IBehavior.cs in the Interfaces folder. Add the following code:

public interface IBehavior
{
  bool Act( Monster monster, CommandSystem commandSystem );
}

We’re going to keep it simple and just have a simple Act(…) method that will take the Monster performing the action and the CommandSystem as parameters.

Creating a Move and Attack Behavior

Our first concrete behavior that will implement our IBehavior interface will be StandardMoveAndAttack.

Briefly lets go over what the StandardMoveAndAttack behavior will do.

  • A monster should perform a standard melee attack on the player if the player is adjacent to the monster.
  • If the player is not within attack range, the monster should move closer to the player via the shortest available path.
  • Only monsters that are aware of the player should chase him.
  • Once the monster is alerted and begins the chase, if the player evades him and remains out of visual range for a period of time the monster should stop pursuit.
  • If the monster can see the player but does not have a valid path (possibly because of being blocked by other monsters) it should wait a turn.

Because we want to have many additional behaviors in the future we should make a new folder named Behaviors and then create the file StandardMoveAndAttack.cs in that folder. Add the following code:

public class StandardMoveAndAttack : IBehavior
{
  public bool Act( Monster monster, CommandSystem commandSystem )
  {
    DungeonMap dungeonMap = Game.DungeonMap;
    Player player = Game.Player;
    FieldOfView monsterFov = new FieldOfView( dungeonMap );

    // If the monster has not been alerted, compute a field-of-view 
    // Use the monster's Awareness value for the distance in the FoV check
    // If the player is in the monster's FoV then alert it
    // Add a message to the MessageLog regarding this alerted status
    if ( !monster.TurnsAlerted.HasValue )
    {
      monsterFov.ComputeFov( monster.X, monster.Y, monster.Awareness, true );
      if ( monsterFov.IsInFov( player.X, player.Y ) )
      {
        Game.MessageLog.Add( $"{monster.Name} is eager to fight {player.Name}" );
        monster.TurnsAlerted = 1;
      }
    }
    
    if ( monster.TurnsAlerted.HasValue )
    {
      // Before we find a path, make sure to make the monster and player Cells walkable
      dungeonMap.SetIsWalkable( monster.X, monster.Y, true );
      dungeonMap.SetIsWalkable( player.X, player.Y, true );

      PathFinder pathFinder = new PathFinder( dungeonMap );
      Path path = null;

      try
      {
        path = pathFinder.ShortestPath( 
        dungeonMap.GetCell( monster.X, monster.Y ), 
        dungeonMap.GetCell( player.X, player.Y ) );
      }
      catch ( PathNotFoundException )
      {
        // The monster can see the player, but cannot find a path to him
        // This could be due to other monsters blocking the way
        // Add a message to the message log that the monster is waiting
        Game.MessageLog.Add( $"{monster.Name} waits for a turn" );
      }

      // Don't forget to set the walkable status back to false
      dungeonMap.SetIsWalkable( monster.X, monster.Y, false );
      dungeonMap.SetIsWalkable( player.X, player.Y, false );

      // In the case that there was a path, tell the CommandSystem to move the monster
      if ( path != null )
      {
        try
        {
          // TODO: This should be path.StepForward() but there is a bug in RogueSharp V3
          // The bug is that a Path returned from a PathFinder does not include the source Cell
          commandSystem.MoveMonster( monster, path.Steps.First() );
        }
        catch ( NoMoreStepsException )
        {
          Game.MessageLog.Add( $"{monster.Name} growls in frustration" );
        }
      }

      monster.TurnsAlerted++;

      // Lose alerted status every 15 turns. 
      // As long as the player is still in FoV the monster will stay alert
      // Otherwise the monster will quit chasing the player.
      if ( monster.TurnsAlerted > 15 )
      {
        monster.TurnsAlerted = null;
      }
    }
    return true;
  }
}

Be aware that if you try to Build right now there will be errors. We never added the TurnsAlerted property to our Monster class. Additionally we are missing the MoveMonster method on our CommandSystem.

Note: You may also notice the TODO comment. I discovered a bug in V3 RogueSharp and have a fix for it but for now this is a workaround.

Monster Class Updates

We should make sure we get back to a state where the project will build as quickly as possible. With that in mind open Monster.cs and add a new TurnsAlerted property as well as a PerformAction method that we’ll use later.

public int? TurnsAlerted { get; set; }

public virtual void PerformAction( CommandSystem commandSystem )
{
  var behavior = new StandardMoveAndAttack();
  behavior.Act( this, commandSystem );
}

Did you notice the question mark on the int? property? That’s shorthand for a Nullable<int> type. Since integers are value types they cannot normally be null, however we want a null integer to represent that the monster has not been alerted.

Notice that the PerformAction method is virtual also. Since this is on our Monster base class, we’ll give any monster the StandardMoveAndAttack behavior by default. We can override this in any of our Monster sub-classes to get more interesting behaviors.

Actor Class Updates

At this point we’re still not building, but we are getting closer. Open Actor.cs and make sure that the Actor class implements IScheduleable so that we can add them to our scheduling system.

public class Actor : IActor, IDrawable, IScheduleable
{
  // ... Previous Actor code omitted

  // IScheduleable
  public int Time
  {
    get
    {
      return Speed;
    }
  }
}

CommandSystem Class Updates

We should get our CommandSystem updated now so we can finally build again. Open CommandSystem.cs and add the IsPlayerTurn property as well as the ActivateMonsters() and MoveMonster(…) methods.

Update 1/31/2017 – Added the EndPlayerTurn() method which was accidentally omitted from the code below. Thank you to Zacharry Field for bringing this to my attention.

public bool IsPlayerTurn { get; set; }

public void EndPlayerTurn()
{
  IsPlayerTurn = false;
}

public void ActivateMonsters()
{
  IScheduleable scheduleable = Game.SchedulingSystem.Get();
  if ( scheduleable is Player )
  {
    IsPlayerTurn = true;
    Game.SchedulingSystem.Add( Game.Player );
  }
  else
  {
    Monster monster = scheduleable as Monster;

    if ( monster != null )
    {
      monster.PerformAction( this );
      Game.SchedulingSystem.Add( monster );
    }

    ActivateMonsters();
  }
}

public void MoveMonster( Monster monster, Cell cell )
{
  if ( !Game.DungeonMap.SetActorPosition( monster, cell.X, cell.Y ) )
  {
    if ( Game.Player.X == cell.X && Game.Player.Y == cell.Y )
    {
      Attack( monster, Game.Player );
    }
  }
}

The ActivateMonsters() method is intended to be called after the Player takes a turn. This will proceed to get the next scheduled Actor from the SchedulingSystem. If this happens to be the Player again, we’ll wait for the Player to make a move. Otherwise we’ll have the Monster perform an action and then call ActivateMonsters() again recursively. This will keep having Monsters perform their actions until it is once again the Player’s turn.

Add Player and Monsters to Scheduling System

Open DungeonMap.cs and update the AddPlayer(…), AddMonster(…), and RemoveMonster(…) methods to use the SchedulingSystem. Just add the calls at the end of each method.

public void AddPlayer( Player player )
{
  // ...previous code omitted
  Game.SchedulingSystem.Add( player );
}

public void AddMonster( Monster monster )
{
  // ...previous code omitted
  Game.SchedulingSystem.Add( monster );
}

public void RemoveMonster( Monster monster )
{
  // ...previous code omitted
  Game.SchedulingSystem.Remove( monster );
}

Updating the Game Class

We’re almost done. The rest of the updates will be in Game.cs so open up the file and add a new public property.

public static SchedulingSystem SchedulingSystem { get; private set; }

Make sure to instantiate a new SchedulingSystem in the Main() method.

SchedulingSystem = new SchedulingSystem();

The last set of updates will be made in the OnRootConsoleUpdate(…) method. We need to ensure that it is the player’s turn before handling key presses. We also need to activate monsters after the player has taken their turn.

private static void OnRootConsoleUpdate( object sender, UpdateEventArgs e )
{
  bool didPlayerAct = false;
  RLKeyPress keyPress = _rootConsole.Keyboard.GetKeyPress();

  if ( CommandSystem.IsPlayerTurn )
  {
    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;
      CommandSystem.EndPlayerTurn();
    }
  }
  else
  {
    CommandSystem.ActivateMonsters();
    _renderRequired = true;
  }
}

Everything should build now and if you run the game you’ll see that monsters will now chase the player around and attack. The scheduling system that we implemented last time is all hooked up now too.

Final Thoughts

This was a long post with a lot of code but it should pave the way for adding more monsters and more interesting behaviors in the future.

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

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

RogueSharp V3 Tutorial – Scheduling System

Next Tutorial Post – Monster Behaviors
Previous Tutorial Post – Simple Combat

Goal

During this tutorial we will create a Scheduling System. Some roguelikes also refer to these as time systems. You can find out how lots of developers create their time systems by reading the Roguelike Dev Subreddit FAQ Friday #41.

Scheduling System Details

Most roguelikes including this tutorial are turn-based, meaning each Actor acts on their turn. The simplest way to achieve this is to just have a giant queue structure and have everyone act in order one-by-one. When the actor is removed from the queue and takes their turn then they get re-added to the back of the queue. The player goes, and then each monster takes a turn, then the player takes their turn again and so on.

Unfortunately if we did something that simple it wouldn’t be very interesting. All Actors would have the same speed. Effectively this would mean that you could never run away from monsters or catch a fleeing monster.

To get around this issue we will give each Actor a Speed statistic which will represent how often they can take actions. You may even remember in a previous tutorial where we defined stats we already allocated a Speed stat. We just never got around to using it, but we are about to fix that!

The concept behind our scheduling system is that it will keep track of the time or number of turns that have passed as well as the current time. The Speed of the actor will determine when they get inserted into the timeline. Note that in this case having a lower speed is better.

SchedulingSystem

Example Schedule

In the Example Schedule above notice that during the first 12 turns the player with a speed of 4 will have acted 3 times. The goblin with a speed of 3 will have acted 4 times. And the Ooze with a speed of 6 will have only acted twice. This means that a player would be able to easily outrun an ooze, but not the goblin. The goblin moves twice as fast as the ooze.

Data Structure

To represent this data structure in code we will use a SortedDictionary<int, List<IScheduleable>>. This may look a little odd at first, but all it really means is that Key in the dictionary is an integer that represents the time. The Value of each dictionary entry is a List<IScheduleable>. But what exactly is an IScheduleable? It’s a custom interface that we’ll make and it will just have a single property Time. What we are saying is that we can put anything on the schedule as long as it has a Time which represents how many turns pass until its time comes up again on the schedule. Add the IScheduleable.cs file to the Interfaces folder and add the following code.

public interface IScheduleable
{
  int Time { get; }
}

So now that we see what an IScheduleable is, the next question is why do we have a List<> of them for each key in the dictionary? The answer goes back to our to our Example Schedule. If you look at it closer you’ll notice that more than one Actor can act on the same time interval. At Time 6 both the Ooze and Goblin act. At time 12 all three act.

Let’s go ahead and create SchedulingSystem.cs in the Systems folder.

public class SchedulingSystem
{
  private int _time;
  private readonly SortedDictionary<int, List<IScheduleable>> _scheduleables;

  public SchedulingSystem()
  {
    _time = 0;
    _scheduleables = new SortedDictionary<int, List<IScheduleable>>();
  }

  // Add a new object to the schedule 
  // Place it at the current time plus the object's Time property.
  public void Add( IScheduleable scheduleable )
  {
    int key = _time + scheduleable.Time;
    if ( !_scheduleables.ContainsKey( key ) )
    {
      _scheduleables.Add( key, new List<IScheduleable>() );
    }
    _scheduleables[key].Add( scheduleable );
  }

  // Remove a specific object from the schedule.
  // Useful for when an monster is killed to remove it before it's action comes up again.
  public void Remove( IScheduleable scheduleable )
  {
    KeyValuePair<int, List<IScheduleable>> scheduleableListFound 
      = new KeyValuePair<int, List<IScheduleable>>( -1, null );

    foreach ( var scheduleablesList in _scheduleables )
    {
      if ( scheduleablesList.Value.Contains( scheduleable ) )
      {
        scheduleableListFound = scheduleablesList;
        break;
      }
    }
    if ( scheduleableListFound.Value != null )
    {
      scheduleableListFound.Value.Remove( scheduleable );
      if ( scheduleableListFound.Value.Count <= 0 )
      {
        _scheduleables.Remove( scheduleableListFound.Key );
      }
    }
  }

  // Get the next object whose turn it is from the schedule. Advance time if necessary
  public IScheduleable Get()
  {
    var firstScheduleableGroup = _scheduleables.First();
    var firstScheduleable = firstScheduleableGroup.Value.First();
    Remove( firstScheduleable );
    _time = firstScheduleableGroup.Key;
    return firstScheduleable;
  }

  // Get the current time (turn) for the schedule
  public int GetTime()
  {
    return _time;
  }

  // Reset the time and clear out the schedule
  public void Clear()
  {
    _time = 0;
    _scheduleables.Clear();
  }
}

Quite a bit of code and if we run the game again nothing will have changed since the last tutorial. We won’t get a chance to actually see our scheduling system in action until next time.

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

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

RogueSharp V3 Tutorial – Simple Combat

Next Tutorial Post – Scheduling System
Previous Tutorial Post – Monster Stats

Goal

During this tutorial we will setup a basic system for combat. At this point only the Player will be able to attack and they will be limited to a single type of attack when trying to move into a space that is occupied by a monster. Dice will be rolled and all the results will be printed to the message log. This is often referred to as “bump attack” because the player bumps into a monster while moving.

SimpleCombat

Simple Combat

Combat Overview

The combat system was covered in the past but I’m going to repeat some of that here for reference. For this tutorial I decided combat would 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.

Important Stats for Combat

  • 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.
  • 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.
  • 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.

Additional DungeonMap Methods

Before we start rolling dice we need to add a couple more methods to DungeonMap.cs to handle getting a monster at a particular position, and removing a monster from the map. We already have a method AddMonster() so lets put the new methods directly below that. Open DungeonMap.cs and add the following code.

public void RemoveMonster( Monster monster )
{
  _monsters.Remove( monster );
  // After removing the monster from the map, make sure the cell is walkable again
  SetIsWalkable( monster.X, monster.Y, true );
}

public Monster GetMonsterAt( int x, int y )
{
  return _monsters.FirstOrDefault( m => m.X == x && m.Y == y );
}

Adding the Attack Command

Now for the fun part, the actual combat. Attacking will be another command just like moving so we will add the code to CommandSystem.cs. The first thing we need to do is update the MovePlayer() method to use our new DungeonMap.GetMonsterAt( x, y ) method to see if there is a monster in the Cell we are trying to move into. When there is then instead of moving we will attack it.

Add the following code to the MovePlayer() method in CommandSystem.cs after the if block where we try to SetActorPosition. This is also after we have established our x and y position based on the direction the player is trying to move.

Monster monster = Game.DungeonMap.GetMonsterAt( x, y );

if ( monster != null )
{
  Attack( Game.Player, monster );
  return true;
}

Now that is in place, lets make our Attack() method.

public void Attack( Actor attacker, Actor defender )
{
  StringBuilder attackMessage = new StringBuilder();
  StringBuilder defenseMessage = new StringBuilder();

  int hits = ResolveAttack( attacker, defender, attackMessage );

  int blocks = ResolveDefense( defender, hits, attackMessage, defenseMessage );

  Game.MessageLog.Add( attackMessage.ToString() );
  if ( !string.IsNullOrWhiteSpace( defenseMessage.ToString() ) )
  {
    Game.MessageLog.Add( defenseMessage.ToString() );
  }

  int damage = hits - blocks;

  ResolveDamage( defender, damage );
}

// The attacker rolls based on his stats to see if he gets any hits
private static int ResolveAttack( Actor attacker, Actor defender, StringBuilder attackMessage )
{
  int hits = 0;

  attackMessage.AppendFormat( "{0} attacks {1} and rolls: ", attacker.Name, defender.Name );

  // Roll a number of 100-sided dice equal to the Attack value of the attacking actor
  DiceExpression attackDice = new DiceExpression().Dice( attacker.Attack, 100 );
  DiceResult attackResult = attackDice.Roll();

  // Look at the face value of each single die that was rolled
  foreach ( TermResult termResult in attackResult.Results )
  {
    attackMessage.Append( termResult.Value + ", " );
    // Compare the value to 100 minus the attack chance and add a hit if it's greater
    if ( termResult.Value >= 100 - attacker.AttackChance )
    {
      hits++;
    }
  }

  return hits;
}

// The defender rolls based on his stats to see if he blocks any of the hits from the attacker
private static int ResolveDefense( Actor defender, int hits, StringBuilder attackMessage, StringBuilder defenseMessage )
{
  int blocks = 0;

  if ( hits > 0 )
  {
    attackMessage.AppendFormat( "scoring {0} hits.", hits );
    defenseMessage.AppendFormat( "  {0} defends and rolls: ", defender.Name );

    // Roll a number of 100-sided dice equal to the Defense value of the defendering actor
    DiceExpression defenseDice = new DiceExpression().Dice( defender.Defense, 100 );
    DiceResult defenseRoll = defenseDice.Roll();

    // Look at the face value of each single die that was rolled
    foreach ( TermResult termResult in defenseRoll.Results )
    {
      defenseMessage.Append( termResult.Value + ", " );
      // Compare the value to 100 minus the defense chance and add a block if it's greater
      if ( termResult.Value >= 100 - defender.DefenseChance )
      {
        blocks++;
      }
    }
    defenseMessage.AppendFormat( "resulting in {0} blocks.", blocks );
  }
  else
  {
    attackMessage.Append( "and misses completely." );
  }

  return blocks;
}

// Apply any damage that wasn't blocked to the defender
private static void ResolveDamage( Actor defender, int damage )
{
  if ( damage > 0 )
  {
    defender.Health = defender.Health - damage;

    Game.MessageLog.Add( $"  {defender.Name} was hit for {damage} damage" );

    if ( defender.Health <= 0 )
    {
      ResolveDeath( defender );
    }
  }
  else
  {
    Game.MessageLog.Add( $"  {defender.Name} blocked all damage" );
  }
}

// Remove the defender from the map and add some messages upon death.
private static void ResolveDeath( Actor defender )
{
  if ( defender is Player )
  {
    Game.MessageLog.Add( $"  {defender.Name} was killed, GAME OVER MAN!" );
  }
  else if ( defender is Monster )
  {
    Game.DungeonMap.RemoveMonster( (Monster) defender );

    Game.MessageLog.Add( $"  {defender.Name} died and dropped {defender.Gold} gold" );
  }
}

That’s a lot of code but I tried to add some comments to help clarify. It should read very similar to the Combat Overview section above. You’ll notice that instead of having one gigantic public method with 100 lines of code we extracted some private methods. I like doing this because you can quickly look at the public method and see what it is doing without having to know all of the details. Resolve the attack rolls, resolve the defense rolls, write the results to the message log, and resolve any damage that was sustained.

Giving the private methods clear names for their purpose can help with readability. Someone who doesn’t care about the internals can just skip over those, or if they do need to see the details of what it means to resolve an attack, they dive in and look at that private method.

You might notice that in the death message it mentions dropping gold. We haven’t actually implemented that yet but we’ll get to it soon. I don’t mind having the message in place and ready to go for when we do.

If the dice expressions seem a little tricky here you can read more about them here:

Run the program now and you should be able to move around the map and bump into monsters to damage them.

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

Closing Thoughts

When you are going through the code examples here, please don’t feel like you have to type them in letter for letter. For example in this tutorial try changing the messages that get added to the log to make them more interesting. Create additional stats for actors and make up your own more exciting combat system. Rename and reorganize anything and everything you want so that it makes sense to you.

The code presented in this tutorial series is far from perfect. I am finding lots of issues that bug me every time I go back to write one of these posts. The point is that the examples I provide here are just one way of doing things. Get some ideas here and then visit other blogs and read other tutorials to collect more ideas. Eventually you’ll settle upon what works best for you and your own unique style.

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

RogueSharp V3 Tutorial – Monster Stats

Next Tutorial Post – Simple Combat
Previous Tutorial Post – Monster Generation

Goal

During this tutorial we’ll show the monster health bars in the right hand stats panel, but only when they are in line-of-sight to the player.

MonsterStats

Monster Stats

Adding a DrawStats() Method to Monster

As you can see in the image above, the goal for drawing monster stats is to first display the symbol of the monster in the appropriate color followed by the monster’s name. Behind the name is a health bar that will decrease based on the percentage of monster health remaining.

Open Monster.cs and add the DrawStats() method as follows:

public class Monster : Actor
{
  public void DrawStats( RLConsole statConsole, int position )
  {
    // Start at Y=13 which is below the player stats.
    // Multiply the position by 2 to leave a space between each stat
    int yPosition = 13 + ( position * 2 );

    // Begin the line by printing the symbol of the monster in the appropriate color
    statConsole.Print( 1, yPosition, Symbol.ToString(), Color );

    // Figure out the width of the health bar by dividing current health by max health
    int width = Convert.ToInt32( ( (double) Health / (double) MaxHealth ) * 16.0 );
    int remainingWidth = 16 - width;

    // Set the background colors of the health bar to show how damaged the monster is
    statConsole.SetBackColor( 3, yPosition, width, 1, Swatch.Primary );
    statConsole.SetBackColor( 3 + width, yPosition, remainingWidth, 1, Swatch.PrimaryDarkest );

    // Print the monsters name over top of the health bar
    statConsole.Print( 2, yPosition, $": {Name}", Swatch.DbLight );
  }
}

I tried to add comments to the code to help make it easier to follow along.

Updating the DungeonMap Draw() Method

Next we need to do a bit of work to DungeonMap.Draw(). It needs an extra parameter statConsole so that when we are drawing monsters, we can also draw out their stats. It is also important that we keep a count of the number of monsters that we have drawn so far. Each time we draw a new health bar stat block for a monster we want it to be below the previous one we drew so that’s why we pass this index into monster.DrawStats().

public void Draw( RLConsole mapConsole, RLConsole statConsole )
{
  // Old code
  foreach ( Cell cell in GetAllCells() )
  {
    SetConsoleSymbolForCell( mapConsole, cell );
  }

  // New code starts here ...

  // Keep an index so we know which position to draw monster stats at
  int i = 0;

  // Iterate through each monster on the map and draw it after drawing the Cells
  foreach ( Monster monster in _monsters )
  {    
    monster.Draw( mapConsole, this );
    // When the monster is in the field-of-view also draw their stats
    if ( IsInFov( monster.X, monster.Y ) )
    {      
      // Pass in the index to DrawStats and increment it afterwards
      monster.DrawStats( statConsole, i );
      i++;
    }
  }
}

Cleaning up Clear() Methods

If you remember from our previous code for our Draw() methods on DungeonMap and MessageLog we call the Clear() method on the corresponding RLConsole every time anything changes. So what this means is that if a player or monster changes position, we Clear() the whole map and redraw everything. Although this may not be the most efficient way to do things, it does make the code a lot more simple and it should be good enough for our purposes.

The problem is that currently the Clear() calls are spread out in several places. Remove the console.Clear() calls in DungeonMap.cs and MessageLog.cs

Then open Game.cs and modify the OnRootConsoleRender() method.

private static void OnRootConsoleRender( object sender, UpdateEventArgs e )
{
   if ( _renderRequired )
   {
      _mapConsole.Clear();
      _statConsole.Clear();
      _messageConsole.Clear();

      DungeonMap.Draw( _mapConsole, _statConsole );

      // Additional old code omitted...
   }
}

If you run the game now you should see the monster health bars as you find them around the map. We still can’t interact with the monsters but we will be able to soon.

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

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

RogueSharp V3 Tutorial – Monster Generation

Next Tutorial Post – Monster Stats
Previous Tutorial Post – Player Stats

Goal

During this tutorial we’ll create our first monster, the lowly Kobold. We’ll also randomly create our Kobold and his friends in rooms across our map. At this point the Kobold will not act or even be able to be attacked. That will come later.

Kobolds

Kobolds

Choosing a Kobold Color

The most important part of creating our monster is choosing a color for him. I’m going to use an existing color from our swatch DbBrightWood. Pick any color you want and add it to Colors.cs.

public static RLColor KoboldColor = Swatch.DbBrightWood;

Create a Monster Base Class

Next we need to create the Monster base class that all of our monsters going forward will inherit from. At this point it will be pretty lackluster and not have any functionality but we will be adding to it in the future. Our Monster class is a type of Actor so it needs to inherit from Actor. Create a new class Monster.cs in the Core folder and add code that looks like the following:

public class Monster : Actor
{
  // Empty for now, we will add functionality later
}

Creating a Kobold

Our first Monster will be the reptilian Kobold. Create a new folder called Monsters and create a new class in the Monsters folder called Kobold.cs. The Kobold class will inherit from the Monster class that we made up above.

public class Kobold : Monster
{
  public static Kobold Create( int level )
  {
    int health = Dice.Roll( "2D5" );
    return new Kobold {
      Attack = Dice.Roll( "1D3" ) + level / 3,
      AttackChance = Dice.Roll( "25D3" ),
      Awareness = 10,
      Color = Colors.KoboldColor,
      Defense = Dice.Roll( "1D3" ) + level / 3,
      DefenseChance = Dice.Roll( "10D4" ),
      Gold = Dice.Roll( "5D5" ),
      Health = health,
      MaxHealth = health,
      Name = "Kobold",
      Speed = 14,
      Symbol = 'k'
    };
  }
}

For now the Kobold will only have a Create() method. We’ll pass the current level of the dungeon into the Create() method so that as the player progresses deeper into the dungeon the Kobolds will get stronger. Feel free to assign the stats to whatever you want. I used the Dice class to roll for some random values. See my previous post about the Dice class for more examples of how that class works.

One thing to note is that we didn’t create a Draw() method for the Kobold, yet he will still be able to be drawn to our map just fine. The reason for this is because of the inheritance chain we set up. Kobold : Monster : Actor.  Because an Actor already has a Draw() method the Kobold will get it automatically through inheritance!

Adding Monsters to the DungeonMap

Now that we have our first monster we need a way to be able to add them to our DungeonMap. Open DungeonMap.cs and add a new private field that is a List<Monster>. Make sure that you initialize the list in the constructor.

private readonly List<Monster> _monsters;

public DungeonMap()
{
  // Initialize all the lists when we create a new DungeonMap
  _monsters = new List<Monster>();
}

Next we’ll need to add a few new methods to DungeonMap.cs to help us add monsters.

public void AddMonster( Monster monster )
{
  _monsters.Add( monster );
  // After adding the monster to the map make sure to make the cell not walkable
  SetIsWalkable( monster.X, monster.Y, false );
}

// Look for a random location in the room that is walkable.
public Point GetRandomWalkableLocationInRoom( Rectangle room )
{
  if ( DoesRoomHaveWalkableSpace( room ) )
  {
    for ( int i = 0; i < 100; i++ )
    {
      int x = Game.Random.Next( 1, room.Width - 2 ) + room.X;
      int y = Game.Random.Next( 1, room.Height - 2 ) + room.Y;
      if ( IsWalkable( x, y ) )
      {
        return new Point( x, y );
      }
    }
  }

  // If we didn't find a walkable location in the room return null
  return null;
}

// Iterate through each Cell in the room and return true if any are walkable
public bool DoesRoomHaveWalkableSpace( Rectangle room )
{
  for ( int x = 1; x <= room.Width - 2; x++ )
  {
    for ( int y = 1; y <= room.Height - 2; y++ )
    {
      if ( IsWalkable( x + room.X, y + room.Y ) )
      {
        return true;
      }
    }
  }
  return false;
}

The first method AddMonster() will add a Monster to list of monsters currently on the map and make sure that the Cell the Monster is located in will not be walkable.

The next two methods GetRandomWalkableLocationInRoom() and DoesRoomHaveWalkableSpace() will both be useful for us in determining a good location to place our Monsters.

The last change to DungeonMap.cs will be to update the Draw() method to make sure that all of the Monsters on the map are also drawn.

// Iterate through each monster on the map and draw it after drawing the Cells
foreach ( Monster monster in _monsters )
{
  monster.Draw( mapConsole, this );
}

Generating and Placing Monsters

We now have the ability to add Monsters to the DungeonMap. The only thing left to do is to actually generate our Monsters. We’ll update MapGenerator.cs and add a method to PlaceMonsters(). The algorithm will basically go through each room and roll a 10-sided die. If the result of the roll is 1-6 then we’ll roll 4-sided die and add that many monsters to the room in any open Cells. We will make extensive use of the methods that we previously added to DungeonMap.cs to help us out.

Add the following method to MapGenerator.cs

private void PlaceMonsters()
{
  foreach ( var room in _map.Rooms )
  {
    // Each room has a 60% chance of having monsters
    if ( Dice.Roll( "1D10" ) < 7 )
    {
      // Generate between 1 and 4 monsters
      var numberOfMonsters = Dice.Roll( "1D4" );
      for ( int i = 0; i < numberOfMonsters; i++ )
      {
        // Find a random walkable location in the room to place the monster
        Point randomRoomLocation = _map.GetRandomWalkableLocationInRoom( room );
        // It's possible that the room doesn't have space to place a monster
        // In that case skip creating the monster
        if ( randomRoomLocation != null )
        {
          // Temporarily hard code this monster to be created at level 1
          var monster = Kobold.Create( 1 );
          monster.X = randomRoomLocation.X;
          monster.Y = randomRoomLocation.Y;
          _map.AddMonster( monster );
        }
      }
    }
  }
}

Don’t forget to add a call to PlaceMonsters() in the CreateMap() method.

public DungeonMap CreateMap()
{
  // ... Existing code
  PlacePlayer();

  // After the existing PlacePlayer() call, add another call to PlaceMonsters()
  PlaceMonsters();
}

If you run the game now you should have some ‘k’ shaped kobolds on your map. They won’t do anything interesting just sit there and act like walls. We will give them some interesting behaviors later on.

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

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

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.