I’m attempting to write a (very simple, at least initially) text adventure in Rust, so I’m going to share progress on my blog starting today. This first post is about the sections, that to day is the rooms of a house for example.
Disclaimer: I’m no expert of adventure games (except maybe as a player), and I’m learning Rust. These writings are not meant as a tutorial, they rather are my thoughts, ideas and work.
The repository with the full code is available on GitHub. The name of the game is The Undergarden. The story is still to write, at present time there are only two sections (or rooms) just to show the player can walk from one to another.
The engine is into the unend module, which is bundled with the game but may become a separate crate if it ever gets mature enough and/or if I’ll want to use it with other games. The engine defines a trait for sections:
So, a section can be any struct the programmer wants, as long as it correctly implements the Visitable
trait, so that
it becomes a place the player can… well… visit.
Besides the obvious getters, there is an exit()
method which must define what happens when the player exits from the
room in a certain direction. At present time (but it’s going to be expanded) direction can be one of the four cardinal
points (defined in a separate enun). The result (method return value) when the player tries to exit can be one of
the following:
Since an exit can lead to any section, we provide out-of-the-box support for things such as rooms in which you can enter but then you can’t exit; or exits which lead to the same room being left; or more than one exit leading to the same room; or even portals (connections between distant rooms, easy because there is no notion of proximity in the data).
The engine also provides a BasicSection
struct, which implements Visitable
and is okay to boostrap with
section creation:
new()
takes tag, name and description of the section. The tag will be used to look the section up when needed
and will be stored in other sections which have exits to this one. We won’t directly store a borrow to a
section struct in another one, as this would likely create crossed borrows which would be a nightmare to manage.
The last parameter is a HashMap
of Exit
s.
exit()
implementation checks that an exit exists in the direction passed as parameter and either returns it
or no exit.
So, let’s use this code to create a room. Why not begin with the kitchen:
The maplit crate is being used here, in order to provide less verbose
HashMap
construction. A custom s!()
macro is also used, because there are really a lot of strings to
create when creating the data of a text adventure, and I wanted the code to still be readable: the macro
resolves to String::from()
.
Only the exits which actually exist or are closed need to be defined: non existing ones will be
handled by the exit()
implementation we provided in BasicSection
for Visitable
.
It’s worth noting that the BasicSection
construction is wrapped inside an enum: UnendSection
. This
is need in order to allow different types of Visitable
s to be values of the same HashMap
(even though
there is only one at the moment).
We are now ready to initialize a nice game with two sections:
Et voilà, it works!
It’s actually a bit more complicated than this, as Game
doesn’t provide I/O,
so that must be implemented in a trait: the ConsoleIO
trait defined inside unend provides I/O from
the text console and is in this case implemented: if we are not in the console but - say - in a
web browser (compiling the game to a wasm target is one of my secret goals), we should write a specific trait.
Since this first post is about sections, we won’t dig into the implementation of Game
to much. At
the moment it’s pretty straightforward anyway, since there are no objects or people to interact with,
and it’s basically a loop which waits for user input. I’m just showing the code which processes the user
commands (only directions + quit):
If command
is a cardinal point, it calls Visitable
s exit()
method implemented for BasicSection
: if it
returns the tag of another Visitable
, it updates the current section tag (so we can load the section at the
next loop iteration); if the exit is closed, it display the explanatory string; if there is no exit, it displays
a default message. The other branches handle quitting the game and invalid commands.
That’s it for now. Hopefully, next time we’ll try to interact with objects.