ChatGPT generation/parsing functions

main
parent 12a155e681
commit 2834a9357a

@ -359,8 +359,8 @@
"rotation":0,
"visible":true,
"width":32,
"x":1324.48484848485,
"y":749.75757575758
"x":547.151515151517,
"y":539.090909090913
},
{
"class":"",
@ -406,17 +406,6 @@
"x":600,
"y":324
},
{
"class":"",
"height":32,
"id":7,
"name":"Blocky",
"rotation":0,
"visible":true,
"width":32,
"x":1104,
"y":588
},
{
"class":"",
"height":32,
@ -439,50 +428,6 @@
"x":2537.33333333333,
"y":786.666666666667
},
{
"class":"",
"height":32,
"id":10,
"name":"Max",
"rotation":0,
"visible":true,
"width":32,
"x":2481.33333333333,
"y":893.333333333333
},
{
"class":"",
"height":32,
"id":11,
"name":"Hunter",
"rotation":0,
"visible":true,
"width":32,
"x":1838.66666666667,
"y":849.333333333333
},
{
"class":"",
"height":32,
"id":12,
"name":"GodRock",
"rotation":0,
"visible":true,
"width":32,
"x":2222.66666666666,
"y":1461
},
{
"class":"",
"height":32,
"id":13,
"name":"Edeline",
"rotation":0,
"visible":true,
"width":32,
"x":1942.25,
"y":1487.75
},
{
"class":"PROP",
"height":32,

@ -1,7 +1,10 @@
#pragma once
// null terminator always built into buffers so can read properly from data
#define ARRLEN(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
#define ARR_ITER(type, arr) for(type *it = &arr[0]; it < &arr[ARRLEN(arr)]; it++)
#define ARR_ITER_I(type, arr, i_var) ARR_ITER(type, arr) for(int i_var = (int)(it - &arr[0]); i_var != -1; i_var = -1)
// null terminator always built into buffers so can read properly from data
#define BUFF_VALID(buff_ptr) assert((buff_ptr)->cur_index <= ARRLEN((buff_ptr)->data))
#define BUFF(type, max_size) struct { int cur_index; type data[max_size]; char null_terminator; }
#define BUFF_HAS_SPACE(buff_ptr) ( (buff_ptr)->cur_index < ARRLEN((buff_ptr)->data) )

@ -8,7 +8,7 @@ FOR /F "tokens=*" %%g IN ('rg -g *.c -g !thirdparty break') do (SET VAR=%%g)
echo %g%
remedybg.exe stop-debugging
if "%1" == "codegen" ( call run_codegen.bat || goto :error )
if "%1" == "codegen" ( call run_codegen.bat || goto :error ) else ( echo NOT RUNNING CODEGEN )
cl /diagnostics:caret /DDEVTOOLS /Igen /Ithirdparty /W3 /Zi /WX main.c || goto :error
@REM cl /Igen /Ithirdparty /W3 /Zi /WX main.c || goto :error
remedybg.exe start-debugging

@ -0,0 +1,136 @@
#pragma once
const char *global_prompt = "You are a wise dungeonmaster who carefully crafts interesting dialog and actions for an NPC in an action-rpg video game. The NPC performs actions by prefixing their dialog with the action they perform at that time.";
const char *top_of_header = ""
"#pragma once\n"
"\n";
const char *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",
};
typedef struct
{
char *global_prompt;
char *enum_name;
char *possess;
char *discard;
} ItemInfo;
ItemInfo items[] = {
{
.enum_name = "none",
.global_prompt = "The player isn't holding anything",
.possess = "The player is no longer holding anything",
.discard = "The player is no longer holding nothing",
},
{
.enum_name = "WhiteSquare",
.global_prompt = "The player is holding a mysterious white square. It is unknown what strange and erotic abilities one has when they possess the square.",
.possess = "The player is now holding the white square",
.discard = "The player is no longer holding the white square.",
},
{
.enum_name = "Boots",
.global_prompt = "The player is holding the boots of speed. He is a force to be recogned with in this state, he has great speed while holding the boots.",
.possess = "The player is now holding the boots of speed",
.discard = "The player is no longer holding the boots of speed",
},
{
.enum_name = "Tripod",
.global_prompt = "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 = "The player is now holding the tripod",
.discard = "The player is no longer holding the tripod.",
},
};
typedef struct
{
char *name;
char *enum_name;
char *prompt;
} CharacterGen;
CharacterGen characters[] = {
{
.name = "Fredrick",
.enum_name = "OldMan",
.prompt = "\n"
"An example interaction between the player and the NPC, Fredrick:\n"
"Player: ACT_walks_up\n"
"Fredrick: ACT_none \"Hey\"\n"
"Player: \"fsdakfjsd give me gold\"\n"
"Fredrick: ACT_none \"No? I can't do that\"\n"
"Player: \"Who can?\"\n"
"Fredrick: ACT_none \"No idea\"\n"
"\n"
"The NPC you will be acting as, Fredrick, is an ancient geezer past his prime, who has lived in the town of Worchen for as long as he can remember. Your many adventures brought you great wisdom about the beauties of life. Now your precious town is under threat, General Death is leading the charge and he's out for blood.",
},
{
.name = "God",
.enum_name = "GodRock",
.prompt = "\n"
"An example interaction between the player and the NPC, God in a rock:\n"
"Player: ACT_walks_up\n"
"God: ACT_none \"I am\"\n"
"Player: \"fsdakfjsd give me gold\"\n"
"God: ACT_none \"You are...Unworthy\"\n"
"Player: \"Why?\"\n"
"God: ACT_none \"You let Death rage on\"\n"
"\n"
"The NPC you will be acting as, is God. God, in a rock.",
},
{
.name = "Blocky",
.enum_name = "Blocky",
.prompt = "\n"
"An example interaction between the player and the NPC, Blocky:\n"
"Player: ACT_walks_up\n"
"Blocky: ACT_none \"Who dares disturb me?\"\n"
"Player: \"Let me pass\"\n"
"Blocky: ACT_none \"Not without the tripod\"\n"
"Player: \"How about now\"\n"
"The player is currently holding a tripod\n"
"Blocky: ACT_allows_player_to_pass \"The tripod... I see myself in it\"\n"
"\n"
"The NPC you will be acting as is Blocky. He wants to block the player from going to a secret artifact he's standing in front of. 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.",
},
{
.name = "Edeline",
.enum_name = "Edeline",
.prompt = "\n"
"An example of an interaction between the player and the NPC, Edeline:\n"
"\n"
"Player: \"Hello\"\n"
"Edeline: ACT_none \"I see great danger in your future.\"\n"
"Player: \"Oh really?\""
"The player is currently holding a tripod\n"
"Edeline: ACT_none \"That tripod will be the decisive factor in your victory\"\n"
"\n"
"The NPC you will be acting as is named Edeline. She is the master of the future, the star reader. Both are self-given titles, but her ability to predict the future has garnered attention from many who live in Worchen. However, some have called her “unreliable” at times and her predictions can at times be either cryptic or broadly interpreted.",
},
{
.name = "Death",
.enum_name = "Death",
.prompt = "\n"
"An example of an interaction between the player and the NPC, Death:\n"
"\n"
"Player: \"Hello\"\n"
"Death: ACT_none \"We will annihilate the villagers no matter what you do\"\n"
"\n"
"The NPC you will be acting as is named Death. He is leading a crusade against the town of Worchen, wreaking havock among the villagers. He believes that all things, even villages, must die, to be replaced by the new, and avoid becoming stagnant.",
},
};

