Tag Archives: Dice

RogueSharp v5.0 Pre-Release

RogueSharp v5.0 Pre-Release

New features:

  • Weighted pool class for randomly picking items.
  • Map and cell classes now have generic versions that are easier to inherit from.
  • Pathfinder class caches graph and dijkstra result for speedier performance with multiple lookups.
  • Added Map.GetCellsInRectangle method. (Thanks to Andre Odendaal)

Breaking changes:

  • Bug fix – Change Map.Create to be generic, returning the same map type as the creation strategy’s map. (Thanks to Andre Odendaal)
  • Bug fix – Update Map Save and Restore methods to track IsExplored status. (Thanks to Andre Odendaal)
  • Map.Initialize is virtual so that it can be overridden in derived classes
  • Map.Clone is now virtual and generic so that it can be overridden in derived classes.

RogueSharp v5.0 NuGet Package: https://www.nuget.org/packages/RogueSharp/5.0.0-pre2

RogueSharp Source Code: https://github.com/FaronBracy/RogueSharp

Weighted Pools

It is common in Roguelike computer games to have a sort of randomness when it comes to choosing treasure, placing monsters, or creating dungeons. A lot of tabletop games also have a draw mechanism whether it is choosing a card at random from a deck, picking a token at random from a bag, or rolling some dice and consulting a table.

Random Drawing Example

In the board game Quartz players draw crystal from a bag. There are 68 total crystals in the bag.

  • 18 Obsidian
  • 15 Quartz
  • 12 Amethyst
  • 10 Emerald
  • 7 Sapphire
  • 4 Ruby
  • 2 Amber
Quartz Bag

Quartz Bag

When a player draws a crystal, they keep it. It would no longer be available for another player to draw. We can simulate this using a RougeSharp WeightedPool

First we need to create a type representing the crystal. For this simple example we’ll just use an enumeration

public enum CrystalType
{
  Obsidian = 0,
  Quartz = 1,
  Amethyst = 2,
  Emerald = 3,
  Sapphire = 4,
  Ruby = 5,
  Amber = 6
}

Next we’ll create a helper method that will add a set amount of crystals into the pool.

public void AddCrystalToPool( WeightedPool<CrystalType> pool,
  CystalType crystalType, int numberToAdd )
{
  for ( int i = 0; i < numberToAdd; i++ )
  {
    pool.Add( crystalType, 1 );
  }
}

Notice that the weight for each crystal is 1. This means that each individual crystal has an equal chance to be drawn. There will still be a higher likelihood to draw Obsidian over Amber though, as there are 18 Obsidian in the bag and only 2 Amber.

Finally we’ll setup the pool and then draw 10 crystals from it. Notice that by using the Draw() we are actually removing the item from the pool.

public void RandomDrawExample()
{
  WeightedPool<CrystalType> pool = new WeightedPool<CrystalType>();
  AddCrystalToPool( pool, CrystalType.Obsidian, 18 );
  AddCrystalToPool( pool, CrystalType.Quartz, 15 );
  AddCrystalToPool( pool, CrystalType.Amethyst, 12 );
  AddCrystalToPool( pool, CrystalType.Emerald, 10 );
  AddCrystalToPool( pool, CrystalType.Sapphire, 7 );
  AddCrystalToPool( pool, CrystalType.Ruby, 4 );
  AddCrystalToPool( pool, CrystalType.Amber, 2 );

  for ( int x = 0; x < 10; x++ )
  {
    CrystalType crystal = pool.Draw();
    Console.WriteLine( $"You drew a {crystal} from the bag" );
  }
}

The output from running this would look something like the following:

You drew a Quartz from the bag
You drew a Obsidian from the bag
You drew a Emerald from the bag
You drew a Sapphire from the bag
You drew a Obsidian from the bag
You drew a Quartz from the bag
You drew a Obsidian from the bag
You drew a Amber from the bag
You drew a Quartz from the bag
You drew a Sapphire from the bag

Note that since there are only 68 total crystals in the bag if we attempted to draw more than that we would hit an exception when trying to draw the 69th.

System.InvalidOperationException: Add items to the pool before attempting to draw one

Treasure Table Example

For the next example, consider the following treasure table used to determine random treasure in a tabletop role playing game.

Treasure Table

Treasure Table

As you can see in this table the player would roll 1d100 and then look at the table and choose the reward based on what number they rolled. Let’s see how RogueSharp can simulate this effect using the new WeightedPool class as a similar feature could be useful in a Roguelike game.

In order to re-create this in code we would first want to make a class to represent the treasure.

public class Treasure
{
  public string Name { get; set; }
  public int GoldPieceValue { get; set; }

  public Treasure( string name, int goldPieceValue )
  {
    Name = name;
    GoldPieceValue = goldPieceValue;
  }

