Morgemil Game Update #4
I've been working off and on making a video game in F#. I thought I'd drop an update saying my progress.
There's been a bit of a dropoff in code output as I've had other obligations the past week or so, but I've got a lot of paper notes scattered throughout the house and I'm optimistic about future productivity.
I've done 91 commits since the beginning of the year according to GitHub. Although progress technically started on Mar 15, 2015.
This number doesn't mean anything to anyone. I just use it so I can look at the last progress update I did and be motivated because I am writing code, even if not all code directly becomes a very visual output.
About a week ago I received an email encouraging me to keep working on stuff. It doesn't mean much to the game's progress, but it sure means a lot to me and I thank you! (name hidden because I haven't asked them if it's okay to show this.)
I have no animated gifs to show today of progress. Rather, I have musings and unanswered questions and work-in-progress and plans.
The entirety of a game in progress is hidden behind a single interface. How the implementation of this interface happens, doesn't really matter so long as it fulfills what it said it would do.
[<RequireQualifiedAccess>] type GameState = | Processing | Results of Character Step list | WaitingForInput type IGameStateMachine = /// Stops the game engine abstract member Stop: unit -> unit /// Gets the current state of the game loop abstract member CurrentState: GameState with get /// Sends input abstract member Input: ActionRequest -> unit /// Acknowledge results abstract member Acknowledge: unit -> unit
The above interface is very well tied into the game's graphical engine loop and the logic becomes a finite state machine. The three states are listed above.
Every turn the graphical engine performs this logic, on the assumption that it's very fast to do so. And that the game engine may continue to be able to render 60 frames per second of video.
match gameState.CurrentState with | GameState.WaitingForInput -> if event.IsSome then gameState.Input event.Value | GameState.Processing -> printfn "processing" | GameState.Results results -> results |> List.iter (fun event -> //DO THINGS WITH RESULTS ) gameState.Acknowledge()
By clearly delineating the line between the graphical game engine and the backing game logic, I have opened the door for a few awesome things!
The potential for multiplayer and game servers and LAN parties! The code behind that interface may be making plain old method calls, or HTTP calls, or GRPC calls, or UDP/TCP calls.
- DISCLAIMER: I don't know if I'm adding multiplayer, but I tend to over-engineer projects without deadlines just because I can.
The only interaction from the player and graphics to the game engine is the player's keyboard and mouse events translated to game input. To save a game run-through, I only have to record the player's inputs and I may view a saved game with ease.
- Though do note that this is only valid because nothing is truly random, I'm using a pre-seeded RNG (Random Number Generator) implemention of Mersenne Twister.
I may chain together pipelines of that interface. Perhaps it might be interesting to create a pipeline interceptor that reads results and aggregates interesting information from that.
- I'd imagine that memorizing the aspects of everything in the dungeon would get boring, but what if everything the player met was added to a bestiary for easier later reference? Intercepting the results and storing it to a compendium would be trivial.
- No matter how careful I am, no matter how many unit tests I write, being able to log the inputs and results during development is very convenient for exploratory testing.
Other state machines
I talked briefly above about putting the actual game behind a state machine. Other state machines exist too, such as game menus.
I'm still thinking things through, and it's not ready by any means. The below is a first draft of the process of game creation. The exit state would be
GameBuilt which then returns another state machine as defined above.
/// The steps and state of a game that's being built. [<RequireQualifiedAccess>] type GameBuilderState = | WaitingForInput | LoadingScenarioData | WaitingForPlayers | BuildingMap | AddingCharacters | GameBuilt of IGameStateMachine /// The interface to interact with a game being built. type GameBuilder = /// The current state of the builder abstract member State: GameBuilderState with get /// List all players connected abstract member ListPlayers: PlayerID list /// False if still waiting for player interaction abstract member Building: bool with get
It's still a work-in-progress, and it doesn't mean anything about the graphical interface being good or not. This is entirely a reflection on that I haven't defined the player-game interaction well enough.
I'm fully aware that the dungeon crawler itself isn't playable yet. My current efforts lie entirely toward getting a workable end-to-end experience, however rough and incomplete it may be. Once I have it where the player may open the game, choose some options and start a game, then I'll return to the actual game experience and iterate quickly.
My code is here. I may not be doing a lot of things in a functional way of programming, but my code is always in a workable state and I'm constantly refactoring to make it better.