github gitlab mastodon
Untitled Rogulike
Nov 3, 2018
6 minutes read

Introduction

A few weeks ago I began working on a roguelike game.
It is something that I have wanted to do for quite a while, but could never find the time.
I am learning Rust right now and the best way for me to do that is to find a project that I care about. So, this is a perfect opportunity finally make a roguelike.

Theme/Core Mechanic

I have not yet decided on a solid theme/core mechanic.
I recently read through the manga “That Time I Got Reincarnated As A Slime” in which the main character is stabbed in the first episode and as he dies there is a back and forth with a voice that responds to his thoughs granting him abilities.
The end result is him becoming a slime with the ability to consume enemies to gain their abilities and analyze things he consumes to combine or recreate them.

I want to incorporate something similar into my roguelike.
My ideas right now are:

  • on death reincarnate with abilities based on how you died/what you accomplished
    would this be easily cheesable?
  • absorb enemies to gain abilities
    make non-permanent/overwrite with new abilities
  • mimic absorbed enemies
    get their abilities and maybe enemies of same type don’t attack

Prototypes

Before I rushed into it I wanted to make prototypes of a few different systems that I though would be needed. I wanted to make some sort of name generator, map generator, and find a good library to use for graphics.

Name Generator

The most common solution I found to name generation was to get a bunch of names I like, stick them in a Markov chain, and generate names with it. This seemed to work well enough for me.

Map Generator

After reading about a bunch of different map generation algorithms I decided to use the alogorithm described by Bob Nystrom here.

place non-overlapping rooms randomly 1. place non-overlapping rooms randomly

fill the empty areas with mazes 2. fill the empty areas with mazes

fully connect the regions (rooms and mazes) 3. fully connect the regions (rooms and mazes)

remove the dead ends of the mazes 4. remove the dead ends of the mazes

I have made some tweaks to the maze fill algorithm to reduce the windy-ness since these screenshots were taken, but I didn’t want to pull out my current generator and make a wrapper to output each step again.

Graphics Library

While trying out some of the different rust terminal libraries I came across a talk by Josh Ge from Roguelike Celebration “How to Make a Roguelike”. This led me to the r/roguelikedev community and a rust version of the libtcod tutorial.

Roguelike Tutorial

I went through the first few parts of the tutorial to get familiar with the basics of libtcod and to get some ideas for how I want things to be structured.
I then made some changes so that it would be easier to add things in the future.

Generic Objects

I liked the idea of using a generic object system from the tutorial, but I did not like the idea of having to write a new function for each object and tile.
I created ObjectStore and TileStore structs that would load object and tile definitions from json files on creation.
When I need to add a new object or tile it is just a new line in a json file instead of a new function and recompile (which can be very slow in rust).

player object definition

{
	"name":"player","x":0,"y":0,"char":"@",
	"color":{"r":255,"g":255,"b":255},
	"blocks":true,"alive":true,"open_doors":true,
	"fighter":{"max_hp":30,"hp":30,"defense":2,"power":5}
}

wall tile definition

{
	"name":"wall","blocked":true,"block_sight":true,"explored":false,
	"light_color":{"r":130,"g":110,"b":50},
	"dark_color":{"r":0,"g":0,"b":100}
}

Event System

I made an Event enum that would be returned from many of the Object methods. Example: The Object move method can return one or more Moved, ReplaceTile, Attack, or Message Events.

pub enum Event {
	// x, y, tile
	ReplaceTile(i32, i32, Tile),
	// x, y
	Moved(i32, i32),
	// x, y, dx, dy
	Moving(i32, i32, i32, i32),
	// x, y, type, amount
	Attack(i32, i32, String, i32),
	// type, amount
	TookDamage(String, i32),
	// x, y, name
	Died(i32, i32, String),
	// player quit
	Exit,
	SkippedTurn,
	Message(String),
	// player used up stairs
	GoUp,
	// player used down stairs
	GoDown,
}

Traits

I defined traits for things that I knew would need to be changed in the future.

pub trait MapGenerator {
	fn generate(&mut self, tile_store: &TileStore,
		object_store: &ObjectStore,
		floor: i32) -> ((i32, i32), Vec<Vec<Tile>>);
}

pub trait Renderer {
	fn draw_tile(&mut self, x: i32, y: i32, color: Color);
	fn draw_char(&mut self, x: i32, y: i32, char: char, color: Color);
	fn draw_bar(&mut self, x: i32, y: i32, width: i32, label: String, 
		value: i32, max: i32, foreground: Color, background: Color);
	fn draw_message(&mut self, x: i32, y: i32, message: &String);

	fn clear_panel(&mut self);
	fn clear_xy(&mut self, x: i32, y: i32);
	fn clear(&mut self);
	fn show(&mut self);
	fn closed(&self) -> bool;
	fn cleanup(&mut self);

	fn is_fullscreen(&self) -> bool;
	fn set_fullscreen(&mut self, fullscreen: bool);

	fn get_key(&mut self) -> Key;
}

pub trait AI {
	fn take_turn(&self, x: i32, y: i32, map: &Map,
		object_store: &ObjectStore) -> Vec<Event>;
}

The current implementations of each trait are:

Trait Implementation Description
MapGenerator MazeMap The algorithm described above
MapGenerator CaveMap Cellular Automata method described here
Renderer TcodRenderer Implementation for libtcod
Renderer TermionRenderer Implementation for termion (terminal ui library)
AI RandomAI Randomly moves around and attacks

Game Loop

My game loop is currently:

  1. Render all of the tiles, objects, and ui elements (stats and messages)
  2. Update player effects (currently just regenerate health)
  3. Handle controls and return events from player actions
  4. Run AI for each npc object
  5. Process events returned by player and npc objects
    • Replace tiles (happens when a door is opened)
    • Apply damage to objects
    • Go up or down a level
  6. Add all messages to the message log

Changing Levels

I have created a MapStore struct that contains a HashMap<i32, Map> and added a HashMap<i32, Vec<Object>> to the ObjectStore to store the map and objects for each level.
When the player uses an up or down stairs I store the current map and objects, check if data exists for the new level, and either retrieve it if it does or generate it if it does not.
I am now trying to decide what I want to do about levels after the player leaves them.

The options I am thinking about right now are:

  • Do not do anything, keep things as they were when the player was last on the level (current behavior)
    This is the least work, but I am not sure it would make for the best gameplay.
  • Continue processing events for all existing levels
    This would allow me to have npcs change level to chase the player or run away.
    Depending on how much stuff is going on this could also kill performance (dwarf fortress fps death!).
  • Continue processing events for just the previous, current, and next levels
    Would still let me have npcs change levels, but without as much of a performance hit.

Also, I am wondering if I should pregenerate the maps and npcs intead of doing it when the player reaches a new level.

TODO

This week I will be working on the ui. I want to add a ui struct on top of the Render trait so that I don’t have have every renderable item keep track of its position on screen.

This is where I am at right now. I made this post so that I could put my thoughts in one place and hopefully get some advice on different aspects that I am having trouble with.

Here are some screenshots of the current game maze map level cave map level



Back to posts