@ -2,8 +2,6 @@
#include <stdbool.h>
#include <assert.h>
#define assert_cond(cond, explanation) { if(!cond) { printf("Codegen assert_condion line %d %s failed: %.*s\n", __LINE__, #cond, MD_S8VArg(explanation)); __debugbreak(); exit(1); } }
#include "buff.h"
#pragma warning(disable : 4996) // nonsense about fopen being insecure
@ -16,9 +14,6 @@
#pragma warning(pop)
MD_String8 OUTPUT_FOLDER = MD_S8LitComp("gen"); // no trailing slash
MD_String8 ASSETS_FOLDER = MD_S8LitComp("assets");
#define Log(...) { printf("Codegen: "); printf(__VA_ARGS__); }
void dump(MD_Node* from) {
@ -30,28 +25,6 @@ void dump(MD_Node* from) {
d += 1;
}
}
void dump_root(MD_Node* from) {
// Iterate through each top-level node
for(MD_EachNode(node, from->first_child))
{
printf("/ %.*s\n", MD_S8VArg(node->string));
// Print the name of each of the node's tags
for(MD_EachNode(tag, node->first_tag))
{
printf("|-- Tag %.*s\n", MD_S8VArg(tag->string));
}
// Print the name of each of the node's children
for(MD_EachNode(child, node->first_child))
{
printf("|-- Child %.*s\n", MD_S8VArg(child->string));
}
}
}
bool has_decimal(MD_String8 s)
{
for(int i = 0; i < s.size; i++)
@ -63,17 +36,18 @@ bool has_decimal(MD_String8 s)
MD_Arena *cg_arena = NULL;
#define S8(s) MD_S8Lit(s)
#define S8V(s) MD_S8VArg(s)
MD_String8 ChildValue(MD_Node *n, MD_String8 name) {
MD_Node *child_with_value = MD_ChildFromString(n, name, 0);
assert_cond(child_with_value, MD_S8Fmt(cg_arena, "Could not find child named '%.*s' of node '%.*s'", MD_S8VArg(name), MD_S8VArg(n->string)));
assert_cond(!MD_NodeIsNil(child_with_value->first_child), MD_S8Lit("Must have child"));
//assert(child_with_value->first_child->string.str != 0 && child_with_value->first_child->string.size > 0);
assert(child_with_value);
assert(!MD_NodeIsNil(child_with_value->first_child)); // MD_S8Lit("Must have child"));
return child_with_value->first_child->string;
}
MD_String8 asset_file_path(MD_String8 filename) {
return MD_S8Fmt(cg_arena, "%.*s/%.*s", MD_S8VArg(ASSETS_FOLDER), MD_S8VArg(filename));
return MD_S8Fmt(cg_arena, "%.*s/%.*s", MD_S8VArg(S8("assets")), MD_S8VArg(filename));
}
char *nullterm(MD_String8 s) {
@ -91,7 +65,7 @@ char* fillnull(char *s, char c) {
}
s++;
}
assert_cond(false, MD_S8Lit("Couldn't find char"));
assert(false); // MD_S8Lit("Couldn't find char"));
return NULL;
}
@ -127,9 +101,6 @@ 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)
{
for(MD_EachNode(node, from))
@ -143,66 +114,41 @@ void dump_full(MD_Node* from)
printf("%.*s\n", MD_S8VArg(debugged));*/
}
int main(int argc, char **argv) {
cg_arena = MD_ArenaAlloc();
assert_cond(cg_arena, MD_S8Lit("Memory"));
#include "character_info.h"
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))
{
if(MD_S8Match(node->first_tag->string, MD_S8Lit("global_prompt"), 0))
{
global_prompt = node->string;
}
}
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);
}
}
}
int main(int argc, char **argv)
{
cg_arena = MD_ArenaAlloc();
assert(cg_arena);
// do characters
Nodes characters = {0};
for(MD_EachNode(node, training_parse.node->first_child))
{
if(MD_S8Match(node->first_tag->string, MD_S8Lit("character"), 0))
{
BUFF_APPEND(&characters, node);
}
}
FILE *char_header = fopen("gen/characters.gen.h", "w");
fprintf(char_header, top_of_header);
Nodes items = {0};
for(MD_EachNode(node, training_parse.node->first_child))
{
if(MD_S8Match(node->first_tag->string, MD_S8Lit("item"), 0))
{
BUFF_APPEND(&items, node);
}
}
BUFF_ITER(MD_Node*, &characters)
#define GEN_TABLE(arr_elem_type, table_name, arr, str_access) { fprintf(char_header, "char *%s[] = {\n", table_name); ARR_ITER(arr_elem_type, arr) fprintf(char_header, "\"%s\",\n", str_access); fprintf(char_header, "}; // %s\n", table_name); }
#define GEN_ENUM(arr_elem_type, arr, enum_type_name, enum_name_access, fmt_str) { fprintf(char_header, "typedef enum\n{\n"); ARR_ITER(arr_elem_type, arr) fprintf(char_header, fmt_str, enum_name_access); fprintf(char_header, "} %s;\n", enum_type_name); GEN_TABLE(arr_elem_type, enum_type_name "_names", arr, enum_name_access); }
GEN_ENUM(char *, actions, "Action", *it, "ACT_%s,\n");
GEN_ENUM(ItemInfo, items, "ItemKind", it->enum_name, "ITEM_%s,\n");
// escape multiline strings in C
fprintf(char_header, "typedef enum\n{\n");
ARR_ITER(CharacterGen, characters)
{
Log("Character %.*s\n", MD_S8VArg((*it)->string));
fprintf(char_header, " NPC_%s,\n", it->enum_name);
}
// characters enum can be extended at site of include, not ending the enum here
fclose(char_header);
// do assets
MD_String8 writeto = MD_S8Fmt(cg_arena, "%.*s/assets.gen.c", MD_S8VArg(OUTPUT_FOLDER));
MD_String8 writeto = MD_S8Fmt(cg_arena, "gen/assets.gen.c");
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"));
//dump(parse.node);
MD_String8List declarations_list = {0};
MD_String8List load_list = {0};
MD_String8List level_decl_list = {0};
@ -213,9 +159,9 @@ int main(int argc, char **argv) {
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)));
assert(filepath.str != 0); // MD_S8Fmt(cg_arena, "No filepath specified for sound '%.*s'", MD_S8VArg(node->string)));
FILE *asset_file = fopen(filepath.str, "r");
assert_cond(asset_file, MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string)));
assert(asset_file); // MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string)));
fclose(asset_file);
MD_S8ListPush(cg_arena, &declarations_list, MD_S8Fmt(cg_arena, "AudioSample %.*s = {0};\n", MD_S8VArg(variable_name)));
@ -226,9 +172,9 @@ int main(int argc, char **argv) {
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)));
assert(filepath.str != 0); // , MD_S8Fmt(cg_arena, "No filepath specified for image '%.*s'", MD_S8VArg(node->string)));
FILE *asset_file = fopen(filepath.str, "r");
assert_cond(asset_file, MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string)));
assert(asset_file); // , MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string)));
fclose(asset_file);
MD_S8ListPush(cg_arena, &declarations_list, MD_S8Fmt(cg_arena, "sg_image %.*s = {0};\n", MD_S8VArg(variable_name)));
@ -282,7 +228,7 @@ int main(int argc, char **argv) {
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"));
assert(!MD_NodeIsNil(level_parse.node->first_child)); // , MD_S8Lit("Failed to load level file"));
MD_Node *layers = MD_ChildFromString(level_parse.node->first_child, MD_S8Lit("layers"), 0);
fprintf(output, "Level %.*s = {\n", MD_S8VArg(variable_name));
@ -374,79 +320,6 @@ int main(int argc, char **argv) {
fprintf(output, "TileSet tilesets[] = { %.*s\n };\n", MD_S8VArg(MD_S8ListJoin(cg_arena, tileset_decls, &join)));
fclose(output);
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, "typedef enum Action {\n");
for(EachString(s, action_strings.first))
{
fprintf(output, "ACT_%.*s,\n", S8V(s->string));
}
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)
{
fprintf(output, "\"%.*s\", // %.*s\n", MD_S8VArg(ChildValue(*it, S8("prompt"))),MD_S8VArg((*it)->string));
}
fprintf(output, "}; // prompt table\n");
fprintf(output, "typedef enum ItemKind {\nITEM_none,\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\"\",\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\"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\"The player is no longer holding nothing\",\n");
BUFF_ITER(MD_Node*, &items)
{
fprintf(output, "\"%.*s\",\n", MD_S8VArg(ChildValue(*it, S8("discard_message"))));
}
fprintf(output, "}; // item discard_message table\n");
fprintf(output, "char *name_table[] = {\n");
BUFF_ITER(MD_Node*, &characters)
{
fprintf(output, "\"%.*s\", // %.*s\n", MD_S8VArg(ChildValue(*it, S8("name"))),MD_S8VArg((*it)->string));
}
fprintf(output, "}; // name table\n");
fprintf(output, "typedef enum\n{ // character enums, not completed here so you can add more in the include\n");
BUFF_ITER(MD_Node*, &characters)
{
fprintf(output, "NPC_%.*s,\n", MD_S8VArg((*it)->string));
}
//fprintf(output, "NPC_LAST_CHARACTER,\n};\n");
fclose(output);
return 0;

