Compare commits

...

10 Commits

25 changed files with 464 additions and 47 deletions

4
.gitignore vendored
View File

@ -1,2 +1,4 @@
raylib.h
game
game
*/data/*.c

View File

@ -3,9 +3,14 @@ CFLAGS=-Wall -lraylib -lGL -lm -lpthread -ldl -lrt -lX11
.PHONY: clean run
game:
game: data/actions.c data/rooms.c data/transitions.c data/words.c data/flags.c *.c
$(CC) *.c $(CFLAGS) -o game
data/%.c: data/%.txt
echo -n "char *data_$*_txt = \"" > data/$*.c
cat data/$*.txt | perl -pe 's/\n/\\n/g' >> data/$*.c
echo "\";" >> data/$*.c
run: game
./game

View File

@ -0,0 +1,76 @@
* Fatal Distractions Chapter 1 - Text Adventures
** Concept
I'm not looking for anything super complicated. I've spent maybe 90 minutes
in my life playing text adventures, I'm sure there are some with really good
mechanics. For this one, I'm aiming for something at about the level of
[[https://homestarrunner.com/dungeonman][Thy Dungeonman]]. A map, moving between rooms, getting items, <verb>ing <item>s
<preposition> <other item>s. If I'm pretty much finished before the end of the
month, I could add
- visual effects
- saving and loading
- pictures
- music
I'm not trying to be a purist about this; I know 99% of all text adventures are
*just* text, but music is also fun to put together.
** Implementation
*** Flags
A flag is a combination name and uinteger value. The value can be changed as the
result of an action, or checked within a predicate. Three uses for these:
- Boolean values, 1 or 0
- Countdowns, start at some positive value and eventually become 0 = false
- Counters, start at zero and increment ("you have rung the bell 10 times")
Counters seem really optional, booleans and countdowns seem actually useful.
Values should evaluate to false if zero, true otherwise.
Effects should be able to enable (set to 1), disable (set to 0), increment, or
decrement; if there's need for it, they'll need to set (set to X), I don't think so.
*** Effects
When an action evaluates, most of the time it will have no effect ("you can't
go north from here"). Sometimes though, an action should change the world. Here
are some effects:
- Modify a flag (detailed above)
- Change the current room
- Add / remove an item from the inventory
- Do some meta thing (save or load)
*** Predicates
I guess I need a DSL for predicates. I don't think I'll need to implement "OR",
my predicates should be able to be a chain of clauses "AND"ed together. Some clauses:
- IN(room_name), true if the current room has that name
- HAS(item_name), true if that item is in the inventory (or in the room)
- ENABLED(flag_name), true if that flag has a value > 0
- TO(item_name), this is the real tough one, for transitive verbs with an object
- 'use key on door'
- 'give trinket to dennis'
- 'take torch from sconce'
Some of these things are items, some are definitely not. Not sure how to codify. Maybe:
- DITRANSITIVE(preposition, object), which checks that the command is of the form:
<verb> <noun> <preposition> <object>
- Even monotransitive verbs ("pull lever") need to specify what they'll act on.
*** Actions
Actions are a command, or part of a command, plus a predicate, plus a priority,
plus a description, plus (optionally) some effects:
PULL | * | 1 | You don't see anything to pull |
PULL | IN(lever_room) | 10 | What do you want to pull? |
PULL LEVER | IN(lever_room) | 100 | You pull the lever. Nice. | ENABLE(lever_pulled)
PULL LEVER | IN(lever_room) & ENABLED(lever_pulled) | 1000 | You already pulled it. |
If the command matches all of the first column, it looks at that action.
">PULL LEVER" would match all four, ">PULL" would only match the first two.
The game would choose between all matched actions, filter to only the ones whose
predicates are fulfilled, and choose from those the one with the highest priority.
This might require a lot of typing, but I think it will have enough flexibility
to do everything I might want to do.
An action describing a transition would look as follows:
NORTH | * | 1 | You can't go north from here. |
NORTH | IN(initial_room) | 2 | You enter the nasty room. | GOTO(nasty_room)
NORTH | IN(other_room) | 2 | You enter the opulent room. | GOTO(opulent_room)
, but there's nicer syntax for transitions I'm sure.
*** Parsing
I'm sure we want a gallery of synonyms for most verbs and nouns. I assume we split the string
by spaces, making an array of canonical words, then write the actions around those canonical
words.
PULL|PULL,YANK,TUG
ROPE|ROPE,CORD,STRING,CABLE
With the above words, "PULL ROPE", "YANK CORD", or "TUG STRING" would all check for
actions as "PULL ROPE".

View File

@ -0,0 +1,50 @@
#include <stdlib.h>
#include <string.h>
#include "action.h"
#include "game.h"
#include "util.h"
void load_action(Game *g, char *line) {
Action *action = &g->actions->actions[g->actions->count];
char *line_token_guy;
char *line_token = strtok_r(line, "|", &line_token_guy);
char command_buffer[200];
strcpy(command_buffer, line_token);
char *command_token_guy;
char *command_word = strtok_r(command_buffer, " ", &command_token_guy);
while (command_word != NULL) {
Word *word = find_word(g->words, command_word);
action->words[action->words_count++] = word;
command_word = strtok_r(NULL, " ", &command_token_guy);
}
line_token = strtok_r(NULL, "|", &line_token_guy);
// predicate bullshit
// char *command_predicate_strtok = strtok_r
line_token = strtok_r(NULL, "|", &line_token_guy);
action->priority = atoi(line_token);
line_token = strtok_r(NULL, "|", &line_token_guy);
action->description = malloc(strlen(line_token) + 1);
strcpy(action->description, line_token);
line_token = strtok_r(NULL, "|", &line_token_guy);
// action bullshit
g->actions->count++;
}
#include "data/actions.c"
void game_load_actions(Game *g) {
parse_multiline_string(g, data_actions_txt, &load_action);
}
Action *find_action(Actions *actions, char *command) {
return NULL;
}

View File

@ -0,0 +1,29 @@
#ifndef _FD_ACTION_
#define _FD_ACTION_
typedef struct Action Action;
typedef struct Actions Actions;
#include "game.h"
#include "word.h"
#include "predicate.h"
struct Action {
Word *words[4];
int words_count;
Predicate *predicates[10];
int predicates_count;
int priority;
char *description;
// Effect *effect;
};
struct Actions {
Action actions[1000];
int count;
};
void game_load_actions(Game *g);
Action *find_action(Actions *actions, char *command);
#endif

View File

@ -0,0 +1,4 @@
PULL|*|1|You don't see anything to pull|
PULL|IN(lever_room)|10|What do you want to pull?|
PULL LEVER|IN(lever_room)|100|You pull the lever. Nice.|ENABLE(lever_pulled)
PULL LEVER|IN(lever_room) & ENABLED(lever_pulled)|1000|You already pulled it.|

View File

@ -0,0 +1,2 @@
LEVER_PULLED|0
STEPS_TAKEN|10

View File

@ -0,0 +1,3 @@
PULL|PULL,YANK,TUG
ROPE|ROPE,CORD,STRING,CABLE
LEVER|LEVER

31
01_text_adventure/flag.c Normal file
View File

@ -0,0 +1,31 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "flag.h"
#include "game.h"
#include "util.h"
void load_flag(Game *g, char *line) {
char *token = strtok(line, "|");
Flag *flag = &g->flags->flags[g->flags->count];
flag->key = malloc(strlen(token) + 1);
strcpy(flag->key, token);
token = strtok(NULL, "|");
flag->value = atoi(token);
g->flags->count++;
}
#include "data/flags.c"
void game_load_flags(Game *g) {
parse_multiline_string(g, data_flags_txt, &load_flag);
}
int flag_value(Flags *f, char *key) {
for (int i = 0; i < f->count; i++) {
if (strcmp(f->flags[i].key, key) == 0) return f->flags[i].value;
}
return -1;
}

22
01_text_adventure/flag.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef _FD_FLAG_
#define _FD_FLAG_
typedef struct Flag Flag;
typedef struct Flags Flags;
#include "game.h"
struct Flag {
char *key;
int value;
};
struct Flags {
Flag flags[200];
int count;
};
void game_load_flags(Game *g);
int flag_value(Flags *f, char *key);
#endif

View File

@ -6,6 +6,11 @@
#include "transition.h"
#include "input.h"
#include "log.h"
#include "util.h"
#include "word.h"
#include "flag.h"
#include "predicate.h"
#include "action.h"
Game *game_create(void) {
Game *g = malloc(sizeof(Game));
@ -29,6 +34,15 @@ Game *game_create(void) {
g->input = input;
g->words = malloc(sizeof(Words));
g->words->count = 0;
g->flags = malloc(sizeof(Flags));
g->flags->count = 0;
g->actions = malloc(sizeof(Actions));
g->actions->count = 0;
return g;
}
@ -118,31 +132,6 @@ void game_handle_command(Game *g, const char *command) {
}
}
#define ROOMS_PATH "./rooms.txt"
#define MAX_ROOM_COUNT 100
char room_buffer[2001];
void game_load_rooms(Game *g) {
FILE *rooms_file = fopen(ROOMS_PATH, "r");
while ((fgets(room_buffer, 2000, rooms_file)) != NULL) {
char *token = strtok(room_buffer, "|");
g->rooms->rooms[g->rooms->count].name = malloc(strlen(token) + 1);
strcpy(g->rooms->rooms[g->rooms->count].name, token);
token = strtok(NULL, "|");
g->rooms->rooms[g->rooms->count].description = malloc(strlen(token) + 1);
strcpy(g->rooms->rooms[g->rooms->count].description, token);
g->rooms->count++;
}
fclose(rooms_file);
g->current_room = &g->rooms->rooms[0];
}
void game_handle_input(Game *g) {
handle_pressed_keys(g->input);
}

View File

@ -18,6 +18,9 @@ typedef enum Command {
#include "room.h"
#include "transition.h"
#include "log.h"
#include "word.h"
#include "flag.h"
#include "action.h"
struct Game {
bool should_close;
@ -26,6 +29,9 @@ struct Game {
Rooms *rooms;
Room *current_room;
Transitions *transitions;
Words *words;
Flags *flags;
Actions *actions;
};
Game *game_create(void);

View File

@ -2,6 +2,7 @@
#include "../raylib.h"
#include "log.h"
#include "input.h"
#include "parse.h"
#include <stdlib.h>
#include <stdio.h>
@ -15,7 +16,20 @@ int main(void) {
Game *g = game_create();
game_load_rooms(g);
g->current_room = &g->rooms->rooms[0];
game_load_transitions(g);
game_load_words(g);
game_load_flags(g);
game_load_actions(g);
for (int i = 0; i < g->actions->count; i++) {
for (int j = 0; j < g->actions->actions[i].words_count; j++) {
printf("%s ", g->actions->actions[i].words[j]->word);
}
printf("|preds|%d|%s|effects", g->actions->actions[i].priority, g->actions->actions[i].description);
printf("\n");
}
game_run_until_close(g);
CloseWindow();

30
01_text_adventure/parse.c Normal file
View File

@ -0,0 +1,30 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "game.h"
#include "word.h"
#define MAX_WORDS_IN_COMMAND 4
void parse(Game *g, char *typed_command) {
printf("Typed command: %s\n", typed_command);
char *bluh = malloc(strlen(typed_command) + 1);
strcpy(bluh, typed_command);
Word **command = malloc(MAX_WORDS_IN_COMMAND * sizeof(Word));
int word_count = 0;
char *token = strtok(bluh, " ");
while (word_count < MAX_WORDS_IN_COMMAND && token != NULL) {
command[word_count] = find_word(g->words, token);
word_count++;
token = strtok(NULL, " ");
}
for(int i = 0; i < word_count; i++) {
printf("%s ", command[i]->word);
}
printf("\n");
free(bluh);
}

View File

@ -0,0 +1,7 @@
#ifndef _FD_PARSE_
#define _FD_PARSE_
#include "game.h"
void parse(Game *g, char *typed_command);
#endif

View File

@ -0,0 +1,19 @@
#include <stdio.h>
#include <string.h>
#include "game.h"
#include "flag.h"
#include "predicate.h"
bool predicate_fulfilled(Game *g, Predicate *p) {
switch (p->type) {
case PREDICATE_TRUE:
return true;
case PREDICATE_IN_ROOM:
return strcmp(g->current_room->name, p->argument) == 0;
case PREDICATE_FLAG_ENABLED:
return flag_value(g->flags, p->argument) > 0;
default:
printf("Invalid predicate type\n");
return false;
}
}

View File

@ -0,0 +1,22 @@
#ifndef _FD_PREDICATE_
#define _FD_PREDICATE_
typedef struct Predicate Predicate;
typedef enum PredicateType {
PREDICATE_TRUE,
PREDICATE_IN_ROOM,
// PREDICATE_HAS_ITEM,
PREDICATE_FLAG_ENABLED,
} PredicateType;
#include "game.h"
struct Predicate {
PredicateType type;
char *argument;
};
bool predicate_fulfilled(Game *g, Predicate *p);
#endif

View File

@ -2,6 +2,26 @@
#include <string.h>
#include <stdio.h>
#include "room.h"
#include "game.h"
#include "util.h"
void load_room(Game *g, char *line) {
char *token = strtok(line, "|");
g->rooms->rooms[g->rooms->count].name = malloc(strlen(token) + 1);
strcpy(g->rooms->rooms[g->rooms->count].name, token);
token = strtok(NULL, "|");
g->rooms->rooms[g->rooms->count].description = malloc(strlen(token) + 1);
strcpy(g->rooms->rooms[g->rooms->count].description, token);
g->rooms->count++;
}
#include "data/rooms.c"
void game_load_rooms(Game *g) {
parse_multiline_string(g, data_rooms_txt, &load_room);
}
void free_room(Room r) {
free(r.name);

View File

@ -3,32 +3,28 @@
#include <string.h>
#include "transition.h"
#include "game.h"
#include "util.h"
#define TRANSITIONS_PATH "./transitions.txt"
void load_transition(Game *g, char *line) {
char *token = strtok(line, "|");
g->transitions->transitions[g->transitions->count].from = find_room(g->rooms, token);
char transition_buffer[2001];
void game_load_transitions(Game *g) {
FILE *transitions_file = fopen(TRANSITIONS_PATH, "r");
token = strtok(NULL, "|");
g->transitions->transitions[g->transitions->count].via = command_from_string(token);
while ((fgets(transition_buffer, 2000, transitions_file)) != NULL) {
char *token = strtok(transition_buffer, "|");
token = strtok(NULL, "|");
g->transitions->transitions[g->transitions->count].to = find_room(g->rooms, token);
g->transitions->transitions[g->transitions->count].from = find_room(g->rooms, token);
token = strtok(NULL, "|");
g->transitions->transitions[g->transitions->count].via = command_from_string(token);
token = strtok(NULL, "|");
g->transitions->transitions[g->transitions->count].to = find_room(g->rooms, token);
token = strtok(NULL, "|");
g->transitions->transitions[g->transitions->count].description = malloc(strlen(token) + 1);
strcpy(g->transitions->transitions[g->transitions->count].description, token);
token = strtok(NULL, "\n");
g->transitions->transitions[g->transitions->count].description = malloc(strlen(token) + 1);
strcpy(g->transitions->transitions[g->transitions->count].description, token);
g->transitions->count++;
}
g->transitions->count++;
}
fclose(transitions_file);
#include "data/transitions.c"
void game_load_transitions(Game *g) {
parse_multiline_string(g, data_transitions_txt, &load_transition);
}
Transition *find_transition(Transitions *t, Room *from, Command via) {

23
01_text_adventure/util.c Normal file
View File

@ -0,0 +1,23 @@
#include <stdlib.h>
#include <string.h>
#include "util.h"
#include "game.h"
void parse_multiline_string(Game *g, char *string, void (*parse_line)(Game *g, char *line)) {
char *buffer = malloc(0);
char *sol = string;
char *eol = strchr(sol, '\n');
while (eol != NULL) {
int line_length = eol - sol;
buffer = realloc(buffer, line_length + 1);
memcpy(buffer, sol, line_length);
buffer[line_length] = '\0';
parse_line(g, buffer);
sol = eol + 1;
eol = strchr(sol, '\n');
}
free(buffer);
}

7
01_text_adventure/util.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef _FD_UTIL_
#define _FD_UTIL_
#include "game.h"
void parse_multiline_string(Game *g, char *string, void (*parse_line)(Game *g, char *line));
#endif

38
01_text_adventure/word.c Normal file
View File

@ -0,0 +1,38 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "word.h"
#include "game.h"
#include "util.h"
void load_word(Game *g, char *line) {
char *token = strtok(line, "|");
Word *word = &g->words->words[g->words->count];
word->word = malloc(strlen(token) + 1);
strcpy(word->word, token);
word->synonyms_count = 0;
while ((token = strtok(NULL, ",")) != NULL) {
word->synonyms[word->synonyms_count] = malloc(strlen(token) + 1);
strcpy(word->synonyms[word->synonyms_count], token);
word->synonyms_count++;
}
g->words->count++;
}
#include "data/words.c"
void game_load_words(Game *g) {
parse_multiline_string(g, data_words_txt, &load_word);
}
Word *find_word(Words *words, char *word_or_syn) {
for (int i = 0; i < words->count; i++) {
for (int j = 0; j < words->words[i].synonyms_count; j++) {
if (strcmp(words->words[i].synonyms[j], word_or_syn) == 0) return &words->words[i];
}
}
printf("Can't find %s\n", word_or_syn);
return NULL;
}

22
01_text_adventure/word.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef _FD_WORD_
#define _FD_WORD_
typedef struct Word Word;
typedef struct Words Words;
#include "game.h"
struct Word {
char *word;
char* synonyms[100];
int synonyms_count;
};
struct Words {
Word words[200];
int count;
};
void game_load_words(Game *g);
Word *find_word(Words *words, char *word);
#endif