mirror of https://git.sr.ht/~rabbits/parade
422 lines
9.4 KiB
C
422 lines
9.4 KiB
C
#include <stdio.h>
|
|
|
|
#define NAMELEN 16
|
|
#define TEXTLEN 256
|
|
#define BUFLEN 512
|
|
|
|
typedef struct Vessel {
|
|
char id, name[NAMELEN], note[TEXTLEN], prog[TEXTLEN];
|
|
struct Vessel *owner, *parent;
|
|
} Vessel;
|
|
|
|
typedef struct Parade {
|
|
int len;
|
|
Vessel vessels[256];
|
|
} Parade;
|
|
|
|
Vessel *guest;
|
|
|
|
/* clang-format off */
|
|
|
|
static char *actions[12] = {
|
|
"create", "become", "enter", "leave",
|
|
"take", "drop", "warp", "transform",
|
|
"note", "program", "use", ""};
|
|
|
|
#pragma mark - Helpers
|
|
|
|
static unsigned char chex(char c) { if(c >= 'a' && c <= 'f') return 10 + c - 'a'; if(c >= 'A' && c <= 'F') return 10 + c - 'A'; return (c - '0') & 0xF; }
|
|
static unsigned char shex(char *s, int len) { int i, n = 0; for(i = 0; i < len; ++i) n |= (chex(s[i]) << ((len - i - 1) * 4)); return n; }
|
|
static int imin(int a, int b) { return a < b ? a : b; }
|
|
static int cisp(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; }
|
|
static int slen(char *s) { int n = 0; while(s[n] && s[++n]) ; return n; }
|
|
static int cpos(char *s, char c) { int i = 0; while(s[i] && s[i]) if(s[i++] == c) return i - 1; return -1; }
|
|
static char * spor(char *s, int c) { int i; for(i = slen(s); i >= 0; --i) if(s[i] == c) return s + i; return s - 1; }
|
|
static int scmp(char *a, char *b) { int i = 0; while(a[i] == b[i]) if(!a[i++]) return 1; return 0; }
|
|
static char * strm(char *s) { char *end; while(cisp(*s)) s++; if(*s == 0) return s; end = s + slen(s) - 1; while(end > s && cisp(*end)) end--; end[1] = '\0'; return s; }
|
|
static int afnd(char *src[], int len, char *val) { int i; for(i = 0; i < len; i++) if(scmp(src[i], val)) return i; return -1; }
|
|
static char * sstr(char *src, char *dest, int from, int to) { int i; char *a = (char *)src + from, *b = (char *)dest; for(i = 0; i < to; i++) b[i] = a[i]; dest[to] = '\0'; return dest; }
|
|
|
|
/* clang-format on */
|
|
|
|
#pragma mark - Generics
|
|
|
|
static int
|
|
isvisible(Vessel *g, Vessel *v)
|
|
{
|
|
if(g->parent != v->parent)
|
|
return 0;
|
|
if(g->parent == v)
|
|
return 0;
|
|
if(g == v)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
isparadox(Vessel *v)
|
|
{
|
|
return v->parent == v;
|
|
}
|
|
|
|
static char
|
|
rune(Vessel *v)
|
|
{
|
|
if(isparadox(v))
|
|
return '^';
|
|
if(slen(v->note) > 0)
|
|
return '*';
|
|
if(slen(v->prog) > 0)
|
|
return '+';
|
|
if(v->owner == guest)
|
|
return '~';
|
|
return 0;
|
|
}
|
|
|
|
static Vessel *
|
|
addvessel(Parade *p, Vessel *v, char *name)
|
|
{
|
|
Vessel *nv = &p->vessels[p->len];
|
|
nv->id = p->len;
|
|
nv->owner = v ? v : nv;
|
|
nv->parent = v ? v->parent : nv;
|
|
sstr(name, nv->name, 0, imin(slen(name), NAMELEN - 1));
|
|
p->len++;
|
|
return nv;
|
|
}
|
|
|
|
static Vessel *
|
|
findvisible(Parade *p, Vessel *v, char *name)
|
|
{
|
|
int i;
|
|
char *n = spor(name, ' ') + 1;
|
|
for(i = 0; i < p->len; ++i) {
|
|
if(!isvisible(v, &p->vessels[i]))
|
|
continue;
|
|
if(scmp(p->vessels[i].name, n))
|
|
return &p->vessels[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static Vessel *
|
|
findinventory(Parade *p, Vessel *v, char *name)
|
|
{
|
|
int i;
|
|
char *n = spor(name, ' ') + 1;
|
|
for(i = 0; i < p->len; ++i) {
|
|
if(&p->vessels[i] == v)
|
|
continue;
|
|
if(p->vessels[i].parent != v)
|
|
continue;
|
|
if(scmp(p->vessels[i].name, n))
|
|
return &p->vessels[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Vessel *
|
|
findany(Parade *p, char *name)
|
|
{
|
|
int i;
|
|
char *n = spor(name, ' ') + 1;
|
|
for(i = 0; i < p->len; ++i)
|
|
if(scmp(p->vessels[i].name, n))
|
|
return &p->vessels[i];
|
|
return NULL;
|
|
}
|
|
|
|
#pragma mark - Actions
|
|
|
|
static void
|
|
createvessel(Parade *p, char *val)
|
|
{
|
|
Vessel *v;
|
|
if(findany(p, val) || slen(val) < 4 || p->len > 255)
|
|
printf("You cannot create the %s.\n", val);
|
|
else {
|
|
v = addvessel(p, guest, spor(val, ' ') + 1);
|
|
printf("You created the %s%c.\n", v->name, rune(v));
|
|
}
|
|
}
|
|
|
|
static void
|
|
becomevessel(Parade *p, char *val)
|
|
{
|
|
Vessel *v = findany(p, val);
|
|
if(!v)
|
|
printf("You do not see the %s.\n", val);
|
|
else {
|
|
guest = v;
|
|
printf("You became the %s%c.\n", v->name, rune(v));
|
|
}
|
|
}
|
|
|
|
static void
|
|
entervessel(Parade *p, char *val)
|
|
{
|
|
Vessel *v = findvisible(p, guest, val);
|
|
if(!v)
|
|
printf("You do not see the %s.\n", val);
|
|
else {
|
|
guest->parent = v;
|
|
printf("You entered the %s%c.\n", v->name, rune(v));
|
|
}
|
|
}
|
|
|
|
static void
|
|
leavevessel(void)
|
|
{
|
|
Vessel *v = guest->parent;
|
|
if(v == v->parent)
|
|
printf("You cannot leave the %s%c.\n", v->name, rune(v));
|
|
else {
|
|
printf("You left the %s%c.\n", v->name, rune(v));
|
|
guest->parent = v->parent;
|
|
}
|
|
}
|
|
|
|
static void
|
|
takevessel(Parade *p, char *val)
|
|
{
|
|
Vessel *v = findvisible(p, guest, val);
|
|
if(!v)
|
|
printf("You do not see the %s.\n", val);
|
|
else {
|
|
v->parent = guest;
|
|
printf("You took the %s%c.\n", v->name, rune(v));
|
|
}
|
|
}
|
|
|
|
static void
|
|
dropvessel(Parade *p, char *val)
|
|
{
|
|
Vessel *v = findinventory(p, guest, val);
|
|
if(!v)
|
|
printf("You do not carry the %s.\n", val);
|
|
else {
|
|
v->parent = guest->parent->parent;
|
|
printf("You dropped the %s%c.\n", v->name, rune(v));
|
|
}
|
|
}
|
|
|
|
static void
|
|
warpvessel(Parade *p, char *val)
|
|
{
|
|
Vessel *v = findany(p, val);
|
|
if(!v)
|
|
printf("You cannot warp to the %s.\n", val);
|
|
else {
|
|
guest->parent = v;
|
|
printf("You warped to the %s%c.\n", v->name, rune(v));
|
|
}
|
|
}
|
|
|
|
static void
|
|
transformvessel(Parade *p, char *val)
|
|
{
|
|
char *name = spor(val, ' ') + 1;
|
|
if(findany(p, name) || slen(name) < 3)
|
|
printf("You cannot transform into the %s.\n", name);
|
|
else {
|
|
sstr(name, guest->name, 0, imin(slen(name), TEXTLEN - 1));
|
|
printf("You transformed into the %s%c.\n", guest->name, rune(guest));
|
|
}
|
|
}
|
|
|
|
static void
|
|
notevessel(char *val)
|
|
{
|
|
Vessel *v = guest->parent;
|
|
if(slen(val) < 1)
|
|
printf("You remove the note of the %s%c.\n", v->name, rune(v));
|
|
else {
|
|
sstr(val, v->note, 0, imin(slen(val), TEXTLEN - 1));
|
|
printf("You added a note to the %s%c.\n", v->name, rune(v));
|
|
}
|
|
}
|
|
|
|
static void
|
|
programvessel(char *val)
|
|
{
|
|
Vessel *v = guest->parent;
|
|
if(slen(val) < 1)
|
|
printf("You remove the program of the %s%c.\n", v->name, rune(v));
|
|
else {
|
|
sstr(val, v->prog, 0, imin(slen(val), TEXTLEN - 1));
|
|
printf("You programmed the %s%c.\n", v->name, rune(v));
|
|
}
|
|
}
|
|
|
|
static void
|
|
lookvessel(Parade *p)
|
|
{
|
|
int i;
|
|
if(isparadox(guest))
|
|
printf("You are the %s%c.\n", guest->name, rune(guest));
|
|
else
|
|
printf("You are the %s%c in the %s%c.\n",
|
|
guest->name,
|
|
rune(guest),
|
|
guest->parent->name,
|
|
rune(guest->parent));
|
|
if(slen(guest->parent->note) > 2)
|
|
printf("%s\n", guest->parent->note);
|
|
for(i = 0; i < p->len; ++i)
|
|
if(isvisible(guest, &p->vessels[i]))
|
|
printf("- %s%c\n", p->vessels[i].name, rune(&p->vessels[i]));
|
|
}
|
|
|
|
#pragma mark - Parade
|
|
|
|
static int usevessel(Parade *p, char *val);
|
|
|
|
static void
|
|
act(Parade *p, char *cmd, char *val)
|
|
{
|
|
/* clang-format off */
|
|
switch(afnd(actions, 12, cmd)) {
|
|
case 0x0: createvessel(p, val); break;
|
|
case 0x1: becomevessel(p, val); break;
|
|
case 0x2: entervessel(p, val); break;
|
|
case 0x3: leavevessel(); break;
|
|
case 0x4: takevessel(p, val); break;
|
|
case 0x5: dropvessel(p, val); break;
|
|
case 0x6: warpvessel(p, val); break;
|
|
case 0x7: transformvessel(p, val); break;
|
|
case 0x8: notevessel(val); break;
|
|
case 0x9: programvessel(val); break;
|
|
case 0xA: usevessel(p, val); break;
|
|
case 0xB: lookvessel(p); break;
|
|
default: printf("Unknown action: %s.\n", cmd); break;
|
|
}
|
|
/* clang-format off */
|
|
}
|
|
|
|
static Vessel *
|
|
spawn(Parade *p)
|
|
{
|
|
addvessel(p, NULL, "library");
|
|
addvessel(p, &p->vessels[0], "ghost");
|
|
return &p->vessels[1];
|
|
}
|
|
|
|
static int
|
|
parse(Parade *p, char *line, int id)
|
|
{
|
|
int split = cpos(line, '|');
|
|
int len = slen(line);
|
|
Vessel *nv = &p->vessels[id];
|
|
if(len < 22 || split < 0 || line[0] == ';')
|
|
return 0;
|
|
nv->id = id;
|
|
nv->owner = &p->vessels[shex(line, 2)];
|
|
nv->parent = &p->vessels[shex(line + 2, 2)];
|
|
strm(sstr(line, nv->name, 5, NAMELEN));
|
|
if(split > 23)
|
|
sstr(line, nv->note, 21, split - 22);
|
|
if(len - split > 3)
|
|
sstr(line, nv->prog, split + 2, len - split - 3);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
save(Parade *p, char *filename)
|
|
{
|
|
int i;
|
|
FILE *f = fopen(filename, "w");
|
|
for(i = 0; i < p->len; ++i)
|
|
fprintf(f, "%02x%02x %-15s %s | %s\n",
|
|
p->vessels[i].owner->id,
|
|
p->vessels[i].parent->id,
|
|
p->vessels[i].name,
|
|
p->vessels[i].note,
|
|
p->vessels[i].prog);
|
|
fclose(f);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
load(Parade *p, char *filename)
|
|
{
|
|
char line[BUFLEN];
|
|
FILE *f = fopen(filename, "r");
|
|
if(f == NULL)
|
|
return 1;
|
|
p->len = 0;
|
|
while(fgets(line, BUFLEN, f)) {
|
|
if(parse(p, line, p->len))
|
|
p->len++;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
answer(Parade *p, char *input)
|
|
{
|
|
int split = cpos(input, ' ');
|
|
char action[NAMELEN], value[TEXTLEN];
|
|
if(cpos(input, '|') >= 0)
|
|
return 1;
|
|
if(split >= 0) {
|
|
sstr(input, action, 0, split);
|
|
sstr(input, value, split + 1, imin(slen(input) - split, TEXTLEN - 1));
|
|
} else if(slen(input) < 2) {
|
|
action[0] = '\0';
|
|
value[0] = '\0';
|
|
} else if(slen(input) >= 0) {
|
|
sstr(input, action, 0, imin(slen(input), NAMELEN - 1));
|
|
value[0] = '\0';
|
|
}
|
|
if(scmp(action, "@quit"))
|
|
return 0;
|
|
if(scmp(action, "@save") && slen(value) > 3)
|
|
return save(p, spor(input, ' ') + 1);
|
|
if(scmp(action, "@load") && slen(value) > 3)
|
|
return load(p, spor(input, ' ') + 1);
|
|
act(p, action, value);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
usevessel(Parade *p, char *val)
|
|
{
|
|
Vessel *v = findvisible(p, guest, val);
|
|
if(!v)
|
|
printf("You do not see %s.\n", val);
|
|
else if(slen(v->prog) < 2)
|
|
printf("You cannot use %s%c.\n", val, rune(v));
|
|
else
|
|
answer(p, v->prog);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
listen(Parade *p)
|
|
{
|
|
char input[TEXTLEN];
|
|
printf("> ");
|
|
if(fgets(input, TEXTLEN, stdin))
|
|
return answer(p, strm(input));
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
Parade parade;
|
|
parade.len = 0;
|
|
guest = spawn(¶de);
|
|
if(argc == 2)
|
|
load(¶de, argv[1]);
|
|
printf("A %s%c appeared in the %s%c.\n",
|
|
guest->name,
|
|
rune(guest),
|
|
guest->parent->name,
|
|
rune(guest->parent));
|
|
while(listen(¶de))
|
|
;
|
|
return 0;
|
|
}
|