Building a roguelike in F# from scratch - It's ALIVE!!!!

Words
421
Reading
2 min
Listen
Play
9y

Introduction

I have the basics running and can move around the map!

Just two stupid bugs in the code base, that was all. Not bad given I have never run a single line until today, not even used the REPL to test while I coded.

This is what it looks like, very basic stuff.

So in this part an overview on the LevelFactory and RenderEngine, both quick and dirty to get a test rig running.

Level Factory

The level factory is just some code to get a level structure built fast. It will be thrown away later when I look at random level generation.

First up an ASCII tile map so I could draw the level

let private testLevelTemplate =
    [
        "                                            "; // y = 0
        "    ################                        "; // y = 1
        "    #..............#         ###########    "; // y = 2
        "    #..............#         #......~~~#    "; // y = 3
        "    #..............###########......~~~#    "; // y = 4
        "    #................................###    "; // y = 5
        "    #..............###########.......#      "; // y = 6
        "    #..............#         #########      "; // y = 7
        "    ################                        "; // y = 8
        "                                            "; // y = 9
        "                                            "; // y = 10
        "                                            "  // y = 11
    ]

let private width = testLevelTemplate.[0].Length
let private height = testLevelTemplate.Length

Then a dummy player with just the basics filled in

let private testPlayer =
    {
        id = 1;
        isNpc = false;
        name = "player";
        stats = [(Health, {current = 10; max = 10})] |> Map.ofSeq
        location = {x = 9; y = 6};
        backpack = []
    }

Then a function to translate an ASCII character into a Tile

let private charToTile character =
    match character with
    | '#' -> Wall
    | '.' -> Floor
    | '~' -> Water
    | _ -> Void

Next a translation to convert the array of strings into a 2D array of tiles. Some basic map/reduce flow.

let private testMap =
    let rowToTiles row = row |> Seq.map charToTile |> Array.ofSeq
    testLevelTemplate |> Seq.rev |> Seq.map rowToTiles |> Array.ofSeq

Finally put the whole lot together in a level record, notice the use of spawnActor to push the player into the level.

let testLevel =
    let level = 
        {
            playerId = testPlayer.id;
            map = 
                {
                    tiles = testMap; 
                    topRight = 
                        {
                            x = testMap.[0].Length - 1; 
                            y = testMap.Length - 1
                        }
                }
            doors = Map.empty<vector, door>
            actors = Map.empty<actorId, actor>
            items = Map.empty<itemId, item>
            mapActors = Map.empty<vector, actorId>
            mapItems = Map.empty<vector, List<itemId>>
        }

    level |> spawnActor testPlayer 

All simple logical stuff :)

Render Engine

The render engine is really basic. it just translates the level into an array of strings. Then clears the console window and prints them out.

A map from Tile to ASCII character, simple reversal of what you have seen.

let private tileToChar tile =
    match tile with
    | Wall -> '#' 
    | Floor -> '.' 
    | Water -> '~'
    | _ -> ' '

To render a given tile I check it it has an Actor, if so displays an "@" otherwise converts the tile to ASCII

let private renderTile level location =
    match level |> Optic.get (mapActorAt_ location) with
    | Some _ -> '@'
    | None -> level |> getTile location |> tileToChar

Finally walk the tiles and build the rows of the display, printing them to the console.

let private xs map = seq{0 .. map.topRight.x}
let private ys map = seq{(map.topRight.y - 1) .. -1 .. 0}

let render level = 
    let buildRow map currentY = 
        xs map 
            |> Seq.map (fun nextX -> {x = nextX; y = currentY})
            |> Seq.map (renderTile level)
            |> Seq.toArray
            |> System.String
    
    Console.Clear()
    ys level.map 
        |> Seq.map (buildRow level.map) 
        |> Seq.iter (printfn "%s")

Really quick and dirty, the screen flashes every redraw but this is just a test rig.

Bug Fixes And Other Tweaks

There were a couple of bug fixes.

  • East and West were reversed in the getPlayerCommand
  • actorId now passed into getPlayerCommand so it is a function and not a value
  • getTile used y twice instead of x y

That was it. Simple obvious bugs that were easy to find.

Summary

I will probably pause on this for a while now while I think about the direction I want to go in.

So next up an introduction into F# before I pick this up again.

And for those who have only just found the series, here is the story so far for Building a roguelike in F# from scratch :

Finally the GitHub Repo

Feel free to ask questions and happy coding :)

Woz

Building a roguelike in F# from scratch - It's ALIVE!!!! | Ecency