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.

9 thoughts on “RogueSharp v5.0 Pre-Release

  1. dreamsteppe

    So happy to see an update here! I’ve been following the tutorial and working off of your completed project’s source code obsessively over the past week or so. Keep up the hard work and stay healthy during these chaotic times!

    Reply
  2. Andy Hodges

    I am loving 5.0 preview. Quick question, are there any examples online that show how to save and load a map in 5.0 in json? I am able to easily save player progress but I have never been able to save dungeonmaps between sessions in roguesharp.

    Reply
  3. Andrea Magnelli

    Sometimes i restarted developing\playing a bit with roguelikes & your library is always an excellent base. Thanks a lot, really an inspiring work.

    Reply
      1. arcath

        I was going to start a new project so I havent used it yet which is why I was inquiring since the prerelease was a few months old.

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.