This project has moved. For the latest updates, please go here.

Documentation

May 14, 2013 at 10:11 PM
Hi - I have been through the code etc .... It lokks great in the example app .. but what I think it really needs is a guide on how to incorporate it into your own project - ie how to use the utilities if you already have your own game and want to incorporate the functionality.

Something like an outline of do.. this - do that.

What is there seems really good but wihtout any docs its very difficult to use.
Coordinator
May 15, 2013 at 6:55 AM
Edited May 15, 2013 at 6:58 AM
The project HexGridExample is intended to be a tutorial on integrating the utilities into a game project.

The interfaces HexGridExample.IMapBoard and HexGridExample.IMapGridHex extend the definition of Hex and Map to the additional functionality of a particular game.

The classes HexGridExample.MapBoard and HexGridExample.MapDisplay and the struct HexGridExample.GridHex implement these interfaces. I found it useful in my own game engine to separate the implementation of IMapBoard between the common functionality to all maps from the specific functionality for detail maps, as opposed to overview maps, and for game functions as opposed to display functions. Separating definition from implementation here is expected to facilitate the differences that would develop, for example, between the game itself and editors for maps and scenarios.

The class HexGridExample.TerrainMap illustrates how to build a map from a simple text-based definition, and display it in a pane.

The form HexGridExample.HexGridExample.cs illustrates a simple integration of the disparate parts in a game application.

I would be delighted to answer specific questions that you might have. At this point many of my design decisions are now so obvious to me, that I may have even forgotten that I made them. Insightful questions would assist me to unravel this into useful documentation.



.
May 15, 2013 at 9:21 AM
Many thanks for your answer.

I will digest carefully what you have said here and look again at the code to see what I can do.

I will come back with any specific questions when I have a greater understanding.
Coordinator
May 15, 2013 at 9:52 AM
I have just checked in modifications to HexGridExample that better reflect probable usage in a real game. I had taken some shortcuts in originally building the TerrainMap class that in retrospect were most likely confusing issues.

There are no changes to the library, just to the examples.
May 15, 2013 at 2:59 PM
OK I will download and take a look.

Perhaps a _limited _example of something would help.

Lets say I have a map that is an image. I also have a hex grid that is also an image that lays over it.

If for this limited example I just wanted to use the library to feed in the hex dimensions and grid dimensions to use it simply to report back the hex co-ordinates under the mouse - is this something I could do ? What would I need to do to make that work.

If you can give me a pointer for something that basic I can work through it and get a better Idea of how it ties together.
Coordinator
May 15, 2013 at 9:29 PM
Ha! It sounds like you are looking for a C# re-write of VASSAL.
;-)

Taking TerrainMap.cs as the starting template, and using line numbers from last night's source code:
  • The dimensions of the grid are currently set in the constructor for MapDisplay on line 47 of MapDisplay.cs. Add another constructor that accepts over-riding values for the width and height. Make the setter for MapDisplay.GridSize public.
  • Add a second parameter to TerrainMap.PaintMap of type System.Drawing.Point that specifies an offset to shift the grid by, in pixels. Apply it on line 56 of TerrainMap.cs, as part of the Translation Transfor for the Graphics object.
  • Create a private field for TerrainMap to hold the grid offset.
  • Create a dialog box that allows the user to dynamically specify changes to grid size and offset, and then refreshes the display (of a portion of the map and grid).
That should get you started. Call back with any difficulties you encounter.
May 15, 2013 at 9:57 PM
Many thanks - I will take a look.

Not so Much VASSAL - more using it with hand drawn map boards to get away from the tiled look ...
May 16, 2013 at 11:32 AM
Hi Pieter,

I've been trying to leverage your code for a project I'm working on. Your code is doing the exact two bits of functionality that I'm looking to implement right now.

I'm struggling with finding a way to use your code in a generic way.

For example, in my application I've got a 100 x 100 map that represents a battlefield. The field has a number of obstacles on it. I'd like to be able to calculate a field of view for a unit on the battlefield by supplying your utility with the map information, the obstacle information, and the source unit's location. What I want to get back from the method call is a list of hexes that are "in view" and/or "in shadow".

Similarly, I'd like to present similar data to the utility to calculate the best path from a source to a destination hex.

Clearly, the application is performing these tasks but I can't seem to distill the contents of the code enough to retrieve just these bits of functionality.

Cheers,

Paul
May 16, 2013 at 11:57 AM
Edited May 16, 2013 at 4:41 PM
Hi - I think I would be correct in saying that this object in the library HexgridPanel.cs seems to be the central component that drives most of the base functionality of the system for map display ?

For example - it seems to be in this that the hotspot hex is set.

My issue would be that I am not using windows forms - but have built the map display etc with DirectX - so what I want to do is just use the various functions and not
the map display part.

So essentially I think if not using windows forms as the base display its going to take a lot of "Picking apart" to get the required functionality ?

It seems to me using the base windows form for constructing the map display is essential for use ?

I guess in some kind of psuedo code what I thought I could do with this was ....

New mapboard()

Mapboard.hexorientation = pointy side up.
Mapboard.size.vertical = 40
Mapboard.size.horizontal = 40

Event MouseMove()
coords = get coords(mouse.x,mouse.y)

That kind of thing ... but it seems heavily tied to the winforms parts and is not a more generic library of functions.
Coordinator
May 16, 2013 at 10:21 PM
Edited May 16, 2013 at 11:17 PM
DBeves::

You are correct that everything revolves around HexgridPanel.cs; however, it should be possible to write your own equivalent. The key is to separate the Winforms functionality, extending Panel, from the Hexgrid functionality that implements the (to be defined) interface IHexGridPanel. I would certainly be interested in working with you on that, as it is a good generalization of the library. I will write more a bit later.

Paul:
  1. Are your obstacles more maze-like or more terrain-like? This affects whether MazeMap.cs or TerrainMap.cs is the better choice of template for your game.
  2. Consider Path-Finding: The method PathFinder.FindPath is where you obtain a Path from; the prototype is this:
    public static IPath<ICoordsCanon> FindPath( 
      ICoordsCanon start, 
      ICoordsCanon goal,
      Func<ICoordsCanon,Hexside,int> stepCost,
      Func<ICoordsCanon,int>         range,
      Func<ICoordsCanon,bool>        isOnBoard
    )
    FiindPath is deliberately a static method on PathFinder, so that it is not dependent on any local state information. Everythign it needs is supplied with the delegate parameters, 3 through 5. Any time the 5 parameters can be supplied to FindPath, it will properly return any best path that exists, as an IPath<ICoordsUser>. If you need assistance with creating the delegates, let me know and provide the ingredients available.
  3. Consider Field-of-View: The method FieldOfView.GetFieldOfView will return an IFieldOfView object when supplied wit parameters that satisfy this prototype:
    public static IFieldOfView GetFieldOfView(
      IBoard<IGridHex> board, 
      ICoordsUser origin, 
      int range, 
      FovTargetMode targetMode
    )
    To invoke this method you will need to create a class around your grid definition that implements the interface IGridHex, and a class around your board definition that implements IBoard<IGridHex>. Again, I would be pleased to assist you with both of these tasks.
  4. A better design for the PathFInder.FindPath would be to pass in an object satisfying an interface definition, so I may work on that generality also for you.
