Category Archives: News

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 v4.1.0 Released

Version 4.1.0 of RogueSharp was released

Announcements:

New features:

  • Optimized cave map generator (Thanks to James Neal)
  • DijkstraShortestPath.FindPath method which terminates upon finding destination for improved speed (Thanks to flend for this update)
  • Added AppVeyor continuous builds (Thanks to Glenn Hoeppner)
  • New constructors for GoalMap and PathFinder to allow for paths which include diagonal movement
  • Map.GetCellsInCircle and Map.GetBorderCellsInCircle methods use midpoint circle algorithm to get cells within a radius
  • Multiple new “Try” methods which work like their normal counterparts except they return null instead of throwing exceptions
  • Path.TryStepForward and Path.TryStepBackward which will return null instead of throwing a NoMoreStepsException
  • PathFinder.TryFindShortestPath will return null instead of throwing a PathNotFoundException
  • GoalMap.TryFindPath will return null instead of throwing a PathNotFoundException

Breaking changes:

  • Bug fix (4.1.0) – selecting border Cells along edge of map no longer selects center Cell
  • Point and Rectangle classes are now structs (Thanks to James Neal)
  • Updated all the appropriate references to Cell with ICell (Thanks to Courtney Strachan)
  • Map.ComputeFov and Map.AppendFov both return a ReadonlyCollection<ICell> for the cells in the field-of-view instead of returning void.
  • The Path returned from PathFinder.ShortestPath now includes the source cell in the returned Path. This behavior is consistent with how the GoalMap pathfinder works
  • Map.GetCellsInArea was renamed to Map.GetCellsInSquare
  • Map.GetBorderCellsInArea was renamed to Map.GetBorderCellsInSquare
  • Map.GetCellsInRadius was renamed to Map.GetCellsInDiamond
  • Map.GetBorderCellsInRadius was renamed to Map.GetBorderCellsInDiamond

RogueSharp 4.0 Pre-Release

Version 4.0.0-pre of RogueSharp was just released

In order to obtain pre-release versions via Nuget make sure to choose “Include prerelease” packages from the Nuget Package Manager.

PrereleaseNuget

Pre-release Nuget Package

RogueSharp 4.0.0-pre NuGet Package
RogueSharp 4.0.0-pre Source Code

Thank you to numerous contributors for this release including James Neal, flend, Glenn Hoeppner, and Courtney Strachan. Any feedback regarding this pre-release version is appreciated!

New features:

  • Optimized cave map generator (Thanks to James Neal)
  • DijkstraShortestPath.FindPath method which terminates upon finding destination for improved speed (Thanks to flend for this update)
  • Added AppVeyor continuous builds (Thanks to Glenn Hoeppner)
  • New constructors for GoalMap and PathFinder to allow for paths which include diagonal movement
  • Map.GetCellsInCircle and Map.GetBorderCellsInCircle methods use midpoint circle algorithm to get cells within a radius
  • Multiple new “Try” methods which work like their normal counterparts except they return null instead of throwing exceptions
  • Path.TryStepForward and Path.TryStepBackward which will return null instead of throwing a NoMoreStepsException
  • PathFinder.TryFindShortestPath will return null instead of throwing a PathNotFoundException
  • GoalMap.TryFindPath will return null instead of throwing a PathNotFoundException

Breaking changes:

  • Point and Rectangle classes are now structs (Thanks to James Neal)
  • Updated all the appropriate references to Cell with ICell (Thanks to Courtney Strachan)
  • Map.ComputeFov and Map.AppendFov both return a ReadonlyCollection<ICell> for the cells in the field-of-view instead of returning void.
  • The Path returned from PathFinder.ShortestPath now includes the source cell in the returned Path. This behavior is consistent with how the GoalMap pathfinder works
  • Map.GetCellsInArea was renamed to Map.GetCellsInSquare
  • Map.GetBorderCellsInArea was renamed to Map.GetBorderCellsInSquare
  • Map.GetCellsInRadius was renamed to Map.GetCellsInDiamond
  • Map.GetBorderCellsInRadius was renamed to Map.GetBorderCellsInDiamond

Pathfinder Diagonal Path Example:

By using the new constructor on the PathFinder class and providing a second argument which is the cost of diagonal movement you can now return paths that consider diagonals.

public Path FindPath( ICell source, ICell destination, IMap map )
{
  // 1.41 is the cost of diagonal movment compared to horizontal or vertical of 1
  var pathFinder = new PathFinder( map, 1.41 );

  // TryFindShortestPath is a new method that will return null
  // instead of throwing PathNotFoundException if there isn't a path.
  return pathFinder.TryFindShortestPath( source, destination );
}
DiagonalPathfinder

Diagonal Pathfinder

GoalMap Diagonal Path Example:

The GoalMap class also received a new constructor which will allow it to use diagonals.

/// Constructs a new instance of a GoalMap for the specified Map 
/// that will consider diagonal movements to be valid if allowDiagonalMovement is set to true.
/// "map" The Map that this GoalMap will be created for
/// "allowDiagonalMovement" True if diagonal movements are allowed. False otherwise
public GoalMap( IMap map, bool allowDiagonalMovement )
DiagonalGoalMap