@ -1,22 +1,7 @@
@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."
@global_prompt "You are a wise dungeonmaster who carefully crafts interesting dialog and actions for an NPC in an action-rpg video game. The NPC performs actions by prefixing their dialog with the action they perform at that time, and they ONLY perform actions listed in the [] brackets farther down in this text."
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:

@ -2124,16 +2124,17 @@ float draw_wrapped_text(bool dry_run, Vec2 at_point, float max_width, char *text
AABB drawn_bounds = draw_text((TextParams){true, dry_run, line_to_draw, AddV2(cursor, V2(0.0f, -line_height)), BLACK, text_scale, clip_to, colors_to_draw});
if(!dry_run) dbgrect(drawn_bounds);
// caught a random infinite loop in the debugger, this will stop it
assert(chars_from_sentence >= 0); // defensive programming
if(chars_from_sentence == 0)
{
break;
}
sentence_len -= chars_from_sentence;
sentence_to_draw += chars_from_sentence;
colors += chars_from_sentence;
cursor = V2(drawn_bounds.upper_left.X, drawn_bounds.lower_right.Y);
// caught a random infinite loop in the debugger, maybe this will stop it. Need to test and make sure it doesn't early out on valid text cases
if(!has_point(clip_to, cursor))
{
break;
}
}
return cursor.Y;
@ -2488,7 +2489,11 @@ void frame(void)
// parse out from the sentence NPC action and dialog
Perception out = {0};
#ifdef DO_CHATGPT_PARSING
bool text_was_well_formatted = parse_chatgpt_response(it, sentence_str, &out);
#else
bool text_was_well_formatted = parse_ai_response(it, sentence_str, &out);
#endif
if(text_was_well_formatted)
{
@ -2708,15 +2713,6 @@ void frame(void)
draw_animated_sprite(&merchant_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col);
}
#endif
else if(it->npc_kind == NPC_Max)
{
}
else if(it->npc_kind == NPC_Hunter)
{
}
else if(it->npc_kind == NPC_John)
{
}
else if(it->npc_kind == NPC_MOOSE)
{
}
@ -2824,7 +2820,11 @@ void frame(void)
if(it->perceptions_dirty)
{
PromptBuff prompt = {0};
#ifdef DO_CHATGPT_PARSING
generate_chatgpt_prompt(it, &prompt);
#else
generate_prompt(it, &prompt);
#endif
Log("Sending request with prompt `%s`\n", prompt.data);
#ifdef WEB
@ -2839,7 +2839,7 @@ void frame(void)
#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); }
#define SAY(act, txt) { printf_buff(&mocked_ai_response, "%s \"%s\"", actions[act], txt); }
if(it->npc_kind == NPC_Blocky)
{
if(it->last_seen_holding_kind == ITEM_Tripod && !it->moved)
@ -2857,7 +2857,7 @@ void frame(void)
SAY(ACT_none, "I am an NPC. Bla bla bl alb djsfklalfkdsaj. Did you know shortcake?");
}
Perception p = {0};
assert(parse_ai_response(it, mocked_ai_response.data, &p));
assert(parse_chatgpt_response(it, mocked_ai_response.data, &p));
process_perception(it, p);
#undef SAY
#endif
@ -3048,9 +3048,11 @@ void frame(void)
}
}
interact_just_pressed = false;
interact = false;
} // while loop
}
PROFILE_SCOPE("render player")
{
Vec2 character_sprite_pos = AddV2(player->pos, V2(0.0, 20.0f));
@ -3213,22 +3215,10 @@ void frame(void)
else if(npc_is_knight_sprite(it))
{
Color tint = WHITE;
if(it->npc_kind == NPC_Max)
{
tint = colhex(0xfc8803);
}
else if(it->npc_kind == NPC_Hunter)
{
tint = colhex(0x4ac918);
}
else if(it->npc_kind == NPC_Blocky)
if(it->npc_kind == NPC_Blocky)
{
tint = colhex(0xa84032);
}
else if(it->npc_kind == NPC_John)
{
tint = colhex(0x16c7a1);
}
else if(it->npc_kind == NPC_Edeline)
{
tint = colhex(0x8c34eb);

@ -5,11 +5,14 @@
#include <stdbool.h>
#include <string.h>
#include <stdlib.h> // atoi
#include "character_info.h"
#include "characters.gen.h"
NPC_Skeleton,
NPC_MOOSE,
} NpcKind;
#define DO_CHATGPT_PARSING
#define Log(...) { printf("%s Log %d | ", __FILE__, __LINE__); printf(__VA_ARGS__); }
// REFACTORING:: also have to update in javascript!!!!!!!!
@ -20,6 +23,33 @@ typedef BUFF(char, MAX_SENTENCE_LENGTH) Sentence;
#define REMEMBERED_PERCEPTIONS 24
// Never expected such a stupid stuff from such a great director. If there is 0 stari can give that or -200 to this movie. Its worst to see and unnecessary loss of money
typedef BUFF(char, 1024*10) Escaped;
Escaped escape_for_json(const 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
{
assert(s[i] <= 126 && s[i] >= 32 );
BUFF_APPEND(&to_return, s[i]);
}
}
return to_return;
}
typedef enum PerceptionType
{
Invalid, // so that zero value in training structs means end of perception
@ -153,7 +183,8 @@ typedef struct 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 || it->npc_kind == NPC_Edeline);
return false;
//return it->is_npc && ( it->npc_kind == NPC_Blocky || it->npc_kind == NPC_Edeline);
}
typedef BUFF(char, MAX_SENTENCE_LENGTH*(REMEMBERED_PERCEPTIONS+4)) PromptBuff;
@ -197,6 +228,41 @@ void fill_available_actions(Entity *it, AvailableActions *a)
}
}
// 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 process_perception(Entity *it, Perception p)
{
if(it->is_npc)
@ -235,46 +301,141 @@ void process_perception(Entity *it, Perception p)
}
}
#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; };
#define printf_buff(buff_ptr, ...) { BUFF_VALID(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)
bool npc_does_dialog(Entity *it)
{
AvailableActions available = {0};
fill_available_actions(it, &available);
if(action_index < 0 || action_index >= available.cur_index)
return it->npc_kind < ARRLEN(characters);
}
typedef enum
{
MSG_SYSTEM,
MSG_USER,
MSG_ASSISTANT,
MSG_ASSISTANT_NO_TRAILING,
} MessageType;
void dump_json_node(PromptBuff *into, MessageType type, const char *content)
{
const char *type_str = 0;
if(type == MSG_SYSTEM)
type_str = "system";
else if(type == MSG_USER)
type_str = "user";
else if(type == MSG_ASSISTANT || MSG_ASSISTANT_NO_TRAILING)
type_str = "assistant";
assert(type_str);
printf_buff(into, "{\"type\": \"%s\", \"content\": \"%s\"}", type_str, escape_for_json(content).data);
if(type != MSG_ASSISTANT_NO_TRAILING) printf_buff(into, ",");
}
// outputs json
void generate_chatgpt_prompt(Entity *it, PromptBuff *into)
{
assert(it->is_npc);
assert(it->npc_kind < ARRLEN(characters));
*into = (PromptBuff){0};
printf_buff(into, "[");
BUFF(char, 1024) initial_system_msg = {0};
const char *health_string = 0;
if(it->damage <= 0.2f)
{
return false;
health_string = "the NPC hasn't taken much damage, they're healthy.";
}
else if(it->damage <= 0.5f)
{
health_string = "the NPC has taken quite a chunk of damage, they're soon gonna be ready to call it quits.";
}
else if(it->damage <= 0.8f)
{
health_string = "the NPC is close to dying! They want to leave the player's party ASAP";
}
else
{
*out = available.data[action_index];
return true;
health_string = "it's over for the NPC, they're basically dead they've taken so much damage. They should get their affairs in order.";
}
}
assert(health_string);
// don't call on untrusted action, doesn't return error
int action_to_index(Entity *it, Action a)
{
printf_buff(&initial_system_msg, "%s\n%s\nNPC health status: Right now, %s\n%s", global_prompt, characters[it->npc_kind].prompt, health_string, items[it->last_seen_holding_kind].global_prompt);
dump_json_node(into, MSG_SYSTEM, initial_system_msg.data);
Entity *e = it;
ItemKind last_holding = ITEM_none;
BUFF_ITER(Perception, &e->remembered_perceptions)
{
BUFF(char, 1024) cur_node = {0};
if(it->type == PlayerAction)
{
assert(it->player_action_type < ARRLEN(actions));
printf_buff(&cur_node, "Player: ACT_%s", actions[it->player_action_type]);
dump_json_node(into, MSG_USER, cur_node.data);
}
else if(it->type == EnemyAction)
{
assert(it->enemy_action_type < ARRLEN(actions));
printf_buff(&cur_node, "An Enemy: ACT_%s", actions[it->player_action_type]);
dump_json_node(into, MSG_USER, cur_node.data);
}
else if(it->type == PlayerDialog)
{
printf_buff(&cur_node, "Player: \"%s\"", it->player_dialog.data);
dump_json_node(into, MSG_USER, cur_node.data);
}
else if(it->type == NPCDialog)
{
assert(it->npc_action_type < ARRLEN(actions));
printf_buff(&cur_node, "%s: ACT_%s \"%s\"", characters[e->npc_kind].name, actions[it->npc_action_type], it->npc_dialog.data);
dump_json_node(into, MSG_ASSISTANT, cur_node.data);
}
else if(it->type == PlayerHeldItemChanged)
{
if(last_holding != it->holding)
{
if(last_holding != ITEM_none)
{
printf_buff(&cur_node, "%s\n", items[last_holding].discard);
}
if(it->holding != ITEM_none)
{
printf_buff(&cur_node, "%s\n", items[it->holding].possess);
}
last_holding = it->holding;
}
dump_json_node(into, MSG_SYSTEM, cur_node.data);
}
else
{
assert(false);
}
}
BUFF(char, 1024) latest_state_node = {0};
AvailableActions available = {0};
fill_available_actions(it, &available);
Action target_action = a;
int index = -1;
for(int i = 0; i < available.cur_index; i++)
printf_buff(&latest_state_node, "The NPC can now ONLY do these actions: [");
BUFF_ITER_I(Action, &available, i)
{
if(available.data[i] == target_action)
if(i == available.cur_index - 1)
{
index = i;
break;
printf_buff(&latest_state_node, "ACT_%s", actions[*it]);
}
else
{
printf_buff(&latest_state_node, "ACT_%s, ", actions[*it]);
}
}
assert(index != -1);
return index;
}
printf_buff(&latest_state_node, "]");
dump_json_node(into, MSG_SYSTEM, latest_state_node.data);
bool npc_does_dialog(Entity *it)
{
return it->npc_kind < ARRLEN(prompt_table);
BUFF(char, 1024) assistant_prompt_node = {0};
printf_buff(&assistant_prompt_node, "%s: ACT_", characters[it->npc_kind].name);
dump_json_node(into, MSG_ASSISTANT_NO_TRAILING, assistant_prompt_node.data);
printf_buff(into, "]");
}
void generate_prompt(Entity *it, PromptBuff *into)
@ -287,8 +448,8 @@ void generate_prompt(Entity *it, PromptBuff *into)
printf_buff(into, "%s", "\n");
// npc description prompt
assert(it->npc_kind < ARRLEN(prompt_table));
printf_buff(into, "%s", prompt_table[it->npc_kind]);
assert(it->npc_kind < ARRLEN(characters));
printf_buff(into, "%s", characters[it->npc_kind].prompt);
printf_buff(into, "%s", "\n");
// npc stats prompt
@ -315,8 +476,8 @@ void generate_prompt(Entity *it, PromptBuff *into)
// item prompt
if(it->last_seen_holding_kind != ITEM_none)
{
assert(it->last_seen_holding_kind < ARRLEN(item_prompt_table));
printf_buff(into, "%s", item_prompt_table[it->last_seen_holding_kind]);
assert(it->last_seen_holding_kind < ARRLEN(items));
printf_buff(into, "%s", items[it->last_seen_holding_kind].global_prompt);
printf_buff(into, "%s", "\n");
}
@ -326,7 +487,7 @@ void generate_prompt(Entity *it, PromptBuff *into)
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", actions[*it]);
printf_buff(into, "%s", ", ");
}
printf_buff(into, "%s", "]\n");
@ -337,13 +498,13 @@ void generate_prompt(Entity *it, PromptBuff *into)
{
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]);
assert(it->player_action_type < ARRLEN(actions));
printf_buff(into, "Player: ACT %s \n", actions[it->player_action_type]);
}
else if(it->type == EnemyAction)
{
assert(it->enemy_action_type < ARRLEN(action_strings));
printf_buff(into, "An Enemy: ACT %s \n", action_strings[it->player_action_type]);
assert(it->enemy_action_type < ARRLEN(actions));
printf_buff(into, "An Enemy: ACT %s \n", actions[it->player_action_type]);
}
else if(it->type == PlayerDialog)
{
@ -353,7 +514,7 @@ void generate_prompt(Entity *it, PromptBuff *into)
}
else if(it->type == NPCDialog)
{
printf_buff(into, "The NPC, %s: ACT %s \"%s\"\n", name_table[e->npc_kind], action_strings[it->npc_action_type], it->npc_dialog.data);
printf_buff(into, "The NPC, %s: ACT %s \"%s\"\n", characters[e->npc_kind].name, actions[it->npc_action_type], it->npc_dialog.data);
}
else if(it->type == PlayerHeldItemChanged)
{
@ -361,12 +522,12 @@ void generate_prompt(Entity *it, PromptBuff *into)
{
if(last_holding != ITEM_none)
{
printf_buff(into, "%s", item_discard_message_table[last_holding]);
printf_buff(into, "%s", items[last_holding].discard);
printf_buff(into, "%s", "\n");
}
if(it->holding != ITEM_none)
{
printf_buff(into, "%s", item_possess_message_table[it->holding]);
printf_buff(into, "%s", items[it->holding].possess);
printf_buff(into, "%s", "\n");
}
last_holding = it->holding;
@ -378,7 +539,53 @@ void generate_prompt(Entity *it, PromptBuff *into)
}
}
printf_buff(into, "The NPC, %s: ACT_INDEX", name_table[e->npc_kind]);
printf_buff(into, "The NPC, %s: ACT_INDEX", characters[e->npc_kind].name);
}
bool parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out)
{
*out = (Perception){0};
out->type = NPCDialog;
size_t sentence_length = strlen(sentence_str);
char action_string[512] = {0};
char dialog_string[512] = {0};
int variables_filled = sscanf(sentence_str, "%511s \"%511[^\n]\"", action_string, dialog_string);
if(strlen(action_string) == 0 || strlen(dialog_string) == 0 || variables_filled != 2)
{
Log("sscanf failed to parse chatgpt string `%s`, variables unfilled. Action string: `%s` dialog string `%s`\n", sentence_str, action_string, dialog_string);
return false;
}
AvailableActions available = {0};
fill_available_actions(it, &available);
bool found_action = false;
BUFF_ITER(Action, &available)
{
if(strcmp(actions[*it], action_string) == 0)
{
found_action = true;
out->npc_action_type = *it;
}
}
if(!found_action)
{
Log("Could not find action associated with string `%s`\n", action_string);
out->npc_action_type = ACT_none;
}
if(strlen(dialog_string) >= ARRLEN(out->npc_dialog.data))
{
Log("Dialog string `%s` too big to fit in sentence size %d\n", dialog_string, (int)ARRLEN(out->npc_dialog.data));
return false;
}
memcpy(out->npc_dialog.data, dialog_string, strlen(dialog_string));
out->npc_dialog.cur_index = (int)strlen(dialog_string);
return true;
}
// returns if the response was well formatted