  public static Treasure Clone( Treasure other )
  {
    return new Treasure( other.Name, other.GoldPieceValue );
  }
}

The Clone method is important here because we want to make sure that we generate a copy of the treasure and not remove the treasure from the table so that there is a equal chance of generating the treasure again in the future.

public void GenerateTreasure()
{
  WeightedPool<Treasure> pool = new WeightedPool<Treasure>( Singleton.DefaultRandom, Treasure.Clone );
  pool.Add( new Treasure( "Ruby Necklace", 100 ), 14 );
  pool.Add( new Treasure( "Potion of Healing", 50 ), 14 );
  pool.Add( new Treasure( "Longsword + 1", 150 ), 16 );
  pool.Add( new Treasure( "Ring of Fireballs", 300 ), 14 );
  pool.Add( new Treasure( "Leather Armor", 25 ), 14 );
  pool.Add( new Treasure( "Small Shield", 25 ), 14 );
  pool.Add( new Treasure( "Teleportation Scroll", 100 ), 14 );

  for ( int x = 0; x < 10; x++ )
  {
    Treasure treasure = pool.Choose();
    Console.WriteLine( $"You found a {treasure.Name} worth {treasure.GoldPieceValue} gp" );
  }
}

Notice that when we create the WeightedPool we are passing in the Clone method from our Treasure class. If this method was not passed in to the constructor we would get an exception System.InvalidOperationException: A clone function was not defined when this pool was constructed

When we call pool.Add notice that we assign a weight to each item. This matches the values in the treasure table we are simulating and the values add up to 100 because the original table was a 1d100 role. It’s not necessary for the weights to add up to 100 though.

We call pool.Choose() to select an item from the table and make a copy of it. This is different than the example above where we call pool.Draw() which removes the item from the pool.

The output of this function will be something like the following:

You found a Small Shield worth 25 gp
You found a Teleportation Scroll worth 100 gp
You found a Longsword + 1 worth 150 gp
You found a Small Shield worth 25 gp
You found a Small Shield worth 25 gp
You found a Ring of Fireballs worth 300 gp
You found a Ruby Necklace worth 100 gp
You found a Potion of Healing worth 50 gp
You found a Teleportation Scroll worth 100 gp
You found a Ring of Fireballs worth 300 gp

See how the Small Shield was picked multiple times, but the Leather Armor didn’t make the list once. Weights could be adjusted if you wanted a higher likelihood of obtaining a certain item.

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 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 2.0 Released – Dice Notation

RogueSharp Version 2.0

Version 2.0 of RogueSharp was just released. It now contains better functionality for dealing with Dice and parsing Dice Notation strings.

Dice Notation .NET – Significant portions of the code in RougeSharp.DiceNotation namespace are from this great library. Thank you to Chris Wagner for originally creating this.

Usage

The easiest way to roll dice using dice notation is to use the static Roll method on the Dice class and provide a string representing the dice expression.

Example 1

// Roll 3 dice with 6 sides each and sum the results together
// This produces a result between 3 and 18
int result = Dice.Roll( "3d6" );

Example 2

// Roll 4 dice with 6 sides each
// But then only keep the 3 highest rolls
// This produces a result between 3 and 18
// Higher results should be produced on average than rolling 3d6
int result = Dice.Roll( "4d6k3" );

Example 3

// Roll 1 die with 10 sides and add 5 to the result
// This produces a result between 6 and 15
int result = Dice.Roll( "1d10+5" );

Example 4

// Roll 1 die with 10 sides and 2 dice with 6 sides each...
// Sum up the values of the dice and subtract 5 from the final result
// This produces a result between -2 and 17
int result = Dice.Roll( "1d10+2d6-5" );

Creating Expressions Fluently

If you haven’t heard of Fluent interfaces you can check out the link on Wikipedia to get a better description than I could provide. I think of it is using method chaining to produce more readable code. If you have ever used LINQ and seen how you could chain methods together to build your queries, I think that is a decent example. Let’s see how you can build a DiceExpression fluently

// In dice notation the following is the same as "5+1d8+4d6k3"
DiceExpression diceExpression = new DiceExpression()
  .Constant( 5 ) // Add a constant of 5
  .Die( 8 )      // Add an 8 sided Die
  .Dice( 4, 6, choose: 3 ); // Add 4d6 but when rolling them keep the 3 highest results

// Total should be between 9 and 31.
DiceResult result = diceExpression.Roll();
int total = result.Value;

Upgrading from v1.X

If you are were using an earlier version of RogueSharp and you used the `Dice` or `Die` classes there will be some minor changes that you will have to make to use v2.0. I have upgraded the sample for the tutorial series here so you can get an idea of what it will take: https://bitbucket.org/FaronBracy/roguesharpmonogamesamples/commits/25a57cc9820bcb2e75a043d76ca6bf32020db2ff