Morgemil Part 19 – Throw me for a loop

Games require human input. Games that do not require human input are simulations.

At the heart of every game is a way of acquiring the player’s input. So most games have the concept of a game loop. A game loop keeps the game running by continuously checking for user input, drawing to the screen, and updating state internally.

I have a basic game loop with some mocked up data

open Morgemil.Core
open Morgemil.Logic
 
[<EntryPoint>]
let main argv = 
  let player = 
    { Entity.Id = EntityId 3
      Type = EntityType.Person }
 
  let position = 
    { PositionComponent.EntityId = player.Id
      Position = Vector2i(5, 5)
      Mobile = true }
 
  let controller = 
    { PlayerComponent.EntityId = player.Id
      IsHumanControlled = true }
 
  let resource = 
    { ResourceComponent.EntityId = player.Id
      ResourceAmount = 50.0 }
 
  let level = 
    Morgemil.Core.DungeonGeneration.Generate({ DungeonParameter.Depth = 1
                                               RngSeed = 5
                                               Type = DungeonGenerationType.Square })
 
  let game = Morgemil.Logic.Game(level, [| player |], [| position |], [| controller |], [| resource |])
 
  let rec _continue() = 
    game.Update()
    let key = System.Console.ReadKey()
 
    let direction = 
      match key.Key with
      | System.ConsoleKey.W -> Vector2i(-1, 0)
      | System.ConsoleKey.E -> Vector2i(1, 0)
      | System.ConsoleKey.N -> Vector2i(0, -1)
      | System.ConsoleKey.S -> Vector2i(0, 1)
      | _ -> Vector2i()
    game.HumanRequest({ RequestedMovement.EntityId = player.Id
                        Direction = direction })
    |> ignore
    _continue()
  _continue()
  0

Running this from console gives this output
19_1
Those messages shown is the results of each turn as generated by this engine

namespace Morgemil.Logic
 
open Morgemil.Core
 
type Game(level : Level, entities : seq<Entity>, positions : seq<PositionComponent>, players : seq<PlayerComponent>, resources : seq<ResourceComponent>) = 
  let _world = World(level, Set.ofSeq (positions), Set.ofSeq (resources), Set.ofSeq (players))
  //TODO: fix
  let mutable _globalTurnQueue = (entities |> Seq.toList).Head
 
  let _handleRequest request = 
    TurnBuilder () { 
      match request with
      | EventResult.EntityMovementRequested(req) -> 
        let old_position = _world.Spatial.[req.EntityId]
        //TODO: Check that this move is actually valid
        let new_position = 
          _world.Spatial.Replace(old_position, { old_position with Position = old_position.Position + req.Direction })
        yield Message.PositionChange(old_position, new_position)
        //Movement takes one resource. More for testing purposes. 
        let old_resource = _world.Resources.[req.EntityId]
        let new_resource = 
          _world.Resources.Replace
            (old_resource, { old_resource with ResourceAmount = old_resource.ResourceAmount - 1.0 })
        yield Message.ResourceChange(old_resource, new_resource)
      | _ -> ()
    }
 
  member this.Update() = 
    let nextEntity = _globalTurnQueue //TODO: Actually have more than one entity (the player)
    let player = _world.Players.[nextEntity.Id]
    match player.IsHumanControlled with
    | true -> ()
    | false -> () //TODO: process AI turn
 
  ///Humans can only move units right now
  member this.HumanRequest(request : RequestedMovement) = 
    let results = TurnQueue.HandleMessages _handleRequest (EventResult.EntityMovementRequested request)
    //TODO: Display results through gui
    printfn ""
    results |> Seq.iter (fun res -> printfn "%A" res)
    true

Notice something interesting about those messages’ names? They are verbs in the past tense. The messages are saying that something happened. It’s then up to everything else to react.