Update:
  1. Note removal of this line from bullet (3), as I have verified that IBoard<IGridHex> is already a minimal pair of interfaces for FOV calculation :
    Likely both of these interface are bigger than actually needed, so we can also address that to improve the generality of the library.
  2. I have defined an interface INavigableBoard as shown below, which is the minimal interface for PathFinding.
      public interface INavigableBoard {
        int   StepCost(ICoordsCanon coords,Hexside hexside);
        int   Range(ICoordsCanon hex, ICoordsCanon goal);
        bool  IsOnBoard(ICoordsUser coords);
      }
    The preferred call into PathFinder to get a path is now this:
      public static IPath<ICoordsCanon> FindPath(
        ICoordsCanon    start,
        ICoordsCanon    goal,
        INavigableBoard board
      );
    Change-Set #24780 contains these changes.
Coordinator
May 16, 2013 at 10:30 PM
Edited May 16, 2013 at 10:31 PM
Paul: Please see consolidated note above.
May 16, 2013 at 10:57 PM
I would say that my obstacles are more terrain like than they are maze like - but I'm not sure I see the distinction. As far as I can tell, the information needed for both concepts are identical. The only difference to me is what bits of hex information I may be interested in.

In a previous discussion I had mentioned that I struggled with the style of your coding. Not that it's wrong, it's just that I don't think in the same patterns.

For example, the FindPath method you describe below. The idea of passing in three delegates doesn't make sense in my head. I would have assumed that there is a map already defined that the FindPath method is acting on. Each hex has an associated movement cost, which I would assume is the cost to move into the hex. Alternatively, each hex could have a predefined list of costs to move into each of the six adjacent hexes. The first concept is simpler, the second is more flexible. The third method, which is the one you chose, is extremely flexible but it means that I have to define delegates each time I want to use the method. It's not wrong, but I find that it removes much of the functionality from the method and externalizes it. Any programmer that wants to use the method now has to understand that they need to code up their own version of the method. I don't follow what the range and isOnBoard delegates do - so I wouldn't know what to pass in for them.

Why does FoV accept a board as a parameter? Can it not leverage the same board that FindPath uses?

I would think that, as a utility, I could set up my board details, modify the board details as needed, and then use methods like FindPath and FoV to act on it.

Ideally, the utility (at least this part of it) should have no knowledge of or connection to the UI layer. If you consider the utility as a WCF service or even as an external DLL, the consumer shouldn't require this level of detailed knowledge. As much as possible, the interface needs to be simple and uncluttered. For me, there needs to be a clearer separation of concern.

Also, I find the three types of coordinate classes completely confound me. I understand two of them, but the customCoordinates confuses me completely. As for the Canon Coordinates (which I believe are the true X, true Y coordinates) - I don't believe they ever need to be exposed. The consumer of the methods doesn't need to be aware of the transformation. Exposing the lot of them is confusing (at least to me). The way that you use them as overrides also confuses me.

By the way - your usage of the language is phenomenal. It completely confuses me at times and it really slows down my understanding of the intention of the code, but as I waded through it I picked up a lot of small things that I didn't know could be done.

Thanks for putting this application together and sharing it. It's really appreciated.

Cheers,

Paul


May 16, 2013 at 11:00 PM
Hi - thanks for your reply.

I guess I will need to take a look at HexgridPanel.cs and break that down to see how it could fit into a more generic model.

From an initial look - it seems mostly related to translating the forms scrolling control attributes to a more generic method as this seems central to getting the coords correct .

I will take a deeper look and perhaps post back some assumptions I would base new code on when I have done so.
May 16, 2013 at 11:35 PM
Edited May 16, 2013 at 11:38 PM
__PaulMouchet wrote:

For example, the FindPath method you describe below. The idea of passing in three delegates doesn't make sense in my head. I would have assumed that there is a map already defined that the FindPath method is acting on. Each hex has an associated movement cost, which I would assume is the cost to move into the hex. Alternatively, each hex could have a predefined list of costs to move into each of the six adjacent hexes. The first concept is simpler, the second is more flexible. The third method, which is the one you chose, is extremely flexible but it means that I have to define delegates each time I want to use the method. It's not wrong, but I find that it removes much of the functionality from the method and externalizes it. Any programmer that wants to use the method now has to understand that they need to code up their own version of the method. I don't follow what the range and isOnBoard delegates do - so I wouldn't know what to pass in for them. Why does FoV accept a board as a parameter? Can it not leverage the same board that FindPath uses? I would think that, as a utility, I could set up my board details, modify the board details as needed, and then use methods like FindPath and FoV to act on it. Ideally, the utility (at least this part of it) should have no knowledge of or connection to the UI layer.__

*** Sorry about the bold above - forum tools are a bit poor.

Well, I think I understand why the delegates are passed - and I think therin lies the conceptual difference in what a library does and what this code does.

My presumption for the delegates is that the system works top down - so at the top you have the mapdisplays - the delegates presumably mean that you can code completely different interpretations of those methods whilst maintaining the name and signature for the method. So the maze map for example could calculate the step cost completely differently from the display map - the methods get passed down through the system and the bottom layer just uses that result - of two methods that for all intent and purposes have the same signature - but work in completely different ways. I can understand the theory - but I think, and here I refer back to pauls comments, herin lies the conceptual difference - ie the base - utilities are not "plug in" in that respect but rely heavily on coding the top layer in a particular way - whereas a library would - as paul suggests be usable by setting up certain board parameters and passing those into whatever calls you wanted to use - say Find path - or Map board would have a single override method for tracking the hotspot hex.

Its a very interesting set of code - but I think I - and perhaps Paul conceptualized it somewhat differently when I first saw it.

Conceptually I initially looked at it as a library independent of the UI but I would now say its and entire system for creating hex maps and has to be taken as a whole in that respect - rather than as a pure utility library.

As Paul says - the code is a real enlightenment - there are all kinds of ways you work it that I would never have thought of doing.
Coordinator
May 16, 2013 at 11:37 PM
Edited May 17, 2013 at 12:55 AM
@dbeves: I believe you and Paul are heading to the same place, from different origins. Reading through his Q's and my A's may help.

@Paul:
  1. ICustomCoords are for renumbering the board, such as to place the origin at the lower-left instead of the default upper-left. Perhaps it is a hang-over from the initial implementation for VASSAL, but then again it may be useful to someone.
  2. You may be correct re ICanonCoords; I will think on it, and test it in my Napoleonic engine. I know that it occasionally allows the client to optimize out a transformation, but it may be a micro-optimization. My Napoleonic engine has a big enough board to do some performance testing.
    Edit: ICanonCoords is exposed to facilitate the writing of the StepCost function used by FindPath.
  3. re "clearer separation of concern.": Yes! Guess why I am here helping you and dbeves; to get to that point faster than I can on my own.
  4. Yes, FOV and FindPath can use the same board; just implement both interfaces on the same class, the way MapBoard does in the example.
  5. FindPath is changed to accept an interface definition with the required methods, instead of the three delegates. Original method retained (but deprecated) for backwards compatibility.
Did I miss anything?
May 16, 2013 at 11:47 PM
Hi again,

I think you addressed all the items I mentioned.

I'm going to take another look at things tomorrow. I find that even this brief exchange has helped my understanding.

Thanks again,

Paul


May 16, 2013 at 11:49 PM
Hi pgeerkens,

thanks for answering these Q's

I think I have a better understanding of what the system is now - I think its a case of looking more closely at the code so I have a better understanding. Personally I would think the first task in morphing this into a genuine "Library" for hex based games is to separate the Win forms and UI elements - and make those into a defined set of data attributes that can be defined by someone using the library rather than it being tied so intrinsically to the winforms implementation.

I will post back when I have had a deeper look and try and identify what I at least think those major items would be.
Coordinator
May 16, 2013 at 11:53 PM
Edited May 18, 2013 at 2:36 PM
@Paul & @dbeves:

The original inspiration for both FindPath and GetFieldOfView are the blog articles by Eric Lippert on both subjects. I have archived them in the documentation download, or you can find them at A-star and at shadow-casting. Both are completely independent of any particular board-drawing technology, though I may have obscured that a bit. Eric is a fun read, and he provides some design notes as he moves along.

