Wishful coding: Adderall, traps and items

I have been pondering over some ideas about traps, items and how to represent what kind of effects they have in-world. I might be over thinking this a bit and going for too general and flexible solution, when simpler solution would work just fine. Currently this isn’t that urgent yet, as there are only few monsters and two types of traps (pits and caltrops). Monsters are too stupid to avoid either one. For caltrops it sort of makes sense, but they really should be able to spot huge pits and go around.

Continue reading

Finite-state machines in Herculeum – part 3

Now that I have fully functional finite-state machine system, I can get started writing AI routines for the game, right? Apparently not yet. When inspecting the code, I realized that the finite-state machine system lacked a very important feature: ability to have values entered with –init– method. So that needed to be taken care of before starting to work with the AI.

Current implementation of rat AI is as follows:

(defclass RatAI []
  [[__doc__ "AI routine for rats"]
   [character None]
   [mode ["transit" None]]
   [--init-- (fn [self character]
           "default constructor"
           (.--init-- (super RatAI self))
           (setv self.character character) None)]
   [act (fn [self model action-factory rng]
      "check the situation and act accordingly"
      (rat-act self model action-factory))]])

–init– is used to give AI reference to the character it’s in charge of operating. act is called every time it’s characters turn to act. I could have changed the system to pass character reference as a parameter of act, but I wanted to keep interface unchanged for now, so chose to add –init– method for finite-state machine system instead.

–init– form of defstatemachine mirrors closely what –init– does for a regular class (in fact, it’s injected to be part of –init– method of final class). It takes a list of parameters and body of code. With this it was simple to define correct parameter list and rest of the game was happily ignorant that it was interacting with finite-state machine instead of plain old class (although, that finite-state machine is still a plain old class in the end of course).

Next design challenge I encountered was related with splitting common functionality into smaller pieces. I spent couple evening prototyping this with state monad from Hymn, but couldn’t come up with a system that I liked. With Hymn passing the state back and forth would have been taken care of automatically and behind the scenes, which was one of the attractive reasons for trying to use it. In the end (or should I say, for now?), I abandoned the idea and went with regular functions. This of course meant that I had to pass the state around manually and modifying it took a bit more coding. The end goal is to have some sort of library for AI routines that can be combined easily for new creatures. Getting a simple rat AI done was lots more work than what I anticipated, but I’m hopeful that next ones will be easier.

Final wart needing solving was the need for deactivation code. When a rat notices an enemy, it switches to a different state and a little exclamation mark is shown in UI to signal player that the rat is now in alert state. When rat loses the sight of the enemy, old AI used to raise a question mark to signal that the rat wasn’t sure about location of its target (they have lousy memory). I couldn’t raise the question mark in finding home state, since then it would be shown every time the rat decides to head home. Instead of that, there was need for codeblock that gets execute when state deactivates, but before next state is activated. Adding the state was almost identical on how on-activated is handled.

Now that I had all the major parts of the code ready, I could assemble whole thing and ended up with the following code:

(defstatemachine RatAI [model action-factory rng]
  "AI routine for rats"
  (--init-- [character] (state character character))

  "find a place to call a home"
  (finding-home initial-state
                (on-activate (when (not (home-location character))
                               (select-home character wallside?)))
                (active (travel-home (a-star (whole-level)) character))
                (transitions [(arrived-destination? character) patrolling]
                             [(detected-enemies character) fighting]))
  
  "patrol alongside the walls"
  (patrolling (on-activate (when (not (home-area character))
                             (map-home-area character
                                            (fill-along-walls (. character level)))) 
                           (clear-current-destination character))
              (active (one-of (patrol-home-area (a-star (along-walls)) character)
                              (wait character)))
              (transitions [(detected-enemies character) fighting]))
  
  "fight enemy"
  (fighting (on-activate (clear-current-destination character)
                         (select-current-enemy character closest-enemy)
                         (show-alert-icon character (current-enemy character)))
            (active (if (in-area area-4-around (. character location) 
                                 (. (current-enemy character) location))
                      (melee character (current-enemy character))
                      (close-in (a-star (whole-level)) 
                                character 
                                (. (current-enemy character) location))))
            (on-deactivate (show-confusion-icon character))
            (transitions [(not (detected-enemies character)) finding-home])))

It’s pretty clear I think and should be easier to fine tune and adjust in the future. It certainly is much more reusable than the previous patrol AI code.

