Skip to content

Commit

Permalink
Documentation (#1)
Browse files Browse the repository at this point in the history
Created documentation and refactored interface
  • Loading branch information
antouhou authored Jul 4, 2020
1 parent 507fca7 commit f17bde6
Show file tree
Hide file tree
Showing 41 changed files with 1,420 additions and 792 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,5 @@ gen
/coverage.report
/nunit-results.xml
/output.mlpd
/.vscode/
.DS_Store
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Contributing

When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.

## Pull Request Process

- Ensure any install or build dependencies are removed before the end of the layer when doing a build.
- Please update the [tests](./HexCore.Tests) to reflect the changes.
- Update the [docs](./Docs) with details of changes to the interface.
- Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is SemVer.
- You may merge the Pull Request in once you have the sign-off of the repo maintainer, or if you do not have permission to do that, you may request the second reviewer to merge it for you.

Thank you!
150 changes: 150 additions & 0 deletions Docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# HexCore Documentation

Welcome to the HexCore documentation! HexCore is a library to perform various operations with a hexagonal grid, such as finding shortest paths from one cell to another, managing terrains and movement types, maintaining the grid state, finding various ranges, neighbors, coordinate systems and converters, and some more stuff you may want to do with a hex grid. This is an in-depth doc, if you're looking for a quick start guide, please take a look here: [Quickstart](../README.md#quickstart)

## Table of contents

- [Introduction](#core-principles)
- [Terrain and movement types](#terrain-and-movement-types)
- [Cell states](#cell-state)
- [Coordinate systems](#coordinates)
- [Finding the shortest path](#finding-the-shortest-path)
- [Get the pawn's movement range](#get-movement-range)
- [Other ranges](#other-ranges)
- [Interacting with cell states](#interacting-with-cell-states)
- [Block cells for movement](#blockunblock-cells-for-movement)
- [Changing the cell's terrain type](#changing-the-cells-terrain-type)
- [Utility methods](#utility-methods)
- [graph.Contains()](#graphcontains)
- [graph.GetAllCells()](#graphgetallcells)

## Core principles

The main class is `HexCore.Graph`. In order to work, the graph needs some data: Cell states and movement types. Cell state keeps all the info about a particular cell: its coordinate, whether its occupied or not, and its terrain type. Terrain types are needed to calculate movement range for a pawn on a particular terrain (for example, a pawn with the movement type "walking" should receive a penalty when swimming through "water").

### Terrain and movement types

As explained above, movement types are essential for the graph. There's a helper class to help you maintain your terrain and movement types. It's called `MovementTypes`. It maintains a table of your movement and terrain types. It's mandatory to specify movement types for a graph. Movement types can be created this way:

*Note: If you want to have just the same constant movement cost across the whole map, create only one terrain and movement type with a movement cost of 1. One terrain type with a movement cost of 1 is not a default option to ensure that this is a deliberate choice and not a mistake.*

```c#
var ground = new TerrainType(1, "Ground");
var water = new TerrainType(2, "Water");

var walkingType = new MovementType(1, "Walking");
var swimmingType = new MovementType(2, "Swimming");

var movementTypes = new MovementTypes(
new ITerrainType[] { ground, water },
new Dictionary<IMovementType, Dictionary<ITerrainType, int>>
{
[walkingType] = new Dictionary<ITerrainType, int>
{
[ground] = 1,
[water] = 2
},
[swimmingType] = new Dictionary<ITerrainType, int>
{
[ground] = 2,
[water] = 1
}
}
);
```

In this example, walking movement types spends two movement points to swim and one to walk, so his movement range in water is limited. In the example above the vice versa also would be true for a swimming type.

### Cell State

Cells have a state. The state contains info about the cell coordinate, whether it's occupied or not, and its terrain type. In order to create a graph, you need to have a list of cell states. You can create it like this:

_Note: This example uses `Coordinate2D` to make it easier to understand what's going on, but it's recommended to use `Coordinate3D` instead. You can read more about coordinate systems in the [coordinate systems section](#coordinates)._
```c#
var graph = new Graph(new CellState[] {
new CellState(false, new Coordinate2D(0,0, OffsetTypes.OddRowsRight), ground),
new CellState(false, new Coordinate2D(0,1, OffsetTypes.OddRowsRight), ground),
new CellState(true, new Coordinate2D(1,0, OffsetTypes.OddRowsRight), water),
new CellState(false, new Coordinate2D(1,1, OffsetTypes.OddRowsRight), water),
new CellState(false, new Coordinate2D(1,2, OffsetTypes.OddRowsRight), ground)
}, movementTypes);
```
The resulting graph would look like this:
```
⬡⬡
⬡⬡⬡
```
The two cells on top would have terrain type "ground", the two cells in the second row would represent our first lake, and the first water cell would be not passable - we can imagine that there's a sharp rock in the lake at that spot.

### Coordinates

There are currently two coordinate systems implemented: offset coordinate type and 3D coordinate type. Offset coordinate type is easy to read for humans, as it's simple `x: rowNumber, y: columnNumber`. The Coordinate2D, however, requires some additional information to work properly - the offset type. It's also not easy to write algorithms for the `Coordinate2D`. The `Coordinate3D`, on the other hand, is not very human-readable, but very easy to operate with code. The library uses `Coordinate3D` internally everywhere, but it's easily possible to convert them into each other:
```c#
new Coordinate2D(0,0, OffsetTypes.OddRowsRight).To3D();
new Coordinate3D(0,0,0).To2D(OffsetTypes.OddRowsRight);
```

Both structures also have static method to convert an enumerable:
```c#
Coordinate2D.To3D(new [] {
Coordinate2D(0,0, OffsetTypes.OddRowsRight),
Coordinate2D(0,1, OffsetTypes.OddRowsRight)
});
Coordinate3D.To2D(new [] {
Coordinate3D(0,0,0),
Coordinate3D(0,1,-1)
}, OffsetTypes.OddRowsRight)
```

For the `Coordinate3D`, it's also possible to perform addition and substraction of two coordinates and multiplication by a scalar:
```c#
var a = new Coordinate3D(-1,1,0);
var b = new Coordinate3D(0,1,-1);

var c = a + b; // c == -1, 2, -1
var d = c * 2; // d == -2, 4, -2
```

All `Graph` methods accept both offset coordinates and 3d coordinates. The result will be returned with the same coordinate type that was passed to the method.

## Finding the shortest path

After the graph is initialized, simply call `graph.GetShortestPath(start, goal, pawnMovementType)` method on it. It accepts three parameters: coordinate from, coordinate to and, your pawn's movement type. If there's a path from a to b, it will return a sorted list containing the path excluding the starting point. For example for the 3x3 graph, where top the top left is `(0,0)` and the bottom right is `(2,2)`, the path from the bottom right to the top left would be `{(1,1),(1,0),(0,0)}`.

If there's no path from a to b, an empty list will be returned.

## Get movement range

To get pawn's movement range, call `graph.GetMovementRange(Coordinate3D startPosition, int movementPoints, IMovementType movementType)`. The first parameter should be the current pawn's position, the second parameter is the amount of "movement points" the pawn can use to move, and the last one is the pawn's movement type. The graph will calculate the area in which the pawn can move. The method returns a list of coordinates to which the pawn currently can move. Note that this area is not necessarily will be round in shape, as if you specified several of terrain types, moving through some terrain types can take more movement points.

## Other ranges

Sometimes you may need a range that is not dependent on terrain types, such as an ability or an attack range. For this there's `graph.GetRange(start, radius)` method. It returns all cells within the radius from the starting point.

## Interacting with cell states

### Changing cell state

Cell state consists of two properties: if the cell can be used to move to/through, and cell's terrain type. Both of them can be changed.

#### Block/unblock cells for movement

`graph.IsCellBlocked(Coordintate3D)` can be used to determine whether the cell is available for movement or not, for example, occupied by the pawn or some object.

`graph.BlockCells()` can be used to block cells. It has two variants: one is `graph.BlockCells(IEnumerable<Coordinate3D>)` to block a range of cells at once, or `BlockCells(Coordinate3D)` to block one cell.

`graph.UnblockCells()` works the same way as `graph.BlockCells()` described above, except it marks cells as passable.

#### Changing the cell's terrain type

Cell's terrain type can be changed using `graph.SetCellsTerrainType()`. Just as with blocking/unblocking cells, there are two variants: `SetCellsTerrainType(IEnumerable<Coordinate3D> coordinates, ITerrainType terrainType)` and `SetCellsTerrainType(Coordinate3D coordinates, ITerrainType terrainType)`.

## Utility methods

### graph.Contains

`graph.Contains(Coordinate3D)` returns `true` if graph contains given coordinate, `false` otherwise.

### graph.GetAllCells

Returns the list with all cells and their states. Please don't modify anything on the returned cell states and use `graph.BlockCells()`, `graph.UnblockCells()` and `graph.SetCellsTerrainType()` instead, as graph caches some data internally to speed up the computations, and modifying the cell state directly will lead to errors.
11 changes: 4 additions & 7 deletions HexCore.Tests/DataStructures/Coordinate2D.Test.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using HexCore.DataStructures;
using HexCore.HexGraph;
using HexCore;
using HexCoreTests.Fixtures;
using NUnit.Framework;

Expand All @@ -19,7 +18,7 @@ public void ConvertsOffsetCoordinatesToCubeCoordinatesCorrectly()
//Down and right: Y - 1, Z + 1
//Down and left: X - 1, Z + 1
//Right: X + 1, Y - 1;
var expectedCubeCoordinates = new List<Coordinate3D>
var expectedCubeCoordinates = new []
{
new Coordinate3D(0, 0, 0),
// Down right:
Expand All @@ -39,10 +38,8 @@ public void ConvertsOffsetCoordinatesToCubeCoordinatesCorrectly()
// Down and left:
new Coordinate3D(1, -3, 2)
};

Assert.That(cubeCoordinates.Count, Is.EqualTo(expectedCubeCoordinates.Count));
for (var index = 0; index < expectedCubeCoordinates.Count; index++)
Assert.That(cubeCoordinates[index], Is.EqualTo(expectedCubeCoordinates[index]));

Assert.That(cubeCoordinates, Is.EqualTo(expectedCubeCoordinates));
}

[Test]
Expand Down
2 changes: 1 addition & 1 deletion HexCore.Tests/DataStructures/Coordinate3D.Test.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using HexCore.DataStructures;
using HexCore;
using NUnit.Framework;

namespace HexCoreTests.DataStructures
Expand Down
26 changes: 15 additions & 11 deletions HexCore.Tests/Fixtures/MovementTypes.Mock.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,43 @@
using System.Collections.Generic;
using HexCore.HexGraph;
using HexCore;

namespace HexCoreTests.Fixtures
{
public static class MovementTypesFixture
{
public static readonly MovementType Ground = new MovementType(1, "Ground");
public static readonly MovementType Water = new MovementType(2, "Water");
public static readonly MovementType Air = new MovementType(3, "Air");
public static readonly MovementType Walking = new MovementType(1, " Walking");
public static readonly MovementType Swimming = new MovementType(2, "Swimming");
public static readonly MovementType Flying = new MovementType(3, "Flying");

public static readonly TerrainType Ground = new TerrainType(1, "Ground");
public static readonly TerrainType Water = new TerrainType(2, "Water");
public static readonly TerrainType Air = new TerrainType(3, "Air");

public static MovementTypes GetMovementTypes()
{
var movementTypes = new MovementTypes(
new Dictionary<IMovementType, Dictionary<IMovementType, int>>
ITerrainType[] terrainTypes = {Ground, Water, Air};
var movementTypes = new MovementTypes(terrainTypes,
new Dictionary<IMovementType, Dictionary<ITerrainType, int>>
{
[Ground] = new Dictionary<IMovementType, int>
[Walking] = new Dictionary<ITerrainType, int>
{
[Ground] = 1,
[Water] = 2,
[Air] = 999
},
[Water] = new Dictionary<IMovementType, int>
[Swimming] = new Dictionary<ITerrainType, int>
{
[Ground] = 2,
[Water] = 1,
[Air] = 999
},
[Air] = new Dictionary<IMovementType, int>
[Flying] = new Dictionary<ITerrainType, int>
{
[Ground] = 1,
[Water] = 1,
[Air] = 1
}
}
);
});
return movementTypes;
}
}
Expand Down
Loading

0 comments on commit f17bde6

Please sign in to comment.