However, you both do have me wondering if two distinct libraries might be a better idea, one for completely generic utilities such as these two, and the other a basic WinForms implementation of a hex-grid Panel. I will think on that.

As for the code issue you have both alluded to; 8 months ago I had never written a single line of C# in my life. Lots of other languages including SQL, VB, C, Java, and some C++, but no C#. The use of delegates, lambdas, generics etc., comes with practice, and I was fortunate to get a lot of practices last fall.

P.S. to dbeves:

Use single asterices for italics, not underline.
Update: Actually both work; I am no longer quite sure why your post above went wonky.
May 17, 2013 at 10:25 PM
pgeerkens wrote:
@Paul & @dbeves:

The original inspiration for both FindPath and GetFieldOfView are the blog articles by Eric Lippert on both subjects. I have archived them in the documentation download, or you can find them at A-star and at shadow-casting. Both are completely independent of any particular board-drawing technology, though I may have obscured that a bit. Eric is a fun read, and he provides some design notes as he moves along.

However, you both do have me wondering if two distinct libraries might be a better idea, one for completely generic utilities such as these two, and the other a basic WinForms implementation of a hex-grid Panel. I will think on that.

As for the code issue you have both alluded to; 8 months ago I had never written a single line of C# in my life. Lots of other languages including SQL, VB, C, Java, and some C++, but no C#. The use of delegates, lambdas, generics etc., comes with practice, and I was fortunate to get a lot of practices last fall.

P.S. to Paul:

Use single asterices for italics, not underline.
I think two libraries would be a huge boon to a lot of people.

To get the functionality you have working here in an easy to implement set of libraries would be a massive input to any hex based game.
May 18, 2013 at 2:00 PM
I have a few simple functions that I can offer to add to the library as well. Nothing magical, but I find them very handy.

Things like - distance between hexes, hexes in radius, etc. They may already be in the library, but I can't easily find them.

PS to dbeves ... I'm not sure what you're referring to about asterices for italics. I don't think I've ever included any formatting in my responses unless hotmail is doing it for me.


Coordinator
May 18, 2013 at 2:35 PM
@Paul:

I am always interested in good ideas. The (Manhattan) distance between hexes is already in the library, as the ICoordsUser.Range(ICoordsUser) and ICoordsCanon.Range(ICoordsCanon) methods. I am not sure what you mean by "hexes in radius".

I have been developing two new classes, PathFinder2 and Path2 that remove all usages of ICoordsCanon from the interface, except for in StepCost. I expect to post it shortly. As the original classes will remain untouched, it will not be a breaking change for anyone else.

I am thinking about how best to pull the library apart into two. I may have something by Monday.

The Post Scriptum about italics was meant to be addressed to dbeves, not you; I will correct the OP.
May 18, 2013 at 2:41 PM
Ah, I see why I wasn't finding them. I don't consider a range function to be associated with a coordinate, although I can understand why you'd have put it there.

The "hexes in radius" method is perhaps a less than stellar name. Basically, for a given coordinate, I return a list of all the hexes within an area based on the radius.

For example, if you pass the method coordinate(x,y) and a range of 1, it will return the immediate hexes surrounding coordinate(x,y). If you give supply a radius of 2, it will return the hexes that are within 2 hexes of the source coordinates (and so on). I have a similar method that only returns the rings. So for a radius of 3, it will return all the hexes that are 3 hexes away from the centre.

I was recently looking at Amit's web page and he has similar functions available. His ring function is much more elegant than mine - but to be honest, I couldn't figure out how to implement his solution.


Coordinator
May 18, 2013 at 2:57 PM
Ah! The Range functionality should be exposed directly on the GridHex interface. I will look at that.

I also see hoe the HexesInRadius(ICoordsUser) could be useful. I will look at implementing that as an IEnumerable<IGridHex> later on this weekend.
May 18, 2013 at 3:07 PM
Here is the code I have for the HexesInRadius and HexesAtRadius methods. Neither are as efficient as the could be, but I tend to go for the simple code first and then improve where necessary. So far, these functions perform quickly enough for my needs.

The "Coordinates" class is just a simple int X, Y pair.

public static List<Coordinates> HexesInRadius(Coordinates centre, int radius)
{
var rtn = new List<Coordinates>();
for (var rad = 1; rad <= radius; rad++)
for (var x = rad * -1; x <= rad; x++)
for (var y = rad * -1; y <= rad; y++)
{
var coord = new Coordinates(centre.X + x, centre.Y + y);
var distance = DistanceBetweenHexes(centre, coord);
if (distance == rad)
rtn.Add(coord);
//Console.WriteLine(string.Format("Radius {0}: x/y {1}/{2}: ", rad, x, y));
}
return rtn;
}

public static List<Coordinates> HexesAtRadius(Coordinates centre, int radius)
{
var rtn = new List<Coordinates>();
for (var x = radius * -1; x <= radius ; x++)
for (var y = radius * -1; y <= radius ; y++)
{
var coord = new Coordinates(centre.X + x, centre.Y + y);
var distance = DistanceBetweenHexes(centre, coord);
if (distance == rad)
rtn.Add(coord);
//Console.WriteLine(string.Format("Radius {0}: x/y {1}/{2}: ", rad, x, y));
}
return rtn;
}

public static int DistanceBetweenHexes(Coordinates startCoordinates, Coordinates endCoordinates)
{
var start = ConvertXyToOblique(startCoordinates);
var end = ConvertXyToOblique(endCoordinates);
return Math.Max(Math.Max(Math.Abs(end.Y - start.Y), Math.Abs(end.X - start.X)), Math.Abs((end.X - start.X) - (end.Y - start.Y)));
}


May 18, 2013 at 6:31 PM
PaulMouchet wrote:
I have a few simple functions that I can offer to add to the library as well. Nothing magical, but I find them very handy. Things like - distance between hexes, hexes in radius, etc. They may already be in the library, but I can't easily find them. PS to dbeves ... I'm not sure what you're referring to about asterices for italics. I don't think I've ever included any formatting in my responses unless hotmail is doing it for me.
Yes - it went a bit funky - actually all I did was press the italics button in the tools ...
May 18, 2013 at 6:36 PM
Edited May 18, 2013 at 6:42 PM
.
Coordinator
May 19, 2013 at 6:32 PM
Edited May 19, 2013 at 6:36 PM
Gentlemen,

I have just completed a reworking of the utility into two DLL's, HexUtilities.dll and HexgridPanel.dll. The external interface of the former is summarized by the following interfaces, as well as an abstract implementation of IMapBoard. If you see anything missing (other the the requested HexesInRadius(), which is in progress), please let me know. I will post all the source code and binaries a bit later today.

Note that the base classes Coords and HexCoords and the external facings of the functionality have ben reworked to completely eliminate the need for a (client) distinction between the different coordinate systems. It is now all buried behind* ICoords*.

namespace PG_Napoleonics.Utilities.HexUtilities {
public interface ICoords {
  IntVector2D User    { get; }
  IntVector2D Canon   { get; }
  IntVector2D Custom  { get; }

  int         Range(ICoords coords);
  ICoords     StepOut(ICoords coords);
  ICoords     StepOut(Hexside hexside);
  string      ToString();