Diagonal Goal Map

Map.GetCellsInCircle Example:

RogueSharp has always had a lot of different Cell selection methods as seen in this old post.

Cell Selection

Previous Cell Selection

What it has been missing is a good method for getting cells in a Circle. We now have two new methods which will help with this.

/// Get an IEnumerable of Cells in a circle around the center Cell up 
/// to the specified radius using Bresenham's midpoint circle algorithm
/// "xCenter" X location of the center Cell with 0 as the farthest left
/// "yCenter" Y location of the center Cell with 0 as the top
/// "radius" The number of Cells to get in a radius from the center Cell
public IEnumerable<ICell> GetCellsInCircle( int xCenter, int yCenter, int radius )


/// Get an IEnumerable of outermost border Cells in a circle around the center 
/// Cell up to the specified radius using Bresenham's midpoint circle algorithm
/// "xCenter"X location of the center Cell with 0 as the farthest left
/// "yCenter"Y location of the center Cell with 0 as the top
/// "radius"The number of Cells to get in a radius from the center Cell
public IEnumerable<ICell> GetBorderCellsInCircle( int xCenter, int yCenter, int radius )
CircleSelection

Circle Selection

Using the new circle selection code it is possible to create poor implementation of circular field-of-view even though RogueSharp doesn’t have native support for it. Here is an example of some code that will do it.

private static IEnumerable<ICell> GetFieldOfView( int x, int y, IMap map )
{
  List<ICell> circleFov = new List<ICell>();
  var fieldOfView = new FieldOfView( map );
  var cellsInFov = fieldOfView.ComputeFov( x, y, (int) ( _selectionSize * 1.5 ), true );
  var circle = map.GetCellsInCircle( x, y, _selectionSize ).ToList();
  foreach ( ICell cell in cellsInFov )
  {
    if ( circle.Contains( cell ) )
    {
      circleFov.Add( cell );
    }
  }
  return circleFov;
}

It will create field-of-view that looks like this:

CircularFieldOfView

Circular Field-of-View

Porting RLNET V3 Tutorial to SadConsole and MonoGame

Porting the Sample RogueSharp V3 Game to SadConsole

A few weeks ago I spent some time porting the RogueSharp sample game from RLNET to SadConsole.

SadConsolePort

SadConsole Port

Source Code

The full source code for this can be found on BitBucket:
https://bitbucket.org/FaronBracy/roguesharpsadconsolesamples/src

Notes from the porting process

Because it was a direct port I’m sure I did some things that were undesirable. What follows are some of my notes from the porting process:

RLNET has a concept of a root console. You could draw directly to the root console or you could also draw to sub consoles and then Blit them to a specific locations of the root.

For SadConsole the examples from ThakraAndy show making your own console classes that inherit from SadConsole’s Console class. I didn’t do that however. I just got rid of the RLNET root console and made each of the sub consoles a new SadConsole.Console in my main Game class. This then means that I pass around references of those consoles to each of my object’s draw methods. For example the Player class’s draw method has a reference to both the map console and the stats console and draws directly to them. It doesn’t seem like this is the intended way to work with consoles however.

I also saw that SadConsole has the idea of an Entity which from what I can tell is independent of all of the regular console cells. It seems like the proper thing to do would be to have the player, monsters, and anything that moves around on the map be an entity, so that I wouldn’t have to keep track of what is under the entity and draw it back after the entity moves.

RLNET didn’t have the same entity concept and since I was doing a straight up conversion with as few changes as possible I actually did not use the SadConsole.Entity class. Basically I keep track of when anything changes (due to player input or monsters moving on their own) and then clear all the consoles cells completely and redraw them based on the current state of the game and game objects. Most likely a bad idea, but it worked.

I couldn’t figure out how to change the background color of a range of cells (health bars for the monsters, and refresh bars for abilities) so I made my own extension method which went through the cells one at a time and changed the background color. I think there must be a better way to do that, but I couldn’t find it right off.

Additionally SadConsole has it’s own way of handling input which I completely ignored and just used standard MonoGame input handling. This was due to time constraints and me being lazy. I plan to look over how SadConsole handles input more closely in the future.

Overall the port went quickly and required far fewer changes than I expected.

RogueSharp 3.0 Released

Version 3.0.0 of RogueSharp was just released

This is considered to be a stable version of 3.0 which was pre-released in October 2015.

As always your feedback is appreciated!

RogueSharp 3.0 NuGet Package
RogueSharp 3.0 Source Code

I’d like to thank Glenn Hoeppner for helping code review this release. He also helped with the setup of FxCop  and fixed some of warnings that uncovered. Now if you can just finish those AppVeyor continuous builds. 🙂

I will also soon be announcing a completely new tutorial series that uses Version 3 of RogueSharp. You can find the complete (and messy) source code for this tutorial series on BitBucket.

https://bitbucket.org/FaronBracy/roguesharprlnetsamples/branch/CompleteTutorial

During the upcoming weeks I will be cleaning up the commits and posting detailed step-by-step instructions on how the sample game was made.

