aboutsummaryrefslogtreecommitdiff
path: root/game.c
diff options
context:
space:
mode:
Diffstat (limited to 'game.c')
-rw-r--r--game.c860
1 files changed, 813 insertions, 47 deletions
diff --git a/game.c b/game.c
index f036fb2..13297c7 100644
--- a/game.c
+++ b/game.c
@@ -1,4 +1,8 @@
1#include <getopt.h>
1#include <stdio.h> 2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5#include <strings.h>
2 6
3#define TB_IMPL 7#define TB_IMPL
4#include "termbox2.h" 8#include "termbox2.h"
@@ -6,6 +10,9 @@
6#define NONSTD_IMPLEMENTATION 10#define NONSTD_IMPLEMENTATION
7#include "nonstd.h" 11#include "nonstd.h"
8 12
13#include "llama.h"
14#include "models.h"
15#include "vectordb.h"
9#include "maps.h" 16#include "maps.h"
10 17
11#define MIN_W 40 18#define MIN_W 40
@@ -61,12 +68,38 @@ typedef struct {
61 char input[128]; 68 char input[128];
62 int input_len; 69 int input_len;
63 int npc_index; 70 int npc_index;
71 const char *npc_name;
64 DialogEntry entries[DIALOG_HISTORY_MAX]; 72 DialogEntry entries[DIALOG_HISTORY_MAX];
65 int entry_count; 73 int entry_count;
66} Dialog; 74} Dialog;
67 75
76typedef struct {
77 const ModelConfig *model_cfg;
78 struct llama_model *model;
79 struct llama_model *embed_model;
80 struct llama_context *embed_ctx;
81 VectorDB *npc_dbs;
82 int *npc_db_loaded;
83 int verbose;
84} GameRuntime;
85
86static void llama_log_callback(enum ggml_log_level level, const char *text, void *user_data) {
87 (void)level;
88 (void)user_data;
89 (void)text;
90}
91
68static int clamp(int value, int min, int max); 92static int clamp(int value, int min, int max);
69 93
94static void show_help(const char *prog) {
95 printf("Usage: %s [OPTIONS]\n", prog);
96 printf("Options:\n");
97 printf(" -m, --model <name> Specify model to use (default: first model)\n");
98 printf(" -e, --embed-model <name> Specify model to use for embeddings\n");
99 printf(" -v, --verbose Enable verbose logging\n");
100 printf(" -h, --help Show this help message\n");
101}
102
70static void draw_border(int x, int y, int w, int h, uintattr_t fg) { 103static void draw_border(int x, int y, int w, int h, uintattr_t fg) {
71 int ix; 104 int ix;
72 int iy; 105 int iy;
@@ -87,7 +120,7 @@ static void draw_border(int x, int y, int w, int h, uintattr_t fg) {
87} 120}
88 121
89static void draw_border_bg(int x, int y, int w, int h, uintattr_t fg, 122static void draw_border_bg(int x, int y, int w, int h, uintattr_t fg,
90 uintattr_t bg) { 123 uintattr_t bg) {
91 int ix; 124 int ix;
92 int iy; 125 int iy;
93 126
@@ -107,8 +140,8 @@ static void draw_border_bg(int x, int y, int w, int h, uintattr_t fg,
107} 140}
108 141
109static void get_layout(int w, int h, int *map_x, int *map_y, int *map_w, 142static void get_layout(int w, int h, int *map_x, int *map_y, int *map_w,
110 int *map_h, int *side_x, int *side_y, int *side_w, int *side_h, 143 int *map_h, int *side_x, int *side_y, int *side_w, int *side_h,
111 int *msg1_y, int *msg2_y) { 144 int *msg1_y, int *msg2_y) {
112 *map_x = 0; 145 *map_x = 0;
113 *map_y = 0; 146 *map_y = 0;
114 *map_w = w - SIDEBAR_W; 147 *map_w = w - SIDEBAR_W;
@@ -226,7 +259,7 @@ static void map_free(Map *map) {
226} 259}
227 260
228static void update_camera(const Map *map, int view_w, int view_h, 261static void update_camera(const Map *map, int view_w, int view_h,
229 const Player *player, int *cam_x, int *cam_y) { 262 const Player *player, int *cam_x, int *cam_y) {
230 int max_cam_x; 263 int max_cam_x;
231 int max_cam_y; 264 int max_cam_y;
232 int margin_x; 265 int margin_x;
@@ -267,7 +300,7 @@ static void update_camera(const Map *map, int view_w, int view_h,
267} 300}
268 301
269static void draw_map(const Map *map, int map_x, int map_y, int view_w, 302static void draw_map(const Map *map, int map_x, int map_y, int view_w,
270 int view_h, const Player *player, int cam_x, int cam_y) { 303 int view_h, const Player *player, int cam_x, int cam_y) {
271 int ix; 304 int ix;
272 int iy; 305 int iy;
273 306
@@ -296,7 +329,7 @@ static void draw_map(const Map *map, int map_x, int map_y, int view_w,
296 } 329 }
297 330
298 if (player->x >= cam_x && player->x < cam_x + view_w && player->y >= cam_y 331 if (player->x >= cam_x && player->x < cam_x + view_w && player->y >= cam_y
299 && player->y < cam_y + view_h) { 332 && player->y < cam_y + view_h) {
300 int sx = map_x + (player->x - cam_x); 333 int sx = map_x + (player->x - cam_x);
301 int sy = map_y + (player->y - cam_y); 334 int sy = map_y + (player->y - cam_y);
302 tb_set_cell(sx, sy, '@', COLOR_GREEN_256 | TB_BOLD, TB_DEFAULT); 335 tb_set_cell(sx, sy, '@', COLOR_GREEN_256 | TB_BOLD, TB_DEFAULT);
@@ -324,7 +357,7 @@ static void draw_progress_bar(int x, int y, int w, int value, int max) {
324 filled = (inner_w * value) / max; 357 filled = (inner_w * value) / max;
325 tb_set_cell(x, y, '[', COLOR_WHITE_256, TB_DEFAULT); 358 tb_set_cell(x, y, '[', COLOR_WHITE_256, TB_DEFAULT);
326 for (ix = 0; ix < inner_w; ix++) { 359 for (ix = 0; ix < inner_w; ix++) {
327 uintattr_t fg = ix < filled ? COLOR_GREEN_256 : COLOR_WHITE_256; 360 uintattr_t fg = ix < filled ? COLOR_GREEN_256 : COLOR_WHITE_256;
328 uint32_t ch = ix < filled ? '=' : ' '; 361 uint32_t ch = ix < filled ? '=' : ' ';
329 tb_set_cell(x + 1 + ix, y, ch, fg, TB_DEFAULT); 362 tb_set_cell(x + 1 + ix, y, ch, fg, TB_DEFAULT);
330 } 363 }
@@ -389,31 +422,127 @@ static void update_status(const char *message) {
389 status_msg = message ? message : ""; 422 status_msg = message ? message : "";
390} 423}
391 424
392static void copy_truncated(char *dst, size_t dst_size, const char *src, int max_chars) { 425static int draw_wrapped(int x, int y, int max_lines, int box_w, uintattr_t fg,
393 int i = 0; 426 uintattr_t bg, const char *prefix, const char *text) {
394 if (dst_size == 0) { 427 if (max_lines <= 0 || box_w <= 0 || text == NULL) {
395 return; 428 return 0;
429 }
430 int lines = 0;
431 int prefix_len = prefix ? (int)strlen(prefix) : 0;
432 if (prefix_len < 0) {
433 prefix_len = 0;
434 }
435 int avail = box_w - 4 - prefix_len;
436 if (avail < 1) {
437 return 0;
438 }
439 char pad[64];
440 int pad_len = prefix_len < (int)sizeof(pad) - 1 ? prefix_len : (int)sizeof(pad) - 1;
441 for (int i = 0; i < pad_len; i++) {
442 pad[i] = ' ';
443 }
444 pad[pad_len] = '\0';
445 const char *p = text;
446 while (*p != '\0' && lines < max_lines) {
447 while (*p == ' ') {
448 p++;
449 }
450 int line_len = 0;
451 int last_space = -1;
452 for (int i = 0; i < avail && p[i] != '\0'; i++) {
453 if (p[i] == '\n') {
454 line_len = i;
455 break;
456 }
457 if (p[i] == ' ') {
458 last_space = i;
459 }
460 line_len = i + 1;
461 }
462 if (line_len == 0) {
463 break;
464 }
465 int cut = line_len;
466 if (cut == avail && p[cut] != '\0' && last_space > 0) {
467 cut = last_space;
468 }
469 char buf[512];
470 int copy_len = cut < (int)sizeof(buf) - 1 ? cut : (int)sizeof(buf) - 1;
471 memcpy(buf, p, (size_t)copy_len);
472 buf[copy_len] = '\0';
473 while (copy_len > 0 && buf[copy_len - 1] == ' ') {
474 buf[copy_len - 1] = '\0';
475 copy_len--;
476 }
477 const char *line_prefix = (lines == 0) ? (prefix ? prefix : "") : pad;
478 tb_printf(x, y + lines, fg, bg, "%s%s", line_prefix, buf);
479 lines++;
480 p += cut;
481 if (*p == '\n') {
482 p++;
483 }
484 }
485 return lines;
486}
487
488static int count_wrapped_lines(int box_w, const char *prefix, const char *text) {
489 if (box_w <= 0 || text == NULL) {
490 return 0;
491 }
492 int prefix_len = prefix ? (int)strlen(prefix) : 0;
493 if (prefix_len < 0) {
494 prefix_len = 0;
396 } 495 }
397 if (max_chars < 0) { 496 int avail = box_w - 4 - prefix_len;
398 max_chars = 0; 497 if (avail < 1) {
498 return 0;
399 } 499 }
400 while (i < max_chars && src[i] != '\0' && i < (int)dst_size - 1) { 500 int lines = 0;
401 dst[i] = src[i]; 501 const char *p = text;
402 i++; 502 while (*p != '\0') {
503 while (*p == ' ') {
504 p++;
505 }
506 int line_len = 0;
507 int last_space = -1;
508 for (int i = 0; i < avail && p[i] != '\0'; i++) {
509 if (p[i] == '\n') {
510 line_len = i;
511 break;
512 }
513 if (p[i] == ' ') {
514 last_space = i;
515 }
516 line_len = i + 1;
517 }
518 if (line_len == 0) {
519 break;
520 }
521 int cut = line_len;
522 if (cut == avail && p[cut] != '\0' && last_space > 0) {
523 cut = last_space;
524 }
525 lines++;
526 p += cut;
527 if (*p == '\n') {
528 p++;
529 }
403 } 530 }
404 dst[i] = '\0'; 531 return lines;
405} 532}
406 533
407static void dialog_open(Dialog *dialog, int npc_index) { 534static void dialog_open(Dialog *dialog, int npc_index, const char *npc_name) {
408 dialog->open = 1; 535 dialog->open = 1;
409 dialog->input_len = 0; 536 dialog->input_len = 0;
410 dialog->input[0] = '\0'; 537 dialog->input[0] = '\0';
411 dialog->npc_index = npc_index; 538 dialog->npc_index = npc_index;
539 dialog->npc_name = npc_name;
412} 540}
413 541
414static void dialog_close(Dialog *dialog) { 542static void dialog_close(Dialog *dialog) {
415 dialog->open = 0; 543 dialog->open = 0;
416 dialog->npc_index = -1; 544 dialog->npc_index = -1;
545 dialog->npc_name = NULL;
417} 546}
418 547
419static void dialog_append(Dialog *dialog, uint32_t ch) { 548static void dialog_append(Dialog *dialog, uint32_t ch) {
@@ -435,19 +564,455 @@ static void dialog_backspace(Dialog *dialog) {
435 dialog->input[dialog->input_len] = '\0'; 564 dialog->input[dialog->input_len] = '\0';
436} 565}
437 566
438static void dialog_submit(Dialog *dialog, const GameMap *game_map) { 567static void trim_leading(char **text) {
568 while (**text == ' ' || **text == '\t' || **text == '\n' || **text == '\r') {
569 (*text)++;
570 }
571}
572
573static void trim_leading_punct(char **text) {
574 while (**text == '"' || **text == '\'' || **text == '`') {
575 (*text)++;
576 trim_leading(text);
577 }
578}
579
580static void trim_trailing(char *text) {
581 size_t len = strlen(text);
582 while (len > 0) {
583 char ch = text[len - 1];
584 if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r') {
585 break;
586 }
587 text[len - 1] = '\0';
588 len--;
589 }
590}
591
592static void strip_any_prefix(char **text, const char *prefix) {
593 if (strncasecmp(*text, prefix, strlen(prefix)) == 0) {
594 *text += strlen(prefix);
595 trim_leading(text);
596 }
597}
598
599
600static char *sanitize_reply(char *reply, const char *name) {
601 if (reply == NULL) {
602 return NULL;
603 }
604 char *start = reply;
605 trim_leading(&start);
606 trim_leading_punct(&start);
607 strip_any_prefix(&start, "Answer:");
608 strip_any_prefix(&start, "NPC:");
609 strip_any_prefix(&start, "Context:");
610 strip_any_prefix(&start, "System:");
611 if (strncmp(start, "<context>", 9) == 0) {
612 start += 9;
613 trim_leading(&start);
614 }
615 char *reminder = strstr(start, "<system-reminder>");
616 if (reminder) {
617 *reminder = '\0';
618 }
619 char *system_tag = strstr(start, "<system");
620 if (system_tag) {
621 *system_tag = '\0';
622 }
623 char *tag = strstr(start, "<|");
624 if (tag) {
625 *tag = '\0';
626 }
627 char *eos = strstr(start, "</s>");
628 if (eos) {
629 *eos = '\0';
630 }
631 char *hash = strstr(start, "###");
632 if (hash) {
633 *hash = '\0';
634 }
635 if (name && name[0] != '\0') {
636 size_t name_len = strlen(name);
637 for (;;) {
638 if (strncasecmp(start, name, name_len) != 0) {
639 break;
640 }
641 start += name_len;
642 while (*start == ':' || *start == '-' || *start == ',') {
643 start++;
644 }
645 trim_leading(&start);
646 trim_leading_punct(&start);
647 }
648 }
649 if (start != reply) {
650 memmove(reply, start, strlen(start) + 1);
651 }
652 trim_trailing(reply);
653 return reply;
654}
655
656static int find_substr_offset(const char *buf, int n, const char *needle) {
657 int needle_len = (int)strlen(needle);
658 if (needle_len <= 0 || n <= 0 || needle_len > n) {
659 return -1;
660 }
661 for (int i = 0; i + needle_len <= n; i++) {
662 int match = 1;
663 for (int j = 0; j < needle_len; j++) {
664 if (buf[i + j] != needle[j]) {
665 match = 0;
666 break;
667 }
668 }
669 if (match) {
670 return i;
671 }
672 }
673 return -1;
674}
675
676static int find_stop_offset(const char *buf, int n) {
677 int stop_at = n;
678 for (int i = 0; i < n; i++) {
679 if (buf[i] == '\n') {
680 stop_at = i;
681 break;
682 }
683 }
684 int off = find_substr_offset(buf, n, "</s>");
685 if (off >= 0 && off < stop_at) {
686 stop_at = off;
687 }
688 off = find_substr_offset(buf, n, "<system-reminder>");
689 if (off >= 0 && off < stop_at) {
690 stop_at = off;
691 }
692 off = find_substr_offset(buf, n, "<system");
693 if (off >= 0 && off < stop_at) {
694 stop_at = off;
695 }
696 off = find_substr_offset(buf, n, "<|");
697 if (off >= 0 && off < stop_at) {
698 stop_at = off;
699 }
700 off = find_substr_offset(buf, n, "###");
701 if (off >= 0 && off < stop_at) {
702 stop_at = off;
703 }
704 off = find_substr_offset(buf, n, "System:");
705 if (off >= 0 && off < stop_at) {
706 stop_at = off;
707 }
708 off = find_substr_offset(buf, n, "User:");
709 if (off >= 0 && off < stop_at) {
710 stop_at = off;
711 }
712 off = find_substr_offset(buf, n, "Assistant:");
713 if (off >= 0 && off < stop_at) {
714 stop_at = off;
715 }
716 return stop_at;
717}
718
719static void append_prompt_context(stringb *sb, const char *npc_name, const char *context,
720 const char *question) {
721 sb_append_cstr(sb, "Context:\n");
722 if (npc_name && npc_name[0] != '\0') {
723 sb_append_cstr(sb, "NPC Name: ");
724 sb_append_cstr(sb, npc_name);
725 sb_append_cstr(sb, "\n");
726 }
727 if (context && context[0] != '\0') {
728 sb_append_cstr(sb, context);
729 }
730 sb_append_cstr(sb, "\nQuestion:\n");
731 sb_append_cstr(sb, question ? question : "");
732}
733
734static char *build_prompt(const ModelConfig *cfg, const char *system, const char *npc_name,
735 const char *context, const char *question) {
736 stringb full = {0};
737 sb_init(&full, 0);
738
739 switch (cfg->prompt_style) {
740 case PROMPT_STYLE_T5:
741 sb_append_cstr(&full, "instruction: ");
742 sb_append_cstr(&full, system ? system : "");
743 sb_append_cstr(&full, "\nquestion: ");
744 sb_append_cstr(&full, question ? question : "");
745 sb_append_cstr(&full, "\ncontext:\n");
746 if (npc_name && npc_name[0] != '\0') {
747 sb_append_cstr(&full, "NPC Name: ");
748 sb_append_cstr(&full, npc_name);
749 sb_append_cstr(&full, "\n");
750 }
751 if (context && context[0] != '\0') {
752 sb_append_cstr(&full, context);
753 }
754 sb_append_cstr(&full, "\nanswer:");
755 break;
756 case PROMPT_STYLE_CHAT:
757 sb_append_cstr(&full, "System:\n");
758 sb_append_cstr(&full, system ? system : "");
759 sb_append_cstr(&full, "\nUser:\n");
760 append_prompt_context(&full, npc_name, context, question);
761 sb_append_cstr(&full, "\nAssistant:");
762 break;
763 case PROMPT_STYLE_PLAIN:
764 default:
765 sb_append_cstr(&full, "System:\n");
766 sb_append_cstr(&full, system ? system : "");
767 sb_append_cstr(&full, "\n");
768 append_prompt_context(&full, npc_name, context, question);
769 sb_append_cstr(&full, "\nAnswer:");
770 break;
771 }
772
773 return full.data;
774}
775
776static char *generate_npc_reply(const GameRuntime *runtime, const GameMap *game_map,
777 int npc_index, const char *prompt) {
778 if (runtime == NULL || prompt == NULL) {
779 return NULL;
780 }
781 const char *fallback = "Demo reply: The old ruins are north of here.";
782 const char *npc_name = NULL;
783 if (game_map && npc_index >= 0 && npc_index < 10) {
784 const char *npc_reply = game_map->npcs[npc_index].reply;
785 npc_name = game_map->npcs[npc_index].name;
786 if (npc_reply && npc_reply[0] != '\0') {
787 fallback = npc_reply;
788 }
789 }
790
791 if (runtime->model == NULL || runtime->model_cfg == NULL || runtime->embed_ctx == NULL
792 || runtime->npc_dbs == NULL || runtime->npc_db_loaded == NULL) {
793 return strdup(fallback);
794 }
795 if (npc_index < 0 || npc_index >= 10 || runtime->npc_db_loaded[npc_index] == 0) {
796 return strdup(fallback);
797 }
798
799 VectorDB *db = &runtime->npc_dbs[npc_index];
800 float query[VDB_EMBED_SIZE];
801 int results[5];
802 for (int i = 0; i < 5; i++) {
803 results[i] = -1;
804 }
805 vdb_embed_query(db, prompt, query);
806 vdb_search(db, query, 5, results);
807
808 size_t context_cap = 1024;
809 size_t context_len = 0;
810 char *context = (char *)malloc(context_cap);
811 if (context == NULL) {
812 return strdup(fallback);
813 }
814 context[0] = '\0';
815 if (runtime->verbose) {
816 fprintf(stderr, "[npc] question: %s\n", prompt);
817 }
818 for (int i = 0; i < 5; i++) {
819 if (results[i] < 0) {
820 continue;
821 }
822 const char *text = db->docs[results[i]].text;
823 if (runtime->verbose) {
824 fprintf(stderr, "[npc] context[%d]: %s\n", i, text);
825 }
826 char header[32];
827 int header_len = snprintf(header, sizeof(header), "Snippet %d:\n", i + 1);
828 size_t text_len = strlen(text);
829 size_t need = context_len + (size_t)header_len + text_len + 2;
830 if (need > context_cap) {
831 while (need > context_cap) {
832 context_cap *= 2;
833 }
834 char *next = (char *)realloc(context, context_cap);
835 if (next == NULL) {
836 free(context);
837 return strdup(fallback);
838 }
839 context = next;
840 }
841 if (header_len > 0) {
842 memcpy(context + context_len, header, (size_t)header_len);
843 context_len += (size_t)header_len;
844 }
845 memcpy(context + context_len, text, text_len);
846 context_len += text_len;
847 context[context_len++] = '\n';
848 context[context_len] = '\0';
849 }
850
851 const char *system_prompt = "You are a helpful NPC. Speak in first person. "
852 "Use only the provided context. If the context does not contain the answer, say \"I don't know.\" "
853 "If asked your name, answer with the NPC Name from the context. "
854 "Do not mention context, system messages, or prompts. Reply with one short sentence.";
855
856 char *full_prompt = build_prompt(runtime->model_cfg, system_prompt, npc_name, context, prompt);
857 if (full_prompt == NULL) {
858 free(context);
859 return strdup(fallback);
860 }
861 free(context);
862
863 if (runtime->verbose) {
864 printf(">> %s\n", full_prompt);
865 }
866
867 const struct llama_vocab *vocab = llama_model_get_vocab(runtime->model);
868 int n_prompt = -llama_tokenize(vocab, full_prompt, strlen(full_prompt), NULL, 0, true, true);
869 llama_token *prompt_tokens = (llama_token *)malloc((size_t)n_prompt * sizeof(llama_token));
870 if (prompt_tokens == NULL) {
871 free(full_prompt);
872 return strdup(fallback);
873 }
874 if (llama_tokenize(vocab, full_prompt, strlen(full_prompt), prompt_tokens, n_prompt, true, true) < 0) {
875 free(full_prompt);
876 free(prompt_tokens);
877 return strdup(fallback);
878 }
879
880 struct llama_context_params ctx_params = llama_context_default_params();
881 ctx_params.n_ctx = runtime->model_cfg->n_ctx;
882 ctx_params.n_batch = runtime->model_cfg->n_batch;
883 ctx_params.embeddings = false;
884
885 struct llama_context *ctx = llama_init_from_model(runtime->model, ctx_params);
886 if (ctx == NULL) {
887 free(full_prompt);
888 free(prompt_tokens);
889 return strdup(fallback);
890 }
891
892 struct llama_sampler_chain_params sparams = llama_sampler_chain_default_params();
893 struct llama_sampler *smpl = llama_sampler_chain_init(sparams);
894 if (runtime->model_cfg->top_k > 0) {
895 llama_sampler_chain_add(smpl, llama_sampler_init_top_k(runtime->model_cfg->top_k));
896 }
897 if (runtime->model_cfg->top_p > 0.0f && runtime->model_cfg->top_p < 1.0f) {
898 llama_sampler_chain_add(smpl, llama_sampler_init_top_p(runtime->model_cfg->top_p, 1));
899 }
900 if (runtime->model_cfg->min_p > 0.0f) {
901 llama_sampler_chain_add(smpl, llama_sampler_init_min_p(runtime->model_cfg->min_p, 1));
902 }
903 llama_sampler_chain_add(smpl, llama_sampler_init_penalties(
904 runtime->model_cfg->repeat_last_n,
905 runtime->model_cfg->repeat_penalty,
906 runtime->model_cfg->freq_penalty,
907 runtime->model_cfg->presence_penalty));
908 llama_sampler_chain_add(smpl, llama_sampler_init_temp(runtime->model_cfg->temperature));
909 llama_sampler_chain_add(smpl, llama_sampler_init_dist(runtime->model_cfg->seed));
910
911 struct llama_batch batch = llama_batch_get_one(prompt_tokens, n_prompt);
912
913 if (llama_model_has_encoder(runtime->model)) {
914 if (llama_encode(ctx, batch)) {
915 llama_sampler_free(smpl);
916 free(full_prompt);
917 free(prompt_tokens);
918 llama_free(ctx);
919 return strdup(fallback);
920 }
921 llama_token decoder_start = llama_model_decoder_start_token(runtime->model);
922 if (decoder_start == LLAMA_TOKEN_NULL) {
923 decoder_start = llama_vocab_bos(vocab);
924 }
925 batch = llama_batch_get_one(&decoder_start, 1);
926 }
927
928 int n_pos = 0;
929 llama_token new_token_id;
930 size_t out_cap = 256;
931 size_t out_len = 0;
932 char *out = (char *)malloc(out_cap);
933 if (out == NULL) {
934 llama_sampler_free(smpl);
935 free(full_prompt);
936 free(prompt_tokens);
937 llama_free(ctx);
938 return strdup(fallback);
939 }
940 out[0] = '\0';
941 int n_predict = runtime->model_cfg->n_predict > 0 ? runtime->model_cfg->n_predict : 64;
942 if (n_predict > 64) {
943 n_predict = 64;
944 }
945 while (n_pos + batch.n_tokens < n_prompt + n_predict) {
946 if (llama_decode(ctx, batch)) {
947 break;
948 }
949 n_pos += batch.n_tokens;
950 new_token_id = llama_sampler_sample(smpl, ctx, -1);
951 if (llama_vocab_is_eog(vocab, new_token_id)) {
952 break;
953 }
954 char buf[128];
955 int n = llama_token_to_piece(vocab, new_token_id, buf, sizeof(buf), 0, true);
956 if (n < 0) {
957 break;
958 }
959 int stop_at = find_stop_offset(buf, n);
960 if (out_len == 0 && stop_at == 0 && n > 0 && buf[0] == '\n') {
961 batch = llama_batch_get_one(&new_token_id, 1);
962 continue;
963 }
964 if (out_len + (size_t)stop_at + 1 > out_cap) {
965 while (out_len + (size_t)stop_at + 1 > out_cap) {
966 out_cap *= 2;
967 }
968 char *next = (char *)realloc(out, out_cap);
969 if (next == NULL) {
970 break;
971 }
972 out = next;
973 }
974 memcpy(out + out_len, buf, (size_t)stop_at);
975 out_len += (size_t)stop_at;
976 out[out_len] = '\0';
977 if (stop_at != n) {
978 break;
979 }
980 batch = llama_batch_get_one(&new_token_id, 1);
981 }
982
983 llama_sampler_free(smpl);
984 free(full_prompt);
985 free(prompt_tokens);
986 llama_free(ctx);
987
988 if (out_len == 0) {
989 free(out);
990 return strdup(fallback);
991 }
992 return out;
993}
994
995static void dialog_submit(Dialog *dialog, const GameMap *game_map, const GameRuntime *runtime) {
439 if (dialog->input_len == 0) { 996 if (dialog->input_len == 0) {
440 return; 997 return;
441 } 998 }
442 { 999 {
443 const char *demo = "Demo reply: The old ruins are north of here."; 1000 const char *npc_name = NULL;
444 const char *reply = demo; 1001 char *reply = generate_npc_reply(runtime, game_map, dialog->npc_index, dialog->input);
1002 const char *fallback = "";
445 if (game_map && dialog->npc_index >= 0 && dialog->npc_index < 10) { 1003 if (game_map && dialog->npc_index >= 0 && dialog->npc_index < 10) {
446 const char *npc_reply = game_map->npcs[dialog->npc_index].reply; 1004 npc_name = game_map->npcs[dialog->npc_index].name;
447 if (npc_reply && npc_reply[0] != '\0') { 1005 fallback = game_map->npcs[dialog->npc_index].reply;
448 reply = npc_reply; 1006 if (fallback == NULL) {
1007 fallback = "";
449 } 1008 }
450 } 1009 }
1010 reply = sanitize_reply(reply, npc_name);
1011 if (reply == NULL || reply[0] == '\0') {
1012 free(reply);
1013 reply = NULL;
1014 }
1015 const char *reply_text = reply != NULL ? reply : fallback;
451 if (dialog->entry_count >= DIALOG_HISTORY_MAX) { 1016 if (dialog->entry_count >= DIALOG_HISTORY_MAX) {
452 for (int i = 1; i < DIALOG_HISTORY_MAX; i++) { 1017 for (int i = 1; i < DIALOG_HISTORY_MAX; i++) {
453 dialog->entries[i - 1] = dialog->entries[i]; 1018 dialog->entries[i - 1] = dialog->entries[i];
@@ -457,8 +1022,9 @@ static void dialog_submit(Dialog *dialog, const GameMap *game_map) {
457 snprintf(dialog->entries[dialog->entry_count].prompt, 1022 snprintf(dialog->entries[dialog->entry_count].prompt,
458 sizeof(dialog->entries[dialog->entry_count].prompt), "%s", dialog->input); 1023 sizeof(dialog->entries[dialog->entry_count].prompt), "%s", dialog->input);
459 snprintf(dialog->entries[dialog->entry_count].response, 1024 snprintf(dialog->entries[dialog->entry_count].response,
460 sizeof(dialog->entries[dialog->entry_count].response), "%s", reply); 1025 sizeof(dialog->entries[dialog->entry_count].response), "%s", reply_text);
461 dialog->entry_count++; 1026 dialog->entry_count++;
1027 free(reply);
462 } 1028 }
463 dialog->input_len = 0; 1029 dialog->input_len = 0;
464 dialog->input[0] = '\0'; 1030 dialog->input[0] = '\0';
@@ -479,7 +1045,7 @@ static void update_npc_status(const GameMap *game_map, int npc_index) {
479} 1045}
480 1046
481static void render(const Map *map, const Player *player, int *cam_x, 1047static void render(const Map *map, const Player *player, int *cam_x,
482 int *cam_y, int *out_view_w, int *out_view_h, const Dialog *dialog) { 1048 int *cam_y, int *out_view_w, int *out_view_h, const Dialog *dialog) {
483 int w; 1049 int w;
484 int h; 1050 int h;
485 int map_x; 1051 int map_x;
@@ -594,23 +1160,49 @@ static void render(const Map *map, const Player *player, int *cam_x,
594 if (max_text < 0) { 1160 if (max_text < 0) {
595 max_text = 0; 1161 max_text = 0;
596 } 1162 }
597 int max_entries = max_lines / 2; 1163 int start = dialog->entry_count;
598 int start = dialog->entry_count - max_entries;
599 if (start < 0) { 1164 if (start < 0) {
600 start = 0; 1165 start = 0;
601 } 1166 }
1167 int used_lines = 0;
1168 for (int i = dialog->entry_count - 1; i >= 0; i--) {
1169 const char *prompt_text = dialog->entries[i].prompt;
1170 const char *response_text = dialog->entries[i].response;
1171 const char *name = dialog->npc_name && dialog->npc_name[0] != '\0' ? dialog->npc_name : "NPC";
1172 char prefix_you[16];
1173 char prefix_npc[64];
1174 snprintf(prefix_you, sizeof(prefix_you), "You: ");
1175 snprintf(prefix_npc, sizeof(prefix_npc), "%s: ", name);
1176 int need = count_wrapped_lines(box_w, prefix_you, prompt_text)
1177 + count_wrapped_lines(box_w, prefix_npc, response_text);
1178 if (used_lines + need > max_lines && used_lines > 0) {
1179 break;
1180 }
1181 used_lines += need;
1182 start = i;
1183 if (used_lines >= max_lines) {
1184 break;
1185 }
1186 }
602 for (int i = start; i < dialog->entry_count && line + 1 <= max_lines; i++) { 1187 for (int i = start; i < dialog->entry_count && line + 1 <= max_lines; i++) {
603 char prompt_buf[128]; 1188 const char *prompt_text = dialog->entries[i].prompt;
604 char response_buf[256]; 1189 const char *response_text = dialog->entries[i].response;
605 copy_truncated(prompt_buf, sizeof(prompt_buf), dialog->entries[i].prompt, max_text); 1190 const char *name = dialog->npc_name && dialog->npc_name[0] != '\0' ? dialog->npc_name : "NPC";
606 copy_truncated(response_buf, sizeof(response_buf), dialog->entries[i].response, max_text); 1191 char prefix_you[16];
607 if (line < max_lines) { 1192 char prefix_npc[64];
608 tb_printf(box_x + 2, log_y + line, COLOR_WHITE_256, 19, "You: %s", prompt_buf); 1193 snprintf(prefix_you, sizeof(prefix_you), "You: ");
609 line++; 1194 snprintf(prefix_npc, sizeof(prefix_npc), "%s: ", name);
1195 int used = draw_wrapped(box_x + 2, log_y + line, max_lines - line, box_w,
1196 COLOR_WHITE_256, 19, prefix_you, prompt_text);
1197 line += used;
1198 if (line >= max_lines) {
1199 break;
610 } 1200 }
611 if (line < max_lines) { 1201 used = draw_wrapped(box_x + 2, log_y + line, max_lines - line, box_w,
612 tb_printf(box_x + 2, log_y + line, COLOR_GREEN_256, 19, "NPC: %s", response_buf); 1202 COLOR_GREEN_256, 19, prefix_npc, response_text);
613 line++; 1203 line += used;
1204 if (line >= max_lines) {
1205 break;
614 } 1206 }
615 } 1207 }
616 1208
@@ -641,17 +1233,71 @@ static int clamp(int value, int min, int max) {
641 return value; 1233 return value;
642} 1234}
643 1235
644int main(void) { 1236int main(int argc, char **argv) {
1237 const char *model_name = NULL;
1238 const char *embed_model_name = NULL;
1239 const ModelConfig *model_cfg = NULL;
1240 struct llama_model *embed_model = NULL;
1241 struct llama_model *gen_model = NULL;
1242 struct llama_context *embed_ctx = NULL;
1243 int tb_ready = 0;
1244 int llama_ready = 0;
1245 int exit_code = 0;
1246 int verbose = 0;
1247
1248 static struct option long_options[] = {
1249 {"model", required_argument, 0, 'm'},
1250 {"embed-model", required_argument, 0, 'e'},
1251 {"verbose", no_argument, 0, 'v'},
1252 {"help", no_argument, 0, 'h'},
1253 {0, 0, 0, 0}
1254 };
1255
1256 int opt;
1257 int option_index = 0;
1258 while ((opt = getopt_long(argc, argv, "m:e:vh", long_options, &option_index)) != -1) {
1259 switch (opt) {
1260 case 'm':
1261 model_name = optarg;
1262 break;
1263 case 'e':
1264 embed_model_name = optarg;
1265 break;
1266 case 'v':
1267 verbose = 1;
1268 break;
1269 case 'h':
1270 show_help(argv[0]);
1271 return 0;
1272 default:
1273 fprintf(stderr, "Usage: %s [-m model] [-v] [-h]\n", argv[0]);
1274 return 1;
1275 }
1276 }
1277
1278 if (model_name != NULL) {
1279 model_cfg = get_model_by_name(model_name);
1280 if (model_cfg == NULL) {
1281 fprintf(stderr, "Unknown model '%s'\n", model_name);
1282 return 1;
1283 }
1284 } else {
1285 model_cfg = &models[0];
1286 }
1287
645 Player player = {0}; 1288 Player player = {0};
646 array(GameMap) maps; 1289 array(GameMap) maps;
647 GameMap map1 = {0}; 1290 GameMap map1 = {0};
648 GameMap *current_map = NULL; 1291 GameMap *current_map = NULL;
1292 VectorDB *npc_dbs = NULL;
1293 int *npc_db_loaded = NULL;
649 int running = 1; 1294 int running = 1;
650 int view_w = 0; 1295 int view_w = 0;
651 int view_h = 0; 1296 int view_h = 0;
652 int cam_x = 0; 1297 int cam_x = 0;
653 int cam_y = 0; 1298 int cam_y = 0;
654 Dialog dialog = {0}; 1299 Dialog dialog = {0};
1300 GameRuntime runtime = {0};
655 1301
656 player_init(&player); 1302 player_init(&player);
657 array_init(maps); 1303 array_init(maps);
@@ -660,10 +1306,96 @@ int main(void) {
660 current_map = &maps.data[0]; 1306 current_map = &maps.data[0];
661 map_init(&current_map->map, current_map->data, current_map->len); 1307 map_init(&current_map->map, current_map->data, current_map->len);
662 1308
1309 if (verbose == 0) {
1310 llama_log_set(llama_log_callback, NULL);
1311 }
1312
1313 npc_dbs = (VectorDB *)calloc(10, sizeof(VectorDB));
1314 npc_db_loaded = (int *)calloc(10, sizeof(int));
1315 if (npc_dbs == NULL || npc_db_loaded == NULL) {
1316 fprintf(stderr, "Failed to allocate NPC vector databases\n");
1317 exit_code = 1;
1318 goto cleanup;
1319 }
1320
1321 llama_backend_init();
1322 ggml_backend_load_all();
1323 llama_ready = 1;
1324 const ModelConfig *embed_cfg = NULL;
1325 if (embed_model_name != NULL) {
1326 embed_cfg = get_model_by_name(embed_model_name);
1327 if (embed_cfg == NULL) {
1328 fprintf(stderr, "Unknown embedding model '%s'\n", embed_model_name);
1329 exit_code = 1;
1330 goto cleanup;
1331 }
1332 } else if (model_cfg->embed_model_name != NULL) {
1333 embed_cfg = get_model_by_name(model_cfg->embed_model_name);
1334 }
1335 if (embed_cfg == NULL) {
1336 embed_cfg = model_cfg;
1337 }
1338
1339 struct llama_model_params gen_params = llama_model_default_params();
1340 gen_params.n_gpu_layers = model_cfg->n_gpu_layers;
1341 gen_params.use_mmap = model_cfg->use_mmap;
1342 gen_model = llama_model_load_from_file(model_cfg->filepath, gen_params);
1343 if (gen_model == NULL) {
1344 fprintf(stderr, "Unable to load generation model\n");
1345 exit_code = 1;
1346 goto cleanup;
1347 }
1348
1349 struct llama_model_params embed_params = llama_model_default_params();
1350 embed_params.n_gpu_layers = embed_cfg->n_gpu_layers;
1351 embed_params.use_mmap = embed_cfg->use_mmap;
1352 embed_model = llama_model_load_from_file(embed_cfg->filepath, embed_params);
1353 if (embed_model == NULL) {
1354 fprintf(stderr, "Unable to load embedding model\n");
1355 exit_code = 1;
1356 goto cleanup;
1357 }
1358
1359 struct llama_context_params cparams = llama_context_default_params();
1360 cparams.n_ctx = embed_cfg->n_ctx;
1361 cparams.n_batch = embed_cfg->n_batch;
1362 cparams.embeddings = true;
1363 embed_ctx = llama_init_from_model(embed_model, cparams);
1364 if (embed_ctx == NULL) {
1365 fprintf(stderr, "Failed to create embedding context\n");
1366 exit_code = 1;
1367 goto cleanup;
1368 }
1369
1370 for (int i = 0; i < 10; i++) {
1371 const char *vdb_path = current_map->npcs[i].vdb_path;
1372 if (vdb_path == NULL || vdb_path[0] == '\0') {
1373 continue;
1374 }
1375 vdb_init(&npc_dbs[i], embed_ctx);
1376 VectorDBErrorCode vdb_rc = vdb_load(&npc_dbs[i], vdb_path);
1377 if (vdb_rc != VDB_SUCCESS) {
1378 fprintf(stderr, "Failed to load vector database %s: %s\n", vdb_path, vdb_error(vdb_rc));
1379 vdb_free(&npc_dbs[i]);
1380 continue;
1381 }
1382 npc_db_loaded[i] = 1;
1383 }
1384
1385 runtime.model_cfg = model_cfg;
1386 runtime.model = gen_model;
1387 runtime.embed_model = embed_model;
1388 runtime.embed_ctx = embed_ctx;
1389 runtime.npc_dbs = npc_dbs;
1390 runtime.npc_db_loaded = npc_db_loaded;
1391 runtime.verbose = verbose;
1392
663 if (tb_init() != TB_OK) { 1393 if (tb_init() != TB_OK) {
664 fprintf(stderr, "Failed to init termbox.\n"); 1394 fprintf(stderr, "Failed to init termbox.\n");
665 return 1; 1395 exit_code = 1;
1396 goto cleanup;
666 } 1397 }
1398 tb_ready = 1;
667 1399
668 tb_set_input_mode(TB_INPUT_ESC); 1400 tb_set_input_mode(TB_INPUT_ESC);
669 tb_set_output_mode(TB_OUTPUT_256); 1401 tb_set_output_mode(TB_OUTPUT_256);
@@ -678,7 +1410,7 @@ int main(void) {
678 if (ev.key == TB_KEY_ESC) { 1410 if (ev.key == TB_KEY_ESC) {
679 dialog_close(&dialog); 1411 dialog_close(&dialog);
680 } else if (ev.key == TB_KEY_ENTER) { 1412 } else if (ev.key == TB_KEY_ENTER) {
681 dialog_submit(&dialog, current_map); 1413 dialog_submit(&dialog, current_map, &runtime);
682 } else if (ev.key == TB_KEY_BACKSPACE || ev.key == TB_KEY_BACKSPACE2) { 1414 } else if (ev.key == TB_KEY_BACKSPACE || ev.key == TB_KEY_BACKSPACE2) {
683 dialog_backspace(&dialog); 1415 dialog_backspace(&dialog);
684 } else if (ev.ch) { 1416 } else if (ev.ch) {
@@ -692,7 +1424,10 @@ int main(void) {
692 u32 target = map_get(&current_map->map, player.x, next_y); 1424 u32 target = map_get(&current_map->map, player.x, next_y);
693 int npc_index = npc_index_from_tile(target); 1425 int npc_index = npc_index_from_tile(target);
694 if (target == 'N' || npc_index >= 0) { 1426 if (target == 'N' || npc_index >= 0) {
695 dialog_open(&dialog, npc_index); 1427 const char *npc_name = current_map && npc_index >= 0 && npc_index < 10
1428 ? current_map->npcs[npc_index].name
1429 : NULL;
1430 dialog_open(&dialog, npc_index, npc_name);
696 update_npc_status(current_map, npc_index); 1431 update_npc_status(current_map, npc_index);
697 } else if (map_is_walkable(&current_map->map, player.x, next_y)) { 1432 } else if (map_is_walkable(&current_map->map, player.x, next_y)) {
698 player.y = next_y; 1433 player.y = next_y;
@@ -702,7 +1437,10 @@ int main(void) {
702 u32 target = map_get(&current_map->map, player.x, next_y); 1437 u32 target = map_get(&current_map->map, player.x, next_y);
703 int npc_index = npc_index_from_tile(target); 1438 int npc_index = npc_index_from_tile(target);
704 if (target == 'N' || npc_index >= 0) { 1439 if (target == 'N' || npc_index >= 0) {
705 dialog_open(&dialog, npc_index); 1440 const char *npc_name = current_map && npc_index >= 0 && npc_index < 10
1441 ? current_map->npcs[npc_index].name
1442 : NULL;
1443 dialog_open(&dialog, npc_index, npc_name);
706 update_npc_status(current_map, npc_index); 1444 update_npc_status(current_map, npc_index);
707 } else if (map_is_walkable(&current_map->map, player.x, next_y)) { 1445 } else if (map_is_walkable(&current_map->map, player.x, next_y)) {
708 player.y = next_y; 1446 player.y = next_y;
@@ -712,7 +1450,10 @@ int main(void) {
712 u32 target = map_get(&current_map->map, next_x, player.y); 1450 u32 target = map_get(&current_map->map, next_x, player.y);
713 int npc_index = npc_index_from_tile(target); 1451 int npc_index = npc_index_from_tile(target);
714 if (target == 'N' || npc_index >= 0) { 1452 if (target == 'N' || npc_index >= 0) {
715 dialog_open(&dialog, npc_index); 1453 const char *npc_name = current_map && npc_index >= 0 && npc_index < 10
1454 ? current_map->npcs[npc_index].name
1455 : NULL;
1456 dialog_open(&dialog, npc_index, npc_name);
716 update_npc_status(current_map, npc_index); 1457 update_npc_status(current_map, npc_index);
717 } else if (map_is_walkable(&current_map->map, next_x, player.y)) { 1458 } else if (map_is_walkable(&current_map->map, next_x, player.y)) {
718 player.x = next_x; 1459 player.x = next_x;
@@ -722,7 +1463,10 @@ int main(void) {
722 u32 target = map_get(&current_map->map, next_x, player.y); 1463 u32 target = map_get(&current_map->map, next_x, player.y);
723 int npc_index = npc_index_from_tile(target); 1464 int npc_index = npc_index_from_tile(target);
724 if (target == 'N' || npc_index >= 0) { 1465 if (target == 'N' || npc_index >= 0) {
725 dialog_open(&dialog, npc_index); 1466 const char *npc_name = current_map && npc_index >= 0 && npc_index < 10
1467 ? current_map->npcs[npc_index].name
1468 : NULL;
1469 dialog_open(&dialog, npc_index, npc_name);
726 update_npc_status(current_map, npc_index); 1470 update_npc_status(current_map, npc_index);
727 } else if (map_is_walkable(&current_map->map, next_x, player.y)) { 1471 } else if (map_is_walkable(&current_map->map, next_x, player.y)) {
728 player.x = next_x; 1472 player.x = next_x;
@@ -742,11 +1486,33 @@ int main(void) {
742 } 1486 }
743 } 1487 }
744 1488
1489cleanup:
745 player_free(&player); 1490 player_free(&player);
746 for (size_t i = 0; i < maps.length; i++) { 1491 for (size_t i = 0; i < maps.length; i++) {
747 map_free(&maps.data[i].map); 1492 map_free(&maps.data[i].map);
748 } 1493 }
749 array_free(maps); 1494 array_free(maps);
750 tb_shutdown(); 1495 if (tb_ready) {
751 return 0; 1496 tb_shutdown();
1497 }
1498 for (int i = 0; i < 10; i++) {
1499 if (npc_db_loaded && npc_db_loaded[i]) {
1500 vdb_free(&npc_dbs[i]);
1501 }
1502 }
1503 free(npc_db_loaded);
1504 free(npc_dbs);
1505 if (embed_ctx != NULL) {
1506 llama_free(embed_ctx);
1507 }
1508 if (embed_model != NULL) {
1509 llama_model_free(embed_model);
1510 }
1511 if (gen_model != NULL) {
1512 llama_model_free(gen_model);
1513 }
1514 if (llama_ready) {
1515 llama_backend_free();
1516 }
1517 return exit_code;
752} 1518}