Building a roguelike in F# from scratch - It's ALIVE!!!!
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 :
- Introduction
- Repo and Initial Types
- First Problems
- Level Queries
- CRUD Operations
- Validation
- Game Loop Time
Finally the GitHub Repo
Feel free to ask questions and happy coding :)
Woz