Tag Archives: Release

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.

Collecting Code Coverage Metrics for .NET Core Libraries in Azure DevOps

Last time I talked about how I added status badges to the Readme file for RogueSharp. One of the badges was for showing the Code Coverage results from my continuous Azure DevOps build.

Before you can have a badge display those numbers, you need to have a build that can actually generate and collect them. While searching I found a lot of information about how to do it for full .NET Framework projects but information on .NET Core was scarce.

Since it wasn’t very straightforward for me I’ll share what I did here. Maybe there is a better way now?

MSTest Version

The first issue I struggled with was realizing that the version of MSTest that was I was using did not support the option to collect code coverage.

https://www.nuget.org/packages/MSTest.TestFramework/

You’ll need at least version 1.4.0.

Here is the commit where I upped my version, so up yours!

https://github.com/FaronBracy/RogueSharp/commit/10775b73baaec0785155ad2be287a5c255d0212b

Azure DevOps Menu

Azure DevOps Continuous Build

The Azure DevOps continuous build should then be using the .NET Core task.

.NET Core Build Task

I added three distinct steps.

  1. Restore NuGet Packages
  2. Build
  3. Run Tests

Build Steps

It’s this third step for running tests that will get us code coverage.

Test Step Setup

  • Under Arguments check the box next to Publish test results and code coverage
  • The Arguments should be --configuration $(BuildConfiguration) --collect "Code coverage"

The --collect "Code coverage" bit will only work if you are on MSTest version 1.4.0 or higher. It also appears to be case sensitive so make sure you type it exactly as shown.

Viewing Code Coverage Metrics

Once this is setup trigger the continuous build. If you look in the summary you should see the code coverage showing up now.

Build Summary

View Build Results In Azure DevOps

Code Coverage Badge

Code coverage

Shields.IO supports code coverage badges for multiple build services.

The formatted URL for Azure DevOps looks like this:

https://img.shields.io/azure-devops/coverage/{ORGANIZATION}/{PROJECT}/{DEFINITION_ID}.svg

See my previous post on status badges/shields for more information

About Faron

Faron Bracy is a husband, father, and TechSmith employee. He maintains the open source library RogueSharp as a hobby project. Other hobbies include painting fantasy miniatures, playing board games, and playing retro video games with his family.

Creating Status Badges and Shields for your Repository

I recently added status badges to the Readme file for the open source project I maintain, RogueSharp. I’ve seen other repositories do this but I wasn’t sure how. After a lot of looking around I compiled a list of services that worked for me. I couldn’t seem to find all this information in one place, so I’m hoping that this post showcasing what worked for me will help others.

Shields.IO

Shields.IO is a service for creating concise, consistent, and legible badges in SVG format. This should be your first stop when looking for a particular badge.

Resource Links

Tutorials: Blog Documentation: Github License: MIT

I use Shields.IO to create badges that link to static resources. To create these you just need a specially formatted URL.

https://img.shields.io/badge/LABEL-MESSAGE-COLOR.svg

To create a blue badge for a BSD 3-Clause license we could use

https://img.shields.io/badge/license-BSD%203--Clause-blue.svg

Because our license has a space and a dash in it we will need to treat those specially. The space will need to be replaced with it’s equivalent URL encoding “%20” and the dash will need to be escaped with a preceding dash.

Finally we need to decorate it with the appropriate markdown to get it to display as an image which links to the license.

[![License: BSD 3-Clause](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg)](https://github.com/TechSmith/hyde/blob/master/LICENSE.txt)

License: BSD 3-Clause

Test Status

Test status

It’s likely that no matter which service you are using for running continuous builds, Shields.IO also has you covered.

The formatted URL to get a test status badge for an Azure DevOps pipeline project will look like this:

https://img.shields.io/azure-devops/tests/ORGANIZATION/PROJECT/DEFINITION_ID.svg

When I did this for my own repo I wasn’t quite sure how to fill out “ORGANIZATION”, “PROJECT” and “DEFINITION_ID” placeholders.

What I found is that you can pull these directly from the query string when going to Azure DevOps and viewing your continuous build. In my case the URL is:

https://dreamersdesign.visualstudio.com/RogueSharp/_build?definitionId=1

In this example “dreamersdesign” is the organization, “RogueSharp” is the project and “1” is the definition ID.

Code Coverage

Code coverage

Shields.IO also supports code coverage badges for multiple build services.

The formatted URL for Azure DevOps looks like this:

https://img.shields.io/azure-devops/coverage/ORGANIZATION/PROJECT/DEFINITION_ID.svg

You’ll notice it looks like the test status badge, only replacing “tests” with “coverage”

BuildStats.info

Even though Shields.IO supported most of what I needed it didn’t support everything. It looks like development keeps happening on the service, so perhaps that will have changed by the time you read this. For my remaining badges I turned to BuildStats.info.

BuildStats.info creates SVG widgets to display build history charts and NuGet badges.

NuGet

Nuget

This one couldn’t be simpler. Just create a URL to

https://buildstats.info/nuget/PACKAGE_NAME

So for RogueSharp this was just

https://buildstats.info/nuget/roguesharp

It will automatically pull in the latest version number as well as the number of downloads.

Build History Chart

Build History

At the time that I setup my badges, BuildStats.info didn’t support the Build History Chart for Azure DevOps. It does look like this is now supported, but for my chart I used AppVeyor.

The formatted URL looks like this:

https://buildstats.info/BUILD_SYSTEM/chart/ACCOUNT/PROJECT

Substituting the values for my repository this yielded:

https://buildstats.info/appveyor/chart/FaronBracy/roguesharp-20n28

Build Service Specific

It’s worth noting that many build services provide their own status badge links. They probably won’t provide you with anything that you can’t get with Shields.IO, but it may be worth looking into.

Azure DevOps

Build status

When you are looking at your build pipeline you’ll see an elipsis for the build… click that.

Azure DevOps Menu

Then click on Status badge

Azure DevOps Status Badges

This will give you markdown that you can copy and paste directly into your readme.

AppVeyor

Build status

Click on AppVeyor configuration for a repo (the gear icon). Then select “Badges” from the tabs on the left.

AppVeyor Status Badges

This will give you markdown that you can copy and paste directly into your readme.

About Faron

Faron Bracy is a husband, father, and TechSmith employee. He maintains the open source library RogueSharp as a hobby project. Other hobbies include painting fantasy miniatures, playing board games, and playing retro video games with his family.

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