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

in #programming7 years ago (edited)

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

Sort:  

Congratulations @woz.software! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

By upvoting this notification, you can help all Steemit users. Learn how here!

See ; is Needed on Your 2nd Image
Well NICE Post

ALSO

KINDLY TRY TO

UPVOTE ME- FOLLOW ME BACK- RESTEAM MY LATEST POST

upvoted for the ; comment

Coin Marketplace

STEEM 0.30
TRX 0.12
JST 0.033
BTC 64386.10
ETH 3142.17
USDT 1.00
SBD 3.98