Skip to content

Commit

Permalink
Merge pull request #312 from Chris3606/develop
Browse files Browse the repository at this point in the history
GoRogue 3.0.0-beta07
  • Loading branch information
Chris3606 authored Jul 11, 2023
2 parents 9a3b53e + bddbfa3 commit 23763b7
Show file tree
Hide file tree
Showing 34 changed files with 885 additions and 387 deletions.
123 changes: 60 additions & 63 deletions GoRogue.Docs/articles/2-to-3-upgrade-guide.md

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions GoRogue.Docs/articles/howtos/factories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
title: Factories
---

# Factory Classes
One common paradigm in development is to have a "factory" whose responsibility is to produce objects of a given type. This can be as simple as a class with static methods whose name corresponds to the name of the object it creates; for example, you could have an `EnemyFactory` class which has methods called `Orc()`, `Goblin()`, etc; however implementations which conveniently allow for serialization and/or customization via user-readable data files can become more complex. GoRogue provides a set of pre-built classes which provide one possible way of implementing this paradigm.

# Factories in Concept
The GoRogue factory implementations consist of two components; the factory class, and blueprints. A blueprint is simply a unique identifier which denotes the type of item it creates, paired with a function which, when called, creates an object of that type. One or more blueprints are added to a factory class. After blueprints have been added, you call the `Create()` function on the factory. This function is passed a type of item as a parameter (eg. the identifier of a blueprint), and will call the appropriate blueprint's `Create()` function in order to create an item of that type and return it to you.

# Basic Usage
The simplest way to use the factory system, is to create a `Factory` which consists of `LambdaFactoryBlueprint` instances, which allow you to specify the creation function as a `Func<TProduced>`:

