Building a roguelike in F# from scratch - Command logging

in #programming7 years ago

Introduction

Not had much time for this recently but here is a quick update, I now report what the player or any actor has done in the game. These messages are just added to the same list as the error messages.

I will extend the logging later to say what the even was, this will allow me to just output the events that are of interest to the player :)

Here are some examples of the messages.

Movement

Taking item(s)

Close a door

The changes

All actually simple enough, just some changes to the Operations module.

First up log and flush were moved to the head of the file. This is because of how F# compiles. No forward references so anything you use must have already been defined. This sounds difficult but is easy as long as you break things up small, you also do not build in the normal layers you might use in an OO program.

I also added logAll which logs a list of messages, save the individual calls to log. It simply gets the current messages, appends that to the supplied messages then pushes the results back into the level. Remember we build the list in reverse as it is a cons list, hence how it is implemented..

let log message level =
    let messages = message :: (level |> Optic.get messages_)
    level |> Optic.set messages_ messages

let logAll newMessages level =
    let messages = 
        level 
            |> Optic.get messages_
            |> Seq.append newMessages
            |> List.ofSeq
    level |> Optic.set messages_ messages

let flush = Optic.set messages_ []

Next up moveActor, this simply has a call to log at the end of it. Job done.

let moveActor direction actorId level =
    let actor, targetLocation = level |> actorTarget direction actorId
    let movedActor = actor |> Optic.set location_ targetLocation
    level 
        |> Optic.set (expectActorWithId_ actorId) movedActor
        |> Optic.set (mapActorAt_ actor.location) None
        |> Optic.set (expectMapActorAt_ targetLocation) actorId
        |> log (actor.name + " moved")

The rest are all similar changes.

let hurtActor damage actorId level =
    let actor = level |> Optic.get (expectActorWithId_ actorId)
    let actorHealth_ = expectActorWithId_ actorId >-> expectStatFor_ Health
    let updatedHealth = decreaseCurrent damage (level |> Optic.get actorHealth_)
    level 
        |> Optic.set actorHealth_ updatedHealth
        |> log (actor.name + " took damage")

let openDoor direction actorId level = 
    let actor, targetLocation = level |> actorTarget direction actorId
    level 
        |> placeDoor Open targetLocation 
        |> log (actor.name + " opened a door")

let closeDoor direction actorId level = 
    let actor, targetLocation = level |> actorTarget direction actorId
    level 
        |> placeDoor Closed targetLocation 
        |> log (actor.name + " closed a door")

let unlockDoor direction actorId level = 
    let actor, targetLocation = level |> actorTarget direction actorId
    level 
        |> placeDoor Closed targetLocation 
        |> log (actor.name + " unlocked a door")

They all call log at the end, apart from that just what you have seen before.

TakeItems was a bit bigger change, I had to tease apart to get access to all the parts I needed. It also now builds the take message for each item taken and logs them all. I have before and after so you can see the changes. All obvious when you see the two.

// Before change
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) []

// After change
let takeItems direction actorId level =
    let actor, targetLocation = level |> actorTarget direction actorId
    let locationItems = level |> itemsAt targetLocation
    let newBackpack = 
        actor 
            |> Optic.get backpack_ 
            |> mergeItemMaps (locationItems |> toItemMap)
    let newActor = actor |> Optic.set backpack_ newBackpack
    let messages = 
        locationItems 
            |> Seq.map (fun item -> actor.name + " took " + (nameOf item))
    level 
        |> Optic.set (expectActorWithId_ actorId) newActor
        |> Optic.set (itemsAt_ targetLocation) []
        |> logAll messages

Summary

As I said, I will extend logging later on, but as proof of concept, tiny changes that have a good impact :)

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
  • U + [WASD] - Unlock the door in that direction

Finally the GitHub Repo

Feel free to ask questions and happy coding :)

Woz

Sort:  

This is incredible! Love it. Followed...

Thanks. Moves slow as just spare time but good fun writing it

Remember to vote it up also :)

nice work thank's to share it with us when exactly we can use this language and what's about the security in app ?

The language, F#, is available free. Either the F# Foundation or part of Visual Studio install.

It is ML based and runs on .NET platform and Mono. So can be used anywhere C# is. This includes phone space with xamerind and the like.

As secure as any other .NET language.

The big difference is NO variables. Everything is const and immutable.

let a = 10
a = a + 1 // Compile error

So requires a very different way of thinking to imperative languages (OO, procedural etc)

Awesome post. if you help me then I will help you a lot
follow me @maruf121. thanks

Coin Marketplace

STEEM 0.20
TRX 0.13
JST 0.030
BTC 65128.68
ETH 3442.23
USDT 1.00
SBD 2.52