James Randall Musings on software development, business and technology.
C# / Blazor Wolfenstein - Part 2 - Models and Discriminated Unions

I’ll preface this post by saying that I have absolutely no idea how often I’ll post these updates. I can imagine getting a couple a day at times. Then two or three weeks going by with nothing. I really don’t know how long this will take as I don’t really know where I’m going with it: it is truly unscripted! With that out the way…

Also I’d like to put a big warning on this blog post: after a couple of years away from C# I’m figuring this stuff out as I go - its likely, well, shit. Please don’t pick up these ideas and use them in production code… at least not without a lot of thinking. Its best to think of this series as a stream of conciousness which, with luck and persistence, by the end will coalesce into something coherent.

After grappling with Blazor in the last part I figured it was time to get to the foundation of things in place - how to model the map and game state.

My early thoughts after attempt 1 at this with modern C# are that the improvements are incredibly worthwhile if you come at C# as a C# developer who leans towards functional but if you’re a functional first developer it leads you into an uncanny valley. An uncanny valley from a From Software game (bleak, scary, and confusing!). By the way - did I say that I’m obsessing over Bloodborne at the moment? I am. Best game I’ve played in years. And I think its on the PS5 equivelant of GamePass (I’m playing it on PS5, but I bought it when it first came out but it was too hard for me at the time).

As we explore this for reference my F# models can be found here. It makes heavy use of DUs(discriminated unions) and their absence in C# has posed a challenge to my modelling of state - after a couple of years of F# I find things what I think what I’d best describe as “back to front” in C#.

First the simple. I have a bunch of DUs that have no associated state and are used pretty much like enums. Easy (I say that now… maybe not later!).


type DoorStatus = | Opening | Closing | Open | Closed


public enum DoorStatus { Opening, Closing, Open, Closed }

But with that simple case out of the way we get into the more complex territory of DUs that have associated state. First lets consider this example - I use an array of arrays of this “Cell” DU to model the immutable map in F# (MapDirection is an enum: MapDirection.North etc.):

