Haskell roguelike - Debugging

Posted on April 2, 2018

start prev next

Admin / debug options

It is useful to have the ability to toggle some features of the UI while you are designing the game. The darkness overlay makes it hard to see what the other actors are doing. This can be a real pain while you are testing the interaction of utilities and later on with combat etc.

Toggle the lights

Let’s add a simple debug command for toggling the lights. I’m using shift+d as the prefix for debug commands, and d as the key to toggle darkness.

16_debug/src/GameEngine.hs (145 to 145)
  , ("shift+d d", "Debug:Light:Toggle")

In handleKey this is set to toggle a property named debug:light

16_debug/src/GameEngine.hs (444 to 444)
  "Debug:Light:Toggle" -> [ActTogglePlayerProp "debug:light" "on"]

Actor Properties

The actor gets a map of properties

16_debug/src/GameCore.hs (60 to 60)
  , _acProps :: !(Map Text Text)

Action

ActTogglePlayerProp is added to RogueAction

16_debug/src/GameCore.hs (100 to 102)
data RogueAction = ActMovePlayer (Int, Int)
                 | ActSetPlayerViewPortStyle ViewPortStyle
                 | ActTogglePlayerProp Text Text

Toggle

The action handler for ActTogglePlayerProp either adds the value to the map or removes the property if it already exists

16_debug/src/GameEngine.hs (466 to 471)
    ActTogglePlayerProp prop valEnabled ->
      world & (wdPlayer . plActor . acProps) %~ Map.alter (toggleMapProp valEnabled) prop

  where
    toggleMapProp v Nothing = Just v
    toggleMapProp _ (Just _) = Nothing

Using the property

darknessFovOverlay then checks if the property is set, if it is no overlay (Map.empty) is returned

16_debug/src/GameEngine.hs (677 to 698)
darknessFovOverlay :: Player -> Actor -> Map PlayerPos Tile
darknessFovOverlay player actor =
  case player ^. plActor ^. acProps ^.at "debug:light" of
    Nothing ->
      let
        (screenWidth, screenHeight) = player ^. plScreenSize

        -- Create a full grid of darkness
        blackBg = Map.fromList [ (PlayerPos (x, y), E.getTile E.Dark)
                               | x <- [0..(screenWidth - 1)]
                               , y <- [0..(screenHeight - 1)]
                               ] 

        lightAt = worldCoordToPlayer (player ^. plWorldTopLeft) <$> flatFov (actor ^. acFov)
        seen = worldCoordToPlayer (player ^. plWorldTopLeft) <$> Set.toList (actor ^. acFovHistory)
      in
      -- Remove the darkness overlay at any position that is to be lit
      --  I.e. any position in the field of view, or previously in the field of view
      foldr Map.delete blackBg $ lightAt <> seen

    Just _ ->
      Map.empty

Other ideas

This was quite a simple addition that makes testing your game a lot easier. There are many other features you can add to assist development. One that helped me was having an option to enable making the actor’s fov visible. These features sometimes also can become game features (e.g. switching on a levels lights with a switch) so it is worth deciding if they are actually debug features or just world features that you have debug access to.

Chapters

start prev next

Changes

src/GameCore.hs

diff -w -B -a -d -u -b --new-file 15_memory/src/GameCore.hs 16_debug/src/GameCore.hs
--- 15_memory/src/GameCore.hs
+++ 16_debug/src/GameCore.hs
@@ -52,10 +52,13 @@
                    -- | The actor's disposition - the values that define the actors personality
                    , _acDisposition :: !Disposition
 
-
                    -- | The actor's memory about positions
                    , _acPosMemory :: !(M.Memory WorldPos)
 
+                   -- | Actors's properties
+
+                   , _acProps :: !(Map Text Text)
+
                    }
 
 data Player = Player { _plConn :: !Host.Connection
@@ -93,8 +96,12 @@
 newtype WorldPos = WorldPos (Int, Int) deriving (Show, Eq, Ord)
 newtype PlayerPos = PlayerPos (Int, Int) deriving (Show, Eq, Ord)
 
+
 data RogueAction = ActMovePlayer (Int, Int)
                  | ActSetPlayerViewPortStyle ViewPortStyle
+                 | ActTogglePlayerProp Text Text
+
+
 
 data ViewPortStyle = ViewPortCentre
                    | ViewPortLock PlayerPos

html/rogue.js

diff -w -B -a -d -u -b --new-file 15_memory/src/GameEngine.hs 16_debug/src/GameEngine.hs
--- 15_memory/src/GameEngine.hs
+++ 16_debug/src/GameEngine.hs
@@ -140,6 +140,10 @@
                                        , ("shift+v p", "Game:ViewPort:Snap")
                                        , ("shift+v b", "Game:ViewPort:Border")
                                        , ("shift+v l", "Game:ViewPort:Lock")
+
+
+                                       , ("shift+d d", "Debug:Light:Toggle")
+
                                        ]
              , _cfgMinMaxBounds = (-300, 300, -300, 300)
              }
@@ -168,6 +172,7 @@
             , _acUtilities = []
             , _acDisposition = UB.emptyDisposition
             , _acPosMemory = M.empty
+            , _acProps = Map.empty
             }
 
     mkEnemyActor aid e (x, y) =
@@ -185,6 +190,7 @@
             , _acUtilities = []
             , _acDisposition = UB.emptyDisposition 
             , _acPosMemory = M.empty
+            , _acProps = Map.empty
             }
     
 
@@ -434,6 +440,10 @@
     "Game:ViewPort:Border" -> [ActSetPlayerViewPortStyle $ ViewPortBorder 2]
     "Game:ViewPort:Lock"   -> [ActSetPlayerViewPortStyle $ ViewPortLock (worldCoordToPlayer topLeft $ actor ^. acWorldPos)]
 
+
+    "Debug:Light:Toggle" -> [ActTogglePlayerProp "debug:light" "on"]
+
+
     _ -> []
 handleKey _ _ = []
 
@@ -453,6 +463,15 @@
       world & (wdPlayer . plViewPortStyle) .~ style
 
 
+    ActTogglePlayerProp prop valEnabled ->
+      world & (wdPlayer . plActor . acProps) %~ Map.alter (toggleMapProp valEnabled) prop
+
+  where
+    toggleMapProp v Nothing = Just v
+    toggleMapProp _ (Just _) = Nothing
+
+
+
 tryMoveActor :: World -> Actor -> (Int, Int) -> Maybe World
 tryMoveActor world actor (dx, dy) =
   let
@@ -654,8 +673,11 @@
     Just e -> (e ^. enType) /= E.Wall
 
   
+
 darknessFovOverlay :: Player -> Actor -> Map PlayerPos Tile
 darknessFovOverlay player actor =
+  case player ^. plActor ^. acProps ^.at "debug:light" of
+    Nothing ->
   let
     (screenWidth, screenHeight) = player ^. plScreenSize
 
@@ -672,6 +694,10 @@
   --  I.e. any position in the field of view, or previously in the field of view
   foldr Map.delete blackBg $ lightAt <> seen
 
+    Just _ ->
+      Map.empty
+
+
   
 flatFov :: Maybe [(WorldPos, [WorldPos])] -> [WorldPos]
 flatFov Nothing = []
--- 15_memory/html/rogue.js
+++ 16_debug/html/rogue.js
@@ -59,6 +59,7 @@
   Mousetrap.pause();
 
   const act = () => {
+    console.log( "key: " + k );
     sendCmd("key", k);
   };
   

Chapters

start prev next