Each summer r/roguelikedev has a summer event[1] in which we all make a simple roguelike, roughly following the libtcod roguelike tutorial. For this year’s summer event I’d like to do something more “fortress mode” and less “adventure mode”. This may prove too ambitious; we shall see. (Yes, it was too ambitious)
Icons from game-icons.net[2], CC BY 3.0. Code repository on github[3]. I spent 32 hours on this, which isn’t a lot per week.
I’ve participated in the event several times and actually finished in 2020, using rot.js[4] and Javascript. That year I kept the scope down by implementing only the topics from the tutorial. Then in 2021 I implemented topics I wanted to try: thin walls, graphics, animations, openable doors, new map generator. I had many more things I wanted to try but I was too ambitious, and didn’t finish. These projects for me are not about making a complete, fun, polished game. This year it’s about exploring what a “fortress mode” game might involve. I had lots of things I wanted to try: friendly NPCs with skills and needs, jobs, room building, multi-agent pathfinding, resource management, crafting, day/night cycle, sleep/hunger/leisure/work cycles, farming, clothing, furniture, leisure. But it was all too much.
One reason the general advice is to “start small” is that if I do too many things at once it’s hard to learn any of them well. Around halfway through this project I realized that at my current learning rate, I need to have a much smaller project, so I reduced the scope quite a bit. And then towards the end I had to reduce the scope to be … a chicken simulator! 😂
0 🖥 Setting up#
Compared to a Python project, I feel the set up is simpler in Javascript. I started with game.html
:
<canvas id="game" width="660" height="660" /> <script src="build/_bundle.js"></script>
If I were using rot.js, there’s be one more <script>
line. This year I’m not using rot.js. I’m using Typescript instead of Javascript. Here’s roguelike-dev.ts
:
const canvas = document.querySelector("#game") as HTMLCanvasElement;
The Typescript file has to be compiled into Javascript before the browser will run it. I’ve been using esbuild[5] for that. I really like it. I have lots of small projects and have standardized on having a file called ./build.sh that will build the project:
#!/bin/sh mkdir -p build/ esbuild roguelike-dev.ts --bundle --outfile=build/_bundle.js
There’s a proposal to allow but ignore types in Javascript’s syntax[6], similar to what Python does. If that passes, then I won’t need this build step. But it only takes 0.06 seconds to run and I run it automatically on file save so it’s only a minor annoyance right now.
For the rest of this project I’m going to try to follow the same section numbers as the original Python tutorial and the r/roguelikedev summer event, but some topics won’t make sense in fortress mode so I will do something else instead.
1 ✅ Moving around#
1.1. Rendering#
I’m using <canvas>
this year, not <svg>
. Conveniently, the game-icons svg icons are all a single<path>
, and paths can be drawn to a <canvas>
using Path2D[7]. To load the icon into memory, I can use esbuild’s loaders to make importing an svg work:
import person from "./game-icons/delapouite/person.svg";
esbuild --loader:.svg=text
This will make person
the contents of the svg file.
Given a Path2D I can draw using any fill and outline style I want (including gradients, line widths, and shadows). I imported sprites in but I somewhere I need to store what style to draw them in. WebGL would open up more possibilities but I’m trying to follow DoTheSimplestThingThatCouldPossiblyWork[8] so I’m going to stick to regular <canvas>
.
1.2. Keyboard#
For the keyboard I’m mostly following what I did two years ago. I add a tabindex
to the <canvas>
to allow it to receive keyboard focus. When it doesn’t have focus, I display a message telling the player to click on the game to give it focus. I think the alternative is to make the entire window’s keyboard events go to the game, but I like this approach better because it allows me to use the arrow keys for scrolling the page, or use an <input>
box that receives keyboard focus.
When the canvas has keyboard focus it will get keydown
events with each key pressed. I might want to get both keyup
and keydown
because I think it might be useful for building to hold down a direction key and then press a letter to build something in that direction. I’ll experiment with that when I get to building.
2 ✅ Entity, render, map#
2.1. Entities#
I made an Entity class just like in the Python tutorial[9], but with two changes:
- I changed
x
andy
being separate fields to a single Location object{x, y}
. This is because I want to be able to pass the location around and also have special values like “not on the map” or “in inventory”. I will later extend Location to be an enum to hold special values. - I changed
char
andcolor
being their own fields to a single Appearance object{sprite}
. I think the sprite characteristics should not be per-entity, but stored elsewhere. I’ll then extend Appearance to have modifiers to override the color, size, etc. per entity.
export type Location = {x: number, y: number}; export type Appearance = {sprite: string}; export class Entity { constructor (public location: Location, public appearance: Appearance) { } moveBy(dx: number, dy: number) { this.location = {x: this.location.x + dx, y: this.location.y + dy}; } }
2.2. Engine#
As Python and Javascript modules act like objects, I didn’t see a strong need to make an Engine
class just yet. The fields of the Engine in the Python tutorial become module-level variables in my project. If there’s a need (either to have multiple instances, or to pass it around to other modules), I’ll change it later.
2.3. Map#
The Python tutorial[10] for the map goes into NumPy dtype
and ndarray
, but I don’t need that in Javascript. I’m also not planning to have FOV in this project, so I don’t need the transparent
and dark
data. What do I actually need?
- A
GameMap
will have an array of tiles. - The tile will be static data (the same for every instance of that tile: appearance, properties like walkability, etc.) and varying data (only the tile type right now, but maybe more later like water depth)
So I think for now the GameMap
can be an array of strings, and the strings will be a index into the static data.
I put in some dummy data for now to test my renderer. Generating the map will be the next section.
3 ✅ Generating a map#
3.1. Terrain generation#
Since I’m making a “fortress style” game, I want the main map to be a wilderness map with resources that the player can use to build their own settlement. What terrain do I want to start with?
- one river going through the map
- grassland near the river
- desert away from the river
- mountains somewhere
I will start by drawing a meandering river through the map, and then define the other elements by how far they are from the river. Will that be a reasonable starting point? We’ll see.
Ok, I think it’s a reasonable river. Let’s add some terrain near it:
I started thinking about sprite graphics. I might want the same sprite shape but different color to mean different things. Some sprites should be smaller than the full tile. And there will be mobile objects like animals drawn on top of static objects like trees drawn on top of terrain floors. I think that’s a lot of information on a tile. I decided to change the floors to flat squares to make it easier to see the foreground objects:
I also decided that the starting point will be the west side of the map, with grass and a river. Then to the east will be a desert, and then mountains. This will create a “left to right” game flow as the player levels up and digs deeper for more minerals.
3.2. Scrolling#
I also need to make the map a bit bigger to have room for both wilderness and building. Since the map will be bigger than what’s on the screen, I need to have scrolling. Normally in a “fortress style” game I’d scroll with the mouse or with dragging, but since I still have a player character walking around, I made it scroll with the player character. To center the view on the player, wherever I’m drawing to the window with:
display.draw(x, y, ch, color);
I need to instead offset that position:
display.draw(x - player.location.x + WINDOW_WIDTH/2, y - player.location.y + WINDOW_HEIGHT/2, ch, color);
The way to think of this is in two steps:
- Subtracting the player position moves the player to 0,0
- Adding the width/2, height/2 moves from 0,0 to the center of the screen
A bonus would be to have a camera that’s separate from the player. Let’s do that:
let camera = {x: player.location.x, y: player.location.y}; … camera.x = player.location.x; camera.y = player.location.y; … display.draw(x - camera.x + WINDOW_WIDTH/2, y - camera.y + WINDOW_HEIGHT/2, ch, color);
Ok, it’s a separate object now but it’s always pointed at the player. Let’s change it so that it follows the player but can be behind by 2 tiles:
camera.x = clamp(camera.x, player.location.x - 2, player.location.x + 2); camera.y = clamp(camera.y, player.location.y - 2, player.location.y + 2);
We can also make it stop scrolling before reaching the end of the map:
const halfwidth = VIEWWIDTH >> 1; const halfheight = VIEWHEIGHT >> 1; camera.x = clamp(camera.x, map.bounds.left + halfwidth, map.bounds.right - halfwidth+ 1); camera.y = clamp(camera.y, map.bounds.top + halfheight, map.bounds.bottom - halfheight + 1);
I think there are more fun things that I could do but I’ll leave that for later.
3.3. Building#
3.3.1. UI#
The generated map is only the wilderness. In this game the player will be building the rooms. I need to figure out the UI for that. If I had a facing direction then the player could build in front of themselves. My first idea was to have you pick up building materials and then place them for building. But the keystrokes for that are annoying. If you want to build a wall on tiles 1, 2, 3:
1 2 3 4 5 6
You have to first pick up the stone, then move to 4, press Up to face towards 1, press E to build, then press Right to face right, then press Right to move right, then press Up to face towards towards 2, press E to build, etc.
It’d be simpler if you didn’t have to pick up the materials, but that’s not the real problem. It’s three keystrokes per tile. I want to reduce the number of keystrokes per tile.
The next idea was to have a build key B to switch to build mode, like vim’s insert vs normal mode. In build mode you can move the cursor around freely to build things. Then press B again to go back to the player mode. What are the keys in build mode? I guess you’d have to choose what to build: W for wall, S for stockpile, etc. In the example, you would move to 4, press B to switch modes, then Up to move to 1, W for wall, Right to move to 2, W for wall, Right to move to 3, W for wall, then B to exit build mode, which would return you back to the player at 4.
This is two keystrokes per tile.
The next idea was to have commands inside build mode. In the example, you would move to 4, press B to start build mode, W to start wall-building mode, then Up to move to 1 and build a wall there, then Right to move to 2 and build there, then Right to move to 3 and build there, then W to exit wall-building mode, then B to exit build mode, which would return you back to the player at 4.
This is down to one keystroke per tile. Other modes like stockpile-drawing mode might use arrow keys to mark a rectangle instead of a line. And others like placing an object might use one or two keystrokes to choose the object, but then return you to build mode. It’s looking like this:
It’d be simpler if you could directly go into wall-building mode by pressing W from the player mode. But that means you can’t start a wall anywhere, but only next to where the player can walk. So I still need build mode to be able to choose where to start the wall.
Stepping back a bit, I had been starting from the “adventure mode” style movement for your player character, but I have to admit, I haven’t come up with any ideas for what you’d actually do in player mode. So maybe I should drop player mode, and treat build mode as the normal mode. Then you’d press the verb (W for walls, S for stockpiles, D for doors, etc.) which would take you into a specialized mode for creating that type of object, and then you’d return to the main mode to move around.
I think this means I can’t use the noun-verb approach[11] I wanted to try last year. Traditional roguelikes use verb-noun, where you choose the action first and then you choose the object. You might choose throw, and then choose from a list of throwable items. With noun-verb you first select an object and then you pick the action to apply. You might first choose a potion and then the action could be throw or drink. Modern games, modern programming languages, and modern UIs typically use noun-verb. For example in a text editor, you first select some text and then you pick an action like cut. Since I want a different UI depending on which verb is used, I think I have to use verb-noun instead of noun-verb.
Other thoughts: Should I switch to the mouse? Dragging a line or rectangle probably makes more sense with a mouse. But I want to see how far I can get with the keyboard. I should also take another look at Dwarf Fortress keys[12].
3.3.2. Implementation#
I refactored the code a little bit, moving keyboard handling into its own module. It’s starting to have a bit to deal with:
- check if the canvas if focused, display message when it is not
- key down handler
- different handlers in regular mode and wall-building mode
And I want it to do more:
- key up handler, keyboard repeat
- stockpile and other building modes
- don’t handle keys if the game isn’t visible on screen
For the different modes, I created a key map object:
actions = { key_ArrowRight() { playerMoveBy(+1, 0); }, key_ArrowLeft() { playerMoveBy(-1, 0); }, key_ArrowDown() { playerMoveBy(0, +1); }, key_ArrowUp() { playerMoveBy(0, -1); }, };
In Javascript, { … } works both as a map (Python dict) and an object. When a key 'ArrowRight'
comes in, I can call actions["key_" + event.key]() to run the corresponding action. When I switch to wall-building mode, I’ll assign a different key map to actions
.
There’s some duplicate code in there to handle the arrow keys for each of the building modes, but I’ll clean that up later once I have something that works.
I implemented the beginnings of wall building but the UI is pretty rough. I need a way to preview the walls that are going to be put down. That means actions
isn’t sufficient. I need to maintain state, and also hook into the rendering. Hm.
3.3.3. UI, part two#
I often come up with more ideas during “beer mode”[13], a relaxed state in which I am more creative. This can happen while going for a walk, or going to sleep, or taking a shower (“shower thoughts”). I had a UI thought last night: I’ve been focused on building walls and then the rooms would come later. What if the player marked the room, and then the walls were calculated? I looked through my notes to see what other games did:
- Dwarf Fortress: build the walls, then mark a room by expanding a region from a piece of furniture. But being Dwarf Fortress, there are also similar concepts done differently, like creating stockpiles[14] or mining designations by marking two corners of a rectangle, or creating farm plots[15] by expanding a rectangle.
- Oxygen Not Included: build the walls, then the game automatically figures out rooms, and assigns one room type.
- RimWorld: build the walls, then the game automatically figures out rooms, and assigns one or more room types.
- Prison Architect: build the walls, then mark the room type you want, and it will give you a list of requirements for that room.
- Two Point Hospital: build the room on a grid, and place a door and needed items; walls are built automatically. Hey, this matches the idea I had last night.
Of these, I liked Two Point Hospital and Prison Architect’s approaches quite a bit. They were not only easy to understand what room was going to be there, they displayed what was needed to make the room work. Prison Architect let you build the room and then then you will be told what to do to make it work. Two Point Hospital won’t let you even build the room until you populate it with the needed items.
I think the rooms-first approach works better with thin walls than with thick walls. So if I decide to do this, I need to:
- implement thin walls
- change the building ui to focus on marking rooms (also figure out how to allow non-rectangular rooms, and modifying existing rooms; see Two Point Hospital[16]’s UI for this)
- automatically calculate the needed walls
- add a door marking ui
I also realized that I was so focused on building walls that I hadn’t thought ahead to rooms, or what rooms are for, or how the player will interact with rooms, or whether I even need rooms. One advantage of following the roguelikedev summer tutorial is that I don’t have to think about that, and I can focus on implementation. But since I’m trying to make a different style of game, I really need to be thinking farther ahead. So I took a break from implementation and started adding some ideas to the rest of this document.
I slept on this idea and decided I should implement it. Thin walls will probably simplify things for my handling of rooms, even though it’s a little more work to represent them. There will no longer be door tiles, so NPCs will never be standing in a doorway. Instead, every tile will be part of a room, or the outdoors. There will be walls between rooms, and some of those walls will have open doorways in them.
Of course now I need to go back and update the implementation.
3.3.4. Implementation, part two#
Fortunately I already implemented thin walls in last year’s r/roguelikedev project, so I went through that and picked out some useful functions. I represent walls as a tile location and then either W for the western edge of the tile or N for the northern edge of the tile. I don’t need the south or east sides, because those are the north or west sides of the next tile over. I’ve written about this before but it was last year that I got to really put it to the test, and it worked pretty well.
I also had to update the rendering code to handle walls. For now I have only walls and doorways. While I was working on the rendering code, I ran into some inconsistencies in coordinate systems. The tile coordinates are 1 step = 1 tile. The pixel coordinates are 22 pixels = 1 tile (although this might vary if I want to add zooming later). If I want to draw outlines, they are 1/22 of a tile wide. The sprite coordinates are 512 pixels = 1 tile. So their outlines are 23.2 pixels wide. Although it’s fine for the rendering code to keep track of these things, I also want the input-handling code, and maybe other modules, to be able to draw on the screen, and that means they need to know about these scales too. I need to pick a coordinate system and stick to it.
With thin walls, I need to change the input handling code from drawing walls to drawing rooms. To start with, the player will be able to mark a rectangle by pointing at the two corners. But I also want to support non-rectangular rooms so I might make it work by starting with a single rectangle and then adding/subtracting rectangles to it. Maybe: if you start a room in an existing room it adds a new rectangle. Maybe: press shift when starting a room to remove a rectangle.
Minor: I’ve been pretty happy with my right hand on the arrows or numpad, and the left hand on the commands (R for room, W for wall). But Enter is far away from both, so I think I will change that to be a left hand command.
4 ✅ Field of view#
My initial thought was that field of view doesn’t apply to a fortress-style game. And I don’t think I want fog of war either, as I will keep things simpler for this project by having small maps. But I think I do want to make it so that you only see underground areas after you excavate them. But I don’t think that requires saving per-tile data. Instead, I’ll have a rule that says you can see any tile if it’s open, or if it’s adjacent to a tile that’s open. As you dig into the mountain, you’ll be able to see the tiles adjacent to your corridor, but no deeper.
I’m going to convert this game to be real-time instead of turn-based. I think that makes more sense for a fortress-mode style game. What’s needed for this?
- The simulation needs to run every tick, not every key input.
- The key inputs need to be queued up and processed during the tick.
- I need to pay special attention to keyboard repeat. I think the right way to handle this is to queue up key down/up events, ignore built-in keyboard repeat, keep track of key down/up. But it gets tricky. If you press and release J and then press and release K in the same tick, I want to process J because it happened first, but do I save K for the next tick, or discard it? And what happens if I press and release J and then press but hold K? I’ve played games that get this kind of thing wrong, but I don’t know the right way to handle it.
Unfortunately the current state of the game has no NPCs (friendly or enemy) and that means nothing happens every tick. So switching to real time is not going to make a visible difference right now, and it’s going to be hard to spot errors. I decided to postpone it until I have NPCs.
5 🐔 Placing enemies#
I want to have friendly NPCs that are performing the fortress tasks (construction, digging, crafting, farming, etc.). I think the focus here will be on pathfinding and job assignment, both algorithm-heavy problems. Given how far beyond a reasonable scope I am, I plan to not have enemy NPCs.
5.1. Jobs, attempt 1#
NPCs need to be assigned a job, and then move towards the job, and then do the job.
- Assigning: Goblin Camp uses the Munkres-Kuhn algorithm. It’s O(N³), but N is small so it’s probably ok. RimWorld and Oxygen Not Included have a job priority system that the player can control. Dwarf Fortress starts out with the player manually assigning jobs, but then can hire a “manager” dwarf who will then take over that job from the player. I really like the idea of doing something manually and then outsourcing or automating it.
- Moving. Pathfinding with A* is the obvious approach. But pathfinding with BFS will probably be an easier place to start. This may finally be a project where I can try out differential heuristics[17].
- Doing. A job takes some time and then has some effect on the world. During that time the job may involve locking some objects so they can’t be used by others.
The more I thought about this the more complicated it got. So I started drawing pictures. Some very smart programmers told me that smart programmers use state diagrams to think through all the cases, so I thought maybe I should try that.
Things I want to make sure I handle:
- two jobs try to move an item to two different places
- a job is cancelled while in progress
- an npc gets two jobs
- an npc dies along the way, and the job has to be reassigned
Unfortunately this conflicts with another goal I had: that each tile has at most one object. The problem is that if a job is cancelled, I need to drop the object, and that might be dropped on a tile that already has an object. What to do? Either I need to allow multiple objects on a tile (and figure out how to draw them on screen), or I need to complicate how jobs are handled. And if I allow multiple objects on a tile, a 1⨉1 stockpile can hold everything, and that isn’t great. But Dwarf Fortress allows multiple objects on a tile, and stockpiles are still useful. Hm. I asked on discord and zendarva
said to distinguish items stored on a tile, and items dropped on a tile. The stored items would be limited to 1, and the dropped items would be unlimited. I like this idea. But there are still more issues, like what happens if the player builds a wall on a tile that contains objects? They all need to be moved somewhere else somehow.
After several days of working on this I realized that I’m stuck. I need to break this down into much smaller pieces to make progress.
5.2. NPC movement#
I decided a much smaller piece would be to get NPCs moving around randomly, ignoring walls, but in a real time loop instead of turn-based. I know the conventional wisdom in the ECS style is to unify everything but I think that goes too far for my tastes. I’m going to distinguish between agents that can move around on the map and take jobs, and items that can be either on the map or being carried. Both are types of entities but they’ll be in separate tables with different types of attributes. I might have a third category for animals, but will figure that out later.
The code structure is getting in my way. I had tried splitting things up into many files like I’m “supposed” to do. But it’s unclear to me at this point how I should structure it, and the current structure is messy. Keyboard input triggers a render, so the keyboard needs to know about the UI. The input tracks the cursor, and the UI needs to draw the cursor, so the UI needs to know about keyboard input. The keyboard input needs to pause/resume the simulation so it knows about the simulation module. And the simulation also needs to render. The entities need to know about the map so they can decide whether a movement is in bounds. The map needs to know about entities since there are entities on the map. Lots of circular dependencies.
I probably should’ve left everything in one big file until I had a better sense of what structure I needed. Just as premature optimization gets in the way, premature abstraction and premature organization can also get in the way, and it’s a lesson I keep failing to learn.
No one is ever going to see your first draft. Nobody cares about your first draft. And that’s the thing that you may be agonizing over, but honestly, whatever you’re doing can be fixed. …
For now, just get the words out. Get the story down however you can get it down, then fix it.
—Neil Gaiman
I placed a handful of agents in the world and implemented them moving around to random destinations.
These are tiny steps but tiny amounts of progress is what I needed to get un-stuck.
This made me think about pathfinding a bit. Right now they move in straight lines, but ideally they would use pathfinding so that they don’t walk through walls. But what if there’s a room with no doors? I’d need to figure that out so that the agent stops trying to walk into it.
In games we can often redesign some rules to avoid a messy situation. And that made me wonder if I can avoid that messy pathfinding situation. I think I can, with two changes:
- Walls need to be built/destroyed instantaneously. The time after a room has been built but before the walls have been updated creates a lot of “churn” that is going to require frequent repathing.
- Room editing needs to ensure that every room has a door to the outside or to another room that has a path to the outside.
On one hand, I was excited about rooms being built wall by wall. I think it looks cool and it also creates a source of jobs for the NPCs. On the other hand, it adds a lot of complexity, and I need to cut back as much as I can on this project. So what if I make rooms insta-build but move the focus of my jobs to other things? I think it’ll be less cool of a game, but more likely that I can keep to the schedule. And since the scope my biggest problem right now, I think, sadly, I will probably give up on room building tile by tile.
And that means I can work on NPC jobs without dealing with rooms. I’ll implement outdoor jobs for now, and get back to rooms later.
5.3. Resources#
In addition to the floor types (grass, desert, water) I also want to place objects on top: trees, stones, berries, gems. This will be useful for gathering jobs, especially if I don’t have building jobs. Which of these jobs makes the most sense right now, since I don’t have room building? I think trees and stones are out. Let me work on berries. Food is a rewewable job type, so the NPCs will always have something to do.
- Berries grow in the grasslands. I want them to re-grow too, so that means I need to have some kind of growth clock. Maybe it starts out as
growth
0 and goes up over time. When the berry gets eaten it goes back to 0. I’ll want to display this visually somehow, maybe with size or color. - NPCs periodically pick a nearby berry to eat. “Periodically”? I think that means I need to add a hunger clock of sorts. Maybe it
fed
starts out as 100 and goes down over time. When it gets below 30 the NPC can go look for food. And when they eat food it can go up +50.
What do I need to make this happen?
- I need to implement Resources. An resource is similar to an Agent, but doesn’t move around, and cannot be moved. I will also at some point need Items. An item is similar to an Agent or Resource, but can be moved, or carried. A berry plant would be a Resource. A berry would be an Item that you can get from a berry plant. But for now, I will only have Resources, and NPCs will directly eat from there.
- Berry plants will need a
growth
value. In an ECS this would probably be a Growable component. I’m using union types instead so I’ll structure it a little differently. - Agents will need a
fed
value. - Simulation ticks should periodically increase
growth
on plants and decreasefed
on agents. - Simulation ticks should periodically evaluate NPCs to see if they need to eat. If they do, find a nearby berry plant and reserve it (this is the job system I described earlier, without the complicated cancellation system), and then pathfind to it, then eat the berry.
Agents, Resources, and Items sure are similar though. I can see why it might be useful to call them all Entity and treat them the same. There are sometimes tradeoffs in type systems between precision and convenience. For example, should meters be the same type as grams? If I’m purely going for type safety, then no. But in practice? They’re both double
in my code. So I’m going to start out with these three being separate types, but I might change my mind later and merge them. Or maybe I’ll change them to Typescript’s union types. It depends on how I end up using these, and I can’t see that far into the future so I’ll do something to make it work now and then plan to change it later.
/* An agent is an NPC that can move around on its own, can only be on * the map, and can perform actions. */ class Agent { dest: Point | null = null; fed: number = 100; constructor (public id: string, public location: Point, public appearance: Appearance) { } } /* A resource is something on the ground that cannot move or be moved. */ class Resource { growth: number = 0; constructor (public id: string, public location: Point, public appearance: Appearance) { } } /* An item is an object that cannot move around on its own, can be * either on the ground or carried by an Agent, and cannot perform * actions. */ class Item { constructor (public id: string, public location: Location, public appearance: Appearance) { } }
I’m going to split this work up into two parts. First, I’ll place Resources.
Then I need to periodically go through all the plants/trees and make them grow.
A thought: my desire to keep everything fast is getting in my way. Premature optimization? No, I am sure I will need to optimize this. But still, it’s getting in my way. In particular, I often want to build an index:
- NPCs have a job, jobs point to their NPCs, jobs point to their items, items point to their jobs. I was trying to make the reverse pointers to avoid looking things up slowly. But it’d be much simpler if I made a single array of jobs that point to their NPCs and their items. I can scan the entire list every time.
- Maps have resources. Resources have locations on the map. Each location on the map has a list of resources. But it’d be much simpler if I kept just one of these and then scan the data structure for the reverse lookup.
This kind of thing happens a lot. I find it’s simpler if I say that one of the directions is “primary”, and the others are “derived”. Then there’s a question of whether to keep the derived information up-to-date when updating the primary (slower but always up to date), or to rebuild the derived information occasionally (faster but can be out of date). I think I’m at the point where I should be focused on only the primary direction. I need to figure out whether it’s even the right data before I spend time trying to make it fast. Things like this make me wish in-memory databases were more commonplace in programming languages.
Anyway, back to growing plants. I’ll scan the map and increase growth
every so often.
Next week, I’ll work on the job system where NPCs look for resources.
6 🍎 Combat#
I don’t have enemies. I have allies. I considered some limited combat in the form of hunting for food, but I need to keep the scope of this project down, so no hunting. Instead, this week I will work on the job system. Initially, the jobs will be to eat food when hungry, as described in the previous section.
I broke this down into smaller parts.
6.1. Eating#
First, I wanted the agents to eat when they’re hungry and they’re walking over food.
I decided it wasn’t obvious which plants had food and which didn’t, so I changed the graphics to show up to three berries per plant:
It was cool to watch the chickens walk past a plant and eat some berries. But all the chickens died, because they don’t walk towards plants when hungry. They’re just walking randomly at this point.
6.2. Finding food#
The next step is to have the chickens decide where to walk. If they’re hungry, they’ll look for nearby food. If they’re not hungry, they’ll pick a random place on the map.
I had to decide where this code lived. My first thought was that the code that increases hunger would see if it exceeded a threshold, and that’s when I would tell the chicken to go look for food. But the other way would be when the chicken is periodically evaluating its options, it can consider hunger as part of its decision-making process. I decided that I would rather keep the entire goal-setting process in one place. That way if there are conflicts, they can be resolved in a single function instead of the logic being spread out in many systems.
I had some bugs that took some time to figure out. The main one was that I would see chickens stop walking and then starve. I switched the random number generator to be deterministic so that I could run the simulation again and see the same chicken starve. I added a line showing where each chicken wants to go. I added text showing the chicken’s id. With seed 12345 I found that chicken 12 would reliably stop moving then starve. I added a “Look” ui where I can move the cursor around and see information about what’s on that tile. I found the bug and fixed it, and then the next problem was that the chickens would starve anyway. They were walking for too long before considering whether they were hungry, so I tweaked the movement range and plant density and got most of the chickens to survive.
7 🤔 Interface#
{ This section is thoughts on other games, not much about this project }
I used to write simulations and then come up with the interface, but I think that leads to parts of the simulation the player can’t see. These days I instead want to start with the interface. What does the player experience? What can they see? If they can’t see it, is it useful to simulate? What are the decisions they need to make, and what information do they need to make those decisions?
I realized I need to at least start thinking about this early in the project. I looked through my notes for other games I’ve liked: Dwarf Fortress, Rimworld, Oxygen Not Included, Factorio, Prison Architect, SimAirport, Airport CEO, Transport Tycoon, Two Point Hospital, Honey I Joined A Cult, Another Brick in the Mall, Academia School Simulator, Overcrowd, Evil Genius 2.
You’ll start out with a handful of colonists, and some supplies, similar to Dwarf Fortress, Rimworld, ONI. You’re trying to provide food, shelter, dining, entertainment, medicine for your colonists.
One of the things I very much want in a game of this style is gradual progression both in systems and complexity.
- Many games gate this through a tech tree. Other progression involves number of people, the skills of those people, or advanced materials. I would like progression of some sort but I fear it may be out of scope for this project.
- I want to encourage redesign, not up front design. I like that Factorio has zero cost of moving something. In games with digging/mining, that’s usually not undoable so it discourages redesign. Some games actively make this hard, like Airport CEO not allowing me to change a gate without shutting it down, but to shut it down I have to sit there and wait a long time before no airplanes want to use the gate.
- I want to see more systems as you progress through the game. Some games introduce too many systems early on. Prison Architect especially requires you to build lots of rooms before you can even open your prison, and then there are too few things to do later on. RimWorld seems pretty good about this, by allowing you to rely on your initial supplies and what you can find in nature. But I am not sure my game will be big enough for this to matter.
- I want to see the systems progress from low cost low return to high cost high return. For example in Oxygen Not Included the hamster wheel power is easy (low time and material cost) to set up, but doesn’t provide a lot of power. Later systems like petroleum power take a lot more investment (high time and material cost) but provide much more power.
- Although I like games with individual personalities and skills per colonist, I think that I’m going to not pursue that for this project. It only works for a small number of colonists, and I don’t think I will have enough time to work on this anyway.
- I want to make system problems visible. In Factorio you can scan for bottlenecks, and use signals and speakers to flag problems. In Airport CEO on the other hand it seemed hard to find out what’s wrong or how to fix it.
- I don’t want to use grants/contracts/quests to push early progression. I feel like Prison Architect inspired other games (SimAiport, Airport CEO, Academia School Simulator) to do this. Those games required you to build too many systems up front so it seemed reasonable to have a system to help you prioritize other systems. But I’d rather do this by having fewer systems to work with early on, so that the choices are more clear.
- A lot of these games end up with me sitting around waiting for something to happen. Then they offer “VCR controls” (pause, play, fast forward) to help you deal with too many events (pause) or too few events (fast forward). It seems like a common problem, which I want solved through game design. I think Transport Tycoon and Factorio have been best at avoiding this problem, for reasons I won’t write about here. But given that the scope of this project does not include balance and fun, I am not planning to even try solving this problem here.
- I’d like exploration to (1) be interesting, (2) lead to different base designs. Factorio is the worst at this. Not only is exploration uninteresting, the designs aren’t driven by the map. Oxygen Not Included has been pretty good about this. Not only do you find objects and creatures while exploring, the varied biomes give you a lot of variety in base layout.
- Some of these games have an inside/outside boundary. In Dwarf Fortress and RimWorld you have a colony (“inside”) where you build things and a bigger world (“outside”) which has encounters and raiders and traders. Factorio is all inside. Prison Architect, SimAirport, and similar games have supplies and people coming from the outside, and even have a map object (major road) to mark this. Evil Genius 2 has your people go out to the world, and also people from the world come into your base.
- Some games have a lot of buildable objects, but sometimes they only fit within certain rooms. Another Brick in the Mall and Honey I Joined a Cult are examples that have long lists of objects, only some applicable. I want to avoid this. But that’s easy in my case because I won’t have time to make lots of objects. Oxygen Not Included, Rimworld, and others will put them into categories. Evil Genius 2 filters the list to only the ones applicable to the room you’re working on.
- In RimWorld, Two Point Hospital, and Evil Genius 2, the job objects reserve space for standing. I like this.
- A minor complaint about some of these games: if there’s time between when I build something and when it’s built, I want to be able to configure the object before it’s built. I don’t want to have to remember to come back to configure it. I’d like to handle this properly in my project but it’s a low priority compared to all the other things.
So what systems could I implement? With the big caveat that I won’t time for all of them:
- Food: foraging, farming, hunting, ranching, bees/honey, cooking, preserving
- Clothing: gathered materials, cotton, wool, leather
- Furniture: floor, wooden, cloth, leather
- Tools: wooden, copper, iron, steel
- Building: wood, stone, metal
- Recreation: games, paintings, sculptures, library
Possible room types: bedroom, dining, kitchen, indoor storage, outdoor storage, farms, ranches, workshops of various sorts, recreation.
I don’t think I’ll get far enough this project to build most of the ideas I’ve described in this section. However, there are two I need:
- Rooms: I have implemented an interface for room building, but it needs more polish, needs a way to specify the type, needs a way to add doors, and needs to handle room collision. Alternatively, I could separate room and zone marking, and make rooms much simpler (always have walls and doors, don’t have a type, doesn’t matter if they collide) and also make zones simpler (never have walls or doors, and always have a type, never collide).
- Objects: I need to implement an interface for placing objects. I also need to deal with objects that are more than 1⨉1 tile. And I need to figure out what happens if you change a room or zone to cut an object in two.
8 ❌ Items and inventory#
{ I fell behind and didn’t get here }
I have gotten rid of the player character so there’s no player inventory. But the NPCs could be carrying things. And there are things on the map. I think I need to figure out stockpiles and also NPC inventory. The simplest thing would be for NPCs to carry only one thing at a time, or only as much as is needed for the current job.
The bigger topic this week is that I need to revisit rooms. I think some rooms will have walls and some will not. Rooms with walls need doors. I need pathfinding to not take NPCs through walls. I’d like non-rectangular rooms but that might be something to add later. Do rooms have floors? What happens to trees and plants when you place a room somewhere? What are the room types, and what are they for?
9 ❌ Ranged scrolls and targeting#
{ I fell behind and didn’t get here }
I won’t have scrolls or targeting. Instead, I need to figure out pathfinding. I have a bunch of ideas for making it fast, but I need to get all of that out of my brain and first make pathfinding work at all, even if it’s slow.
10 👎 Saving and loading#
I am planning to skip this. It’s not hard, but it’s not something I’m interested in spending my time on for this project. I need to keep the scope down for my first fortress style game, and can revisit this the next time I work on a game like this.
11 ❌ Dungeon levels#
I don’t plan to have z-levels.
12 ❌ Increasing difficulty#
I want the player to learn more systems as they scale up. This could be more advanced tech, something to support larger populations, or something involving increased skills. But I won’t have any ideas about what this might actually involve until I get the basic gameplay down.
{ I never got the basic gameplay down, so this didn’t come up }
13 ❌ Gearing up#
Tools could qualify. A tool-making workshop could give NPCs tools, and then the tools would make them more effective. But I think that’s a stretch.
14 😀 Conclusion#
This was a fun project. I got nowhere near as much done as I had hoped to, but I also learned a lot and am better prepared for the next time I attempt something like this.
I copied the grid drawing and event handling from a previous project, so I could have something running right away. I was pretty happy with my map generator, not using Simplex/Perlin noise but instead having a simple left-to-right pattern.
I was hoping that I could find a use for a player running around, like in Factorio, but I should’ve thought about this harder before implementing that player. I ended up removing it. Next time I will start with a cursor instead of a player.
Converting the roguelikedev engine from turn based to real time was surprisingly easy, except for handling keyboard repeat. I have some ideas for that but I didn’t implement it. That’s something I would’ve done better if I had started with real time instead of starting with turn based and then converting it.
The room building turned out to be more complicated than I thought, especially to interact with jobs. As the room is built, paths would get invalidated. Instead of getting to this point, I thought a little bit ahead and realized this problem before I got to it. I tried switching to “thin walls” and I think it might make things simpler, but I can’t be sure. I think room building is complicated enough that it might be worth making a separate prototype where I can try out a few different ideas. I need to figure out connectivity, disconnected rooms, room editing, door placement, etc. I didn’t realize room building would be complicated, so this was probably my first sign that I had too large of a scope.
The jobs system turned out to be far more complicated than I thought. The main thing I was trying to deal with was minimizing special cases. For example, if the player cancelled a room construction, the NPC carrying building materials to the site would drop them, but what if there’s already an object there? They might have to find another place to drop the item, and pathfind there. But what if a different NPC has dropped an item there in the meantime? Thanks to some helpful people on discord, I decided I should allow unlimited dropped items but only one intentionally placed item on each tile. But I never got around to implementing this.
Splitting the code into modules slowed me down quite a bit more than I expected. I should’ve left most everything as one big file until I had a better sense of the natural module boundaries.
I should’ve thought more about the core game loop at the beginning of this project. There were many times when I didn’t know what to implement because I didn’t know where I was heading. Around halfway through the project I started thinking about it, but I was demoralized by the room and job systems both being much more complicated than I had wanted. I ended up reducing the scope quite a bit. I was hoping to have some NPCs moving around doing simple jobs only. But I had to reduce the scope even more, so I ended up with a chicken simulator. I’m reasonably happy with the chicken simulator but I feel like I need to do this project three to five more times to really get a hang of it.
The next time I do a project like this I would like to really think carefully about the job system and the room system — data structures, states, algorithms — before I code anything up. State machines in particular have been helpful for me to understand all the corner cases, and ideally find a design that avoids them.
Maybe next year!