[!code-csharp[](../../../GoRogue.Snippets/HowTos/Factories/Factory.cs#BasicExample)]

You could also create a subclass of `Factory` and have the creation functions be (static or non-static) methods on that subclass, if you prefer:

[!code-csharp[](../../../GoRogue.Snippets/HowTos/Factories/Factory.cs#SubclassExample)]

This may be cleaner than anonymous functions if your creation methods are more complex and entail a fair a bit of code.

`LambdaFactoryBlueprint` instances work best as your blueprint type when your blueprints have no state or wrapper code associated with them. If you do have some state, have more advanced customization or parameterization you wish to do, or simply prefer creating subclasses for each item type, the blueprint types need only implement `IFactoryBlueprint`; so you may create your own subclass:

[!code-csharp[](../../../GoRogue.Snippets/HowTos/Factories/Factory.cs#CustomBlueprintExample)]

In some cases, you may wish to pass some parameters and/or state to the blueprint when the `Create()` method is called, rather than when the blueprint is created. For this, you should use `AdvancedFactory` instead of `Factory`. `AdvancedFactory` is identical to `Factory` except that it lets you specify an additional type parameter which is the type of a parameter you pass to the factory's `Create` function. This parameter is, in turn, passed to the blueprint.

Below is an example which aims to pass a Point to the `Create()` function which specifies the object's initial position:

[!code-csharp[](../../../GoRogue.Snippets/HowTos/Factories/AdvancedFactory.cs#AdvancedFactoryExample)]

You may also implement an `AdvancedFactory` subclass if you wish, or create custom blueprints by implementing the `IAdvancedFactoryBlueprint` interface yourself, just like the above examples which use `Factory` do.
163 changes: 30 additions & 133 deletions GoRogue.Docs/articles/howtos/map-generation.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
---
title: Using Dice Notation
title: Rolling Dice
---

# Dice Notation Parser
GoRogue contains a versatile [dice notation](https://en.wikipedia.org/wiki/Dice_notation) parser that allows you to roll virtual dice as your method of random number generation. The parser allows you to enter complex dice notation expressions, and emulate the result of rolling them via GoRogue's random number generation framework.
GoRogue contains a versatile [dice notation](https://en.wikipedia.org/wiki/Dice_notation) parser that allows you to roll virtual dice. The parser allows you to enter complex dice notation expressions, and emulate the result of rolling them via GoRogue's random number generation framework.

# Rolling Dice Expressions
The simplest way to use the dice roller is to use the provided `Roll` method:

```CSharp
int attackRoll = Dice.Roll("1d20+2");
```
[!code-csharp[](../../../GoRogue.Snippets/HowTos/DiceNotation.cs#BasicRolling)]

# Valid Expressions
The dice expression parser is robust and supports most typical dice notation syntax, which is based around the following string format:
Expand All @@ -24,8 +22,8 @@ string valid = $"{numberOfDice}d{sidesOnDie}";
Some examples of valid expressions to roll include:
* Roll 3 six-sided dice: `3d6`
* Roll an eight-sided die and add 2 to the result: `1d8+2`
* Roll a twelve-sided die, double the result, and add three: `d12*2+3`
* Roll a twelve-sided die, halve the result, and subtract 3: `1d12/2-1`
* Roll a twelve-sided die, double the result, and add 3: `d12*2+3`
* Roll a twelve-sided die, halve the result, and subtract 1: `1d12/2-1`
* Roll 10 ten-sided die, and only keep the top three: `10d10k3`
* Roll 4 six-sided die, add 1 to the entire roll, and only keep the top three: `4d6+1k3`

Expand All @@ -42,19 +40,8 @@ If you need to add support for an operand that is not supported by the default p
All of the terms for existing operators are defined as distinct classes, and the `DiceNotation.Parser` implementation, which serves as the default `IParser` implementation, is fairly straightforward. In the future the parsing code may be broken out into more modular pieces that are more easily re-usable (without copy-paste), but for now, basing custom parsing code off of the `Parser` class is the intended approach.

# Repeating/Obtaining Attributes About Rolls
If you are repeating a roll many times, or need attributes about a roll other than its result, you may want to call the `Dice.Parse` function instead of `Dice.Roll`. This returns you a `DiceExpression`, which you can use to roll the dice or get attributes about the expression:

```CSharp
DiceExpression expr = Dice.Parse("1d12+3");
If you are repeating a roll many times, or need attributes about a roll other than its result, you may want to call the `Dice.Parse` function instead of `Dice.Roll`. This returns you a `DiceExpression`, which you can use to roll the dice multiple times without re-parsing the dice notation string. `DiceExpression` also allows you to retrieve useful information about the expression:

// Returns the minimum possible value for the expression (4)
int minVal = expr.MinRoll();

// Returns the maximum possible value for the expression (15)
int maxVal = expr.MaxRoll();

// Rolls the expression. Can be called many times on the same DiceExpression
int value = expr.Roll();
```
[!code-csharp[](../../../GoRogue.Snippets/HowTos/DiceNotation.cs#DiceExpression)]

You can also inspect the actual terms within the expression tree of the dice expression via the `DiceExpression.RootTerm` field.
8 changes: 5 additions & 3 deletions GoRogue.Docs/articles/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
- name: How-Tos
href: howtos/index.md
items:
- name: Using Dice Notation
href: howtos/dice-notation.md
- name: Using Map Generation
- name: Factories
href: howtos/factories.md
- name: Map Generation
href: howtos/map-generation.md
- name: Rolling Dice
href: howtos/rolling-dice.md
15 changes: 9 additions & 6 deletions GoRogue.Docs/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@
"files": [
"GoRogue/bin/Debug/net6.0/GoRogue.dll"
],
"exclude": [
"exclude": [
"**/obj/**"
]
}
],
"dest": "api",
"disableGitFeatures": false,
"disableDefaultFilter": false,
"properties": {
"TargetFramework": "net6"
}
"properties": {
"TargetFramework": "net6"
},
"namespaceLayout": "nested"
}
],
"build": {
Expand Down Expand Up @@ -67,9 +68,11 @@
"keepFileLink": false,
"cleanupCacheHistory": false,
"disableGitFeatures": false,
"xref": [
"xref": [
"https://thesadrogue.github.io/TheSadRogue.Primitives/xrefmap.yml"
],
"xrefService": [ "https://xref.docs.microsoft.com/query?uid={uid}" ],
"xrefService": [
"https://xref.docs.microsoft.com/query?uid={uid}"
],
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,7 @@ public static void ScanlineOddEvenHashSetEncountered(PolygonAreaMock polygon)
foreach (var boundary in GetBoundariesContaining(polygon, x, y))
{
if (boundary.Any(p => p.Y < y))
{
if (!linesEncountered.Contains(boundary))
linesEncountered.Add(boundary);
}
linesEncountered.Add(boundary);
}
}
else
Expand Down
14 changes: 14 additions & 0 deletions GoRogue.Snippets/GoRogue.Snippets.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\GoRogue\GoRogue.csproj" />
</ItemGroup>

</Project>
26 changes: 26 additions & 0 deletions GoRogue.Snippets/HowTos/DiceNotation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using GoRogue.DiceNotation;
// ReSharper disable UnusedVariable

namespace GoRogue.Snippets.HowTos;
public static class DiceNotation
{
public static void ExampleCode()
{
#region BasicRolling
int attackRoll = Dice.Roll("1d20+2");
#endregion

#region DiceExpression
DiceExpression expr = Dice.Parse("1d12+3");

// Returns the minimum possible value for the expression (4)
int minVal = expr.MinRoll();

// Returns the maximum possible value for the expression (15)
int maxVal = expr.MaxRoll();

// Rolls the expression. Can be called many times on the same DiceExpression
int roll = expr.Roll();
#endregion
}
}
37 changes: 37 additions & 0 deletions GoRogue.Snippets/HowTos/Factories/AdvancedFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using GoRogue.Factories;
using SadRogue.Primitives;
// ReSharper disable NotAccessedPositionalProperty.Local
// ReSharper disable UnusedVariable

namespace GoRogue.Snippets.HowTos.Factories;

#region AdvancedFactoryExample
public static class AdvancedFactoryExample
{
record Terrain(Point Position, int Glyph, bool IsWalkable, bool IsTransparent)
: IFactoryObject<string>
{
public string DefinitionID { get; set; } = "";
}

public static void ExampleCode()
{
// LambdaAdvancedFactoryBlueprint is the same as LambdaFactoryBlueprint but
// implements IAdvancedFactoryBlueprint instead, which allows its creation
// function to take parameters. This is useful, for example, to create objects
// that require parameters to be passed to their constructor.
var factory = new AdvancedFactory<string, Point, Terrain>
{
new LambdaAdvancedFactoryBlueprint<string, Point, Terrain>(
"Floor",
pos => new Terrain(pos, '.', true, true)),
new LambdaAdvancedFactoryBlueprint<string, Point, Terrain>(
"Wall",
pos => new Terrain(pos, '#', false, false))
};

var floorTile = factory.Create("Floor", new Point(1, 2));
var wallTile = factory.Create("Wall", new Point(3, 4));
}
}
#endregion
107 changes: 107 additions & 0 deletions GoRogue.Snippets/HowTos/Factories/Factory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using GoRogue.Factories;
// ReSharper disable NotAccessedPositionalProperty.Local
// ReSharper disable UnusedVariable

namespace GoRogue.Snippets.HowTos.Factories;

#region BasicExample
public static class BasicExample
{
// Arbitrary class we want to create instances of. Implementing the IFactoryObject
// interface is optional, however when we do the DefinitionID field will automatically
// be set to the ID of the blueprint used to create it when a factory's Create method
// is called.
record Terrain(int Glyph, bool IsWalkable, bool IsTransparent) : IFactoryObject<string>
{
public string DefinitionID { get; set; } = "";
}

public static void ExampleCode()
{
// We'll identify the blueprints with strings in this instance, but this could be
// an enum or any hashable type
var factory = new Factory<string, Terrain>()
{
new LambdaFactoryBlueprint<string, Terrain>(
"Floor",
()=> new Terrain('.', true, true)),
new LambdaFactoryBlueprint<string, Terrain>(
"Wall",
() => new Terrain('#', false, false))
};

var floorTile = factory.Create("Floor");
var wallTile = factory.Create("Wall");
}
}
#endregion

#region SubclassExample
public static class SubclassExample
{
record Terrain(int Glyph, bool IsWalkable, bool IsTransparent) : IFactoryObject<string>
{
public string DefinitionID { get; set; } = "";
}

class MyFactory : Factory<string, Terrain>
{
public MyFactory()
{
Add(new LambdaFactoryBlueprint<string, Terrain>("Floor", Floor));
Add(new LambdaFactoryBlueprint<string, Terrain>("Wall", Wall));
}

private Terrain Floor() => new('.', true, true);
private Terrain Wall() => new('#', false, false);
}

public static void ExampleCode()
{
var factory = new MyFactory();

var floorTile = factory.Create("Floor");
var wallTile = factory.Create("Wall");
}

}
#endregion

#region CustomBlueprintExample

public static class CustomBlueprintExample
{
record Terrain(int Glyph, bool IsWalkable, bool IsTransparent) : IFactoryObject<string>
{
public string DefinitionID { get; set; } = "";
}

// A blueprint for terrain which counts the number of times each item type is
// instantiated.
record TerrainBlueprint(string ID, int Glyph, bool IsWalkable, bool IsTransparent)
: IFactoryBlueprint<string, Terrain>
{
private static readonly Dictionary<string, int> s_countingDictionary = new();

public Terrain Create()
{
s_countingDictionary[ID] = s_countingDictionary.GetValueOrDefault(ID, 0) + 1;
return new Terrain(Glyph, IsWalkable, IsTransparent);
}
}

public static void ExampleCode()
{
var factory = new Factory<string, Terrain>()
{
new TerrainBlueprint(
"Floor", '.', true, true),
new TerrainBlueprint(
"Wall", '#', false, false)
};

var floorTile = factory.Create("Floor");
var wallTile = factory.Create("Wall");
}
}
#endregion
Loading

0 comments on commit 23763b7

Please sign in to comment.