  IEnumerable<NeighbourCoords> GetNeighbours(Hexside hexsides);
}

public interface IMapBoard : IBoard<IGridHex> {
  /// <summary>Distance in hexes out to which Field-of-View is to be calculated.</summary>
  int     FovRadius   { get; set; }
  IFov    FOV         { get; }
  ICoords GoalHex     { get; set; }
  ICoords HotSpotHex  { get; set; }
  IPath2  Path        { get; }
  ICoords StartHex    { get; set; }
}
public interface IBoard<TGridHex> : INavigableBoard, IFovBoard<TGridHex> 
  where TGridHex : class, IGridHex {
  INavigableBoard     NavigableBoard { get; }
  IFovBoard<TGridHex> FovBoard       { get; }
}

/// <summary>Interface required to make use of A-* Path Finding utility.</summary>
public interface INavigableBoard {
  /// <summary>The cost of leaving the hex with coordinates <c>coords</c>through <c>hexside</c>.</summary>
  int   StepCost(ICoords coords, Hexside hexside);
  /// <summary>Returns an A_* heuristic value from a hexagonal Manhattan distance<c>range</c>.</summary>
  int   Heuristic(int range);
  /// <summary>Returns whether the hex with coordinates <c>coords</c>is "on board".</summary>
  bool  IsOnBoard(ICoords coords);
}
/// <summary>Structure returned by the A-* Path Finding utility.</summary>
public interface IPath2 : IEnumerable<ICoords>
{ 
  Hexside   LastDirection { get; }
  ICoords   LastStep      { get; }
  IPath2    PreviousSteps { get; }
  int       Count         { get; }
  uint      TotalCost     { get; }
  uint      TotalSteps    { get; }
}

/// <summary>Interface required to make use of ShadowCasting Field-of-View calculation.</summary>
/// <typeparam name="TGridHex"></typeparam>
public interface IFovBoard<TGridHex> where TGridHex : class, IGridHex {
  /// <summary>The rectangular extent of the board's hexagonal grid, in hexes.</summary>
  Size     SizeHexes             { get; }
  /// <summary>Returns the <c>TGridHex</c> at location <c>coords</c>.</summary>
  TGridHex this[ICoords  coords] { get; }

  /// <summary>Returns whether the hex with coordinates <c>coords</c>is "on board".</summary>
  bool     IsOnBoard(ICoords coords);

  /// <summary>Returns whether the hex at location <c>coords</c> is passable.</summary>
  /// <param name="coords"></param>
  bool     IsPassable(ICoords coords);
}
/// <summary>Structure returned by the Field-of-View utility.</summary>
public interface IFov {
  /// <summary>True if the hex at coordinates <c>coords> is visible in this field-of-view.</summary>
  bool this[ICoords coords] { get; }
}
}
May 19, 2013 at 8:15 PM
I think this should work nicely.

However, I'm left wondering though why you'd want to have the FoV and Path elements as properties of the IMapBoard and not method parameters for FieldOfView and PathFinder methods.

Regarding the IPath2 interface - does this need to be exposed by the utility? I would think the path being returned needs only be a list of coordinates for the path. Each coordinate might also include a cost, or alternatively, the path returned could be limited by a movement rate that is passed in as a parameter to the PathFinder method.


Coordinator
May 19, 2013 at 10:15 PM
The FoV and Path elements of an IMapBoard are the currently displayed field-of-view and path respectively for that MapBoard instance. Perhaps that is still a bit of WinForms showing through, or perhaps not, I have not used WPF or other drawing technologies much yet. The PathFinder.FindPath() and FieldOfVoiew.GetFieldOfView() methods can always be invoked directly in order to obtain additional fields-of-view or paths.

Regarding the Path2 interface:
  1. PreviousSteps and LastStep are the path itself; they are required to allow a client to walk a path at a measured path without recalculating it each time.
  2. LastDirection, is needed when drawing path arrow in each hex.
  3. TotalCost is exposed to assist in AI calculations
  4. Count was redundant, thank you; I have removed it;
  5. TotalSteps is again exposed to allow an AI to analyze a path's efficiency (ie TotalCost / Range).
  6. If only the sequence of ICoords objects is required, then the base enumerator can be used to provide that, as here:
    foreach (ICoords p in Path) {
      // work with p here
    }
One of the next pieces to build is the provision of a road-map, comprising pre-plotted paths between major junctions, to speed-up long paths on large maps.

I am thinking of appropriate ways to generalize the concept of HexesInRadius that you and dbeves raised earlier. In terms of a base-level function, I am looking at something like this:
IEnumerable<ICoords> HexesInRadius ( int range, IntVector2 vectorStart, IntVector2 vectorEnd) {}
or this
IEnumerable<IGridHex> HexesInRadius ( int range, IntVector2 vectorStart, IntVector2 vectorEnd) {}
This would generate a sequence of hexes between the given vectors, inclusive, at exactly the range specified. An overload would allow omitting vectorEnd in order to go full circle, and a complementary method HexesOutToRadius() would loop out to the specified radius. Can you think of any difficulties with this design?
May 19, 2013 at 10:50 PM
The differences between WinForms and WPF (Windows Presentation Foundation) are very similar in concept, but very different in implementation. However, the visual implementation strategy must be completely separated from the utility. Consider this - I may want to be able to present the visual component on a client app, or as a browser app, or as a phone app. If there is any visual integration into the utility, it becomes much harder to make use of.

Are you familiar with the concepts of MVC, MVVM and/or MVP? These methodologies are very similar in nature but have a slightly different method of implementation. For the purpose of this discussion, I'll go into MVP because it's the most obvious method.

In MVP the application is divided up into 3 distinct areas of concern. The Model, which is the business of the application. The View, which is the component that renders the information in a user-friendly format. The Presenter, which manages the communications between the Model and the Presentation. Under this method, the view has no concept of the model and the model has no concept of the view. The presenter takes Data Transfer Objects from the model and manipulates them to create Data Transfer Objects that it will present to the View. The view has no business knowledge at all - it's only job is to present the information provided by the presenter. Now, in truth, this is rarely this clear cut - but it's the model that the designer needs to strive for when putting together the application architecture.

If you keep the utility a "pure model", it will make it easier to consume and easier to understand.

The description of the path2 interface helped to clear up it's purpose significantly. I'm not suggesting to reduce it's content, but consider this - the idea of the PathFinder is to provide a path, which is at a minimum a list of directions or a list of coordinates. Providing the additional data is meaningful, but it seems as though it is mostly meaningful if you want to render something visually. Consider your statement for point 2 "LastDirection is needed when drawing ..."

If I have a list of Coordinates and Costs I can calculate all of the other items with ease.

I'm not sure what TotalSteps adds to the result. Is it different from being the count of coordinates? Is it a running total of the costs to move into each hex? I don't think the AI needs any of this data since the PathFinder is the AI in this case. It's already rendered it's decision. What more could the consumer do with the data?

I'm not sure that adding a second vector to the HexesInRadius function is a good idea. They represent two very distinct pieces of logic. What if the second vector is at a different distance than the radius? What set of data would it return?

Also, for my own understanding, what is the difference between a vector and a coordinate? How does it apply to the hexes in a circle with a radius of X? To me, a vector implies direction and I'm not sure how that fits.

Cheers,

Paul


Coordinator
May 20, 2013 at 12:01 AM
Edited May 20, 2013 at 12:01 AM
Hi Paul,

I admit to not yet being completely comfortable with Inversion of Control and MVC. I tend to get there, but not in a direct or completely planned fashion. That being said, there is nothing hidden or inaccessible behind IMapBoard* and it's abstract implementation MapBoard; so, I would rather include a bit more than necessary, than any bit too less. I appreciate your comments, and will take them under advisement.

IPath2.TotalStesp is just the length, in steps, of the path. It is the one piece that may be unnecessary, so I will again consider it's real usefulness. As for the rest, I fully expect to use them in my game engine immediately upon generation of certain path types. Not every game will need them, but they are expensive to regenerate and cheap to store.