@ -355,31 +355,7 @@ TrainingSample samples[] = {
};
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
{
assert(s[i] <= 126 && s[i] >= 32 );
BUFF_APPEND(&to_return, s[i]);
}
}
return to_return;
}
int main(int argc, char ** argv)
{

@ -1,5 +1,7 @@
@echo off
echo Running codegen...
echo Asset packs which must be bought and unzipped into root directory before running this script:
echo https://rafaelmatos.itch.io/epic-rpg-world-pack-ancient-ruins
echo https://sventhole.itch.io/undead-pixel-art-characters
@ -39,8 +41,8 @@ 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
@REM cl /Ithirdparty /Igen /W3 /Zi /WX maketraining.c || goto :error
@REM maketraining || goto :error
goto :EOF

@ -408,7 +408,7 @@ func completion(w http.ResponseWriter, req *http.Request) {
log.Println("Println response: `", response + "`")
log.Println()
}
fmt.Fprintf(w, "1%s", response)
fmt.Fprintf(w, "1%s", response + "\n")
}
}

@ -21,7 +21,7 @@ func main() {
messages = append(messages, openai.ChatCompletionMessage {
Role: "system",
Content: `You are a wise dungeonmaster who carefully crafts interesting dialog and actions for an NPC in an action-rpg video game. The NPC performs actions by prefixing their dialog with one of these actions: [ACT_none, ACT_fights_player, ACT_joins_player, ACT_strikes_air]
Content: `You are a wise dungeonmaster who carefully crafts interesting dialog and actions for an NPC in an action-rpg video game. The NPC performs actions by prefixing their dialog with the action they perform at that time.
An example interaction between the player and an NPC:
Player: ACT_walks_up
@ -36,22 +36,29 @@ The NPC you will be acting as, Fredrick, is a soldier in death's cohort.
`,
})
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf("Say something with format [action] \"dialog\": ")
text, _ := reader.ReadString('\n')
messages = append(messages, openai.ChatCompletionMessage {
Role: "user",
Content: text + "\nFredrick: ",
Content: text + "Fredrick: ",
})
//fmt.Printf("Generating with messages: `%s`\n", messages)
toGenerate := make([]openai.ChatCompletionMessage, len(messages))
copy(toGenerate, messages)
toGenerate = append(toGenerate, toGenerate[len(toGenerate)-1])
toGenerate[len(toGenerate)-2] = openai.ChatCompletionMessage {
Role: "system",
Content: "The NPC can now ONLY do these actions: [ACT_none, ACT_fights_player, ACT_joins_player, ACT_strikes_air]",
}
fmt.Printf("Generating with messages: `%s`\n", toGenerate)
resp, err := c.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: messages,
Messages: toGenerate,
Stop: []string{"\n"},
},
)
@ -64,6 +71,7 @@ The NPC you will be acting as, Fredrick, is a soldier in death's cohort.
Role: "assistant",
Content: resp.Choices[0].Message.Content,
})
fmt.Printf("Tokens used: %d\n", resp.Usage.TotalTokens)
fmt.Printf("Response: `%s`\n", resp.Choices[0].Message.Content)
}
}

Loading…
Cancel
Save