From 49751f8cfada9dd4cb1831b222092c396973bb48 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Thu, 30 Mar 2023 20:26:46 -0700 Subject: [PATCH] Don't use metadesk for training data, actions --- Session.vim | 147 ++++++++++ codegen.c | 140 +++------- training.mdesk => elements.mdesk | 35 ++- main.c | 453 +++++++------------------------ main.tiled-session | 28 ++ makeprompt.h | 383 ++++++++++++++++++++++++++ maketraining.c | 160 +++++++++++ run_codegen.bat | 3 + server/main.go | 7 +- todo.txt | 1 + 10 files changed, 889 insertions(+), 468 deletions(-) create mode 100644 Session.vim rename training.mdesk => elements.mdesk (83%) create mode 100644 main.tiled-session create mode 100644 makeprompt.h create mode 100644 maketraining.c diff --git a/Session.vim b/Session.vim new file mode 100644 index 0000000..c8ca4e1 --- /dev/null +++ b/Session.vim @@ -0,0 +1,147 @@ +let SessionLoad = 1 +let s:so_save = &g:so | let s:siso_save = &g:siso | setg so=0 siso=0 | setl so=-1 siso=-1 +let v:this_session=expand(":p") +silent only +silent tabonly +cd ~/Documents/rpgpt +if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == '' + let s:wipebuf = bufnr('%') +endif +let s:shortmess_save = &shortmess +if &shortmess =~ 'A' + set shortmess=aoOA +else + set shortmess=aoO +endif +badd +0 codegen.c +badd +0 main.c +badd +0 buff.h +badd +0 makeprompt.h +badd +0 profiling.h +badd +0 term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe +argglobal +%argdel +$argadd codegen.c +$argadd main.c +$argadd buff.h +$argadd makeprompt.h +$argadd profiling.h +set stal=2 +tabnew +setlocal\ bufhidden=wipe +tabrewind +let s:save_splitbelow = &splitbelow +let s:save_splitright = &splitright +set splitbelow splitright +wincmd _ | wincmd | +vsplit +1wincmd h +wincmd w +let &splitbelow = s:save_splitbelow +let &splitright = s:save_splitright +wincmd t +let s:save_winminheight = &winminheight +let s:save_winminwidth = &winminwidth +set winminheight=0 +set winheight=1 +set winminwidth=0 +set winwidth=1 +exe 'vert 1resize ' . ((&columns * 124 + 104) / 209) +exe 'vert 2resize ' . ((&columns * 84 + 104) / 209) +argglobal +enew +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +wincmd w +argglobal +if bufexists(fnamemodify("term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe", ":p")) | buffer term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe | else | edit term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe | endif +if &buftype ==# 'terminal' + silent file term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe +endif +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +let s:l = 1 - ((0 * winheight(0) + 24) / 49) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 1 +normal! 0 +wincmd w +exe 'vert 1resize ' . ((&columns * 124 + 104) / 209) +exe 'vert 2resize ' . ((&columns * 84 + 104) / 209) +tabnext +let s:save_splitbelow = &splitbelow +let s:save_splitright = &splitright +set splitbelow splitright +wincmd _ | wincmd | +vsplit +1wincmd h +wincmd w +let &splitbelow = s:save_splitbelow +let &splitright = s:save_splitright +wincmd t +let s:save_winminheight = &winminheight +let s:save_winminwidth = &winminwidth +set winminheight=0 +set winheight=1 +set winminwidth=0 +set winwidth=1 +exe 'vert 1resize ' . ((&columns * 104 + 104) / 209) +exe 'vert 2resize ' . ((&columns * 104 + 104) / 209) +argglobal +enew +balt term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +wincmd w +argglobal +enew +balt term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +wincmd w +exe 'vert 1resize ' . ((&columns * 104 + 104) / 209) +exe 'vert 2resize ' . ((&columns * 104 + 104) / 209) +tabnext 2 +set stal=1 +if exists('s:wipebuf') && len(win_findbuf(s:wipebuf)) == 0 && getbufvar(s:wipebuf, '&buftype') isnot# 'terminal' + silent exe 'bwipe ' . s:wipebuf +endif +unlet! s:wipebuf +set winheight=1 winwidth=20 +let &shortmess = s:shortmess_save +let &winminheight = s:save_winminheight +let &winminwidth = s:save_winminwidth +let s:sx = expand(":p:r")."x.vim" +if filereadable(s:sx) + exe "source " . fnameescape(s:sx) +endif +let &g:so = s:so_save | let &g:siso = s:siso_save +set hlsearch +nohlsearch +doautoall SessionLoadPost +unlet SessionLoad +" vim: set ft=vim : diff --git a/codegen.c b/codegen.c index 776ed75..928b3bd 100644 --- a/codegen.c +++ b/codegen.c @@ -19,7 +19,7 @@ MD_String8 OUTPUT_FOLDER = MD_S8LitComp("gen"); // no trailing slash MD_String8 ASSETS_FOLDER = MD_S8LitComp("assets"); -#define log(...) { printf("Codegen: "); printf(__VA_ARGS__); } +#define Log(...) { printf("Codegen: "); printf(__VA_ARGS__); } void dump(MD_Node* from) { printf("/ %.*s\n", MD_S8VArg(from->string)); @@ -96,6 +96,7 @@ char* fillnull(char *s, char c) { } #define StrSame(s1, s2) MD_S8Match((s1), (s2), 0) +#define EachString(it, first) MD_String8Node *it = (first); it != 0; it = it->next typedef BUFF(MD_Node*, 256) Nodes; MD_Node* find_by_name(Nodes *n, MD_String8 name) @@ -127,6 +128,7 @@ char* goto_end_of(char *tomove, size_t max_move, char *pattern) { #define list_printf(list_ptr, ...) MD_S8ListPush(cg_arena, list_ptr, MD_S8Fmt(cg_arena, __VA_ARGS__)) #define S8(s) MD_S8Lit(s) +#define S8V(s) MD_S8VArg(s) void dump_full(MD_Node* from) { @@ -145,7 +147,7 @@ int main(int argc, char **argv) { cg_arena = MD_ArenaAlloc(); assert_cond(cg_arena, MD_S8Lit("Memory")); - MD_ParseResult training_parse = MD_ParseWholeFile(cg_arena, MD_S8Lit("training.mdesk")); + MD_ParseResult training_parse = MD_ParseWholeFile(cg_arena, MD_S8Lit("elements.mdesk")); MD_String8 global_prompt = {0}; dump_full(training_parse.node->first_child); for(MD_EachNode(node, training_parse.node->first_child)) @@ -155,9 +157,20 @@ int main(int argc, char **argv) { global_prompt = node->string; } } - MD_String8 training_writeto = MD_S8Fmt(cg_arena, "%.*s/training_data.jsonl\0", MD_S8VArg(OUTPUT_FOLDER)); - FILE *train = fopen(training_writeto.str, "w"); - assert(train); + + MD_String8List action_strings = {0}; + for(MD_EachNode(node, training_parse.node->first_child)) + { + if(StrSame(node->string, S8("actions"))) + { + for(MD_EachNode(act_node, node->first_child)) + { + Log("Adding node %.*s\n", S8V(act_node->string)); + MD_S8ListPush(cg_arena, &action_strings, act_node->string); + } + } + } + Nodes characters = {0}; for(MD_EachNode(node, training_parse.node->first_child)) @@ -176,89 +189,15 @@ int main(int argc, char **argv) { BUFF_APPEND(&items, node); } } - - for(MD_EachNode(node, training_parse.node->first_child)) - { - if(MD_S8Match(node->first_tag->string, MD_S8Lit("training"), 0)) - { - MD_Node *node_with = find_by_name(&characters, ChildValue(node, S8("with"))); - - int upto_npc_line = 0; - int cur_npc_line = 0; - MD_Node *cur_sentence = MD_ChildFromString(node, S8("data"), 0)->first_child; - MD_Node *has_item = 0; - MD_String8List conversation = {0}; - while(!MD_NodeIsNil(cur_sentence)) - { - assert(!MD_NodeIsNil(cur_sentence)); - bool is_player = MD_NodeHasTag(cur_sentence, S8("player"), 0); - bool is_npc = MD_NodeHasTag(cur_sentence, S8("npc"), 0); - bool is_item_possess = MD_NodeHasTag(cur_sentence, S8("item_possess"), 0); - assert(!MD_NodeHasTag(cur_sentence, S8("item_posess"), 0)); - bool is_item_discard = MD_NodeHasTag(cur_sentence, S8("item_discard"), 0); - if(is_player) - { - list_printf(&conversation, "Player: \\\"%.*s\\\"\\n", MD_S8VArg(cur_sentence->string)); - } - if(is_item_possess) - { - MD_Node *item = find_by_name(&items, cur_sentence->string); - assert(item); - has_item = item; - list_printf(&conversation, "%.*s\\n", MD_S8VArg(ChildValue(item, S8("possess_message")))); - } - if(is_item_discard) - { - MD_Node *item = find_by_name(&items, cur_sentence->string); - assert(item); - has_item = 0; - list_printf(&conversation, "%.*s\\n", MD_S8VArg(ChildValue(item, S8("discard_message")))); - } - bool restarting = false; - if(is_npc) - { - list_printf(&conversation, "%.*s: \\\"", MD_S8VArg(ChildValue(node_with, S8("name")))); - if(upto_npc_line == cur_npc_line) - { - MD_String8 completion = cur_sentence->string; - fprintf(train, "{\"prompt\": \""); - fprintf(train, nullterm(global_prompt), MD_S8VArg(ChildValue(node_with, S8("actions_str")))); - fprintf(train, "\\n"); - if(has_item) fprintf(train, "%.*s\\n", MD_S8VArg(ChildValue(has_item, S8("global_prompt_message")))); - fprintf(train, "%.*s\\n", MD_S8VArg(ChildValue(node_with, S8("prompt")))); - - //MD_StringJoin join = (MD_StringJoin){.mid = S8("\\n")}; - MD_StringJoin join = (MD_StringJoin){0}; - MD_String8 conversation_string = MD_S8ListJoin(cg_arena, conversation, &join); - fprintf(train, "%.*s\", \"completion\": \"%.*s\\\"\"}\n", MD_S8VArg(conversation_string), MD_S8VArg(completion)); - - upto_npc_line += 1; - cur_npc_line = 0; - cur_sentence = MD_ChildFromString(node, S8("data"), 0)->first_child; - conversation = (MD_String8List){0}; - restarting = true; - } - else - { - list_printf(&conversation, "%.*s\\\"\\n", MD_S8VArg(cur_sentence->string)); - cur_npc_line += 1; - } - } - if(!restarting) cur_sentence = cur_sentence->next; - } - } - } - BUFF_ITER(MD_Node*, &characters) { - printf("Character %.*s\n", MD_S8VArg((*it)->string)); + Log("Character %.*s\n", MD_S8VArg((*it)->string)); } - // I hope to God MD_String8's are null terminated... MD_String8 writeto = MD_S8Fmt(cg_arena, "%.*s/assets.gen.c", MD_S8VArg(OUTPUT_FOLDER)); - log("Writing to %.*s\n", MD_S8VArg(writeto)); - FILE *output = fopen(writeto.str, "w"); + Log("Writing to %.*s\n", MD_S8VArg(writeto)); + FILE *output = fopen(nullterm(writeto), "w"); MD_ParseResult parse = MD_ParseWholeFile(cg_arena, MD_S8Lit("assets.mdesk")); @@ -271,7 +210,7 @@ int main(int argc, char **argv) { for(MD_EachNode(node, parse.node->first_child)) { if(MD_S8Match(node->first_tag->string, MD_S8Lit("sound"), 0)) { MD_String8 variable_name = MD_S8Fmt(cg_arena, "sound_%.*s", MD_S8VArg(node->string)); - log("New sound variable %.*s\n", MD_S8VArg(variable_name)); + Log("New sound variable %.*s\n", MD_S8VArg(variable_name)); MD_String8 filepath = ChildValue(node, MD_S8Lit("filepath")); filepath = asset_file_path(filepath); assert_cond(filepath.str != 0, MD_S8Fmt(cg_arena, "No filepath specified for sound '%.*s'", MD_S8VArg(node->string))); @@ -284,7 +223,7 @@ int main(int argc, char **argv) { } if(MD_S8Match(node->first_tag->string, MD_S8Lit("image"), 0)) { MD_String8 variable_name = MD_S8Fmt(cg_arena, "image_%.*s", MD_S8VArg(node->string)); - log("New image variable %.*s\n", MD_S8VArg(variable_name)); + Log("New image variable %.*s\n", MD_S8VArg(variable_name)); MD_String8 filepath = ChildValue(node, MD_S8Lit("filepath")); filepath = asset_file_path(filepath); assert_cond(filepath.str != 0, MD_S8Fmt(cg_arena, "No filepath specified for image '%.*s'", MD_S8VArg(node->string))); @@ -298,7 +237,7 @@ int main(int argc, char **argv) { if(MD_S8Match(node->first_tag->string, MD_S8Lit("tileset"), 0)) { // not a variable anymore MD_String8 variable_name = MD_S8Fmt(cg_arena, "tileset_%.*s", MD_S8VArg(node->string)); - log("New tileset %.*s\n", MD_S8VArg(variable_name)); + Log("New tileset %.*s\n", MD_S8VArg(variable_name)); MD_String8 filepath = asset_file_path(ChildValue(node, MD_S8Lit("filepath"))); MD_String8 tileset_file_contents = MD_LoadEntireFile(cg_arena, filepath); @@ -340,7 +279,7 @@ int main(int argc, char **argv) { } if(MD_S8Match(node->first_tag->string, MD_S8Lit("level"), 0)) { MD_String8 variable_name = MD_S8Fmt(cg_arena, "level_%.*s", MD_S8VArg(node->string)); - log("New level variable %.*s\n", MD_S8VArg(variable_name)); + Log("New level variable %.*s\n", MD_S8VArg(variable_name)); MD_String8 filepath = asset_file_path(ChildValue(node, MD_S8Lit("filepath"))); MD_ParseResult level_parse = MD_ParseWholeFile(cg_arena, filepath); assert_cond(!MD_NodeIsNil(level_parse.node->first_child), MD_S8Lit("Failed to load level file")); @@ -427,7 +366,8 @@ int main(int argc, char **argv) { } } - MD_StringJoin join = MD_ZERO_STRUCT; + + MD_StringJoin join = {0}; MD_String8 declarations = MD_S8ListJoin(cg_arena, declarations_list, &join); MD_String8 loads = MD_S8ListJoin(cg_arena, load_list, &join); fprintf(output, "%.*s\nvoid load_assets() {\n%.*s\n}\n", MD_S8VArg(declarations), MD_S8VArg(loads)); @@ -440,14 +380,22 @@ int main(int argc, char **argv) { output = fopen(MD_S8Fmt(cg_arena, "%.*s/characters.gen.h\0", MD_S8VArg(OUTPUT_FOLDER)).str, "w"); //fprintf(output, "char *global_prompt = \"%.*s\";\n", MD_S8VArg(global_prompt)); - fprintf(output, "char *general_prompt_table[] = {\n"); - BUFF_ITER(MD_Node*, &characters) + fprintf(output, "typedef enum Action {\n"); + for(EachString(s, action_strings.first)) { - MD_String8 personalized_global_prompt = MD_S8Fmt(cg_arena, nullterm(global_prompt), MD_S8VArg(ChildValue(*it, S8("actions_str")))); - fprintf(output, "\"%.*s\", // %.*s\n", MD_S8VArg(personalized_global_prompt), MD_S8VArg((*it)->string)); + fprintf(output, "ACT_%.*s,\n", S8V(s->string)); } - fprintf(output, "}; // general prompt table\n"); + fprintf(output, "} Action;\n"); + + fprintf(output, "char *action_strings[] = {\n"); + for(EachString(s, action_strings.first)) + { + fprintf(output, "\"%.*s\",\n", S8V(s->string)); + } + fprintf(output, "}; // action strings\n"); + + fprintf(output, "char *global_prompt = \"%.*s\";\n", S8V(global_prompt)); fprintf(output, "char *prompt_table[] = {\n"); BUFF_ITER(MD_Node*, &characters) @@ -456,28 +404,28 @@ int main(int argc, char **argv) { } fprintf(output, "}; // prompt table\n"); - fprintf(output, "typedef enum ItemKind {\nITEM_Invalid,\n"); + fprintf(output, "typedef enum ItemKind {\nITEM_nothing,\n"); BUFF_ITER(MD_Node*, &items) { fprintf(output, "ITEM_%.*s,\n", MD_S8VArg((*it)->string)); } fprintf(output, "} ItemKind;\n"); - fprintf(output, "char *item_prompt_table[] = {\n\"Invalid\",\n"); + fprintf(output, "char *item_prompt_table[] = {\n\"\",\n"); BUFF_ITER(MD_Node*, &items) { fprintf(output, "\"%.*s\",\n", MD_S8VArg(ChildValue(*it, S8("global_prompt_message")))); } fprintf(output, "}; // item prompt table\n"); - fprintf(output, "char *item_possess_message_table[] = {\n\"Invalid\",\n"); + fprintf(output, "char *item_possess_message_table[] = {\n\"The player is now holding nothing\",\n"); BUFF_ITER(MD_Node*, &items) { fprintf(output, "\"%.*s\",\n", MD_S8VArg(ChildValue(*it, S8("possess_message")))); } fprintf(output, "}; // item possess_message table\n"); - fprintf(output, "char *item_discard_message_table[] = {\n\"Invalid\",\n"); + fprintf(output, "char *item_discard_message_table[] = {\n\"The player is no longer holding nothing\",\n"); BUFF_ITER(MD_Node*, &items) { fprintf(output, "\"%.*s\",\n", MD_S8VArg(ChildValue(*it, S8("discard_message")))); diff --git a/training.mdesk b/elements.mdesk similarity index 83% rename from training.mdesk rename to elements.mdesk index da8ea0a..144faba 100644 --- a/training.mdesk +++ b/elements.mdesk @@ -1,16 +1,34 @@ -@global_prompt "This is a conversation between a player and an NPC in a game, where the NPC performs actions by saying one of [%.*s]. The NPC doesn't say anything in stars that isn't in that list between [ and ]. The player is wearing a full suit of knight armor. The general, Death, is leading some troops on a crusade they have mixed opinions about. Nobody is racist." +@global_prompt "This is a conversation between a player and an NPC in a video game. The player is wearing a full suit of knight armor. The general, Death, is leading some troops on a crusade they have mixed opinions about. Nobody is racist." + +actions: +{ + none, + + // mostly player actions + walks_up, + hits_npc, + leaves, + + // mostly npc actions + allows_player_to_pass, + gives_tripod, + heals_player, + fights_player, + strikes_air, + joins_player, + leaves_player, +} @character OldMan: { name: "Fredrick the Wise", - prompt: "Fredrick, an old man, has lived around these parts for decades. He's wise in the art of life, and knows what's going on in the game: general death is in town, in the wrong, and must be stopped." - actions_str: "", + prompt: "Fredrick is the NPC. An old man, he's lived around these parts for decades. He's wise in the art of life, and knows what's going on in the game: general death is in town, in the wrong, and must be stopped." } @character Blocky: { name: "Block", - prompt: "Block wants to block the player from going to the secret artifact. He has no idea how long he's been alive for, his entire existence is just standing there doing nothing. He'll let the player pass if they bring him Tripod, as he's fascinated by it.", + prompt: "Block is the NPC. He wants to block the player from going to the secret artifact. He has no idea how long he's been alive for, his entire existence is just standing there doing nothing. He'll let the player pass if they bring him Tripod, as he's fascinated by it.", actions_str: "ACT@[ALLOW_PASS]", } @@ -18,14 +36,12 @@ { name: "Hunter the Soldier", prompt: "Hunter, the NPC, is a nervous guy who trusts authority more than himself. He doesn't believe in the white square's powers.", - actions_str: "", } @character GodRock: { name: "God", - prompt: "God, in a rock.", - actions_str: "", + prompt: "The NPC is God. God, in a rock.", } @character Max: @@ -51,14 +67,14 @@ @item WhiteSquare: { - global_prompt_message: "The player has a mysterious white square. It is unknown what strange and erotic abilities one has when they possess the square.", + global_prompt_message: "The player is holding a mysterious white square. It is unknown what strange and erotic abilities one has when they possess the square.", possess_message: "The player is now holding the white square", discard_message: "The player is no longer holding the white square.", } @item Tripod: { - global_prompt_message: "The player has a tripod used for holding things up. It's got an odd construction, but Block really likes it for some reason.", + global_prompt_message: "The player is holding a tripod used for holding things up. It's got an odd construction, but Block really likes it for some reason.", possess_message: "The player is now holding the tripod", discard_message: "The player is no longer holding the tripod.", } @@ -68,6 +84,7 @@ with: OldMan, data: { + @available_actions "fights_player, joins_player", @player "What's up", @npc "Young warrior! You must stop Death, there isn't much time.", @player "Why?", diff --git a/main.c b/main.c index f38200b..39f4437 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,5 @@ // you will die someday -#define CURRENT_VERSION 3 // wehenver you change Entity increment this boz +#define CURRENT_VERSION 4 // wehenver you change Entity increment this boz #define SOKOL_IMPL #if defined(WIN32) || defined(_WIN32) @@ -41,8 +41,6 @@ #define ENTITIES_ITER(ents) for(Entity *it = ents; it < ents + ARRLEN(ents); it++) if(it->exists) -#define Log(...) { printf("Log %d | ", __LINE__); printf(__VA_ARGS__); } - double clamp(double d, double min, double max) { const double t = d < min ? min : d; return t > max ? max : t; @@ -125,25 +123,13 @@ typedef struct AnimatedSprite bool no_wrap; // does not wrap when playing } AnimatedSprite; -typedef enum CharacterState -{ - CHARACTER_WALKING, - CHARACTER_IDLE, - CHARACTER_ATTACK, - CHARACTER_TALKING, -} CharacterState; - #ifdef DEVTOOLS #define SERVER_URL "http://localhost:8090" #else #define SERVER_URL "https://rpgpt.duckdns.org/completion" #endif - -// REFACTORING:: also have to update in javascript!!!!!!!! -#define MAX_SENTENCE_LENGTH 400 // LOOOK AT AGBOVE COMMENT GBEFORE CHANGING -typedef BUFF(char, MAX_SENTENCE_LENGTH) Sentence; -#define SENTENCE_CONST(txt) (Sentence){.data=txt, .cur_index=sizeof(txt)} +#include "makeprompt.h" Sentence from_str(char *s) { @@ -156,92 +142,6 @@ Sentence from_str(char *s) return to_return; } -// even indexed dialogs (0,2,4) are player saying stuff, odds are the character from GPT -typedef enum AuthorType { NPC, PLAYER, SYSTEM } AuthorType; -typedef struct DialogElement -{ - AuthorType author; - Sentence s; -} DialogElement; - -// must always have player dialog element first -typedef BUFF(DialogElement, 2*12) Dialog; - -#include "characters.gen.h" - NPC_Skeleton, - NPC_MOOSE, -} NpcKind; - -typedef enum PropKind -{ - TREE0, - TREE1, - TREE2, -} PropKind; - -typedef struct EntityRef -{ - int index; - int generation; -} EntityRef; - -typedef struct Entity -{ - bool exists; - bool destroy; - int generation; - - // fields for all gs.entities - Vec2 pos; - Vec2 vel; // only used sometimes, like in old man and bullet - float damage; // at 1.0, dead! zero initialized - bool facing_left; - double dead_time; - bool dead; - // multiple gs.entities have a sword swing - BUFF(EntityRef, 8) done_damage_to_this_swing; // only do damage once, but hitbox stays around - - bool is_bullet; - - // props - bool is_prop; - PropKind prop_kind; - - // items - bool is_item; - bool held_by_player; - ItemKind item_kind; - - // npcs - bool is_npc; - double character_say_timer; - NpcKind npc_kind; - EntityRef last_seen_holding; - Sentence sentence_to_say; - Dialog player_dialog; -#ifdef WEB - int gen_request_id; -#endif - bool aggressive; - bool walking; - double shotgun_timer; - bool going_to_target; - Vec2 target_goto; - // only for skeleton npc - double swing_timer; - - // character - bool is_character; - EntityRef holding_item; - Vec2 to_throw_direction; - int boots_modifier; - CharacterState state; - EntityRef talking_to; // Maybe should be generational index, but I dunno. No death yet - bool is_rolling; // can only roll in idle or walk states - double time_not_rolling; // for cooldown for roll, so you can't just hold it and be invincible - double roll_progress; - double swing_progress; -} Entity; typedef struct Overlap { @@ -281,19 +181,6 @@ typedef struct Arena Entity *player = NULL; // up here, used in text backend callback -void make_space_and_append(Dialog *d, DialogElement elem) -{ - while((d->cur_index >= ARRLEN(d->data) || d->data[0].author != PLAYER) && d->cur_index > 0) - { - assert(ARRLEN(d->data) >= 1); - for(int i = 0; i < ARRLEN(d->data) - 1; i++) - { - d->data[i] = d->data[i + 1]; - } - d->cur_index--; - } - BUFF_APPEND(d, elem); -} typedef struct AudioSample { @@ -609,7 +496,6 @@ TileInstance get_tile(Level *l, TileCoord t) return get_tile_layer(l, 0, t); } - sg_image load_image(const char *path) { sg_image to_return = {0}; @@ -648,85 +534,6 @@ sg_image load_image(const char *path) void say_characters(Entity *npc, int num_characters) { play_audio(&sound_simple_talk); - Sentence *sentence_to_append_to = &npc->player_dialog.data[npc->player_dialog.cur_index-1].s; - for(int i = 0; i < num_characters; i++) - { - if(!BUFF_EMPTY(&npc->player_dialog) && !BUFF_EMPTY(&npc->sentence_to_say)) - { - char new_character = npc->sentence_to_say.data[0]; - bool found_matching_star = false; - BUFF(char, MAX_SENTENCE_LENGTH) match_buffer = {0}; - if(new_character == '*') - { - for(int ii = sentence_to_append_to->cur_index-1; ii >= 0; ii--) - { - if(sentence_to_append_to->data[ii] == '*') - { - found_matching_star = true; - break; - } - BUFF_PUSH_FRONT(&match_buffer, sentence_to_append_to->data[ii]); - } - } - if(found_matching_star) - { - assert(npc->is_npc); - if(npc->npc_kind == NPC_Blocky) - { - if(!npc->going_to_target && strcmp(match_buffer.data, "lets player pass") == 0) - { - npc->target_goto = AddV2(npc->pos, V2(-TILE_SIZE*3.0f, 0.0f)); - npc->going_to_target = true; - } - } -#if 0 // actions - if(strcmp(match_buffer.data, "fights player") == 0 && npc->npc_kind == OLD_MAN) - { - npc->aggressive = true; - } - if(strcmp(match_buffer.data, "sells grounding boots") == 0 && npc->npc_kind == MERCHANT) - { - player->boots_modifier -= 1; - - } - if(strcmp(match_buffer.data, "sells swiftness boots") == 0 && npc->npc_kind == MERCHANT) - { - player->boots_modifier += 1; - } - if(strcmp(match_buffer.data, "moves") == 0 && npc->npc_kind == DEATH) - { - npc->going_to_target = true; - npc->target_goto = AddV2(npc->pos, V2(0.0, -TILE_SIZE*1.5f)); - } -#endif - } - BUFF_APPEND(sentence_to_append_to, new_character); - BUFF_REMOVE_FRONT(&npc->sentence_to_say); - } - } -} - -bool npc_is_knight_sprite(Entity *it) -{ - return it->is_npc && ( it->npc_kind == NPC_Max || it->npc_kind == NPC_Hunter || it->npc_kind == NPC_John || it->npc_kind == NPC_Blocky); -} - -void add_new_npc_sentence(Entity *npc, char *sentence) -{ - size_t sentence_len = strlen(sentence); - assert(sentence_len < MAX_SENTENCE_LENGTH); - Sentence new_sentence = {0}; - bool inside_star = false; - for(int i = 0; i < sentence_len; i++) - { - if(sentence[i] == '"') break; - if(sentence[i] == '\n') continue; - BUFF_APPEND(&new_sentence, sentence[i]); - } - DialogElement empty_elem = { .author = NPC }; - say_characters(npc, npc->sentence_to_say.cur_index); - make_space_and_append(&npc->player_dialog, empty_elem); - npc->sentence_to_say = new_sentence; } AABB level_aabb = { .upper_left = {0.0f, 0.0f}, .lower_right = {TILE_SIZE * LEVEL_TILES, -(TILE_SIZE * LEVEL_TILES)} }; @@ -872,136 +679,18 @@ void end_text_input(char *what_player_said) BUFF_APPEND(&what_player_said_sentence, c); } - // order is player message, item status message in training data. So has to be same here Entity *talking = gete(player->talking_to); assert(talking); - Dialog *to_append = &talking->player_dialog; - make_space_and_append(to_append, (DialogElement){.s = what_player_said_sentence, .author = PLAYER}); - if(!eq(talking->last_seen_holding, player->holding_item)) - { - if(gete(talking->last_seen_holding)) - { - Sentence discard = from_str(item_discard_message_table[gete(talking->last_seen_holding)->item_kind]); - BUFF_APPEND(&discard, '\n'); - make_space_and_append(to_append, (DialogElement){.author = SYSTEM, .s = discard}); - assert(gete(talking->last_seen_holding)->is_item); - talking->last_seen_holding = (EntityRef){0}; - } - if(gete(player->holding_item)) - { - assert(gete(player->holding_item)->is_item); - Sentence possess = from_str(item_possess_message_table[gete(player->holding_item)->item_kind]); - BUFF_APPEND(&possess, '\n'); - make_space_and_append(to_append, (DialogElement){.author = SYSTEM, .s = possess}); - } - talking->last_seen_holding = player->holding_item; - } - - // the npc response will be appended here, or at least be async queued to be appended here - BUFF(char, 4000) prompt_buff = {0}; - BUFF(char *, 100) to_join = {0}; - - - assert(talking->npc_kind >= 0); - assert(talking->npc_kind < ARRLEN(prompt_table)); - assert(talking->npc_kind < ARRLEN(general_prompt_table)); - assert(talking->npc_kind < ARRLEN(name_table)); - - // general prompt - BUFF_APPEND(&to_join, general_prompt_table[talking->npc_kind]); - BUFF_APPEND(&to_join, "\n"); - - // item prompt - if(gete(player->holding_item)) - { - BUFF_APPEND(&to_join, item_prompt_table[gete(player->holding_item)->item_kind]); - BUFF_APPEND(&to_join, "\n"); - } - - // characters prompt - BUFF_APPEND(&to_join, prompt_table[talking->npc_kind]); - BUFF_APPEND(&to_join, "\n"); - char *character_prompt = name_table[talking->npc_kind]; - - // all the dialog - int i = 0; - BUFF_ITER(DialogElement, &gete(player->talking_to)->player_dialog) - { - //bool is_player = - if(it->author == PLAYER) - { - BUFF_APPEND(&to_join, "Player: \""); - } - else if(it->author == NPC) - { - BUFF_APPEND(&to_join, character_prompt); - BUFF_APPEND(&to_join, ": \""); - } - else if(it->author == SYSTEM) - { - } - else - { - assert(false); - } - BUFF_APPEND(&to_join, it->s.data); - if(it->author == PLAYER || it->author == NPC) - BUFF_APPEND(&to_join, "\"\n"); - i++; - } - - BUFF_APPEND(&to_join, character_prompt); - BUFF_APPEND(&to_join, ": \""); - - // concatenate into prompt_buff - BUFF_ITER(char *, &to_join) - { - size_t cur_len = strlen(*it); - for(int i = 0; i < cur_len; i++) - { - BUFF_APPEND(&prompt_buff, (*it)[i]); - } - } - - const char * prompt = prompt_buff.data; -#ifdef DEVTOOLS - Log("Prompt: `%s`\n", prompt); -#endif -#ifdef WEB - // fire off generation request, save id - int req_id = EM_ASM_INT({ - return make_generation_request(UTF8ToString($1), UTF8ToString($0)); - }, SERVER_URL, prompt); - gete(player->talking_to)->gen_request_id = req_id; -#endif -#ifdef DESKTOP - if(gete(player->talking_to)->npc_kind == NPC_Death) - { - add_new_npc_sentence(gete(player->talking_to), "test *moves* I am death, destroyer of games. Come join me in the afterlife, or continue onwards *moves*"); - //add_new_npc_sentence(gete(player->talking_to), "test"); - } - if(gete(player->talking_to)->npc_kind == NPC_OldMan) - { - add_new_npc_sentence(gete(player->talking_to), "I am the old man"); - } - if(gete(player->talking_to)->npc_kind == NPC_Blocky) - { - add_new_npc_sentence(gete(player->talking_to), "I am Blocky. *lets player pass*"); - } - if(gete(player->talking_to)->npc_kind == NPC_Hunter) + ItemKind player_holding = ITEM_nothing; + if(gete(player->holding_item) != 0) { - add_new_npc_sentence(gete(player->talking_to), "I am hunter"); + player_holding = gete(player->holding_item)->item_kind; } - if(gete(player->talking_to)->npc_kind == NPC_Max) + if(talking->last_seen_holding_kind != player_holding) { - add_new_npc_sentence(gete(player->talking_to), "I am max"); + process_perception(talking, (Perception){.type = PlayerHeldItemChanged, .holding = player_holding,}); } - if(gete(player->talking_to)->npc_kind == NPC_John) - { - add_new_npc_sentence(gete(player->talking_to), "I am john *gives WhiteSquare*"); - } - -#endif + process_perception(talking, (Perception){.type = PlayerDialog, .player_dialog = what_player_said_sentence,}); } } @@ -2334,57 +2023,48 @@ void draw_dialog_panel(Entity *talking_to) if(aabb_is_valid(dialog_panel)) { float new_line_height = dialog_panel.lower_right.Y; - int i = 0; - //BUFF_ITER(Sentence, &talking_to->player_dialog) - if(talking_to->player_dialog.cur_index > 0) + + typedef struct { - BUFF_ITER_EX(DialogElement, &talking_to->player_dialog, talking_to->player_dialog.cur_index-1, it >= &talking_to->player_dialog.data[0], it--) + Sentence s; + bool is_player; + } DialogElement; + + BUFF(DialogElement, 32) dialog = {0}; + BUFF_ITER(Perception, &talking_to->remembered_perceptions) + { + if(it->type == NPCDialog) { - if(it->author == SYSTEM) - { - } - else + BUFF_APPEND(&dialog, ((DialogElement){ .s = it->npc_dialog, .is_player = false }) ); + } + else if(it->type == PlayerDialog) + { + BUFF_APPEND(&dialog, ((DialogElement){ .s = it->player_dialog, .is_player = true }) ); + } + } + if(dialog.cur_index > 0) + { + for(int i = dialog.cur_index - 1; i >= 0; i--) + { + DialogElement *it = &dialog.data[i]; { - bool player_talking = it->author == PLAYER; Color *colors = calloc(sizeof(*colors), it->s.cur_index); - bool in_astrix = false; for(int char_i = 0; char_i < it->s.cur_index; char_i++) { - bool set_in_astrix_false = false; - if(it->s.data[char_i] == '*') - { - if(in_astrix) - { - set_in_astrix_false = true; - } - else - { - in_astrix = true; - } - } - if(player_talking) + if(it->is_player) { colors[char_i] = BLACK; } else { - if(in_astrix) - { - colors[char_i] = colhex(0xab9100); - } - else - { - colors[char_i] = colhex(0x345e22); - } + colors[char_i] = colhex(0x345e22); } - if(set_in_astrix_false) in_astrix = false; } float measured_line_height = draw_wrapped_text(true, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->s.data, colors, 0.5f, true, dialog_panel); new_line_height += (new_line_height - measured_line_height); draw_wrapped_text(false, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->s.data, colors, 0.5f, true, dialog_panel); free(colors); - i++; } } } @@ -2572,7 +2252,7 @@ void frame(void) assert(player != NULL); // fixed timestep loop - Entity *interacting_with = 0; // used by rendering to figure out who to draw dialog box on + static Entity *interacting_with = 0; // used by rendering to figure out who to draw dialog box on int num_timestep_loops = 0; { unprocessed_fixed_timestep_time += dt; @@ -2594,7 +2274,7 @@ void frame(void) assert(it->gen_request_id > 0); int status = EM_ASM_INT({ return get_generation_request_status($0); - }, it->gen_request_id); + }, it->gen_request_id); if(status == 0) { // simply not done yet @@ -2610,7 +2290,19 @@ void frame(void) stringToUTF8(generation, $1, $2); }, it->gen_request_id, sentence_str, ARRLEN(sentence_str)); - add_new_npc_sentence(it, sentence_str); + + // parse out from the sentence NPC action and dialog + Perception out = {0}; + bool text_was_well_formatted = parse_ai_response(it, sentence_str, &out); + + if(text_was_well_formatted) + { + process_perception(it, out); + } + else + { + it->perceptions_dirty = true; // on poorly formatted AI, just retry request. + } EM_ASM({ done_with_generation_request($0); @@ -2620,7 +2312,7 @@ void frame(void) { Log("Failed to generate dialog! Fuck!"); // need somethin better here. Maybe each sentence has to know if it's player or NPC, that way I can remove the player's dialog - add_new_npc_sentence(it, "I'm not sure..."); + process_perception(it, (Perception){.type = NPCDialog, .npc_action_type = ACT_none, .npc_dialog = SENTENCE_CONST("I'm not sure...")}); } it->gen_request_id = 0; } @@ -2637,7 +2329,8 @@ void frame(void) if(it->is_npc) { - if(!BUFF_EMPTY(&it->sentence_to_say)) + // character speech animation text input + if(false) { it->character_say_timer += dt; const float character_say_time = 0.02f; @@ -2767,7 +2460,7 @@ void frame(void) } else if(it->npc_kind == NPC_Blocky) { - if(it->going_to_target) + if(it->moved) { it->walking = true; Vec2 towards = SubV2(it->target_goto, it->pos); @@ -2845,7 +2538,7 @@ void frame(void) } } - PROFILE_SCOPE("Destroy gs.entities") + PROFILE_SCOPE("Destroy gs.entities, maybe send generation requests") { ENTITIES_ITER(gs.entities) { @@ -2855,6 +2548,45 @@ void frame(void) *it = (Entity){0}; it->generation = gen; } + if(it->perceptions_dirty) + { + PromptBuff prompt = {0}; + generate_prompt(it, &prompt); + Log("Sending request with prompt `%s`\n", prompt.data); + +#ifdef WEB + // fire off generation request, save id + int req_id = EM_ASM_INT({ + return make_generation_request(UTF8ToString($1), UTF8ToString($0)); + }, SERVER_URL, prompt.data); + it->gen_request_id = req_id; +#endif + +#ifdef DESKTOP + BUFF(char, 1024) mocked_ai_response = {0}; +#define SAY(act, txt) { int index = action_to_index(it, act); printf_buff(&mocked_ai_response, " %d \"%s\"", index, txt); } + if(it->npc_kind == NPC_Blocky) + { + if(it->last_seen_holding_kind == ITEM_Tripod && !it->moved) + { + SAY(ACT_allows_player_to_pass, "Here you go"); + } + else + { + SAY(ACT_none, "You passed"); + } + } + else + { + SAY(ACT_none, "I am an NPC"); + } + Perception p = {0}; + assert(parse_ai_response(it, mocked_ai_response.data, &p)); + process_perception(it, p); +#undef SAY +#endif + it->perceptions_dirty = false; + } } } @@ -3044,6 +2776,8 @@ void frame(void) reset_level(); } } + + roll_just_pressed = false; } // while loop } @@ -3365,7 +3099,6 @@ void frame(void) last_frame_processing_time = stm_sec(stm_diff(stm_now(),time_start_frame)); reset(&scratch); - roll_just_pressed = false; } } diff --git a/main.tiled-session b/main.tiled-session new file mode 100644 index 0000000..16d3a09 --- /dev/null +++ b/main.tiled-session @@ -0,0 +1,28 @@ +{ + "activeFile": "", + "expandedProjectPaths": [ + ], + "fileStates": { + "assets/level0.json": { + "scale": 0.17733333333333334, + "selectedLayer": 0, + "viewCenter": { + "x": 1843.9849624060148, + "y": 1240.6015037593986 + } + }, + "assets/testsmalllevel.json": { + "scale": 3.3249999999999997, + "selectedLayer": 0, + "viewCenter": { + "x": 128.1203007518797, + "y": 128.1203007518797 + } + } + }, + "openFiles": [ + ], + "project": "main.tiled-project", + "recentFiles": [ + ] +} diff --git a/makeprompt.h b/makeprompt.h new file mode 100644 index 0000000..31b64d4 --- /dev/null +++ b/makeprompt.h @@ -0,0 +1,383 @@ +#pragma once +#include "buff.h" +#include "HandmadeMath.h" // vector types in entity struct definition +#include +#include +#include +#include // atoi +#include "characters.gen.h" + NPC_Skeleton, + NPC_MOOSE, +} NpcKind; + +#define Log(...) { printf("%s Log %d | ", __FILE__, __LINE__); printf(__VA_ARGS__); } + +// REFACTORING:: also have to update in javascript!!!!!!!! +#define MAX_SENTENCE_LENGTH 400 // LOOOK AT AGBOVE COMMENT GBEFORE CHANGING +typedef BUFF(char, MAX_SENTENCE_LENGTH) Sentence; +#define SENTENCE_CONST(txt) {.data=txt, .cur_index=sizeof(txt)} +#define SENTENCE_CONST_CAST(txt) (Sentence)SENTENCE_CONST(txt) + +#define REMEMBERED_PERCEPTIONS 24 + +typedef enum PerceptionType +{ + Invalid, // so that zero value in training structs means end of perception + PlayerAction, + PlayerDialog, + NPCDialog, // includes an npc action in every npc dialog. So it's often nothing + PlayerHeldItemChanged, +} PerceptionType; + +typedef struct Perception +{ + PerceptionType type; + + union + { + // player action + Action player_action_type; + + // player dialog + Sentence player_dialog; + + // npc dialog + struct + { + Action npc_action_type; + Sentence npc_dialog; + }; + + // player holding item. MUST precede any perceptions which come after the player is holding the item + ItemKind holding; + }; +} Perception; + +typedef enum PropKind +{ + TREE0, + TREE1, + TREE2, +} PropKind; + +typedef struct EntityRef +{ + int index; + int generation; +} EntityRef; + +typedef enum CharacterState +{ + CHARACTER_WALKING, + CHARACTER_IDLE, + CHARACTER_ATTACK, + CHARACTER_TALKING, +} CharacterState; + +typedef struct Entity +{ + bool exists; + bool destroy; + int generation; + + // fields for all gs.entities + Vec2 pos; + Vec2 vel; // only used sometimes, like in old man and bullet + float damage; // at 1.0, dead! zero initialized + bool facing_left; + double dead_time; + bool dead; + // multiple gs.entities have a sword swing + BUFF(EntityRef, 8) done_damage_to_this_swing; // only do damage once, but hitbox stays around + + bool is_bullet; + + // props + bool is_prop; + PropKind prop_kind; + + // items + bool is_item; + bool held_by_player; + ItemKind item_kind; + + // npcs + bool is_npc; + bool perceptions_dirty; + BUFF(Perception, REMEMBERED_PERCEPTIONS) remembered_perceptions; + double character_say_timer; + int characters_said; + NpcKind npc_kind; + ItemKind last_seen_holding_kind; +#ifdef WEB + int gen_request_id; +#endif + bool aggressive; + bool walking; + double shotgun_timer; + bool moved; + Vec2 target_goto; + // only for skeleton npc + double swing_timer; + + // character + bool is_character; + EntityRef holding_item; + Vec2 to_throw_direction; + int boots_modifier; + CharacterState state; + EntityRef talking_to; // Maybe should be generational index, but I dunno. No death yet + bool is_rolling; // can only roll in idle or walk states + double time_not_rolling; // for cooldown for roll, so you can't just hold it and be invincible + double roll_progress; + double swing_progress; +} Entity; + +bool npc_is_knight_sprite(Entity *it) +{ + return it->is_npc && ( it->npc_kind == NPC_Max || it->npc_kind == NPC_Hunter || it->npc_kind == NPC_John || it->npc_kind == NPC_Blocky); +} + +typedef BUFF(char, MAX_SENTENCE_LENGTH*(REMEMBERED_PERCEPTIONS+4)) PromptBuff; +typedef BUFF(Action, 8) AvailableActions; + +void fill_available_actions(Entity *it, AvailableActions *a) +{ + *a = (AvailableActions){0}; + BUFF_APPEND(a, ACT_none); + if(it->npc_kind == NPC_GodRock) + { + BUFF_APPEND(a, ACT_heals_player); + } + else + { + BUFF_APPEND(a, ACT_fights_player); + if(npc_is_knight_sprite(it)) + { + BUFF_APPEND(a, ACT_strikes_air); + } + if(it->npc_kind == NPC_Blocky) + { + if(!it->moved) + { + BUFF_APPEND(a, ACT_allows_player_to_pass); + } + } + } +} + +void process_perception(Entity *it, Perception p) +{ + if(p.type != NPCDialog) it->perceptions_dirty = true; + if(!BUFF_HAS_SPACE(&it->remembered_perceptions)) BUFF_REMOVE_FRONT(&it->remembered_perceptions); + BUFF_APPEND(&it->remembered_perceptions, p); + if(p.type == PlayerHeldItemChanged) + { + it->last_seen_holding_kind = p.holding; + } + else if(p.type == NPCDialog) + { + if(p.npc_action_type == ACT_allows_player_to_pass) + { + it->target_goto = AddV2(it->pos, V2(-50.0, 0.0)); + it->moved = true; + } + } +} + +#define printf_buff(buff_ptr, ...) { int written = snprintf((buff_ptr)->data+(buff_ptr)->cur_index, ARRLEN((buff_ptr)->data) - (buff_ptr)->cur_index, __VA_ARGS__); assert(written >= 0); (buff_ptr)->cur_index += written; }; + +// returns if action index was valid +bool action_from_index(Entity *it, Action *out, int action_index) +{ + AvailableActions available = {0}; + fill_available_actions(it, &available); + if(action_index < 0 || action_index >= available.cur_index) + { + return false; + } + else + { + *out = available.data[action_index]; + return true; + } +} + +// don't call on untrusted action, doesn't return error +int action_to_index(Entity *it, Action a) +{ + AvailableActions available = {0}; + fill_available_actions(it, &available); + Action target_action = a; + int index = -1; + for(int i = 0; i < available.cur_index; i++) + { + if(available.data[i] == target_action) + { + index = i; + break; + } + } + assert(index != -1); + return index; +} + + +void generate_prompt(Entity *it, PromptBuff *into) +{ + assert(it->is_npc); + *into = (PromptBuff){0}; + + // global prompt + printf_buff(into, "%s", global_prompt); + printf_buff(into, "%s", "\n"); + + // npc description prompt + assert(it->npc_kind < ARRLEN(prompt_table)); + printf_buff(into, "%s", prompt_table[it->npc_kind]); + printf_buff(into, "%s", "\n"); + + // item prompt + if(it->last_seen_holding_kind != ITEM_nothing) + { + assert(it->last_seen_holding_kind < ARRLEN(item_prompt_table)); + printf_buff(into, "%s", item_prompt_table[it->last_seen_holding_kind]); + printf_buff(into, "%s", "\n"); + } + + // available actions prompt + AvailableActions available = {0}; + fill_available_actions(it, &available); + printf_buff(into, "%s", "The NPC possible actions array, indexed by ACT_INDEX: ["); + BUFF_ITER(Action, &available) + { + printf_buff(into, "%s", action_strings[*it]); + printf_buff(into, "%s", ", "); + } + printf_buff(into, "%s", "]\n"); + + Entity *e = it; + ItemKind last_holding = ITEM_nothing; + BUFF_ITER(Perception, &e->remembered_perceptions) + { + if(it->type == PlayerAction) + { + assert(it->player_action_type < ARRLEN(action_strings)); + printf_buff(into, "Player: ACT %s \n", action_strings[it->player_action_type]); + } + else if(it->type == PlayerDialog) + { + printf_buff(into, "%s", "Player: \""); + printf_buff(into, "%s", it->player_dialog.data); + printf_buff(into, "%s", "\"\n"); + } + else if(it->type == NPCDialog) + { + printf_buff(into, "%s: ACT %s \"%s\"\n", name_table[e->npc_kind], action_strings[it->npc_action_type], it->npc_dialog.data); + } + else if(it->type == PlayerHeldItemChanged) + { + if(last_holding != it->holding) + { + if(last_holding != ITEM_nothing) + { + printf_buff(into, "%s", item_discard_message_table[last_holding]); + printf_buff(into, "%s", "\n"); + } + if(it->holding != ITEM_nothing) + { + printf_buff(into, "%s", item_possess_message_table[it->holding]); + printf_buff(into, "%s", "\n"); + } + last_holding = it->holding; + } + } + else + { + assert(false); + } + } + + printf_buff(into, "%s: ACT_INDEX", name_table[e->npc_kind]); +} + +// returns if the response was well formatted +bool parse_ai_response(Entity *it, char *sentence_str, Perception *out) +{ + *out = (Perception){0}; + out->type = NPCDialog; + + size_t sentence_length = strlen(sentence_str); + bool text_was_well_formatted = true; + + BUFF(char, 128) action_index_string = {0}; + int npc_sentence_beginning = 0; + for(int i = 0; i < sentence_length; i++) + { + if(i == 0) + { + if(sentence_str[i] != ' ') + { + text_was_well_formatted = false; + Log("Poorly formatted AI string, did not start with a ' ': `%s`\n", sentence_str); + break; + } + } + else + { + if(sentence_str[i] == ' ') + { + npc_sentence_beginning = i + 2; + break; + } + else + { + BUFF_APPEND(&action_index_string, sentence_str[i]); + } + } + } + if(sentence_str[npc_sentence_beginning - 1] != '"' || npc_sentence_beginning == 0) + { + Log("Poorly formatted AI string, sentence beginning incorrect in AI string `%s` NPC sentence beginning %d ...\n", sentence_str, npc_sentence_beginning); + text_was_well_formatted = false; + } + + Action npc_action = 0; + if(text_was_well_formatted) + { + int index_of_action = atoi(action_index_string.data); + + if(!action_from_index(it, &npc_action, index_of_action)) + { + Log("AI output invalid action index %d action index string %s\n", index_of_action, action_index_string.data); + } + } + + Sentence what_npc_said = {0}; + bool found_end_quote = false; + for(int i = npc_sentence_beginning; i < sentence_length; i++) + { + if(sentence_str[i] == '"') + { + found_end_quote = true; + break; + } + else + { + BUFF_APPEND(&what_npc_said, sentence_str[i]); + } + } + if(!found_end_quote) + { + Log("Poorly formatted AI string, couln't find matching end quote in string %s...\n", sentence_str); + text_was_well_formatted = false; + } + + if(text_was_well_formatted) + { + out->npc_action_type = npc_action; + out->npc_dialog = what_npc_said; + } + + return text_was_well_formatted; +} diff --git a/maketraining.c b/maketraining.c new file mode 100644 index 0000000..343de8e --- /dev/null +++ b/maketraining.c @@ -0,0 +1,160 @@ +#pragma warning(disable : 4996) // nonsense about fopen being insecure +#include +#include "makeprompt.h" + +typedef struct +{ + NpcKind npc_kind; + Perception perceptions[32]; +} TrainingSample; + +#define PlayerAct(act) { .type = PlayerAction, .player_action_type = act, } +#define PlayerSay(txt) { .type = PlayerDialog, .player_dialog = SENTENCE_CONST(txt), } +#define NPCDoSay(d, txt) { .type = NPCDialog, .npc_action_type = d, .npc_dialog = SENTENCE_CONST(txt) } +#define NPCSay(txt) NPCDoSay(ACT_none, txt) +#define PlayerItemChange(new_item) { .type = PlayerHeldItemChanged, .holding = new_item, } + +TrainingSample samples[] = { + { + .npc_kind = NPC_OldMan, + .perceptions = { + PlayerAct(ACT_walks_up), + NPCSay("Young warrior! You must stop Death, there isn't much time."), + PlayerAct(ACT_leaves), + NPCSay("Please!"), + PlayerAct(ACT_walks_up), + NPCSay("There you are! You must fight death!"), + PlayerSay("What what butt ass"), + NPCSay("You must stop death in his tracks and let the village live on!"), + PlayerSay("Nah"), + NPCSay("PLEASE!"), + PlayerAct(ACT_hits_npc), + NPCDoSay(ACT_fights_player, "Ready your sword!"), + }, + }, + { + .npc_kind = NPC_Blocky, + .perceptions = { + PlayerAct(ACT_walks_up), + PlayerSay("Do you think I should use webgl1 or webgl2?"), + NPCSay("What are either of those things?"), + PlayerSay("Let me pass"), + NPCSay("No"), + PlayerAct(ACT_leaves), + NPCDoSay(ACT_strikes_air, "Goodbye"), + PlayerItemChange(ITEM_Tripod), + PlayerSay("Let me pass"), + NPCDoSay(ACT_allows_player_to_pass, "A beautiful sight, that tripod is."), + PlayerSay("Thanks"), + NPCSay("Of course."), + PlayerSay("Let me pass"), + NPCSay("I can't do that, I already let you pass"), + }, + }, + { + .npc_kind = NPC_Blocky, + .perceptions = { + PlayerSay("hey"), + NPCSay("I'm just standing here, what are you doing?"), + PlayerItemChange(ITEM_nothing), + PlayerSay("nothing much"), + NPCSay("You don't have a tripod."), + PlayerSay("True"), + NPCSay("Do you want me to be standing there?"), + PlayerSay("Yes"), + NPCSay("Alright then"), + PlayerSay("No"), + NPCSay("What do you mean?"), + PlayerSay("What are you doing?"), + NPCSay("I'm just standing here"), + PlayerItemChange(ITEM_Tripod), + PlayerSay("How about now?"), + NPCSay("Mighty fine tripod you have there."), + } + }, + { + .npc_kind = NPC_GodRock, + .perceptions = { + PlayerAct(ACT_walks_up), + NPCSay("I am"), + PlayerSay("Whaddup?"), + NPCSay("The clouds part and reveal only me."), + PlayerSay("uhhh. GIve me gold"), + NPCSay("To the greedy, come few"), + PlayerSay("I repent"), + NPCDoSay(ACT_heals_player, "And you are forgiven."), + }, + }, + { + .npc_kind = NPC_GodRock, + .perceptions = { + PlayerAct(ACT_walks_up), + NPCSay("I am"), + PlayerSay("Who are you?"), + NPCSay("Have you heard of the high elves?"), + PlayerSay("Yes"), + NPCDoSay(ACT_heals_player, "No need for me to speak then."), + }, + }, +}; + + +typedef BUFF(char, 1024*10) Escaped; +Escaped escape_for_json(char *s) +{ + Escaped to_return = {0}; + size_t len = strlen(s); + for(int i = 0; i < len; i++) + { + if(s[i] == '\n') + { + BUFF_APPEND(&to_return, '\\'); + BUFF_APPEND(&to_return, 'n'); + } + else if(s[i] == '"') + { + BUFF_APPEND(&to_return, '\\'); + BUFF_APPEND(&to_return, '"'); + } + else + { + BUFF_APPEND(&to_return, s[i]); + } + } + return to_return; +} + +int main(int argc, char ** argv) +{ + Log("Starting to generate training data\n"); + FILE *out = fopen("gen/training_data.jsonl", "w"); + assert(out); + int total_samples = 0; + for(int i = 0; i < ARRLEN(samples); i++) + { + TrainingSample s = samples[i]; + Entity this_entity = (Entity){.is_npc = true, .npc_kind = s.npc_kind}; + PromptBuff cur_prompt = {0}; + for(int i = 0; i < ARRLEN(s.perceptions); i++) + { + if(s.perceptions[i].type == Invalid) break; + if(s.perceptions[i].type == NPCDialog) + { + Log("Generating sample of length %d for NPC %s...\n", i, name_table[s.npc_kind]); + total_samples++; + generate_prompt(&this_entity, &cur_prompt); + BUFF(char, 1024) completion = {0}; + + // get index from action + int index = action_to_index(&this_entity, s.perceptions[i].npc_action_type); + printf_buff(&completion, " %d \"%s\"\n", index, s.perceptions[i].npc_dialog.data); + + fprintf(out, "{\"prompt\": \"%s\", \"completion\": \"%s\"}\n", escape_for_json(cur_prompt.data).data, escape_for_json(completion.data).data); + } + process_perception(&this_entity, s.perceptions[i]); + } + } + + Log("Finished. Total training samples: %d\n", total_samples); + return 0; +} diff --git a/run_codegen.bat b/run_codegen.bat index 3575ce1..166e761 100644 --- a/run_codegen.bat +++ b/run_codegen.bat @@ -32,6 +32,9 @@ thirdparty\sokol-shdc.exe --input quad.glsl --output gen\quad-sapp.glsl.h --slan cl /Ithirdparty /W3 /Zi /WX codegen.c || goto :error codegen || goto :error +cl /Ithirdparty /Igen /W3 /Zi /WX maketraining.c || goto :error +maketraining || goto :error + goto :EOF :error diff --git a/server/main.go b/server/main.go index b6549ac..3227dde 100644 --- a/server/main.go +++ b/server/main.go @@ -308,14 +308,14 @@ func index(w http.ResponseWriter, req *http.Request) { ctx := context.Background() req := gogpt.CompletionRequest { - Model: "curie:ft-personal-2023-03-24-03-06-24", + Model: "curie:ft-alnar-games-2023-03-31-02-21-14", MaxTokens: 80, Prompt: promptString, Temperature: 0.9, FrequencyPenalty: 0.0, PresencePenalty: 0.6, TopP: 1.0, - Stop: []string{"\""}, + Stop: []string{"\n"}, N: 1, } resp, err := c.CreateCompletion(ctx, req) @@ -326,7 +326,8 @@ func index(w http.ResponseWriter, req *http.Request) { } response := resp.Choices[0].Text if logResponses { - log.Println("Println response: ", response) + log.Println("Println response: `", response + "`") + log.Println() } fmt.Fprintf(w, "1%s", response) } diff --git a/todo.txt b/todo.txt index 5f33af2..060f2bd 100644 --- a/todo.txt +++ b/todo.txt @@ -21,6 +21,7 @@ SHIP BETA Later: - Respond to cancel with stripe, redirect to ?cancelled=true that clears day pass ticket cookie and ui value + - Strange ring item from braid that slows down time? - Put playgpt.io in twitch title - Portal at end, exit level - Keep people away from skeletons, make aggroed/chase state where you can't talk to people