The second vector is absolutely required in my game engine, as I have will troop types with varying (and narrow) field-of-fire widths. There will be syntactic sugar methods for the simpler cases, but I am first looking at the base functionality that supports the rest. In fact, the supplied vectors would often have different lengths, quite legitimately; it is only the associated direction that is relevant here. The distance is explicitly provided in the method call.

As to the difference between a vector and a coordinate; A vector's magnitude doesn't change under transformation, while a coordinate's does. Consider a translation of the origin from (0,0) to (1,0). The vector (3,4) remains the vector (3,4), while the coordinate (3,4) becomes the coordinate 2,4). In that sense my IntVector2D type is really a coordinate type as used by Coords and company, not a true vector type.
May 20, 2013 at 12:46 AM
I follow the usefulness of the 2nd vector for the purposes of creating a narrow field, but I just don't think it belongs as an overload of the hexesInRadius method. Basically, the HexesInRadius method gives you a list of all the hexes in a circle. I (personally) would keep the two methods separate for the purpose of clarity. My personal style is to write what I call Kindergarten code. I try to keep everything as simple as possible to make it readily adoptable and maintainable by other developers.

Regarding IMapBoard - it's not that things are hidden, it's that it's performing tasks that are not necessary for the purposes of the Utility. The utility should be true to the data and it's methods. It should have nothing to do with the presentation.

To be honest, your description of the differences between a vector and a coordinate only confused me further. ;) That being said, I'm also easily confused.

Cheers,

Paul


May 20, 2013 at 5:40 PM
Edited May 20, 2013 at 8:11 PM
Hi,

Been running through your new "split" code ...

One thing that strikes me immediately is that the code to read the hotspot hex / mouse position still resides in the windows form "HexGridPanel"
I am thinking of the code below ...

protected ICoords GetHexCoords(Point point, Size autoScroll) {
  if( Host == null ) return HexCoords.EmptyCanon;

  autoScroll = TransposeSize(autoScroll);

  // Adjust for origin not as assumed by GetCoordinate().
  var grid    = new Size((int)(GridSize.Width*2F/3F), (int)GridSize.Height);
  var margin  = new Size((int)(MapMargin.Width *MapScale), 
                         (int)(MapMargin.Height*MapScale));
  point      -= autoScroll + margin + grid;

  return HexCoords.NewCanonCoords( GetCoordinate(matrixX, point), 
                                GetCoordinate(matrixY, point) );
}
Should this method not reside in the MapBoard class ?
I know its driven by the mouseover event on the form panel but I would have thought it was better to have this critical function in the actual utillity dll ?
If its there - with appropriate parameters for the method call and fields added to the MapBoardClass that define the dimensions of the board - then this part is more generic and can be used for any implementation - as it stands it seems retrieving this piece of info is still tied too heavily to the visual part of the system and its pretty central to how everything works ....

I guess this would mean moving IsTransposed and any other display based attribute.
May 20, 2013 at 8:16 PM
Edited May 20, 2013 at 8:53 PM
It does seem to me on further inspection there are many items in the Hex grid panel that need to move to the mapboard class for the utilities binary to be truly independent of the win forms display.

Perhaps I am misunderstanding what is going on in the hex grid display class - but it seems to me that even with the new split - there is still code in the hex grid panel thats needed in the "Utilities" for it to work independently without writing a lot of bespoke code to support it ?

I guess what I am trying to say is that the MapBoard class (or some other in the utilities should have a method called something akin to "GetHexUnderCursor" which would accept an x and y point and then - based on other attributes already set for the class (margin, Hex dimensions etc ) be able to work it out ....

I of course may have posted the above before I have looked enough to fully understand - so forgive me if I have.
Coordinator
May 20, 2013 at 9:01 PM
I must be getting close to a sweet spot; Paul argues that MapBoard be returned to HexGridPanel.dll and dbeves argues that MapBoard needs to be expanded where it is.
;-)
That said, I agree that the hex-picking functionality belongs in HexUtilities.dll. Let me think a bit on how best to do it.
May 20, 2013 at 11:56 PM
As long as there is clear separation of concern, it all works. You just don't want to have to force users to make anything interface related if all they want is a set of hex related functions.

I've spent all day coding combat AI ... and I'm too tired to offer anything in the way of a deep opinion. :)

Cheers,

Paul


Coordinator
May 21, 2013 at 5:33 AM
I have just posted what I think is a clean separation of concern, as Paul puts it.

The hex-picking and related functionality has been abstracted out of HexgridPanel and into new classes HexGrid and TranposedHexGrid. The interface IHexGridHost is what HexGridPanel in turn provides to enable that functionality.

In the same change set is an abstraction of the CustomCoords functionality out of Coords and HexCoords. This now allows multiple custom coordinates instances as it is no longer static functionality in HexCoords.

As always, comments and suggestions welcome.
May 21, 2013 at 5:42 AM
pgeerkens wrote:
I have just posted what I think is a clean separation of concern, as Paul puts it.

The hex-picking and related functionality has been abstracted out of HexgridPanel and into new classes HexGrid and TranposedHexGrid. The interface IHexGridHost is what HexGridPanel in turn provides to enable that functionality.

In the same change set is an abstraction of the CustomCoords functionality out of Coords and HexCoords. This now allows multiple custom coordinates instances as it is no longer static functionality in HexCoords.

As always, comments and suggestions welcome.
Excellent p, I have some time today so will give this a run through ...
May 21, 2013 at 1:39 PM
Edited May 21, 2013 at 2:21 PM
pgeerkens wrote:
I have just posted what I think is a clean separation of concern, as Paul puts it.

The hex-picking and related functionality has been abstracted out of HexgridPanel and into new classes HexGrid and TranposedHexGrid. The interface IHexGridHost is what HexGridPanel in turn provides to enable that functionality.

In the same change set is an abstraction of the CustomCoords functionality out of Coords and HexCoords. This now allows multiple custom coordinates instances as it is no longer static functionality in HexCoords.

As always, comments and suggestions welcome.
Hi p, I presume when you say uploaded you mean just on the Source Tab. The download zip files dont contain the new code ?

Also in the source code I cant find a Transposed Hexgrid class ... edit ...... Ah ... forget that its in the same file as hexgrid.
May 21, 2013 at 4:36 PM
I hadn't realized that there was more new code uploaded. I took a quick look and I'm going to say that I still think that the MapDisplay holds too much base functionality. The fact that I want to work with a TerrainMap shouldn't tie me into having to use the MapDisplay class.

Consider this.

Lets suppose that I have a battle map that is 100 x 100 hexes. Let's further suppose that my hex details are insanely complicated and terribly convoluted. It's so horrendous that I could never hope to integrate my mapping system with anybody else's map specific utility.

However, let's assume that there is a utility that has nice basic features that I might be able to leverage. This utility tracks hexes with only the most basic information.

Now, my samples below are extremely simplified and not necessarily well thought out. However, it illustrates the simplicity that needs to be presented to the client of the utility so that they can easily make use of it.

There is no visual representation of anything. It's only business data and a set of rules that are implemented in the background. (I see that I don't have a path finding heuristic - which likely means I'm missing other things too...)

Paul

Class MapHex
{
public int X {get; set;}
public int Y {get; set;}
public int TerrainType {get; set;}
}

The utility also provides a GameBoard class that provides a set of simple methods to manage the map.