RogueSharp 3.0 Pre-Release – Goal Maps

Version 3.0.0-pre of RogueSharp was just released

This is considered a pre-release version. In order to obtain it via Nuget make sure to choose “Include Prerelease” packages from the Nuget Package Manager.

IncludePrerelease

Include Prerelease

Any feedback regarding this pre-release version is appreciated!

RogueSharp 3.0-pre NuGet Package
RogueSharp 3.0-pre Source Code

New features:

  • Added Path class to provide a type with more functionality than simple IEnumerable
    • Returned from GoalMap and PathFinder classes
  • Reworked GoalMap interface to be more consistent with Pathfinder class
    • Ability to add and remove obstacles
    • Ability to remove Goals once they are added
  • Numerous bug fixes to GoalMap class
    • Fixed possible infinite recursion in some edge cases
    • Avoidance algorithm now will never choose  a Path through a Goal that is being avoided
  • FxCop clean up

Breaking changes:

  • Pathfinder now returns Path type instead of IEnumerable
  • GoalMap now returns Path type instead of IEnumerable
  • Map creation algorithms moved to their own namespace
  • DiceNotation “Type” renamed to “TermType” to avoid being a keyword

GoalMap / Path Example:

// Create a new map that only has a wall around the border
var map = Map.Create( new BorderOnlyMapCreationStrategy
<Map>( 50, 50 ) );

// Create a new GoalMap for the map
var goalMap = new GoalMap( map );

// Add a Goal at X=10, Y=25 with a weight of 10
// Lower weights are more desirable
goalMap.AddGoal( 10, 25, 10 );

// Add a second Goal at X=40, Y=15 with a weight of 10
goalMap.AddGoal( 40, 15, 10 );

// Add an obstacle at X=1, Y=2
// An obstacle is something that a Path must not go through
// Examples: an ally, an enemy, water, etc.
goalMap.AddObstacle( 1, 2 );  

// Find a "best" path to a Goal based on Goal weight and distance to the Goal
// Start the path from X=1, Y=1
// The path will not pass through obstacles
Path path = goalMap.FindPath( 1, 1 );

// Get the number of steps in the path
// Output = 34 
Console.WriteLine( path.Length );

// Get the start of the path
// Output = 1:1
Console.WriteLine( "{0}:{1}", path.Start.X, path.Start.Y );

// Get the end of the path
// Output = 10:25
Console.WriteLine( "{0}:{1}", path.End.X, path.End.Y );

// Take a step forward along the path
Cell nextStep = path.StepForward();
// Output = 2:1
Console.WriteLine( "{0}:{1}", nextStep.X, nextStep.Y );

// Take a backward along the path
Cell previousStep = path.StepBackward();
// Output = 1:1
Console.WriteLine( "{0}:{1}", previousStep.X, previousStep.Y );

// Iterate through each Step along the Path
// Output = 1:1 - 2:1 - 2:2 - 2:3 - 2:4 - 2:5 - 2:6 - 2:7 - 2:8 - 2:9 - 2:10 - 
// 2:11 - 2:12 - 2:13 - 2:14 - 2:15 - 2:16 - 2:17 - 2:18 - 2:19 - 2:20 - 2:21 - 
// 2:22 - 2:23 - 2:24 - 2:25 - 3:25 - 4:25 - 5:25 - 6:25 - 7:25 - 8:25 - 9:25 - 10:25 -
foreach ( Cell step in path.Steps )
{
   Console.Write( "{0}:{1} - ", step.X, step.Y );
}

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

RogueSharp 1.2 Released – Portable Class Library

RogueSharp Version 1.2

Version 1.2 of RogueSharp was just released. It is now a Portable Class Library. Thank you to Mattias Cibien for having the idea and doing the work for this release.

Portable Class Library

Portable Class Library

Targeting:

  • .NET Framework 4
  • Silverlight 5
  • Windows 8
  • Windows Phone 8.1
  • Windows Phone Silverlight 8

RogueSharp 1.1 Released – Cave-Like Map Creation Using Cellular Automata

RogueSharp Version 1.1

Version 1.1 of RogueSharp was just released. It now includes a new map creation strategy for creating cave-like maps based off of an article on RogueBasin with some custom modifications.

Usage

IMapCreationStrategy<Map> mapCreationStrategy = 
   new CaveMapCreationStrategy<Map>( 50, 30, 45, 4, 2 );
IMap map = Map.Create( mapCreationStrategy );

Parameters

  • width – The width of the Map (in Cells not pixels) to be created
  • height – The height of the Map (in Cells not pixels) to be created</param>
  • fillProbability – Recommend using values between 40 and 60. Percent chance that a given cell will be a floor when randomizing all cells before starting the cellular automata algorithm.
  • totalIterations – Recommend using values between 2 and 5. Number of times to execute the cellular automata algorithm.
  • cutoffOfBigAreaFill – Recommend using value less than 4. The iteration number to switch from the large area fill algorithm to a nearest neighbor algorithm
  • random (optional) – A class implementing IRandom that will be used to generate pseudo-random numbers necessary to create the Map. If this parameter is omitted then a default implementation of .NET’s System.Random will be used.