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.

Advertisements

19 thoughts on “RogueSharp V3 Tutorial – Stairs

  1. Pingback: RogueSharp V3 Tutorial – Doors | Creating a Roguelike Game in C#

  2. Andrea Magnelli

    Hey pal, this series is exceptional. I was trying to search some nasty tutorial to start up my project and this one sounds fantastic!
    I’m here waiting for others tutorials 🙂

    What will be really cool would be adding to future tutorials:

    – Going upstair \ downstair mantaining dungeon structures\mobs killed etc
    – Handling up lore forms or dialogue forms (which then brings to item shop etc)
    – Maybe mouse interaction?

    Congrats again, really really good stuff

    Andrea

    Reply
      1. Andrea Magnelli

        Hi Faron, thanks for your fast answer 🙂

        I got a little difficult\doubt that is concerning me.

        I’ve tried to play a bit with the console layout of your tutorial project, and i was hoping that would have been pretty possible to display one console with a font & another with another font.

        Why that?

        Basically because sometimes you want to use a smaller font for various text\UI\interface, and maybe a bit more readable size for the map (maybe half of the map tile font).
        I know you can “extend” the font bitmap adding rows over the 256 cells that already are settled up, but that only extends the symbol collection.

        I’ve seen you brought up this “tutorial” with SadConsole too, and it looks easy understandable once you understood the concepts of your tutorial. It uses SadConsole v3.00 (now i think is gone out the 6.00).

        Every subConsole has a method to access & modify the current font, which is very useful, but i’m knocking a bit my head off trying to achieve that on RLNET.

        I’ve tried to extend\build my own RLConsole\RLCell etc classes to do what RLRootConsole basically does (it seems the only one to have a editable font-png source), but i surrendered after some tries ’cause it enters a bit too much in the hearth of GL libraries and i was pretty sure to mess up everything.

        Did you have tried something like that or is not possible at all with RLNET?
        Considering that, do you suggest me (if i really need this framework function) to switch over SadConsole 3.00?
        I’m a bit afraid that the 3.00 version will not receive too much support in the future ’cause it is a bit aged.

        I hope I’ve explained my doubt & concerns (i’m not an english mother tongue :D) and sorry for WoT!

        Thanks for your kindness Faron,

        cheers

        Andrea

      2. Faron Bracy Post author

        Hi Andrea,

        As far as I am aware RLNet only allows one size of symbols for a console so it would not currently support two different font sizes.

        For choosing between RLNet and SadConsole, it’s a matter of preference. I can tell you that it seems the creator of RLNet has not been doing any updates or maintenance to the library. SadConsole on the other hand is very active and the author is constantly making updates as you pointed out by the frequent version changes.

        I assume by the SadConsole tutorial you mean the port that I did here (https://roguesharp.wordpress.com/2016/05/02/porting-rlnet-v3-tutorial-to-sadconsole-and-monogame/). That code could be using an older version of SadConsole, I used the most recent version that was stable at the time. If you want to make your own unique roguelike and not just follow the tutorial I would recommend using the latest version of SadConsole. My RogueSharp library should still work just fine with whatever console you choose.

        Another option is to use Unity (https://unity3d.com/unity). RogueSharp works nicely with Unity and it is has a large and active community.

        I hope this helps to give you some options,
        Faron

      3. Andrea Magnelli

        Hi Faron here is Andrea again,

        i noticed a bit problem: i tried to resize a bit the rooms & to add some monsters.

        Keeping the “moving” arrows pressed can result in some heavy stuttering\rendering lag (tried with different systems & console renderer – SadConsole & RLNET), and it seems to be related to the InputState controller instead of problems linked to pathfinding algorithms, scheduler or others, it happens also with a normal sized map with a bit more of monsters.

      4. Andrea Magnelli

        I investigated a bit more on the issue, i replicated it in a clean project, by adding a lot of monsters in one room and then “keeping movement key pressed”, the problem seems stronger when the map size exceed the console dimension and the stutter lag can became around 0.6-0.8 sec at turn, at this point i dont know if it is related to the input method or something else.

        Andrea (sorry for spam here but i dont know where to write you :D)

      5. Andrea Magnelli

        Okay, i noticed something else, that happens less when the monsters are just a few.

        The “output” console in VS pull out an enormous amount (in 10-12 turn — 300 or so) of “Exception thrown: ‘RogueSharp.PathNotFoundException’ in RogueSharp.dll” and some “Exception thrown: ‘RogueSharp.NoMoreStepsException’ in RogueSharp.dll”, from what i remember throwing out a lot of exception is not good in performance tuning, also if those are catched & handled by the wrapper classes that calls the function.

        If you pick your base example and but a considerable amout of monsters in a room, and start to “move around\combat” keeping keys pushed down you will notice that.

        Andrea

    1. Faron Bracy Post author

      Thanks for all of the feedback on the tutorial. You are correct about there being plenty of room for performance improvements. The code hasn’t been optimized for performance at all.

      I suspect most of the issues you are seeing are around the pathfinder. A new pathfinder is created for each monster and perhaps it would be better if they shared the same one for a given turn maybe using the GoalMap class instead of the PathFinder. The PathNotFoundException happens when asking for a path for a monster that is blocked by other monsters, which happens of course much more frequently when there are lots of monsters in a room. If monsters did an efficient check for being blocked before finding a path it could help. For big maps, only cells within a certain range of a monster could be passed in for pathfinding instead of the entire map.

      When working on my own projects, I usually save performance improvements for the very end and only if there is a noticeable issue as they can be time consuming and aren’t always necessary. Some even say premature optimization is the root of all evil https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize

      If I get extra time this weekend I may look into profiling some code out of curiosity. I don’t plan on updating the tutorial even if it is inefficient as it is really just meant to give people an example and some ideas. If there are performance improvements that could be made to the RogueSharp library without making it more complicated to use, then I am definitely interested in that. There are already some great contributions to RogueSharp v4 that are in the works.

      –Faron

      Reply
      1. Andrea Magnelli

        That phrase “premature optimization is the root of all evil” is in the first page of my computer engineering thesis 😀 (thanks Knuth)

        Don’t worry about performance that’s a problem that you can fight with later, i’m writing here just because i don’t know where to do it and it is useful to keep a track.

        Sorry again for the spam and good work on RS v4

        Andrea.

      2. Andrea Magnelli

        Hello again Faron,

        just to try something, i replaced PathFinder class with a personal one that basically doenst “throw the exception”, just returns null if there is no path, and the difference is really huge. I placed 5d4 monsters in a room with a 300×300 map and the experience was pretty smooht (i mean, you notice some little slow-thinking by the game but, nothing really laggy).

        Maybe this can lead to another design solution for PathFinder handling.

        Andrea

      3. Andrea Magnelli

        I’m actually still working on it a bit, because it is a bit situational to be honest and the “big” maps can lead to problems too.

        I would love to have and “Edit” function here on wordpress instead of spamreplying every time 😀

        Andrea

      4. Faron Bracy Post author

        Last weekend I ran the DotTrace profilier against a level with a decent sized room with a bunch of enemies as you can see here:

        It didn’t run too bad really. In a turn based game I didn’t see it as an unreasonable amount of time for the monsters to take their actions.

        Here is the trace:
        04.01.2017-12.28.33

        There are a ton of ActivateMonster calls which I collapsed into 1 node at the bottom there, but as you can see each monster is using it’s own pathfinder and is calling “GetBorderCellsInRadius” which could use some improvements.

        I think the way to really improve it is to just use GoalMapPathfinder to create a single Dijkstra Map of the level and have all of the monsters use that one map. Don’t update it on every monster’s actions, but only after the player moves.

        As far as I can tell the PathNotFoundExceptions, while annoying that they show up in the log, are not causing a concerning amount of performance overhead.

        Thank you for continuing to look into it. Hope you find it to be a fun programming exercise.
        –Faron

      5. Andrea Magnelli

        That is an interesting test and thank you for spending time perpetuating that.

        What i’ve done in the midtime is simply making a boolean mask around the monsters (like an “awarness” mask) that make them calculate the shortest path only in a determined radius of cell around themselves (which can be also a feature).

        The test that you work out is a good example, but i suggest you to run the same test with 2 different conditions – a bigger map (try something around 200 x 200) because of the exponential growth of graph point and vexels, and an example where the player is constantly surrounded by enemies, making them throttle on the graph searching for other paths. Those were the two situation that i found a bit stuttering, but i’l tell you again, those are probably limit situation and i noticed that is not “the number of enemies”, but the dimension of the graph that weights a lot.

        Thank you again for you time and your tutorial Faron, i’m using it as a “base structure” for working on something bigger.

        Andrea

  3. Andrea Magnelli

    Thanks again Faron for your insights.

    I think that the reason why i started following this tutorial was pretty the same that pushed you towards the completition of it, basically: think about the game, less about fancy graphics & stuff like that. And i think that RLNET pushes very well in this direction, with a simple but concrete DLL.

    I think that if I (or We, or just you) work well on the core game (RogueSharp or variations) then the only concern if one day we want to switch to something else should be just handling keyboard\mouse interaction, mapping new commands for printing on console and maybe adding some fancy animations or overlays. But the core, i think, should remain the same and that is the very soul of the game.

    I tried Unity too but i was searching for something more compact right now.

    Thanks again Faron, hope to read you soon,

    Andrea

    Reply
  4. Ben Barney

    OMG I didn’t realize the tutorial was still unfinished!

    I have to say I have been completely in love with this project, following it’s instructions for hours every day for a week building my own Rogue game! What great work you are doing, Faron! Thank you so much! May I please add my voice to those encouraging you. I hope we can finish our games soon!

    Thanks!

    Reply
      1. Ben Barney

        I know I already used up my one “OMG” on my first post, but I did not know there was a completed copy of the code and it makes me very excited! I’ve played Rogue and then Angband for almost twenty years and wanted to create my own for equally as long. So my excitement over this project is like (though it may sound silly) having a childhood wish come true. Thank you, earnestly, for your work and generosity in creating this library and tutorial and sharing it with us.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s