Class GameBoard
{
public List<MapHex> Hexes {get; set;}

public int StepCost(MapHex start, int direction)
{
... calculates the cost to move from one hex to another, possibly based on the target hex's terrain.
}

public int TerrainTypeCost (int TerrainType)
{
... returns the movement cost associated with a terrain type.
}

public bool TerrainBlocksLineOfSight(int TerrainType)
{
... returns true if the terrain blocks line of sight.
}

public List<MapHex> PathFinder(MapHex start, MapHex end)
{
... returns a list of hexes that is the cheapest movement cost from start to end
}

public List<MapHex> FieldOfView(MapHex pointOfView, int range)
{
... returns a list of hexes that can be seen from the pointOfView based on a range of int.
}

}


May 21, 2013 at 7:32 PM
Edited May 21, 2013 at 8:09 PM
Hi P ...

I kind of have your new code working - bar the fact there is a mismatch in the co-ordinates shown on my map

One question I have is around the GridSizeF property in the hex grid class - the code says this

/// <summary>Scaled <code>Size</code> of each hexagon in grid, being the 'full-width' and 'full-height'.</summary>
SizeF   GridSizeF       { get; }
Which seems to suggest the property is defining the dimensions of the individual hex ? Is this correct ? If so - I presume this is the actual pixel width and height ? Again if so - for transposed grids is it measured the same way and the transposition takes care of this or do you have to set using the vertical pointing hex as opposed to the normal flat hex top.?

I am also unsure about the "Scaled" part of the comment - if this is the actual pixel size of the hex then I would presume it wouldnt change and that the transformation would take care of the scaling of the hex size you are dealing with at any given level of zoom - perhaps I am misunderstanding what you mean by scaled ?

Just that in your hexgrid panel project you set this

SizeF IHexGridHost.GridSizeF { get { return Host.GridSize.Scale(MapScale); } }

So I am presuming the grid size for IMapDisplay is the same thing here ?

Also - what does the

GetClipCells funtion do ? I have an idea that it represents the actual visible rectangle of the viewable map ?

For what its worth - whilst as he says its simplistic for clarity - I would have to agree that pauls post above is the model for a utility library that someone can pick up and use I would aim for ... unless you are going to write reams of documentation the target should be to provide a set of functions that do a specific job and any dependemt code hidden within the class so that one method provides the result for the funtionality the user requires.

Something like this ...

public List<MapHex> FieldOfView(MapHex pointOfView, int range)
{
... returns a list of hexes that can be seen from the pointOfView based on a range of int.
}

is easy for someone just to work out what it is and how to use it - with a single comment line... (or even without)
May 21, 2013 at 11:08 PM
Edited May 21, 2013 at 11:09 PM
Well ... Success - now implemented and seems to be reading the Co-ordinates perfectly ...
My main issues were defining the hex size in its transposed form rather than standard orientation and letting the transposed hexgrid do the work.
Secondly - it was the actual measurement - ie what width and height were - following your drawing code in hex grid panel I managed to figure out what you were doing.

I think also you have to define a fractional value for the grid sizes if you are not using a predifined hex size (I had the hex layer graphically already) - where as in your code you obviously chose dimensions that divided exactly for the drawing process.
May 21, 2013 at 11:39 PM
Edited May 22, 2013 at 12:06 AM
One thing I would suggest to add to hex grid or wherever - is a Margin that doesnt get scaled in the tranformation.
Most maps will be sitting on screen with a UI around it that of course wont be scaled - the current map margin gets scaled.

Also with regard to the margin thats there - if I am using a transposed hex grid I have to set this

public Size MapMargin
    {
        get { return new Size(40,150); }
    }
150 is actually the width of the margin - so it seems I also have to swap these if the map is not standard - which is a little counter intuitive.

Also - I cant quite figure out if the scaling is implemented in the new split code it would seem it would need to scale the individual hex dimensions here ? But doesnt seem to account for the host scale setting ...

public virtual ICoords GetHexCoords(Point point, Size autoScroll) {
  if( Host == null ) return HexCoords.EmptyCanon;

  // Adjust for origin not as assumed by GetCoordinate().
  var grid    = new Size((int)(Host.GridSizeF.Width*2F/3F), (int)Host.GridSizeF.Height);
  var margin  = new Size((int)(Host.MapMargin.Width  * Host.MapScale), 
                         (int)(Host.MapMargin.Height * Host.MapScale));
  point      -= autoScroll + margin + grid;

  return HexCoords.NewCanonCoords( GetCoordinate(matrixX, point), 
                                   GetCoordinate(matrixY, point) );
}
edit ... am I correct in presuming this is what doest the Hex dimension scaling in your panel example?

SizeF IHexGridHost.GridSizeF { get { return Host.GridSize.Scale(MapScale); } }

I think this should happen by default in the hexgrid class - I cant think of a situation where the map is scaled in some way and you would not want to scale the hex dimensions ...?
Coordinator
May 21, 2013 at 11:44 PM
@dbeves: Glad to hear that you sorted that out; I have been paring down abstract class MapBoard, quite successfully I think; more below. As I am creating my maps from tiles, the dimensions are always exact integers. However, I believe it is most efficient to have the graphics perform the scaling once, after the map is drawn 1-to-1. That is what I do for the mouse-wheel-zooming in the example.

Here is the new abstract MapBoard class, and helpers
  public abstract class MapBoard : IMapBoard {
    public MapBoard(Size sizeHexes) { SizeHexes = sizeHexes; }

    ///<inheritdoc/>
    public          int        FovRadius      { get; set; }

    ///<inheritdoc/>
    public virtual  Size       SizeHexes      { get; private set; }

    /// <summary>Returns this instance as a <c>IFovBoard<IGridHex></c>.</summary>
    public IFovBoard<IGridHex> FovBoard       { get { return this; } }

    /// <summary>Returns this instance as a <c>INavigableBoard</c>.</summary>
    public INavigableBoard     NavigableBoard { get { return this; } }

    ///<inheritdoc/>
    public virtual  int    Heuristic(int range) { return range; }

    ///<inheritdoc/>
    public          bool   IsOnBoard(ICoords coords)  {
      return 0<=coords.User.X && coords.User.X < SizeHexes.Width
          && 0<=coords.User.Y && coords.User.Y < SizeHexes.Height;
    }

    ///<inheritdoc/>
    public virtual  bool   IsPassable(ICoords coords) { return IsOnBoard(coords); }

    ///<inheritdoc/>
    public abstract int    StepCost(ICoords coords, Hexside hexSide);

    ///<inheritdoc/>
    IGridHex IFovBoard<IGridHex>.this[ICoords coords]  { get { return GetGridHex(coords); } }

    /// <summary>Returns the hex at coordinates specified by <c>coords</c>.</summary>
    protected abstract IGridHex GetGridHex(ICoords coords);
  }

  public static partial class Extensions {
    /// <summary>Returns the field-of-view on <c>board</c> from the hex specified by coordinates <c>coords</c>.</summary>
    public static IFov GetFov(this IFovBoard<IGridHex> @this, ICoords origin) {
      return FieldOfView.GetFieldOfView(@this,origin);
    }

    /// <summary>Returns the field-of-view from this hex.</summary>
    public static IFov GetFov(this IGridHex @this) {
      return FieldOfView.GetFieldOfView(@this.Board, @this.Coords);
    }

    /// <summary>Returns a least-cost path from the hex <c>start</c> to the hex <c>goal.</c></summary>
    public static IPath2 GetPath(this INavigableBoard board, ICoords start, ICoords goal) {
      return PathFinder2.FindPath(start, goal, board);
    }

    /// <summary>Returns a least-cost path from this hex to the hex <c>goal.</c></summary>
    public static IPath2 GetPath(this IGridHex @this, ICoords goal) {
      return PathFinder2.FindPath( @this.Coords, goal, @this.Board);
    }
  }
