The Code Behind Paint Wars: Merging Functional and OOP

This post is the fourth in a series of posts about the code behindPaintWars. In this series, I will be talking about how the design and implementation of the game differed in C# and F#.  Along the way, I’ll also be talking about some of the fun and exciting features of the F# language and examine how they can be used to solve practical software problems. For a description of the game, see this post.

In my previous two PaintWars posts, I covered the design of Paint Wars from both an OOP and a functional standpoint. In this post, I’ll get practical and combine the two disciplines to produce a real result. We’ll use F# to do it because it excels at being multi-paradigm.

We’re going to dive right into code. I won’t go into details about the syntax here, but the code below forms the basis of all of the interactions in the game.

type IGameObject =
    abstract Id : Id
    abstract Position : Vector
    abstract Attackable : bool
    abstract Color : Color
    abstract Texture : Texture2D
    abstract DrawOffset : Vector
    abstract BoundingObject : BoundingObject option
    abstract Solid : bool
    abstract GetFrame : Time -> Rectangle
    abstract Update : GameState -> GameStateTransform List
    abstract ApplyTransform : GameState -> ObjectTransform -> IGameObject

The first thing that’s we define is an interface for game objects. This is similar to the abstract base class that we had in the OOP approach. The most important functions here are the Update and ApplyTransform functions since they are the plumbing for most of the engine. As you can infer from the interface definition, none these functions modify the state of the object.

and ObjectTransform = 
    | MoveAbsolute of Vector 
    | MoveRelative of Vector 
    | ChangeAnimation of Animation 
    | GetPickedUp of Color 
    | GetPutDown of Vector 
    | StartPickingUp 
    | StopPickingUp 
    | AcquireObject of IGameObject 
    | DropChildren 
    | TakeDamage of (Color * HitPoints) 
    | SetSpawnTicks of int 
    | SetPaintRechargeTime of Time 
    | Die 
    | WaitForNextSpawn 

Next we describe the set of transforms that can be applied to a GameObject. I talked about how the transforms work in the last post, but these transforms are used by a GameObject’s Update function to produce a new GameObject from an existing one. In the OOP approach, these ObjectTransforms would be Abstract methods on the base object.

and GameState = 
    { PlayingArea : Rectangle 
      ObjectsById: Dictionary<int, IGameObject> 
      Partition : PartitionElement 
      Canvas : Canvas 
      CanvasState: CanvasState 
      PaintList : PaintableGameObject List 
      TimeSinceLastState : Time 
      Time: Time } 

The next thing that we define is the GameState record. This contains the state of the game before and after an update. Most of the members should be self explanatory, The Canvas, CanvasState, and PaintList all deal with how the paint gets drawn on the map, and the Partition member deals with the spatial partitioning algorithm used for collision detection. We’ll ignore those for the purpose of this post. Also of interest is the ObjectsByID dictionary. It’s mutable for performance reasons, but an immutable container would work if we wanted to stay pure.

and GameStateTransform =
    | AreaOfEffect of (BoundingObject * ObjectTransform)
    | ById of (Id * ObjectTransform)
    | Remove of Id
    | Add of IGameObject
    | Paint of PaintableGameObject

Finally, we define the GameStateTransform type which lists all of the transforms that can be performed on a GameState.

state.ObjectsById.Values
|> ParallelSeq.map (fun gameObject -> gameObject.Update state)
|> Seq.reduce (fun currentList toAppend -> currentList @ toAppend)
|> Seq.fold ApplyTransform state

Now we get to the fun part. After doing all the hard work of defining the above types and the IGameObject implementors, we can run them with the above code. Let’s go line by line to explain exactly what is happening.

|> ParallelSeq.map (fun gameObject -> gameObject.Update state)

This bit of code generates a list of list of GameStateTransforms from the list of objects on the previous state. As described in the last post, we can do this in parallel since none of the generator functions share any mutable state. Note that the ParallelSeq function is just a wrapper around map/select in the Task Parallel Library.

|> Seq.reduce (fun currentList toAppend -> currentList @ toAppend)

This line just transforms the list of lists into a flat list of GameStateTransforms.

|> Seq.fold ApplyTransform state

Lastly, we just apply each transform to the GameState to produce our state for the next frame of gameplay. The ApplyTransform function is defined elsewhere, but as it’s name indicates, all it does is apply a GameStateTransform to a Gamestate to produce an updated GameState.

Although there is a lot of other code in the PaintWars project, most of the engine is written out above. It is possible to write in such a small space because F# code can be both terse and multi-paradigm. This combination is also one of the reasons that F# is such a fun language to program in.

This entry was posted in F#, Functional, Paint Wars, SrtInsights. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

Post a Comment

Your email is never published nor shared. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*
*