United Kingdom: +44 (0)208 088 8978

Let’s look at Wolfenstein 3D in F#

Dragos takes a look the F# port of the 1992 classic Wolfenstein 3D

We're hiring Software Developers

Click here to find out more

Introduction

If you have seen some of my other blog posts you know I always find an excuse to talk about games made in F#, so once I saw James Randall's talk at fsharpConf about his F# port of Wolfstein 3D I knew I had to talk about it in this blog post.

Overview

This port can be run on desktop or in the browser, you can play it right now in the browser.

Controls in both cases: Cursor keys - movement Left Ctrl - fire Space bar - action (e.g. open door)

According to the creator, the codebase mostly uses a functional style, which seems to have affected the performance in some cases. This is a common scenario we can notice in other games made in F#, that immutability seems to be a hindrance most of the time and mutability is preferred.

Let's take a quick look at some samples of code so we can a better insight on how the project has been built.

Code examples

In the module App.Game we can see how a record is being created for the initial game state:

let private initialGameState =
  { Map = []
    Areas = []
    CompositeAreas = []
    Level = -1
    Player = {
      Lives = 3<life>
      Score = 0<points>
      Health = 100<hp>
      Radius = 0.5
      Weapons = [  ]
      CurrentWeaponIndex = -1
      Ammunition = 9<bullets>
      CurrentFaceIndex = 0
      TimeToFaceChangeMs = 1500.<ms>
    }
    Camera = {
      Position = { vX = 12. ; vY = 6. }
      Direction = { vX = -1. ; vY = 0. }
      Plane = { vX = 0. ; vY = 1. } }
      FieldOfView = 1.
    }
    ControlState = ControlState.None
    GameObjects = []
    IsFiring = false
    TimeToNextWeaponFrame = None
    Doors = []
    ViewportFilter = ViewportFilter.None
    PixelDissolver = None
    ResetLevel = (fun g _ -> g)
  }

And it has a type called Game defined in module Model.fs

type Game =
  { Level: int
    Map: Cell list list
    Areas: int list list
    CompositeAreas: CompositeArea list
    GameObjects: GameObject list
    Player: Player
    Camera: Camera
    ControlState: ControlState
    IsFiring: bool
    TimeToNextWeaponFrame: float<ms> option
    Doors: DoorState list
    ViewportFilter: ViewportFilter
    PixelDissolver: PixelDissolver option
    ResetLevel: Game -> Player -> Game
  }

In the App.Game module we can also see the game loop:

  let gameLoop (game:Game) (frameTime:float<ms>) =
    let updatedGameState =
      drawScene statusBarTextures graphics sprites game
      |> updateFrame game nextLevel frameTime (fun _ -> Dissolver.create viewportWidth viewportHeight 2)
    updatedGameState
  let updateControlState game controlState =
    { game with ControlState = game.ControlState ^^^ controlState }

In Audio.Platform module we can see how audio is sourced and played:

let private soundEffects =
  let audioPath filename = sprintf "assets/sounds/%s" (System.Uri.EscapeUriString filename)  

  SoundEffect.All
  |> List.map (fun soundEffectType -> soundEffectType,soundEffectType |> App.Assets.audioFilename "mp3" |> audioPath)
  |> List.map (fun (soundEffectType,path) -> soundEffectType,(createOverlayingAudioPlayer path))
  |> Map.ofList

let playSoundEffect soundEffect =
  let audioObject = soundEffects.[soundEffect]
  audioObject 1.0

let playSoundEffectAtVolume (volume:float) soundEffect  =
  let audioObject = soundEffects.[soundEffect]
  audioObject volume

In the App.AI module we can see how the AI has been implemented:

let applyAi frameTime (game,gameObject,newObjects) =
  match gameObject with
  | GameObject.Enemy enemy ->
    if enemy.IsAlive then
      let canSeePlayer = enemy |> isPlayerVisibleToEnemy game
      let updatedEnemy, updatedGame =
        (enemy,game)
        |> preProcess canSeePlayer
        |> updateBasedOnCurrentState canSeePlayer frameTime
        |> firingOnPlayer canSeePlayer
      updatedGame,updatedEnemy |> GameObject.Enemy,newObjects
    else
      game,gameObject,newObjects
  | _ -> game,gameObject,newObjects

Conclusion

In this post we had a quick look at James Randall's port of Wolfestein 3D and some bits of code, I hope you gained some new insights on how a game can be made with F# and let's hope for more F# games in the future 🙂