and the new abstract class GridHex to accompany it:
  public abstract class GridHex : IGridHex {
    public GridHex(IBoard<IGridHex> board, ICoords coords) { 
      Coords = coords; 
      Board  = board;
    }

    ///<inheritdoc/>
    public virtual  IBoard<IGridHex> Board          { get; private set; }

    ///<inheritdoc/>
    public virtual  ICoords          Coords         { get; private set; }

    ///<inheritdoc/>
    public virtual  int              ElevationASL   { get; protected set; }

    ///<inheritdoc/>
    public virtual  int              HeightObserver { get { return ElevationASL + 1; } }

    ///<inheritdoc/>
    public virtual  int              HeightTarget   { get { return ElevationASL + 1; } }

    ///<inheritdoc/>
    public abstract int              HeightTerrain  { get; }

    ///<inheritdoc/>
    public int                       Range(ICoords target) { return Coords.Range(target); }

    ///<inheritdoc/>
    public abstract int              StepCost(Hexside direction);
  }
With the extension methods, I think this addresses Paul's points.

Having additional eyes to point out one's forgotten assumptions is great; thank you both once again. I will post refreshed downloads a bit later this evening.
Coordinator
May 22, 2013 at 4:47 AM
Edited May 22, 2013 at 4:50 AM
The refactoring continued, and continued, and the surface area of the external interface kept shrinking. I finished up by renaming the key externally visible classes to be:
  • Hex (renamed from GridHex),
  • HexBoard (renamed from MapBoard),
  • HexCoords,
  • HexGrid, and
  • (for the WinForms users) HexgridPanel.
Even that pesky GetClipCells() method retreated back under the covers where it belonged.

@dbeves: Regarding your suggestion for a non-scaling MapMargin; try adding this definition to your sub-class:
Size IHexGridHost.MapMargin { 
  get { return Size.Round(MapMargin.Scale(1.0F / MapScale)); } 
}
Source and Binaries available as download V 5.1
May 22, 2013 at 10:57 AM
Great - I will give the new code and your suggestion a try ....
May 22, 2013 at 12:28 PM
Edited May 22, 2013 at 1:06 PM
One thing I believe the library is missing is a method to convert a Canon co-ordinate for example back to a relevant x,y screen position. Say the hex centre

Say for example if you wanted a graphical outline for the hot spot hex to appear when you clicked on a particular hex.
May 22, 2013 at 4:19 PM
I've had a chance to look at the new setup for a while.

I'm still thinking that the organization might be amiss, unless I'm starting from the wrong spot.

I'm looking at the HexBoard as being the primary domain infrastructure.

In my mind, I am going to build my "map world" on this abstract class. However, I don't see any place in the class to store my actual map data (the actual graph of hexes). They way this is set up, the data is external to the class, but the class provides methods that are acting on the data.

Also, I'm not sure why this class needs to be defined as abstract. Doing this forces me to write code to create a class that should be performing the functionality that the utility is providing to me. Ideally, the utility should work out of the box, providing it's base feature set. Everything it does should be extensible or exposed, allowing me to override the methods - but they should otherwise be standalone functional.

Does it not make sense that I should be able to do this:

var myMap = new HexBoard(100, 100);
myMap.HexSet = MyUserFunctions.GenerateHexMapData();
var fov = myMap.GetFieldOfView(sourceCoords, myRangeValue);
var bestPath = myMap.GetPath(sourceCoords, destinationCoords);
var hasLineOfSight = myMap.HasLineOfSight(sourceCoords, destinationCoords);

If it worked this way, the only knowledge the user *must* have is to know/understand what the definition of the hex class is so that they can generate the hex data for the map itself.

Another small note - I would remove the use of the Size class in the HexBoard. It's forcing the loading of the System.Drawing library. Since this class is purely Model only, it shouldn't need anything visual. If it has no visual components, there is no need for the drawing library.

I don't think FovRadius is needed as a property of HexBoard. It's only needed for the FoV method and it can be passed in as a parm.

I don't think SizeHexes belongs here since it's only meaningful for the UI. Since this has no UI component, it's not adding any meaningful content.

I don't follow why you want to have FovBoard and NavigableBoard as properties of the HexBoard.

I would also move the Range and StepOut methods away from the ICoords interface and put them in HexBoard. In my opinion, a class should never have functionality that relies on external data. The idea of distance/cost between hexes is relative to the board the hexes are on and not to the hex itself.

While I'm being picky, I would suggest adding a new folder under HexUtilities called Interfaces and store your interfaces there, with one interface per file. It just makes them easier to locate. As it is now you have some of them grouped in an Interfaces.cs file and others in named files (ICoords.cs and IHeap.cs).

I hope these comments are helpful and not just annoying.

Cheers,

Paul



May 22, 2013 at 5:52 PM
After giving it more thought, I might choose to withdraw my objection to the StepOut method being in the ICoordinates interface. Inasmuch as I think it could easily live in the "map" class, it does have value in the ICoordinates interface as well as long as it's used to reference internal properties of the class and not to reference the map data that the coordinates exist in.

Cheers,

Paul


Coordinator
May 22, 2013 at 10:25 PM
@Paul:

The answers may be annoying, but the comments aren't. You are simply forcing me to write my design notes. ;-)
  1. Hexboard is abstract because the method protected abstract IHex GetGridHex(ICoords coords); is abstract. It is here that the storage of hex details is exposed. A basic sub-classing would look like this:
public abstract class MapDisplay : HexBoard {
  public MapDispay(Size sizeHexes) : base (sizeHexes) {
    /* define stuff here */
  }  
  private HexGrid[][] Board;

