Sample NPC corpus data and map definition

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-02-18 19:25:47 +0100
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-02-18 19:25:47 +0100
Commit d8c826ac5738dc349734ab21d3f18abb1bac7c3a (patch)
-rw-r--r-- Makefile 8
-rw-r--r-- corpus/map1_bromm.txt 18
-rw-r--r-- corpus/map1_dagna.txt 18
-rw-r--r-- corpus/map1_keldor.txt 18
-rw-r--r-- corpus/map1_skara.txt 18
-rw-r--r-- corpus/map1_thrain.txt 18
-rw-r--r-- game.c 324
-rw-r--r-- maps.h 40
-rw-r--r-- maps/map1.h 6
-rw-r--r-- maps/map1.txt 6
-rw-r--r-- nonstd.h 6
-rw-r--r-- vectordb.c 1
-rw-r--r-- vectordb.h 2
13 files changed, 426 insertions, 57 deletions
diff --git a/Makefile b/Makefile
...
16
MAP_TXT := $(wildcard maps/*.txt)
16
MAP_TXT := $(wildcard maps/*.txt)
17
MAP_HEADERS := $(MAP_TXT:.txt=.h)
17
MAP_HEADERS := $(MAP_TXT:.txt=.h)
18
  
18
  
  
19
CORPUS_TXT := $(wildcard corpus/*.txt)
  
20
CORPUS_VDB := $(CORPUS_TXT:.txt=.vdb)
  
21
  
19
help: .help
22
help: .help
20
  
23
  
21
build/llama.cpp: .assure # Build llama.cpp libraries
24
build/llama.cpp: .assure # Build llama.cpp libraries
...
37
  
40
  
38
build/maps: $(MAP_HEADERS) # Generate maps in  C style header
41
build/maps: $(MAP_HEADERS) # Generate maps in  C style header
39
  
42
  
  
43
build/corpus: $(CORPUS_VDB) # Build vector DBs for all corpuses
  
44
  
40
run/fetch-models: .assure # Fetch GGUF models
45
run/fetch-models: .assure # Fetch GGUF models
41
	-mkdir -p models
46
	-mkdir -p models
42
	cd models && wget -nc -i ../models.txt
47
	cd models && wget -nc -i ../models.txt
...
55
  
60
  
56
maps/%.h: maps/%.txt .assure
61
maps/%.h: maps/%.txt .assure
57
	xxd -i $< > $@
62
	xxd -i $< > $@
  
63
  
  
64
corpus/%.vdb: corpus/%.txt build/context
  
65
	./context -i $< -o $@
diff --git a/corpus/map1_bromm.txt b/corpus/map1_bromm.txt
  
1
Bromm is a dwarf stonemason who measures walls by touch and sound.
  
2
Bromm keeps a black slate with chalk marks for every safe path in the ruins.
  
3
Bromm believes the northern ruins predate the current road by two eras.
  
4
Bromm once found a bronze hinge in the ruins and still carries it for luck.
  
5
Bromm teaches travelers how to test a stone by tapping for a hollow ring.
  
6
Bromm mistrusts quick repairs and prefers heavy timber bracing.
  
7
Bromm says the ruins smell of old lime and wet ash after rain.
  
8
Bromm trades small carvings for dried meat and lamp oil.
  
9
Bromm thinks the marsh lights are reflections from a buried lens.
  
10
Bromm has a friendly rivalry with Dagna about whose warnings are wiser.
  
11
Bromm keeps his beard braided with a single iron bead from his clan.
  
12
Bromm wants to map every chamber in the north before winter.
  
13
Bromm is soft spoken but grows excited when discussing arches.
  
14
Bromm believes the ruins hide a collapsed stair with carved runes.
  
15
Bromm says the safest approach is to enter at dawn and leave by noon.
  
16
Bromm can describe three alternate routes to avoid the broken bridge.
  
17
Bromm worries that careless digging will wake something that sleeps in stone.
  
18
Bromm asks visitors if they have seen mason marks shaped like a trident.
diff --git a/corpus/map1_dagna.txt b/corpus/map1_dagna.txt
  
1
Dagna is a dwarf well-keeper who knows every bucket and rope in the village.
  
2
Dagna believes the well is safe because the water tastes of iron, not rot.
  
3
Dagna keeps a ledger of how much water each household draws in a week.
  
4
Dagna replaced the well crank with a dwarven gear she forged herself.
  
5
Dagna says the well has a second shaft sealed by a stone plug.
  
6
Dagna once pulled up a smooth glass bead that does not scratch.
  
7
Dagna offers travelers a cup of water and a blunt warning about haste.
  
8
Dagna can name every herb that grows within ten paces of the well.
  
9
Dagna suspects the marsh lights are bait for thieves.
  
10
Dagna thinks Bromm worries too much about the ruins and not enough about the road.
  
11
Dagna keeps a small shrine to the Deep Mother near the well wall.
  
12
Dagna loves riddles and answers only after a trade of facts.
  
13
Dagna claims the well water calms fever if boiled with bitterroot.
  
14
Dagna dislikes gossip but listens closely for news of caravans.
  
15
Dagna believes a hidden aquifer feeds the village from the northern hills.
  
16
Dagna is saving for a brass pump to replace the old rope.
  
17
Dagna can spot forged coin by the sound it makes on stone.
  
18
Dagna asks travelers if they have seen a faint blue glow in deep water.
diff --git a/corpus/map1_keldor.txt b/corpus/map1_keldor.txt
  
1
Keldor is a dwarf scout who watches the marsh from the old footpath.
  
2
Keldor claims the lights in the marsh move in patterns like a slow dance.
  
3
Keldor keeps a lantern hooded until the last moment to avoid drawing notice.
  
4
Keldor believes the marsh hides a buried wagon sunk in peat.
  
5
Keldor can follow frog calls to find the driest stepping stones.
  
6
Keldor says the safest crossing is after three dry days, not two.
  
7
Keldor carries a whistle tuned to a pitch only his hound can hear.
  
8
Keldor tells stories of a pale heron that never casts a shadow.
  
9
Keldor thinks the ruins and the marsh are linked by an old drainage tunnel.
  
10
Keldor traded a silver button to learn a fisher's secret route.
  
11
Keldor trusts Dagna's water but refuses to drink after midnight.
  
12
Keldor marks his trail with tiny chips of white quartz.
  
13
Keldor says the marsh lights went dark on the night the moon turned red.
  
14
Keldor is curious about old maps and collects any scraps he finds.
  
15
Keldor believes Bromm's trident mark is a warning, not a signature.
  
16
Keldor is patient in silence but asks direct questions when pressed.
  
17
Keldor wants proof that the marsh lights are not a signal to smugglers.
  
18
Keldor asks travelers to describe any strange scents like bitter metal or smoke.
diff --git a/corpus/map1_skara.txt b/corpus/map1_skara.txt
  
1
Skara is a dwarf bell-ringer who keeps time for the village with a bronze handbell.
  
2
Skara claims the fog carries echoes that belong to no bell in town.
  
3
Skara keeps her bell clapper wrapped in cloth to avoid false rings.
  
4
Skara believes the marsh hides an old shrine with a cracked chime.
  
5
Skara can tell distance by the way sound bends in wet air.
  
6
Skara remembers every funeral toll and writes the names in a small book.
  
7
Skara warns travelers to avoid singing in the marsh after sunset.
  
8
Skara thinks Keldor's lights might be signals from smugglers.
  
9
Skara says Bromm once found a bell-shaped stone near the ruins.
  
10
Skara trades stories for thin copper wire and beeswax.
  
11
Skara is suspicious of mirrors and keeps hers covered.
  
12
Skara believes Dagna's well water dulls the ringing in her ears.
  
13
Skara says the bells in fog sound like chains, not bronze.
  
14
Skara is gentle in speech but firm about her warnings.
  
15
Skara wants to tune the village bell to a lower, steadier note.
  
16
Skara can teach a simple knock code used by miners.
  
17
Skara asks travelers if they have heard three rings with no pause.
  
18
Skara says the marsh grows quiet just before the lights appear.
diff --git a/corpus/map1_thrain.txt b/corpus/map1_thrain.txt
  
1
Thrain is a dwarf bridge warden who inspects beams by listening for a low hum.
  
2
Thrain keeps a pouch of pegs and wedges for emergency repairs.
  
3
Thrain believes the old bridge was built by traders, not soldiers.
  
4
Thrain marks safe planks with tiny chalk dots no one else notices.
  
5
Thrain once saved a cart by spotting a hairline crack at dawn.
  
6
Thrain says the river below turns louder right before a storm.
  
7
Thrain trades advice for nails, tar, and braided rope.
  
8
Thrain is skeptical of the marsh lights and calls them trick mirrors.
  
9
Thrain respects Dagna's ledger and asks her for bridge traffic counts.
  
10
Thrain thinks Bromm's trident mark is a builder's guild sign.
  
11
Thrain keeps a small tin whistle for signaling across the span.
  
12
Thrain fears rot more than storms and checks every joint twice.
  
13
Thrain wants to replace the center beam with black oak from the hills.
  
14
Thrain can point out a hidden ford two bends downstream.
  
15
Thrain says the safest crossing is single file with steady steps.
  
16
Thrain believes the bells in fog come from chains under the bridge.
  
17
Thrain asks travelers if they have spare pitch or tar.
  
18
Thrain is patient with questions but impatient with boasts.
diff --git a/game.c b/game.c
...
6
#define NONSTD_IMPLEMENTATION
6
#define NONSTD_IMPLEMENTATION
7
#include "nonstd.h"
7
#include "nonstd.h"
8
  
8
  
9
#include "maps/map1.h"
9
#include "maps.h"
10
  
10
  
11
#define MIN_W 40
11
#define MIN_W 40
12
#define MIN_H 12
12
#define MIN_H 12
...
49
	Inventory inventory;
49
	Inventory inventory;
50
} Player;
50
} Player;
51
  
51
  
  
52
#define DIALOG_HISTORY_MAX 16
  
53
  
52
typedef struct {
54
typedef struct {
53
	const unsigned char *data;
55
	char prompt[128];
54
	int len;
56
	char response[256];
55
	int width;
57
} DialogEntry;
56
	int height;
58
  
57
	u32 *cells;
59
typedef struct {
58
} Map;
60
	int open;
  
61
	char input[128];
  
62
	int input_len;
  
63
	int npc_index;
  
64
	DialogEntry entries[DIALOG_HISTORY_MAX];
  
65
	int entry_count;
  
66
} Dialog;
59
  
67
  
60
static int clamp(int value, int min, int max);
68
static int clamp(int value, int min, int max);
61
  
69
  
...
78
	tb_set_cell(x + w - 1, y + h - 1, CP_BR, fg, TB_DEFAULT);
86
	tb_set_cell(x + w - 1, y + h - 1, CP_BR, fg, TB_DEFAULT);
79
}
87
}
80
  
88
  
81
static void draw_room(int x, int y, int w, int h, uintattr_t fg) {
89
static void draw_border_bg(int x, int y, int w, int h, uintattr_t fg,
82
	draw_border(x, y, w, h, fg);
90
	uintattr_t bg) {
83
	tb_set_cell(x + w / 2, y, '+', fg, TB_DEFAULT);
91
	int ix;
  
92
	int iy;
  
93
  
  
94
	for (ix = 0; ix < w; ix++) {
  
95
		tb_set_cell(x + ix, y, CP_H, fg, bg);
  
96
		tb_set_cell(x + ix, y + h - 1, CP_H, fg, bg);
  
97
	}
  
98
	for (iy = 0; iy < h; iy++) {
  
99
		tb_set_cell(x, y + iy, CP_V, fg, bg);
  
100
		tb_set_cell(x + w - 1, y + iy, CP_V, fg, bg);
  
101
	}
  
102
  
  
103
	tb_set_cell(x, y, CP_TL, fg, bg);
  
104
	tb_set_cell(x + w - 1, y, CP_TR, fg, bg);
  
105
	tb_set_cell(x, y + h - 1, CP_BL, fg, bg);
  
106
	tb_set_cell(x + w - 1, y + h - 1, CP_BR, fg, bg);
84
}
107
}
85
  
108
  
86
static void get_layout(int w, int h, int *map_x, int *map_y, int *map_w,
109
static void get_layout(int w, int h, int *map_x, int *map_y, int *map_w,
...
187
  
210
  
188
static int map_is_walkable(const Map *map, int x, int y) {
211
static int map_is_walkable(const Map *map, int x, int y) {
189
	u32 ch = map_get(map, x, y);
212
	u32 ch = map_get(map, x, y);
190
	return ch == MAP_FLOOR_CH || ch == '$';
213
	return ch == MAP_FLOOR_CH || ch == '$' || ch == 'N'
  
214
		|| (ch >= '0' && ch <= '9');
  
215
}
  
216
  
  
217
static int npc_index_from_tile(u32 ch) {
  
218
	if (ch >= '0' && ch <= '9') {
  
219
		return (int)(ch - '0');
  
220
	}
  
221
	return -1;
191
}
222
}
192
  
223
  
193
static void map_free(Map *map) {
224
static void map_free(Map *map) {
...
245
			int mx = cam_x + ix;
276
			int mx = cam_x + ix;
246
			int my = cam_y + iy;
277
			int my = cam_y + iy;
247
			u32 ch = map_get(map, mx, my);
278
			u32 ch = map_get(map, mx, my);
  
279
			u32 draw_ch = (ch >= '0' && ch <= '9') ? 'N' : ch;
248
			uintattr_t fg = COLOR_WHITE_256;
280
			uintattr_t fg = COLOR_WHITE_256;
249
			if (ch == MAP_FLOOR_CH) {
281
			if (ch == MAP_FLOOR_CH) {
250
				fg = MAP_FLOOR_FG;
282
				fg = MAP_FLOOR_FG;
...
254
				fg = COLOR_ORANGE_256;
286
				fg = COLOR_ORANGE_256;
255
			} else if (ch == 'B' || ch == 'S' || ch == 'G') {
287
			} else if (ch == 'B' || ch == 'S' || ch == 'G') {
256
				fg = COLOR_RED_256;
288
				fg = COLOR_RED_256;
257
			} else if (ch == 'N') {
289
			} else if (ch == 'N' || (ch >= '0' && ch <= '9')) {
258
				fg = COLOR_CYAN_256;
290
				fg = COLOR_CYAN_256;
259
			} else if (ch >= MAP_BORDER_MIN && ch <= MAP_BORDER_MAX) {
291
			} else if (ch >= MAP_BORDER_MIN && ch <= MAP_BORDER_MAX) {
260
				fg = COLOR_BORDER_256;
292
				fg = COLOR_BORDER_256;
261
			}
293
			}
262
			tb_set_cell(map_x + ix, map_y + iy, ch, fg, TB_DEFAULT);
294
			tb_set_cell(map_x + ix, map_y + iy, draw_ch, fg, TB_DEFAULT);
263
		}
295
		}
264
	}
296
	}
265
  
297
  
...
357
	status_msg = message ? message : "";
389
	status_msg = message ? message : "";
358
}
390
}
359
  
391
  
  
392
static void copy_truncated(char *dst, size_t dst_size, const char *src, int max_chars) {
  
393
	int i = 0;
  
394
	if (dst_size == 0) {
  
395
		return;
  
396
	}
  
397
	if (max_chars < 0) {
  
398
		max_chars = 0;
  
399
	}
  
400
	while (i < max_chars && src[i] != '\0' && i < (int)dst_size - 1) {
  
401
		dst[i] = src[i];
  
402
		i++;
  
403
	}
  
404
	dst[i] = '\0';
  
405
}
  
406
  
  
407
static void dialog_open(Dialog *dialog, int npc_index) {
  
408
	dialog->open = 1;
  
409
	dialog->input_len = 0;
  
410
	dialog->input[0] = '\0';
  
411
	dialog->npc_index = npc_index;
  
412
}
  
413
  
  
414
static void dialog_close(Dialog *dialog) {
  
415
	dialog->open = 0;
  
416
	dialog->npc_index = -1;
  
417
}
  
418
  
  
419
static void dialog_append(Dialog *dialog, uint32_t ch) {
  
420
	if (ch < 32 || ch > 126) {
  
421
		return;
  
422
	}
  
423
	if (dialog->input_len >= (int)(sizeof(dialog->input) - 1)) {
  
424
		return;
  
425
	}
  
426
	dialog->input[dialog->input_len++] = (char)ch;
  
427
	dialog->input[dialog->input_len] = '\0';
  
428
}
  
429
  
  
430
static void dialog_backspace(Dialog *dialog) {
  
431
	if (dialog->input_len <= 0) {
  
432
		return;
  
433
	}
  
434
	dialog->input_len--;
  
435
	dialog->input[dialog->input_len] = '\0';
  
436
}
  
437
  
  
438
static void dialog_submit(Dialog *dialog, const GameMap *game_map) {
  
439
	if (dialog->input_len == 0) {
  
440
		return;
  
441
	}
  
442
	{
  
443
		const char *demo = "Demo reply: The old ruins are north of here.";
  
444
		const char *reply = demo;
  
445
		if (game_map && dialog->npc_index >= 0 && dialog->npc_index < 10) {
  
446
			const char *npc_reply = game_map->npcs[dialog->npc_index].reply;
  
447
			if (npc_reply && npc_reply[0] != '\0') {
  
448
				reply = npc_reply;
  
449
			}
  
450
		}
  
451
		if (dialog->entry_count >= DIALOG_HISTORY_MAX) {
  
452
			for (int i = 1; i < DIALOG_HISTORY_MAX; i++) {
  
453
				dialog->entries[i - 1] = dialog->entries[i];
  
454
			}
  
455
			dialog->entry_count = DIALOG_HISTORY_MAX - 1;
  
456
		}
  
457
		snprintf(dialog->entries[dialog->entry_count].prompt,
  
458
			sizeof(dialog->entries[dialog->entry_count].prompt), "%s", dialog->input);
  
459
		snprintf(dialog->entries[dialog->entry_count].response,
  
460
			sizeof(dialog->entries[dialog->entry_count].response), "%s", reply);
  
461
		dialog->entry_count++;
  
462
	}
  
463
	dialog->input_len = 0;
  
464
	dialog->input[0] = '\0';
  
465
}
  
466
  
  
467
static void update_npc_status(const GameMap *game_map, int npc_index) {
  
468
	static char status_buf[128];
  
469
	const char *name = NULL;
  
470
	if (game_map && npc_index >= 0 && npc_index < 10) {
  
471
		name = game_map->npcs[npc_index].name;
  
472
	}
  
473
	if (name && name[0] != '\0') {
  
474
		snprintf(status_buf, sizeof(status_buf), "You approach %s.", name);
  
475
	} else {
  
476
		snprintf(status_buf, sizeof(status_buf), "You approach the NPC.");
  
477
	}
  
478
	update_status(status_buf);
  
479
}
  
480
  
360
static void render(const Map *map, const Player *player, int *cam_x,
481
static void render(const Map *map, const Player *player, int *cam_x,
361
	int *cam_y, int *out_view_w, int *out_view_h) {
482
	int *cam_y, int *out_view_w, int *out_view_h, const Dialog *dialog) {
362
	int w;
483
	int w;
363
	int h;
484
	int h;
364
	int map_x;
485
	int map_x;
...
436
	tb_print(2, msg1_y, COLOR_GREEN_256, TB_DEFAULT, status_msg);
557
	tb_print(2, msg1_y, COLOR_GREEN_256, TB_DEFAULT, status_msg);
437
	tb_print(2, msg2_y, COLOR_WHITE_256, TB_DEFAULT, "Move: arrows  Quit: q/ESC");
558
	tb_print(2, msg2_y, COLOR_WHITE_256, TB_DEFAULT, "Move: arrows  Quit: q/ESC");
438
  
559
  
  
560
	if (dialog->open) {
  
561
		int box_w = map_w - 4;
  
562
		int box_h = 12;
  
563
		int box_x = map_x + 2;
  
564
		int box_y = map_y + map_h - box_h - 1;
  
565
		if (box_w > w - 2) {
  
566
			box_w = w - 2;
  
567
			box_x = 1;
  
568
		}
  
569
		if (box_h > h - 2) {
  
570
			box_h = h - 2;
  
571
			box_y = 1;
  
572
		}
  
573
		if (box_w < 20) {
  
574
			box_w = 20;
  
575
			box_x = map_x + 1;
  
576
		}
  
577
		if (box_y < map_y + 1) {
  
578
			box_y = map_y + 1;
  
579
		}
  
580
		for (int iy = 0; iy < box_h; iy++) {
  
581
			for (int ix = 0; ix < box_w; ix++) {
  
582
				tb_set_cell(box_x + ix, box_y + iy, ' ', COLOR_WHITE_256, 19);
  
583
			}
  
584
		}
  
585
		draw_border_bg(box_x, box_y, box_w, box_h, COLOR_WHITE_256, 19);
  
586
		{
  
587
			int input_y = box_y + box_h - 3;
  
588
			int footer_y = box_y + box_h - 2;
  
589
			int log_y = box_y + 1;
  
590
			int max_lines = input_y - log_y;
  
591
			int max_text = box_w - 4 - 5;
  
592
			int line = 0;
  
593
  
  
594
			if (max_text < 0) {
  
595
				max_text = 0;
  
596
			}
  
597
			int max_entries = max_lines / 2;
  
598
			int start = dialog->entry_count - max_entries;
  
599
			if (start < 0) {
  
600
				start = 0;
  
601
			}
  
602
			for (int i = start; i < dialog->entry_count && line + 1 <= max_lines; i++) {
  
603
				char prompt_buf[128];
  
604
				char response_buf[256];
  
605
				copy_truncated(prompt_buf, sizeof(prompt_buf), dialog->entries[i].prompt, max_text);
  
606
				copy_truncated(response_buf, sizeof(response_buf), dialog->entries[i].response, max_text);
  
607
				if (line < max_lines) {
  
608
				tb_printf(box_x + 2, log_y + line, COLOR_WHITE_256, 19, "You: %s", prompt_buf);
  
609
					line++;
  
610
				}
  
611
				if (line < max_lines) {
  
612
					tb_printf(box_x + 2, log_y + line, COLOR_GREEN_256, 19, "NPC: %s", response_buf);
  
613
					line++;
  
614
				}
  
615
			}
  
616
  
  
617
			tb_printf(box_x + 2, input_y, COLOR_WHITE_256, 19, "Say: %s", dialog->input);
  
618
			{
  
619
				int cursor_x = box_x + 2 + 5 + dialog->input_len;
  
620
				int cursor_y = input_y;
  
621
				if (cursor_x < box_x + box_w - 1) {
  
622
					tb_set_cell(cursor_x, cursor_y, '_', COLOR_WHITE_256 | TB_BOLD, 19);
  
623
				}
  
624
			}
  
625
			tb_print(box_x + 2, footer_y, COLOR_WHITE_256, 19, "Enter: send  ESC: close");
  
626
		}
  
627
	}
  
628
  
439
	tb_present();
629
	tb_present();
440
	*out_view_w = view_w;
630
	*out_view_w = view_w;
441
	*out_view_h = view_h;
631
	*out_view_h = view_h;
...
453
  
643
  
454
int main(void) {
644
int main(void) {
455
	Player player = {0};
645
	Player player = {0};
456
	Map map = {0};
646
	array(GameMap) maps;
  
647
	GameMap map1 = {0};
  
648
	GameMap *current_map = NULL;
457
	int running = 1;
649
	int running = 1;
458
	int view_w = 0;
650
	int view_w = 0;
459
	int view_h = 0;
651
	int view_h = 0;
460
	int cam_x = 0;
652
	int cam_x = 0;
461
	int cam_y = 0;
653
	int cam_y = 0;
  
654
	Dialog dialog = {0};
462
  
655
  
463
	player_init(&player);
656
	player_init(&player);
464
	map_init(&map, maps_map1_txt, (int)maps_map1_txt_len);
657
	array_init(maps);
  
658
	map1 = make_map1();
  
659
	array_push(maps, map1);
  
660
	current_map = &maps.data[0];
  
661
	map_init(&current_map->map, current_map->data, current_map->len);
465
  
662
  
466
	if (tb_init() != TB_OK) {
663
	if (tb_init() != TB_OK) {
467
		fprintf(stderr, "Failed to init termbox.\n");
664
		fprintf(stderr, "Failed to init termbox.\n");
...
474
	while (running) {
671
	while (running) {
475
		struct tb_event ev;
672
		struct tb_event ev;
476
  
673
  
477
		render(&map, &player, &cam_x, &cam_y, &view_w, &view_h);
674
		render(&current_map->map, &player, &cam_x, &cam_y, &view_w, &view_h, &dialog);
478
		tb_poll_event(&ev);
675
		tb_poll_event(&ev);
479
		if (ev.type == TB_EVENT_KEY) {
676
		if (ev.type == TB_EVENT_KEY) {
480
			if (ev.key == TB_KEY_ESC || ev.ch == 'q') {
677
			if (dialog.open) {
481
				running = 0;
678
				if (ev.key == TB_KEY_ESC) {
482
			} else if (ev.key == TB_KEY_ARROW_UP) {
679
					dialog_close(&dialog);
483
				int next_y = player.y - 1;
680
				} else if (ev.key == TB_KEY_ENTER) {
484
				if (map_is_walkable(&map, player.x, next_y)) {
681
					dialog_submit(&dialog, current_map);
485
					player.y = next_y;
682
				} else if (ev.key == TB_KEY_BACKSPACE || ev.key == TB_KEY_BACKSPACE2) {
486
				}
683
					dialog_backspace(&dialog);
487
			} else if (ev.key == TB_KEY_ARROW_DOWN) {
684
				} else if (ev.ch) {
488
				int next_y = player.y + 1;
685
					dialog_append(&dialog, ev.ch);
489
				if (map_is_walkable(&map, player.x, next_y)) {
  
490
					player.y = next_y;
  
491
				}
686
				}
492
			} else if (ev.key == TB_KEY_ARROW_LEFT) {
687
			} else {
493
				int next_x = player.x - 1;
688
				if (ev.key == TB_KEY_ESC || ev.ch == 'q') {
494
				if (map_is_walkable(&map, next_x, player.y)) {
689
					running = 0;
495
					player.x = next_x;
690
				} else if (ev.key == TB_KEY_ARROW_UP) {
  
691
					int next_y = player.y - 1;
  
692
					u32 target = map_get(&current_map->map, player.x, next_y);
  
693
					int npc_index = npc_index_from_tile(target);
  
694
					if (target == 'N' || npc_index >= 0) {
  
695
						dialog_open(&dialog, npc_index);
  
696
						update_npc_status(current_map, npc_index);
  
697
					} else if (map_is_walkable(&current_map->map, player.x, next_y)) {
  
698
						player.y = next_y;
  
699
					}
  
700
				} else if (ev.key == TB_KEY_ARROW_DOWN) {
  
701
					int next_y = player.y + 1;
  
702
					u32 target = map_get(&current_map->map, player.x, next_y);
  
703
					int npc_index = npc_index_from_tile(target);
  
704
					if (target == 'N' || npc_index >= 0) {
  
705
						dialog_open(&dialog, npc_index);
  
706
						update_npc_status(current_map, npc_index);
  
707
					} else if (map_is_walkable(&current_map->map, player.x, next_y)) {
  
708
						player.y = next_y;
  
709
					}
  
710
				} else if (ev.key == TB_KEY_ARROW_LEFT) {
  
711
					int next_x = player.x - 1;
  
712
					u32 target = map_get(&current_map->map, next_x, player.y);
  
713
					int npc_index = npc_index_from_tile(target);
  
714
					if (target == 'N' || npc_index >= 0) {
  
715
						dialog_open(&dialog, npc_index);
  
716
						update_npc_status(current_map, npc_index);
  
717
					} else if (map_is_walkable(&current_map->map, next_x, player.y)) {
  
718
						player.x = next_x;
  
719
					}
  
720
				} else if (ev.key == TB_KEY_ARROW_RIGHT) {
  
721
					int next_x = player.x + 1;
  
722
					u32 target = map_get(&current_map->map, next_x, player.y);
  
723
					int npc_index = npc_index_from_tile(target);
  
724
					if (target == 'N' || npc_index >= 0) {
  
725
						dialog_open(&dialog, npc_index);
  
726
						update_npc_status(current_map, npc_index);
  
727
					} else if (map_is_walkable(&current_map->map, next_x, player.y)) {
  
728
						player.x = next_x;
  
729
					}
496
				}
730
				}
497
			} else if (ev.key == TB_KEY_ARROW_RIGHT) {
731
				if (map_get(&current_map->map, player.x, player.y) == '$') {
498
				int next_x = player.x + 1;
732
					player.gold += 10;
499
				if (map_is_walkable(&map, next_x, player.y)) {
733
					map_set(&current_map->map, player.x, player.y, MAP_FLOOR_CH);
500
					player.x = next_x;
734
					update_status("You pick up 10 gold.");
501
				}
735
				}
502
			}
736
			}
503
			if (map_get(&map, player.x, player.y) == '$') {
737
			player.x = clamp(player.x, 0, current_map->map.width > 1 ? current_map->map.width - 1 : 0);
504
				player.gold += 10;
738
			player.y = clamp(player.y, 0, current_map->map.height > 1 ? current_map->map.height - 1 : 0);
505
				map_set(&map, player.x, player.y, MAP_FLOOR_CH);
  
506
				update_status("You pick up 10 gold.");
  
507
			}
  
508
			player.x = clamp(player.x, 0, map.width > 1 ? map.width - 1 : 0);
  
509
			player.y = clamp(player.y, 0, map.height > 1 ? map.height - 1 : 0);
  
510
		} else if (ev.type == TB_EVENT_RESIZE) {
739
		} else if (ev.type == TB_EVENT_RESIZE) {
511
			player.x = clamp(player.x, 0, map.width > 1 ? map.width - 1 : 0);
740
			player.x = clamp(player.x, 0, current_map->map.width > 1 ? current_map->map.width - 1 : 0);
512
			player.y = clamp(player.y, 0, map.height > 1 ? map.height - 1 : 0);
741
			player.y = clamp(player.y, 0, current_map->map.height > 1 ? current_map->map.height - 1 : 0);
513
		}
742
		}
514
	}
743
	}
515
  
744
  
516
	player_free(&player);
745
	player_free(&player);
517
	map_free(&map);
746
	for (size_t i = 0; i < maps.length; i++) {
  
747
		map_free(&maps.data[i].map);
  
748
	}
  
749
	array_free(maps);
518
	tb_shutdown();
750
	tb_shutdown();
519
	return 0;
751
	return 0;
520
}
752
}
diff --git a/maps.h b/maps.h
  
1
#ifndef MAPS_H
  
2
#define MAPS_H
  
3
  
  
4
#include "nonstd.h"
  
5
  
  
6
#include "maps/map1.h"
  
7
  
  
8
typedef struct {
  
9
	const unsigned char *data;
  
10
	int len;
  
11
	int width;
  
12
	int height;
  
13
	u32 *cells;
  
14
} Map;
  
15
  
  
16
typedef struct {
  
17
	const char *name;
  
18
	const char *reply;
  
19
} NpcSettings;
  
20
  
  
21
typedef struct {
  
22
	const unsigned char *data;
  
23
	int len;
  
24
	Map map;
  
25
	NpcSettings npcs[10];
  
26
} GameMap;
  
27
  
  
28
static inline GameMap make_map1(void) {
  
29
	GameMap map = {0};
  
30
	map.data = maps_map1_txt;
  
31
	map.len = (int)maps_map1_txt_len;
  
32
	map.npcs[0] = (NpcSettings){.name = "Bromm", .reply = "Bromm: The old ruins are north of here."};
  
33
	map.npcs[1] = (NpcSettings){.name = "Dagna", .reply = "Dagna: The well is safe, mostly."};
  
34
	map.npcs[2] = (NpcSettings){.name = "Keldor", .reply = "Keldor: I saw lights in the marsh last night."};
  
35
	map.npcs[3] = (NpcSettings){.name = "Thrain", .reply = "Thrain: Mind the bridge; the beams sing when they're weak."};
  
36
	map.npcs[4] = (NpcSettings){.name = "Skara", .reply = "Skara: If you hear bells in the fog, turn back."};
  
37
	return map;
  
38
}
  
39
  
  
40
#endif
diff --git a/maps/map1.h b/maps/map1.h
...
24
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
24
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
25
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
25
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
26
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
26
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
27
  0x2e, 0x2e, 0x0a, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
27
  0x2e, 0x2e, 0x0a, 0x2e, 0x2e, 0x2e, 0x2e, 0x30, 0x31, 0x32, 0x33, 0x2e,
28
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
28
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
29
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
29
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
30
  0x2e, 0x2e, 0x53, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x7e,
30
  0x2e, 0x2e, 0x53, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x7e,
...
104
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
104
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
105
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
105
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
106
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x0a, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
106
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x0a, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
107
  0x2e, 0x2e, 0x2e, 0xe2, 0x95, 0x91, 0x2e, 0x2e, 0x2e, 0x4e, 0x2e, 0x2e,
107
  0x2e, 0x2e, 0x2e, 0xe2, 0x95, 0x91, 0x2e, 0x2e, 0x2e, 0x34, 0x2e, 0x2e,
108
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0xe2, 0x95, 0x91,
108
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0xe2, 0x95, 0x91,
109
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
109
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
110
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
110
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
...
114
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
114
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
115
  0x2e, 0x2e, 0x0a, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
115
  0x2e, 0x2e, 0x0a, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
116
  0xe2, 0x95, 0x91, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
116
  0xe2, 0x95, 0x91, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
117
  0x2e, 0x2e, 0x4e, 0x2e, 0x2e, 0x2e, 0xe2, 0x95, 0x91, 0x2e, 0x24, 0x2e,
117
  0x2e, 0x2e, 0x35, 0x2e, 0x2e, 0x2e, 0xe2, 0x95, 0x91, 0x2e, 0x24, 0x2e,
118
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x24,
118
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x24,
119
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
119
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
120
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
120
  0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
...
diff --git a/maps/map1.txt b/maps/map1.txt
1
....................................................................................................
1
....................................................................................................
2
..................~~~~~~............................................................................
2
..................~~~~~~............................................................................
3
...................~~~~.............................................................................
3
...................~~~~.............................................................................
4
...................................S........~~~~~~~~~...............................................
4
....0123...........................S........~~~~~~~~~...............................................
5
........G...................................~~~~~~~~~...............................................
5
........G...................................~~~~~~~~~...............................................
6
............................................~~~~~~~~................................................
6
............................................~~~~~~~~................................................
7
............................................~~~~~~.......G..........................................
7
............................................~~~~~~.......G..........................................
...
10
......$..╔═══════════════╗.........................................$................................
10
......$..╔═══════════════╗.........................................$................................
11
..$......║...............║..........................................................................
11
..$......║...............║..........................................................................
12
.........║...............║..........................................................................
12
.........║...............║..........................................................................
13
.........║...N...........║..........................................................................
13
.........║...4...........║..........................................................................
14
.........║...........N...║.$............$...........................................................
14
.........║...........5...║.$............$...........................................................
15
.........║...............║..........................................................................
15
.........║...............║..........................................................................
16
.........║...............║...............................G..........................................
16
.........║...............║...............................G..........................................
17
.........║...............║...................................................G......................
17
.........║...............║...................................................G......................
...
diff --git a/nonstd.h b/nonstd.h
...
334
#define COLOR_WARNING "\033[33m"
334
#define COLOR_WARNING "\033[33m"
335
#define COLOR_ERROR "\033[31m"
335
#define COLOR_ERROR "\033[31m"
336
  
336
  
337
#endif // NONSTD_H
  
338
  
  
339
#ifdef NONSTD_IMPLEMENTATION
337
#ifdef NONSTD_IMPLEMENTATION
340
  
338
  
341
NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) {
339
NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) {
...
807
  
805
  
808
#endif // NONSTD_IMPLEMENTATION
806
#endif // NONSTD_IMPLEMENTATION
809
  
807
  
  
808
#endif // NONSTD_H
  
809
  
810
/*
810
/*
811
BSD 2-Clause License
811
BSD 2-Clause License
812
  
812
  
...
832
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
832
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
833
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
833
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
834
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
834
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
835
*/
835
*/
diff --git a/vectordb.c b/vectordb.c
...
2
#include <string.h>
2
#include <string.h>
3
#include <math.h>
3
#include <math.h>
4
#include <stdint.h>
4
#include <stdint.h>
  
5
#include <errno.h>
5
  
6
  
6
#include "llama.h"
7
#include "llama.h"
7
#include "vectordb.h"
8
#include "vectordb.h"
...
diff --git a/vectordb.h b/vectordb.h
...
3
  
3
  
4
#include "llama.h"
4
#include "llama.h"
5
  
5
  
6
#include <errno.h>
  
7
  
  
8
#define VDB_MAX_DOCS    1000
6
#define VDB_MAX_DOCS    1000
9
#define VDB_EMBED_SIZE  768
7
#define VDB_EMBED_SIZE  768
10
#define VDB_MAX_TEXT    1024
8
#define VDB_MAX_TEXT    1024
...