Here and there I used functions as parameters facilitate fine tuning algorithms. For example, when the rat is finding its way to home area, whole level is considered as valid ground for traveling. A* algorithm will try and find the shortest possible route. However, as soon as the rat arrives to home area, it switches to different mode and completely ignores anything but edges of rooms (and areas in front of doorframes, so it can pass those too). It’s still the same A* routine, but slightly differently configured.

Another example is shown below. It’s a function that maps character’s home area and assigns it to AI data:

(defn map-home-area [character neighbours]
  "build map of home area for character"
  (let [[state (ai-state character)]
        [to-check [(home-location character)]]
        [result []]]
    (while to-check
      (let [[it (.pop to-check)]]
        (when (not (in it result))
          (.append result it)
          (.extend to-check (neighbours it)))))
    (assoc state :home-area result)))

It basically does a flood fill from home location of character. Function that provides coordinates for neighbouring cells is provided from outside and its implementation or internal behaviour isn’t any concern of this function. As a consequence, this function (couple with the neighbours function) can be used to flood fill whole level, edges of room, center of room or any other continuous area. It could even be completely random random area that gets filled.

Most of the AI functions manipulate shared state in a way or another. I’m basing this on a convention, rather than contract. Any function can read, write and otherwise manipulate the state. This allows them to be combined in a way that I didn’t anticipate at the time of writing them. Problem of course is that if I’m not careful, unexpected interactions between those functions might arise and manifest as bugs.

This was also pretty tricky code to test. While coding, I kept thinkig that I should have some sort of automated system for testing the code, but it would be pain to write and maintain. I would have to set up whole level with creatures, items and worst of all, specific internal state of the AI. These tests would break all the time if even the slightest change was made in the internal representation of AI. In the end I decided that it wouldn’t be worth the gain to go through all that pain and just resolved testing the AI by playing the game. It’s slow process that will miss errors, but it’s still better than nothing (just not by that much).

Wishful coding: evolving language

One of the goals with pyherc/herculeum has been to build a language that can be used to write a roguelike game. That part of the project has barely begun and most parts are concerned with level generation. While reading about SHRDLU (a really cool program designed as an experiment of understanding natural language) one morning and thinking about languages in general, I started thinking what would happen if the language used in level generation would be an evolving one? Nothing limits this thought experiment to level generation only, but I’m going to tackle one thing at a time.

Continue reading

Conceptualizing the characters

Herculeum currently has three playable characters (more may or may not been added later): the Adventurer, the Master Engineer and the Warrior. They’re nameless, but still individuals with some semblance of back story, personal traits and such. I’m trying to avoid writing down their backstory in game as much as possible and conceptualize them by their starting items and skills. This hopefully will leave some parts of the story open for interpretation and players can write part of it while playing the game. Not everyone will experience exactly the same story, but the general direction should be more or less the same.

The Warrior

The Warrior is your typical action hero, who tends to solve problems with force. If that doesn’t work, they just didn’t use enough force. The warrior has devoted much of their life for martial ability and is well versed with various weapons and combat techniques. The driving force between this character is their quest for perfecting their battlefield skills and using them to escape the prison.

The Warrior’s starting equipment reflects their devotion to combat: some armour, martial weapons and first aid kit is all they need to get started. The heirloom sword might not be the best weapon available, but it has lots of personal value to the Warrior and they won’t easily part with it.

Different combat techniques will help the Warrior to dispatch their opponents quickly and effectively. They are all about dashing in the middle of the enemies and inflicting maximum amount of damage. Very effective when it works out, very dangerous when it doesn’t.

The Master Engineer

In contrast to the Warrior, the Master Engineer tends to avoid direct fighting and uses various tools and gadgets for solving problems. They’re able to devise gadgets from piles of junk lying about and bust doors open or cause cave ins at tactical locations. The Master Engineer ventured into prison tunnels in search of ancient wisdom and gadgets that are bound to be somewhere there, if you just know where to look at.

While the Master Engineer doesn’t have as formidable armour or weapons as the Warrior, they have small selection of potions and traps to get them started. And instead of a weapon, they chose to carry a shovel for clearing obstacles and digging trenches. The Master Engineer is also carrying a leatherbound notebook, where they sometimes jot down notes about architecture or interesting contraptions.

The Master Engineer has learned how to use the landscape to their advantage and especially how to modify it to make combat easier for them and harder for their enemies. Their skills are all about digging trenches, building fortifications and causing cave ins in strategically selected locations.