  protected override IHex GetGridHex(ICoords cords) { 
    return Board[coords.y][coords.X];
}
  1. All the methods except GetGridHex are standalone; other than values being assigned to the properties and defining the board storage, the class is ready to go. It surely would be presumptive for the library to mandate how the class stores hexes; for instance the map might have corners cut out! Check out how I create Hex objects on the fly in TerrainMap.GetGridHex, for a simple way to manage small maps.
  2. The Size class is used everywhere (122 separate instances) in the implementation; I would simply have to muddy my code to rewrite it if I took it out. It also has a useful property of being able to be added to a Point, which a Point cannot do out of the box. Blame Bill.
  3. To my mind current visibility (ie FovRadius) is a state of the game, and hence must be in HexBoard. If you need to explicitly control Visibility Range, you always have access to the second overload of GetFieldOfView().
  4. You are misinterpreting SizeHexes; it is the board dimensions in units of hexes. Caching of SizeHexes is impled by default, as it is used frequently in GetPath, but this could be overriden easily if desired (check out MapDisplay for how to do this).The hex dimensions are in GridSize and are only passed in to HexGrid, where they are of course required to do hex picking.
  5. Range is purely a calculation on the coordinate system. It is exposed by Hex but is really a property of each Hex`s ICoords property.
  6. You may have a simpler terrain model, but my game engine needs the polymorphism enabled by putting StepCost in the hexes.
  7. The current arrangement of interface locations is unsuitable for the long term. I had not thought of a separate directory, but that might work.it; it might work. I usually try to defin interfaces where they are needed, but then I grouped them to allow you and dbeves to find them more easily during the refactoring; I guess that didn't work.
P.S. I have tried to be uniform in my use of terminology, but if you spot any possibilities for better names please let me know. Now is the time to do it.
May 23, 2013 at 11:04 AM
Hi Pieter,

Regarding the SizeHexes class - I'm not sure what I was looking at when I suggested it had embedded UI content. It may have been the MapDisplay class I was looking at - which is okay to have UI content since it's a part of the UI project.

Or were you referring to some other part of my previous post (or the whole post entirely). :)

Paul


Coordinator
May 23, 2013 at 11:28 AM
Paul:
No, I referred to your 7:22 pm post yesterday, which was just a copy of mine an hour earlier.

Pieter
May 23, 2013 at 11:31 AM
Well that's odd. I didn't post that (at least not intentionally). Here is what I tried to post:

I'm not sure if this is just an example, but the example of your abstract MapDisplay is forcing the HexBoard to be used as a user interface component.

I'm also not sure why I used SizeHexes as the example of UI content infiltrating the utilities. I don't actually see what it was I was referring to now. I should try to remember to cut-paste my examples. ;)

I follow what you're referring to with Visibility Range. In my case, the visibility range of units changes from unit to unit. Further, in my case, the weapons that the units are wielding have varying ranges. For the sake of knowing what is visible, I need to know what is visible and what is in range. That being said, I follow where you're going now with your implementation.

My objection to the use of the Size class is only because it forces the use of a library that the utilities don't need. The fact that they're being leveraged beyond the utility makes sense, but that's not a good reason to use it. (That might sound harsher than intended.) What I'm meaning with that is that it's an example of separation of concern. A class that implies presentation is sneaking into a model that should have no visual aspect.

The terrain model I'm using is reasonably simple, but but it creates complexity because the units each interpret their environment differently. This is why (for me) I need to be able to talk to the utility in a simple manner so that each unit can manage themselves in a timely manner. If the utility has too much complexity and weight, it won't work for me.

That being said, if my needs don't fit well with how you'd like the utility to work, I'd be happy to share my version of the methods if you can help me work my way through your code.

Cheers,

Paul


May 23, 2013 at 4:53 PM
Edited May 23, 2013 at 9:42 PM
Hi P,

One issue that seems apparent to me is that there seems to be no allowance for transposed grids in the Coords class - if you take the example below ...

if (hexGrid.HotSpotHex.Canon.Y % 2 != 0)
or
if (hexGrid.HotSpotHex.Canon.X % 2 != 0)

I am using this as a test to check an odd numbered Y row on a transposed grid so I can deduct the appropriate position x value to display an outline around the hex. I use this to deduct from the hex center method to find the hex center of an odd numbered board row ... (this would also be useful to include)

Now - using Canon.Y does not work - as that actually appears to be the X coordinate on a transposed board so substituting .X does work as this actually seems to be the Y co-ordinate.


This makes using any Co-ords confusing - so perhaps there needs to be something that checks within the Coords class to see if its operating on a transposed board. Havent thought enough about where or how it should go - but using any co-ords is not intuitive on a transposed board at the moment.
Coordinator
May 23, 2013 at 9:41 PM
@Paul:
Unfortunately there is other stuff in System.Drawing.dll that HexUtility uses; it should really be termed System.Geometry.dll.

Is the variable visibility of your units associated with their height? If so, basing ObserverHeight on the unit in a hex may have the effect you desire. That is (part of) how I am implementing variable sightlines.

Yes, I would be pleased to help out with interpreting the model for your needs. I might see appropriate ways to further generalize HexUtility.dll when I see specific cases of the issues you are encountering. Let's start another thread for that however.
Coordinator
May 23, 2013 at 9:44 PM
@dbeves: The idea of the transposition was to make it completely transparent to the client; now you want to poke around inside!
;-)

Have you considered using a CustomCoords as a model for a sub-class of HexCoords that puts the User coordinates back. Just overriding the User property with the definition of Custom in CustomCoords should do it, with an off-diagonal transformation matrix like this : 0,1, 1,0, 0,0
May 23, 2013 at 9:47 PM
Edited May 23, 2013 at 9:50 PM
Perhaps I am misunderstanding how you have viewed the co-ord numbering system ?

In normal layout the top left will be x=0 y=0 - when using a transposed grid perhaps you do not maintain this - ie do you simply flip the board 90 degrees to the left but maintain the same numbering system ie on a transposed board the x values would then represent the vertical as opposed to the horizontal ?

I just caught your last post ... it sounds as if that is correct ...? I will look into what you say - I think I took for granted the word trasnpose was doing more than it is ...
May 23, 2013 at 9:53 PM
Edited May 23, 2013 at 9:57 PM
pgeerkens wrote:
@dbeves: The idea of the transposition was to make it completely transparent to the client; now you want to poke around inside!
;-)

Have you considered using a CustomCoords as a model for a sub-class of HexCoords that puts the User coordinates back. Just overriding the User property with the definition of Custom in CustomCoords should do it, with an off-diagonal transformation matrix like this : 0,1, 1,0, 0,0
I think the issue here P is that someone using the library would expect the co-ordinate system to remain the same whether the board was transposed or not ... ie the X co-ord as presented by the GetHexCoOrd method should represent the horizontal in either case ...

I think in more general use there will be many instances where someone using it will need to retrive a hex co-ord to use in functionality outside of the library functions.

I will look at what you said in your post above to see if I can figure out a solution.
Coordinator
May 23, 2013 at 10:08 PM
@dbeves: I whipped off transposition quickly, and don't use it myself. I will look at it over the next day or two, so please let me know any further thoughts you have on it.
May 23, 2013 at 10:38 PM
As far as I can tell, all of the places you're using the Drawing library is to provide visual content from the utility.

For example, the HexGrid class is only being used by HexPanel, which is a UI component.

The only place where the these drawing features might come into play is with the FoV and line of sight algorithms. I haven't really looked into that code very deeply though, so I'm not sure.

Regarding my units variable visibility: the game I'm working on is fantasy based. An example of my features is that units can create barriers that present cover to friendly units, but they don't impede their view of enemies. So, for any enemies the hexes with the barriers block line of sight. For all friendly units, the barriers can be considered as "not there".

I have intentionally left out height line of sight issues for now. Things are either blocked or they're not. Once I get the rest of my mechanics working, I'll start looking into elevation and unit sizes. However, if I can use your utility to integrate those features right away - I'll be that much further ahead.

I'll see if I can start a general usage questions thread. I'm very new to this board, so I'm not sure what I can do and/or how to do it (yet).

On a side note ... I think I've decided that you're an evil genius. I was comparing your path finding code to Eric Lippert's. Your changes are very interesting, but the reasoning behind them is well beyond my mathematics skill level. Kudos.


May 23, 2013 at 11:11 PM
Just to interject ... the Hex Grid alss contains several important methods for managing the hex grid ... such as the items we are discussing above
May 23, 2013 at 11:13 PM
pgeerkens wrote:
@dbeves: I whipped off transposition quickly, and don't use it myself. I will look at it over the next day or two, so please let me know any further thoughts you have on it.
P, I have created a new thread for the transposed board issues - I will post any comments beyond that I have already made in there.
May 23, 2013 at 11:56 PM
I don't doubt that there is a lot of useful utility functionality in the HexGrid. I'm not trying to beat a dead horse, but there needs to be a clear separation between the business model and presentation features.

I fear though that the two are so tightly coupled that decoupling them may result in a major refactoring.

Cheers,

Paul


May 24, 2013 at 12:23 AM
PaulMouchet wrote:
I don't doubt that there is a lot of useful utility functionality in the HexGrid. I'm not trying to beat a dead horse, but there needs to be a clear separation between the business model and presentation features. I fear though that the two are so tightly coupled that decoupling them may result in a major refactoring. Cheers, Paul
Sorry - I probably didnt say the original strongly enough. I dont think there is any fat in the hex grid class - all that is there is pertinent to managing a hex board. A couple of the fields could appear as relating to visual (such as scroll position - still trying to work out exactly how to use that ) but they are more related to non visual functionality.
Coordinator
Aug 3, 2013 at 6:54 AM
Latest release includes code documentation generated by Doxygen, including class diagrams. Only about half the methods have summaries so far, and few have more than that, but it is a start.