The ability for the human to interact with the system is this method

  ///Humans can only move units right now
  member this.HumanRequest(request : RequestedMovement) = 
    let results = TurnQueue.HandleMessages _handleRequest (EventResult.EntityMovementRequested request)
    //TODO: Display results through gui
    printfn ""
    results |> Seq.iter (fun res -> printfn "%A" res)
    true

Given a human input, the game loop will process while messages are still being generated. That happens thanks to this method

    let results = TurnQueue.HandleMessages _handleRequest (EventResult.EntityMovementRequested request)

The logic in that is a continuous loop while messages are available to process

namespace Morgemil.Logic
 
///The callback to handle a request and results.
type EventMessageHandler = EventResult -> TurnStep
 
///Receives and processes events that make up a turn.
///Example: The action of moving onto a tile with a trap causes a new message chain starting with the trap's activation.
module TurnQueue = 
  let HandleMessages (handler : EventMessageHandler) (initialRequest : EventResult) : TurnStep = 
    let _processedEvents = new System.Collections.Generic.List<EventResult>()
    let _eventQueue = new System.Collections.Generic.Queue<EventResult>()
 
    let rec _handle() = 
      match _eventQueue.Count with
      | 0 -> ()
      | _ -> 
        let request = _eventQueue.Dequeue()
        request |> _processedEvents.Add
        request
        |> handler
        |> Seq.iter _eventQueue.Enqueue
        _handle()
    _eventQueue.Enqueue(initialRequest)
    _handle()
    (List.ofSeq _processedEvents)

See that fancy type definition of a callback at the top?

///The callback to handle a request and results.
type EventMessageHandler = EventResult -> TurnStep

Perhaps the definition of an EventResult will start to clear things up

namespace Morgemil.Logic
 
open Morgemil.Core
 
type RequestedMovement = 
  { EntityId : EntityId
    Direction : Vector2i }
 
type ResultMoved = 
  { EntityId : EntityId
    MovedFrom : Vector2i
    MovedTo : Vector2i }
 
type ResultResourceChanged = 
  { EntityId : EntityId
    OldValue : double
    NewValue : double }
  member this.ResourceChanged = this.OldValue - this.NewValue
 
///This represents the results of an action
type EventResult = 
  | EntityMoved of ResultMoved
  | EntityMovementRequested of RequestedMovement
  | EntityResourceChanged of ResultResourceChanged

But that doesn’t say what a TurnStep is

type TurnStep = List<EventResult>

So an EventMessageHandler is a type definition for a function that given an EventResult, returns a list of EventResult.

///The callback to handle a request and results.
type EventMessageHandler = EventResult -> TurnStep

The game engine has a method which is exactly that:

  let _handleRequest request = 
    TurnBuilder () { 
      match request with
      | EventResult.EntityMovementRequested(req) -> 
        let old_position = _world.Spatial.[req.EntityId]
        //TODO: Check that this move is actually valid
        let new_position = 
          _world.Spatial.Replace(old_position, { old_position with Position = old_position.Position + req.Direction })
        yield Message.PositionChange(old_position, new_position)
        //Movement takes one resource. More for testing purposes. 
        let old_resource = _world.Resources.[req.EntityId]
        let new_resource = 
          _world.Resources.Replace
            (old_resource, { old_resource with ResourceAmount = old_resource.ResourceAmount - 1.0 })
        yield Message.ResourceChange(old_resource, new_resource)
      | _ -> ()
    }

Now you might notice that the function in no way returns a list of event results. But… it does yield EventResult within the context of a TurnBuilder.

  let _handleRequest request = 
    TurnBuilder () { 
      ...
    }

A TurnBuilder is a computation expression which takes away the hassle of having to keep track of a return list as it does it for me.

type TurnBuilder() = 
  member this.Bind(x, f) = f x
  member this.Zero() = TurnStep.Empty
  member this.Yield(expr : EventResult) : TurnStep = [ expr ]
  member this.Return(expr) = TurnStep.Empty
  member this.Yield(expr) = TurnStep.Empty
  member this.Combine(a : TurnStep, b : TurnStep) = List.concat [ a; b ]
  member this.Delay(f) = f()