The Adventurer

The Adventurer loves plots, the more complicated, the better. They love misdirection and playing tricks to their opponents, while snatching gold and other valuable items as much as possible. This actually is the reason why they chose to originally venture into prison tunnels. The Adventurer played one trick too many to a wrong person and had to flee. Now they’re in for the adventure of their lifetime.

The Adventurer is traveling lightly, rather trusting their skills of avoidance and misdirection than help of tools or weapons. They have a small dagger and few baits to lure monsters away, but that’s about it. In addition to that, the Adventurer is carrying a love letter from a local nobility, which they would never dream to leave behind.

Since the Adventurer prefers to avoid combat as much as possible, they are quite capable of running from cover to cover and from shadow to shadow without their enemies noticing them. The Adventurer has encountered so many traps during their adventurers that they have developed 5th sense about them and can sometimes dodge a trap.

New control scheme

When I originally started writing the game, I was planning to use the standard one key – one action control scheme, which would have used most of the keyboard. This is pretty standard way how roguelikes do it anyway, especially the ones with lots and lots of different actions.

Then at somepoint I started wondering if the game would be more fun if it had less actions the player can perform and they would all be accessible with standard XBox controller. I did some restructuring and got the game working pretty well. The problem with this scheme was that Qt doesn’t have support for controllers straight out of the box, so I had to resort to third party solutions.

Recently I started thinking that if the game will be played on PC most of the time anyway, I could just add proper mouse support and maybe even drop the controller completely (not 100% of this yet, but I’m thinking it). I would have to restructure code and inputs and some of the UI once again, but I was sort of going to clean up that at somepoint anyway.

Continue reading

Wishful coding: Symbol based AI

I have been reading Gödel, Escher, Bach: An Eternal Golden Braid, which among many things, talks about how symbols and simple rules create emergent system and eventually consciousness. One of the reasons I got in the process of writing Herculeum was that I wanted to tinker with various AI routines, so this section of the book is really interesting. Reading of course got me interested on trying to think how to code such a system.
Continue reading

Compound actions

Recently I decided to add a lunge action in Herculeum, which would make the character first move and then attack. This could be used to cover short distances and launching surprise attacks. Naturally I wanted to avoid duplication of code and set to look into how to combine two actions and execute them as one. Like quite often in software world a serendipitous event happened and I learned that there exists monad library for Hy called hymn. With some work on the code, I could use either monad to chain actions together in a rather neat way.

Continue reading

Wishful coding: moral baggage

Wishful coding is a series of posts, where I talk about all the grand ideas that I would like to code one day, but which will take a long time still before I’m getting them done (if I’m getting them done ever). It’s a sort of like public notebook, where I write down things.

Moral baggage is something I have been thinking on and off for quite a while already. Core idea is that player actions would have a long term effect, in addition to short term ones. If they choose to solve all problems with brute violence, that is bound to change how they see and experience the world. It would also affect how others see them and what kind of reactions player might expect to encounter.

One example is decision of using violence or cunning to get past enemies. Player who uses violence, might grow physically stronger, but they would find it difficult to run away from combat because of the bloodlust. Character who likes to sneak past their enemies instead of fighting wouldn’t develop physically strong body, but might small details in their surrounding others wouldn’t spot.

In later stages of game a character with tendency to solve problems with violence might be asked to help raiding a nomad camp. Character who likes to sneak past enemies might on the other hand be asked to retrieve a stolen necklace without being spotted. Or people in some village might refuse to interact with bloodlust maniac, but player who solves problems peacefully, they would welcome.

So possibilities are endless. System should be written in a way that is transparent to player, so that they can make informed decisions along the way. Whenever they acquire a trait, it should be clearly stated and some of the possible outcomes hinted. Nothing is more frustrating than opaque system that leaves too much for guessing.

Little level dsl tricks

I have been tinkering with how levels are configured and made some minor changes. I’m trying to keep things relatively simple, but at the same time I want to have configuration code to look more like a configuration file than a function definition.

Below is shown a configuration for area called “first gate”. This is the area where the game starts. Two requires at the top are used to signal that I want to use certain macros. (level-config-dsl) is just a cute way to have pile of imports without cluttering the code. After that follows a list of levels, with just single entry. That entry first contains name and description of the level and rest is just configuring how it connects to other levels and how it is going to be generated. I made a post earlier explaining how the level generation works.

Continue reading