Howdy, writers and readers!
Today it's more of a technical article, sorry. It follows one of my earliest posts about making text games in C. This post requires prior knowledge of that old dusty programming language.
The single-pass state machine game
A few months ago I rewrote Plouffe: the Game (a wacky adventure feat my former linear algebra teacher). The original had a lot of switch statements in the main loop:
switch (room) {
case ROOM_ENTRANCE:
puts("You are in front of a door.\n"
"1 Open door\n"
"2 Dance the macarena\n"
// other options...
);
switch(get_input()) {
case '1':
puts("It's locked!");
return ROOM_ENTRANCE;
case '2':
puts("You successfully clip through the wall.");
return ROOM_AMPHITHEATER;
// other actions for this room...
}
case ROOM_AMPHITHEATER:
puts("You see a lot of students.\n"
"Oh no, not those again, you think to yourself.");
// other rooms etc...
}
As you can feel, it's quite a lot of code for a minimal "state machine game".
Since then, I discovered the full potential of structs. Here is the game data, now that it sits outside the main loop:
struct action { char from, to, *label, *text; } actions = {
{0,0,"1 Open door","It's locked!"},
{0,1,"2 Dance the macarena","You clip through, yay"},
// etc
};
There is only one line per action, very compact! And most interestingly, here is the entire main loop itself:
int main() {
int state = 0, input = '\n';
while (input != 'q') {
for (struct action *a = actions; a->label; a++) {
if (a->from == state) {
if (input == '\n') puts(a->label);
if (input == a->label[0]) {
puts(a->text);
state = a->to;
break;
}
}
}
if (input == '\n') printf("> "); fflush(stdout);
input = getchar();
}
}
It takes advantage of the terminal, which inputs a cute little newline character when you press Enter. That's when the game has to display your current options. Other characters in the input are actions. Now a single code block remains.
If you want clean input when you use an alternative standard library like Musl, you will need to add "fflush(stdout)" after every print without a new line.
Example session:
Welcome to Plouffe the Game. You are in front of a door. 1 Open door 2 Dance the macarena > 1 It's locked! 1 Open door 2 Dance the macarena > 2 You clip through, yay! In class, you see a lot of students. Oh no, not those again, you think to yourself. 1 Teach 2 Swing various fresh fruit around 3 Go to sleep > q Bye!
- Musl, an alternative to glibc, great for small static programs
- The C reference. Well-made, can't go very far without it!
That's great but not the only thing I found recently...
Raw mode
Ever wanted to just skip the silly "press Enter" requirement to your text game? Enter Raw mode. I do it like this, but there are other ways. Instead of just "input = getchar()":
system("stty raw");
input = getchar();
system("stty cooked");
What does it mean?
- The player is happy! :D Unless they are on Windows, I didn't work on it. But if they're on an Unix system, they have now postponed their muscle strains thanks to you.
- You don't need to display the little "> " prompt.
- However, you have to list the options in another loop instead of the same one which already listens to characters. What a terrible tragedy for minimalism!
Example of a main loop:
while (input != 'q') {
// list options
for (struct action *a = actions; a->label; a++)
if (a->from == state) puts(a->label);
// handle input
system("stty raw");
input = getchar();
system("stty cooked");
// handle choice
for (struct action *a = actions; a->label; a++) {
if (a->from == state && input == a->label[0]) {
puts(a->text);
state = a->to;
break;
}
}
}
The tree machine
(for the lack of a better term)
Why stop at state machines? They're quite a bit limited, you can't put a world/inventory model in them, or just a very simple one. That was the main subject of my "making text adventures" article, and I came up with a complicated bunch of code, but since then I found a dark and mysterious data structure.
The tree machine. Here it is:
enum { YOU = 1, FIELD, TAVERN, APPLE, NB_OBJECTS };
char where[NB_OBJECTS] = {
[YOU] = FIELD,
[APPLE] = FIELD,
};
struct act { char from[4], to[4], *label, *text; } acts[] = {
{{YOU, FIELD, APPLE, FIELD}, {APPLE, YOU}, "take apple", "You take it."},
{{YOU, FIELD, APPLE, YOU}, {APPLE, FIELD}, "drop apple", "Bye bye apple!"},
{{YOU, FIELD}, {YOU, TAVERN}, "enter tavern", "What a jolly place! You sit down."},
{{YOU, TAVERN}, {YOU, FIELD}, "leave tavern", "You go back outside."},
{{YOU, TAVERN}, {}, "order drink", "It'll take a while."},
{{YOU, TAVERN, APPLE, YOU}, {APPLE, 0}, "eat apple", "Omnomnomnom!"},
{0}};
As you can see:
- There is a list of objects in the game.
- Instead of a single number, the state is now a "where array". Basically it's a tree of locations, that one in particular is "you are in the field and the apple is in the field".
- The actions changed. The first one has 2 conditions ("you are in the field" and "the apple is in the field"), and one effect ("the apple will be on you"). Prompts and descriptions work the same way as before.
This tree machine can simulate a complex world model with rooms, inventory, and containers.
It's still very basic. There is no support for generic actions like "the apple is at the same place as you", logic like "the apple is NOT on you", or overrides like "the apple is on you and that other option isn't available". But for simple games (already far more complex than state machine games!) it works, and it's kinda elegant.
If you know an official term for that particular data structure, let me know!
And now, let's step out of the Unix world...
Behind the mirror slash
(a figure of speech because, you know, backslash in Windows. anyway)
To change the scenery from the Unix ubiquity of my PC, I installed both FreeDOS (an operating system) and DOSBox (an emulator). DOS is a very old system that works very well as a bedrock platform. Since it's "dead", it's very mature and stable for long-term software.
The first compiler I found for it is Bruce's C Compiler. Even its documentation says it's a bit buggy lol. Remember what I said about old software? Anyway, it's a no-effort way to cross-compile to DOS, so I choose you, Pikachu- I mean bcc.
- Permacomputing on what is a "bedrock platform"
- Bruce's C Compiler (no official website because, well, it's quite older than the web)
Conclusion
I really like text games, because unlike other video games they can last decades without maintenance and you can still discover tricks about them. But often they're more like math tricks (called theorems by normal people).
On that, keep text-gaming and see ya in the next one!