Tag Archives: Pool

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.