Building a roguelike in F# from scratch - Items again

in #programming7 years ago (edited)

Introduction

There has been a gap since my last post, lots going IRL that means I have not had the time to do much coding at home. Finally got back to my Rogue.

In this part I have re-structured the items to support different types of items and how they are used. I will run through the changes and why they have been made

New item types

This has been the biggest area of change. It is to support the different types of game items and their effect on the game.

First up some body locations that armor can be worn.

type slot = | Helmet | Torso | Legs | Gloves | Boots

Next up we have a type for each class of item, they hold the various stats that have an effect on the game.

  • key - no special details, key name is used to identify the door
  • armor - defense bonus and damage absorbed
  • weapon - attack bonus and damage done
  • potion - the stat effected and the effect with can be +-
type key = {id: int; name: string}
type armor = 
    {id: int; name: string; slot: slot; defense: int; absorbs: int}
type weapon = 
    {id: int; name: string; attack: int; damage: int}
type potion = {id: int; name: string; stat: stats; effect: int}

These are grouped in a union type which allows working with sets of items.

type item =
    | Key of key
    | Armor of armor
    | Weapon of weapon
    | Potion of potion

Last some helpers that provide access to id and name. I think this are ugly having to pattern match when all the fields are the same name. There is probably a smarter way to do this but I will hut for that later on.

let idOf item = 
    match item with
    | Key k -> k.id
    | Armor a -> a.id
    | Weapon w -> w.id
    | Potion p -> p.id

let nameOf item = 
    match item with
    | Key k -> k.name
    | Armor a -> a.name
    | Weapon w -> w.name
    | Potion p -> p.name

let hasId id item = (idOf item) = id

Actor changes

The backpack has changed to be a Map instead of a List, this is to support the two new fields:

  • equipped - map of items equipped in different slots
  • weapon - the weapon that is equipped

The basic idea being when you equip something it is removed from the backpack and put in either equipped or weapon depending the type.

type actor = 
    {
        id: int
        isNpc: bool
        name: string
        stats: Map<stats, stat>
        location: vector
        backpack: Map<int, item>
        equipped: Map<slot, item>
        weapon: item option
    }

As the backpack has changed so did the related lenses

let backpackItemWithId_ id = 
    backpack_ >-> Map.value_ id

let expectBackpackItemWithId_ id = 
    backpack_ >-> expectValue_ id

No lenses for equipped and weapon yet as no code currently uses them

Queries

The level queries around items are obvious really. The only point of interest being toItemMap which takes a list of items and produces a Map of id, item. This is a helper as it makes it far easier to push the floor items into the backpack later on.

let itemsAt location = Optic.get (itemsAt_ location)

let toItemMap = 
    List.map (fun item -> ((idOf item), item))
    >> Map.ofList

let hasItems location = 
    itemsAt location >> Seq.isEmpty >> not

let findItem location id = 
    Optic.get (itemWithId_ location id)

let expectItem location id = 
    Optic.get (expectItemWithId_ location id)

Operations

takeItems has had some minor tweaks to support the fact the backpack is a Map instead of a List. mergeItemMaps is used to merge two Item Maps together, it adds items2 into items1 using Map/Reduce type operations. fold is like Aggregate in C# LINQ.

let private mergeItemMaps items1 items2 =
    let folder items id item = Map.add id item items
    Map.fold folder items1 items2

let takeItems direction actorId level =
    let actor, targetLocation = level |> actorTarget direction actorId
    let locationItems = level |> itemsAt targetLocation |> toItemMap
    let newBackpack =
        actor |> Optic.get backpack_ |> mergeItemMaps locationItems
    let newActor =
        actor |> Optic.set backpack_ newBackpack
    level 
        |> Optic.set (expectActorWithId_ actorId) newActor
        |> Optic.set (itemsAt_ targetLocation) []

Summary

That is it apart from the test level factory, actually a minor impact to the code base given how far reaching the Item structure changes were. I now have a solid structure for storing all the types of game items I want to create.

Now I am happy with this I have a few possible things to explore next

  • Dig further into item manipulation
  • Line of sight logic
  • Basic NPC so I can attack something
  • The real render engine with bitmapped tiles

I will probably procrastinate on what to take on next over the rest of the weekend. If you have any preferences what to code next let me know as saves the procrastination when taken out of my hands lol

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

The current game commands are:

  • W - Up
  • A - Left
  • S - Down
  • D - Right
  • Space - Idle
  • T + [WASD] - Take all items in that direction
  • O + [WASD] - Open the door in that direction
  • C + [WASD] - Close the door in that direction

Finally the GitHub Repo

Feel free to ask questions and happy coding :)

Woz

Sort:  

I like reading your posts about the game from scratch. Do you actually plan to release your game at some point? J

It is more a learning process but when it becomes more playable I will build an exe and host that on github :)

Roguelikes have a lot of hidden complexity to them which makes them a great development exercise. I will be interested to see the performance once I get path finding up an running as that can be a costly process.