This post ended up being me throwing a lot of code out there with minimal explanation. That’s probably a sign for me to nail down some of this logic.

Morgemil Part 18 – Side effects

A beautiful thing about functional languages is that they are not fond of side-effects. And that’s what makes the following code so ugly, it’s full of side effects.

But first, a refresher on the entity and its components defined so far:

type EntityId = 
  | EntityId of int
 
type EntityType = 
  | Person
  | Door
  | Stairs
  | Object
  | Spell
 
type Entity = 
  { Id : EntityId
    Type : EntityType }
 
type PositionComponent = 
  { EntityId : EntityId
    Position : Vector2i
    Mobile : bool }
 
type PlayerComponent = 
  { EntityId : EntityId
    IsHumanControlled : bool }
 
type ResourceComponent = 
  { EntityId : EntityId
    ResourceAmount : double }

From there I created “component systems” which is fancy terminology for wrapping a dictionary: a way for me to access components by their EntityId efficiently. Do note that the coloring on the following code snippet is thrown off by the single quote in the generic type ‘T

open Morgemil.Core
open Morgemil.Logic.Extensions
 
type ComponentSystem<'T when 'T : comparison>(initialComponents : Set<'T>, getId : 'T -> EntityId) = 
 
  let mutable _components : Map<EntityId, 'T> = 
    [ for item in initialComponents -> (getId item), item ]
    |> Map.ofSeq
 
  let _added = new Event<'T>()
  let _removed = new Event<'T>()
  let _replaced = new Event<'T * 'T>()
  let _matches entityId (item : 'T) = (getId (item) = entityId)
  new(getId) = ComponentSystem(Set.empty, getId)
  member this.Components = _components |> Seq.map (fun t -> t.Value)
  member this.ComponentRemoved = _removed.Publish
  member this.ComponentAdded = _added.Publish
  member this.ComponentReplaced = _replaced.Publish
 
  member this.Find entityId = 
    match _components.ContainsKey(entityId) with
    | true -> Some(_components.[entityId])
    | _ -> None
 
  member this.Item 
    with get (entityId : EntityId) = _components.[entityId]
 
  member this.Add item = 
    _components <- _components.Add(getId item, item)
    _added.Trigger(item)
 
  member this.Remove item = 
    _components <- _components.Remove(getId item)
    _removed.Trigger(item)
 
  member this.Remove entityId = this.Remove _components.[entityId]
 
  member this.Replace(old_value : 'T, new_value : 'T) = 
    _components <- _components.Replace(getId old_value, new_value)
    _replaced.Trigger(old_value, new_value)
    new_value
 
  member this.Replace(entityId : EntityId, replacement) = 
    let old_value = this.[entityId]
    (old_value, this.Replace(old_value, replacement old_value))

There are multiple component systems in a world. One system for each component type.

open Morgemil.Core
 
type World(level, spatialComponents, resourceComponents, playerComponents) = 
  let _spatial = SpatialSystem(spatialComponents)
  let _resources = ComponentSystem<ResourceComponent>(resourceComponents, (fun resource -> resource.EntityId))
  let _players = ComponentSystem<PlayerComponent>(playerComponents, (fun player -> player.EntityId))
  let _level : Level = level
  member this.Spatial = _spatial
  member this.Level = _level
  member this.Resources = _resources
  member this.Players = _players
  static member Empty = World(Level.Empty, Set.empty, Set.empty, Set.empty)

Wait a second, I just said “One system for each component type” but there are only two components here…

The third component “PositionComponent” is there in a specialized system: “SpatialSystem”.

open Morgemil.Core
 
type SpatialSystem(initial) = 
  inherit ComponentSystem<PositionComponent>(initial, (fun position -> position.EntityId))
  member this.InRectangle(area : Rectangle) = this.Components |> Seq.filter (fun value -> area.Contains(value.Position))
  static member Empty = SpatialSystem(Set.empty)

This inherits from ComponentSystem, and tacks on some extra functionality. I can now easily check for entities that are positioned inside a rectangle. A good spatial system would do that efficiently except I’m more concerned with correctness right now.

Want an example? How about my basic game engine right now

open Morgemil.Core
open Morgemil.Logic.Extensions
 
type Game(level : Level, entities : seq<Entity>, positions : seq<PositionComponent>, players : seq<PlayerComponent>, resources : seq<ResourceComponent>) = 
  let _world = World(level, Set.ofSeq (positions), Set.ofSeq (resources), Set.ofSeq (players))
  //TODO: fix
  let mutable _globalTurnQueue = (entities |> Seq.toList).Head
 
  let _handleRequest request = 
    TurnBuilder () { 
      match request with
      | EventResult.EntityMovementRequested(req) -> 
        let old_position = _world.Spatial.[req.EntityId]
        //TODO: Check that this move is actually valid
        let new_position = 
          _world.Spatial.Replace(old_position, { old_position with Position = old_position.Position + req.Direction })
        yield Message.PositionChange(old_position, new_position)
        //Movement takes one resource. More for testing purposes. 
        let old_resource = _world.Resources.[req.EntityId]
        let new_resource = 
          _world.Resources.Replace
            (old_resource, { old_resource with ResourceAmount = old_resource.ResourceAmount - 1.0 })
        yield Message.ResourceChange(old_resource, new_resource)
      | _ -> ()
    }
 
  member this.Update() = 
    let nextEntity = _globalTurnQueue //TODO: Actually have more than one entity (the player)
    let player = _world.Players.[nextEntity.Id]
    match player.IsHumanControlled with
    | true -> ()
    | false -> () //TODO: process AI turn
 
  ///Humans can only move units right now
  member this.HumanRequest(request : RequestedMovement) = 
    let results = TurnQueue.HandleMessages _handleRequest (EventResult.EntityMovementRequested request)
    //TODO: Display results through gui
    printfn ""
    results |> Seq.iter (fun res -> printfn "%A" res)
    true

The world is created from passed in data, doesn’t really matter how right now.

What matters is the ease of use

      match request with
      | EventResult.EntityMovementRequested(req) -> 
        let old_position = _world.Spatial.[req.EntityId]
        //TODO: Check that this move is actually valid
        let new_position = 
          _world.Spatial.Replace(old_position, { old_position with Position = old_position.Position + req.Direction })
        yield Message.PositionChange(old_position, new_position)
        //Movement takes one resource. More for testing purposes. 
        let old_resource = _world.Resources.[req.EntityId]
        let new_resource = 
          _world.Resources.Replace
            (old_resource, { old_resource with ResourceAmount = old_resource.ResourceAmount - 1.0 })
        yield Message.ResourceChange(old_resource, new_resource)
      | _ -> ()

When an entity has movement requested, I’m taking the old position and replacing it with a new value, which is the old position + direction requested.

        let old_position = _world.Spatial.[req.EntityId]
        //TODO: Check that this move is actually valid
        let new_position = 
          _world.Spatial.Replace(old_position, { old_position with Position = old_position.Position + req.Direction })

I do the same thing with the resources.

        //Movement takes one resource. More for testing purposes. 
        let old_resource = _world.Resources.[req.EntityId]
        let new_resource = 
          _world.Resources.Replace
            (old_resource, { old_resource with ResourceAmount = old_resource.ResourceAmount - 1.0 })

This has side effects, the list of components is constantly changing with removal and insertions. But the side effects are limited in that I’m never updating the component’s value directly. the only way for it to be updated, is for that entire component associated with that Entity to be replaced.

Even with this compromise, record immutability has some fantastic possibilities. I can pass those component records in messages with wild abandon. Indeed, I’ve been doing so and that is next week’s topic.