type Wall =
  { NorthSouthTextureIndex: int
    EastWestTextureIndex: int
  member this.IsExit =
    this.NorthSouthTextureIndex = 40 || this.EastWestTextureIndex = 40 ||
    this.NorthSouthTextureIndex = 41 || this.EastWestTextureIndex = 41

type Cell =
  | Wall of Wall
  | Door of int // index of the door in the door state array
  | TurningPoint of MapDirection
  | Empty

In this instance I’ve modelled this in a more traditional way C# using object orientation:

public abstract record Cell((int x, int y) MapPosition);

public record Wall((int x, int y) MapPosition, int NorthSouthTextureIndex, int EastWestTextureIndex) : Cell(MapPosition)
    public bool IsExit =>
        NorthSouthTextureIndex == 40 || EastWestTextureIndex == 40 ||
        NorthSouthTextureIndex == 41 || EastWestTextureIndex == 41;

public record Door((int x, int y) MapPosition, int DoorIndex) : Cell(MapPosition);

public record TurningPoint((int x, int y) MapPosition, MapDirection TurnsToDirection) : Cell(MapPosition);

public record Empty((int x, int y) MapPosition) : Cell(MapPosition);

I’ve yet to make any use of this so lets park that for now but you can see one of the clear differences between traditional C# and F# here - the former modelling through inheritance and the latter composition.

The next example I’ve tackled is how I track the current activity state of the enemy - what the enemy is doing. Most of these states require no additional data but a handful do. Its a field on the enemy record, is updated by the game logic, and is nearly always used (if not exclusively) with pattern matching. In F# it looks like this:

type EnemyStateType =
  | Standing
  | Ambushing
  | Attack
  | Path of PathState
  | Pain of EnemyStateType
  | Shoot
  | Chase of int*int // co-ordinates we are moving to as part of the chase
  | Die
  | Dead

type Enemy =
  { State: EnemyStateType
    // .... more properties

Inheritance for this felt horrible. As did having some sort of sense of optional state on the enemy type. I really really wanted to use a DU. Had a look around and came across the OneOf package and figured I could combine that with pattern matching to come close to F#. First off I needed to replicate the basic states into some sort of DUish structure using the OneOf package:

public class EnemyState : OneOfBase<
    private EnemyState(
        OneOf<Type.Standing, Type.Ambushing, Type.Attack, Type.Path,
            Type.Shoot, Type.Chase, Type.Die, Type.Dead,
            Type.Pain> input) : base(input)

    public static class Type
        public record Standing;
        public record Ambushing;
        public record Attack;
        public record Path(PathState PathState);
        public record Shoot;
        public record Chase((int x, int y) TargetMapPosition);
        public record Die;
        public record Dead;
        public record Pain(EnemyState PreviousStateType);

    public static readonly EnemyState Standing = new(new Type.Standing());
    public static readonly EnemyState Ambushing = new(new Type.Ambushing());
    public static readonly EnemyState Attack = new(new Type.Attack());
    public static EnemyState Path(PathState pathState) => new(new Type.Path(pathState));
    public static readonly EnemyState Shoot = new(new Type.Shoot());
    public static EnemyState Chase((int x, int y) targetMapPosition) =>
        new EnemyState(new Type.Chase(targetMapPosition));
    public static readonly EnemyState Die = new(new Type.Die());
    public static readonly EnemyState Dead = new(new Type.Dead());
    public static EnemyState Pain(EnemyState previousStateType) => new(new Type.Pain(previousStateType));

It’s quite a bit of typing and the static properties and methods I don’t think are strictly needed but my intuition tells me they’ll make using this a bit cleaner. If I’m wrong they will be mercilessly deleted (I’m so watching Flash Gordon this evening).

Rendered output

An example of this in use is shown below:

public float AnimationTimeForState =>
    EnemyProperties.State.Value switch
        EnemyState.Type.Attack => 200.0f,
        EnemyState.Type.Chase => 100.0f,
        EnemyState.Type.Path => 300.0f,
        EnemyState.Type.Pain => 100.0f,
        EnemyState.Type.Dead or EnemyState.Type.Dead => 100.0f,
        _ => 0.0f

While I’m here - hat tip to my friends over at Endjin who had an excellent post on C# pattern matching on their blog that I happened upon via Google. Was just the right length and littered with code samples so I could skim through and pick up what I’d missed over the last couple of years.

Ok. So that’s two approaches down. I have a third (I know - by now you’re begging me to stop). In truth my third approach is a twist on the inheritance approach but with a bit more reshaping going on. In F# my game objects are modelled by a DU:

type GameObject =
  | Static of BasicGameObject
  | Enemy of Enemy
  member x.BasicGameObject =
    match x with
    | GameObject.Static t -> t
    | GameObject.Enemy e -> e.BasicGameObject

In C# this has become:

public abstract record AbstractGameObject;

public record StaticGameObject(BasicGameObjectProperties CommonProperties) : AbstractGameObject;

public record EnemyGameObject(BasicGameObjectProperties CommonProperties, EnemyProperties EnemyProperties) : AbstractGameObject;

I’m finding myself super tempted to move this to the OneOf DU approach. But I’m going to resist until I’ve tried this out in the game code.

I’m yet to use any of this to any great length and I suspect I will end up doing a lot of rework on this stuff as I do so: I’m someone who likes to feel their way through new stuff somewhat, not someone who sits in a room stroking his beard until things are fully formed. Its a starting point and is somewhat referencable back to the F# code which isn’t necessarily an end game goal but is, as I say, a starting point.

A final point on records. To me the ideal construction syntax should not result in default values being provided for properties that are not explicitly initialized but I do want to use the proeprty names during initialization: so many errors can be caused due to implicit initialization and you have to be super diligent around it.

To that end I’ve been using the definition syntax for records like this:

// Definition
public record Alert(string Message, string TItle);

// Usage
var alert = new Alert(Message: "Hello World", Title: "Alert");

The code for this part can be found here. If you want to discuss this or have any questions then the best place is on GitHub. I’m only really using Twitter to post updates to my blog these days.