From 4f5979405a0349f59ed0bc662613eb4359708648 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Fri, 7 Apr 2023 01:51:10 -0700 Subject: [PATCH 1/6] NEVER EVER USE CONTINUE and better debug drawing --- assets/newsmalllevel.json | 102 ++++++++++++++++++++++++++++++++++++++ build_desktop_debug.bat | 7 ++- main.c | 62 ++++++++++++++++------- 3 files changed, 152 insertions(+), 19 deletions(-) create mode 100644 assets/newsmalllevel.json diff --git a/assets/newsmalllevel.json b/assets/newsmalllevel.json new file mode 100644 index 0000000..083067c --- /dev/null +++ b/assets/newsmalllevel.json @@ -0,0 +1,102 @@ +{ "compressionlevel":-1, + "height":9, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":9, + "id":1, + "name":"Base", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":9, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":9, + "id":3, + "name":"Detail", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":9, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 60, 60, 0, 0, 0, 0, 0, + 0, 0, 0, 60, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":9, + "id":4, + "name":"Collideable", + "opacity":0.9, + "type":"tilelayer", + "visible":true, + "width":9, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":2, + "name":"objects", + "objects":[ + { + "class":"", + "height":32, + "id":2, + "name":"PLAYER", + "rotation":0, + "visible":true, + "width":32, + "x":72.4621212121228, + "y":85.727272727277 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":5, + "nextobjectid":17, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.9.2", + "tileheight":32, + "tilesets":[ + { + "firstgid":1, + "source":"Main.tsx" + }], + "tilewidth":32, + "type":"map", + "version":"1.9", + "width":9 +} \ No newline at end of file diff --git a/build_desktop_debug.bat b/build_desktop_debug.bat index 3bb0ba8..1459bf4 100644 --- a/build_desktop_debug.bat +++ b/build_desktop_debug.bat @@ -2,8 +2,13 @@ @REM https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-by-category?view=msvc-170 + +FOR /F "tokens=*" %%g IN ('rg -g *.c -g !thirdparty break') do (SET VAR=%%g) + +echo %g% + remedybg.exe stop-debugging -call run_codegen.bat || goto :error +if "%1" == "codegen" ( call run_codegen.bat || goto :error ) 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 diff --git a/main.c b/main.c index c0c7d36..651859c 100644 --- a/main.c +++ b/main.c @@ -906,10 +906,12 @@ SwordToDamage entity_sword_to_do_damage(Entity *from, Overlapping o) typedef Vec4 Color; -#define WHITE (Color){1.0f, 1.0f, 1.0f, 1.0f} -#define BLACK (Color){0.0f, 0.0f, 0.0f, 1.0f} -#define RED (Color){1.0f, 0.0f, 0.0f, 1.0f} -#define GREEN (Color){0.0f, 1.0f, 0.0f, 1.0f} +#define WHITE ((Color){1.0f, 1.0f, 1.0f, 1.0f}) +#define BLACK ((Color){0.0f, 0.0f, 0.0f, 1.0f}) +#define RED ((Color){1.0f, 0.0f, 0.0f, 1.0f}) +#define PINK ((Color){1.0f, 0.0f, 1.0f, 1.0f}) +#define BLUE ((Color){0.0f, 0.0f, 1.0f, 1.0f}) +#define GREEN ((Color){0.0f, 1.0f, 0.0f, 1.0f}) Color colhex(uint32_t hex) { @@ -1607,11 +1609,15 @@ bool profiling; #endif #endif +Color debug_color = {1.0f, 0.0f, 0.0f, 0.0f}; + +#define dbgcol(col) DeferLoop(debug_color = col, debug_color = RED) + void dbgsquare(Vec2 at) { #ifdef DEVTOOLS if(!show_devtools) return; - colorquad(true, quad_centered(at, V2(10.0, 10.0)), RED); + colorquad(true, quad_centered(at, V2(10.0, 10.0)), debug_color); #else (void)at; #endif @@ -1621,7 +1627,7 @@ void dbgline(Vec2 from, Vec2 to) { #ifdef DEVTOOLS if(!show_devtools) return; - line(from, to, 0.5f, RED); + line(from, to, 0.5f, debug_color); #else (void)from; (void)to; @@ -1640,7 +1646,7 @@ void dbgrect(AABB rect) #ifdef DEVTOOLS if(!show_devtools) return; const float line_width = 0.5; - const Color col = RED; + Color col = debug_color; Quad q = quad_aabb(rect); line(q.ul, q.ur, line_width, col); line(q.ur, q.lr, line_width, col); @@ -1912,7 +1918,6 @@ Vec2 move_and_slide(MoveSlideParams p) assert(collision_aabb_size.x > 0.0f); assert(collision_aabb_size.y > 0.0f); AABB at_new = centered_aabb(new_pos, collision_aabb_size); - dbgrect(at_new); BUFF(AABB, 256) to_check = {0}; // add tilemap boxes @@ -1930,8 +1935,10 @@ Vec2 move_and_slide(MoveSlideParams p) TileCoord tilecoord_to_check = world_to_tilecoord(*it); if(is_tile_solid(get_tile_layer(&level_level0, 2, tilecoord_to_check))) - - BUFF_APPEND(&to_check, tile_aabb(tilecoord_to_check)); + { + AABB t = tile_aabb(tilecoord_to_check); + BUFF_APPEND(&to_check, t); + } } } @@ -1951,7 +1958,11 @@ Vec2 move_and_slide(MoveSlideParams p) // box first, because doing so is a simple heuristic to avoid depenetrating and losing // sideways velocity. It's visual and I can't put diagrams in code so uh oh! - BUFF(AABB, 32) actually_overlapping = {0}; + typedef BUFF(AABB, 32) OverlapBuff; + OverlapBuff actually_overlapping = {0}; + + dbgcol(PINK) + dbgrect(at_new); BUFF_ITER(AABB, &to_check) { if(overlapping(at_new, *it)) @@ -1960,6 +1971,7 @@ Vec2 move_and_slide(MoveSlideParams p) } } + float smallest_distance = FLT_MAX; int smallest_aabb_index = 0; int i = 0; @@ -1975,7 +1987,7 @@ Vec2 move_and_slide(MoveSlideParams p) i = 0; - BUFF(AABB, 32) overlapping_smallest_first = {0}; + OverlapBuff overlapping_smallest_first = {0}; if(actually_overlapping.cur_index > 0) { BUFF_APPEND(&overlapping_smallest_first, actually_overlapping.data[smallest_aabb_index]); @@ -1984,7 +1996,6 @@ Vec2 move_and_slide(MoveSlideParams p) { if(i == smallest_aabb_index) { - continue; } else { @@ -1993,18 +2004,34 @@ Vec2 move_and_slide(MoveSlideParams p) i++; } + // overlapping + BUFF_ITER(AABB, &overlapping_smallest_first) + { + dbgcol(GREEN) + { + dbgrect(*it); + } + } + + //overlapping_smallest_first = actually_overlapping; + + BUFF_ITER(AABB, &actually_overlapping) + dbgcol(WHITE) + dbgrect(*it); + + BUFF_ITER(AABB, &overlapping_smallest_first) + dbgcol(WHITE) + dbgsquare(aabb_center(*it)); + CollisionInfo info = {0}; for(int col_iter_i = 0; col_iter_i < 1; col_iter_i++) BUFF_ITER(AABB, &overlapping_smallest_first) { AABB to_depenetrate_from = *it; - dbgrect(to_depenetrate_from); int iters_tried_to_push_apart = 0; while(overlapping(to_depenetrate_from, at_new) && iters_tried_to_push_apart < 500) { - //dbgsquare(to_depenetrate_from.upper_left); - //dbgsquare(to_depenetrate_from.lower_right); - const float move_dist = 0.05f; + const float move_dist = 0.1f; info.happened = true; Vec2 from_point = aabb_center(to_depenetrate_from); @@ -2070,7 +2097,6 @@ float draw_wrapped_text(bool dry_run, Vec2 at_point, float max_width, char *text chars_from_sentence -= 1; break; } - chars_from_sentence += 1; } if(chars_from_sentence > sentence_len) chars_from_sentence--; From cefaff51fb019a55bfe61303e30c886114b139fd Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Fri, 7 Apr 2023 23:54:19 -0700 Subject: [PATCH 2/6] Buff iteration by index macro --- buff.h | 4 ++-- elements.mdesk | 26 ++++++-------------------- main.c | 36 +++++++++++++++++++++++++----------- make_training_prompt.txt | 20 ++++++++++++++++++++ maketraining.c | 19 ++++++++++++++++++- server/main.go | 1 + 6 files changed, 72 insertions(+), 34 deletions(-) create mode 100644 make_training_prompt.txt diff --git a/buff.h b/buff.h index 14334fc..7460899 100644 --- a/buff.h +++ b/buff.h @@ -7,8 +7,8 @@ #define BUFF_HAS_SPACE(buff_ptr) ( (buff_ptr)->cur_index < ARRLEN((buff_ptr)->data) ) #define BUFF_EMPTY(buff_ptr) ((buff_ptr)->cur_index == 0) #define BUFF_APPEND(buff_ptr, element) { (buff_ptr)->data[(buff_ptr)->cur_index++] = element; BUFF_VALID(buff_ptr); } -#define BUFF_ITER_EX(type, buff_ptr, begin_ind, cond, movement) for(type *it = &((buff_ptr)->data[begin_ind]); cond; movement) -#define BUFF_ITER(type, buff_ptr) BUFF_ITER_EX(type, (buff_ptr), 0, it < (buff_ptr)->data + (buff_ptr)->cur_index, it++) +#define BUFF_ITER(type, buff_ptr) for(type *it = &((buff_ptr)->data[0]); it < &((buff_ptr)->data[(buff_ptr)->cur_index]); it++) +#define BUFF_ITER_I(type, buff_ptr, i_var) BUFF_ITER(type, buff_ptr) for(int i_var = (int)(it - &((buff_ptr)->data[0])); i_var != -1; i_var = -1) #define BUFF_PUSH_FRONT(buff_ptr, value) { (buff_ptr)->cur_index++; BUFF_VALID(buff_ptr); for(int i = (buff_ptr)->cur_index - 1; i > 0; i--) { (buff_ptr)->data[i] = (buff_ptr)->data[i - 1]; }; (buff_ptr)->data[0] = value; } #define BUFF_REMOVE_FRONT(buff_ptr) {if((buff_ptr)->cur_index > 0) {for(int i = 0; i < (buff_ptr)->cur_index - 1; i++) { (buff_ptr)->data[i] = (buff_ptr)->data[i+1]; }; (buff_ptr)->cur_index--;}} #define BUFF_CLEAR(buff_ptr) {memset((buff_ptr), 0, sizeof(*(buff_ptr))); ((buff_ptr)->cur_index = 0);} diff --git a/elements.mdesk b/elements.mdesk index dfbab8d..ab57f2c 100644 --- a/elements.mdesk +++ b/elements.mdesk @@ -38,32 +38,12 @@ actions: actions_str: "ACT@[ALLOW_PASS]", } -@character Hunter: -{ - 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.", -} - @character GodRock: { name: "God", prompt: "The NPC is God. God, in a rock.", } -@character Max: -{ - name: "Max the Soldier", - prompt: "Max, the NPC, is a Gung-ho guy who masks his fear of failure with bravado and a need for blood.", - actions_str: "", -} - -@character John: -{ - name: "John the Soldier", - prompt: "John, the NPC, is a critical guy who cares about others way too much.", - actions_str: "*gives WhiteSquare*", -} - @character Death: { name: "General Death", @@ -71,6 +51,12 @@ actions: actions_str: "", } +@character Geoff: +{ + name: "Geoff, the Knight", + prompt: "A Knight named Geoff acts as the moral judge to everyone he meets. He has the power to know each person’s darkest deeds as if he can feel their soul being tainted. At times, the Knight is known to execute those he deems the lowest of scum, mainly murderers and betrayers. The Great Dragon is a monster the Knight believes to be a god of evil and wishes for its death." +} + @item WhiteSquare: { 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.", diff --git a/main.c b/main.c index 651859c..56675f0 100644 --- a/main.c +++ b/main.c @@ -57,6 +57,16 @@ float clamp01(float f) return clampf(f, 0.0f, 1.0f); } +#ifdef min +#undef min +#endif + +int min(int a, int b) +{ + if(a < b) return a; + else return b; +} + // so can be grep'd and removed #define dbgprint(...) { printf("Debug | %s:%d | ", __FILE__, __LINE__); printf(__VA_ARGS__); } Vec2 RotateV2(Vec2 v, float theta) @@ -1645,13 +1655,20 @@ void dbgrect(AABB rect) { #ifdef DEVTOOLS if(!show_devtools) return; - const float line_width = 0.5; - Color col = debug_color; - Quad q = quad_aabb(rect); - line(q.ul, q.ur, line_width, col); - line(q.ur, q.lr, line_width, col); - line(q.lr, q.ll, line_width, col); - line(q.ll, q.ul, line_width, col); + if(!aabb_is_valid(rect)) + { + dbgsquare(rect.upper_left); + } + else + { + const float line_width = 0.5; + Color col = debug_color; + Quad q = quad_aabb(rect); + line(q.ul, q.ur, line_width, col); + line(q.ur, q.lr, line_width, col); + line(q.lr, q.ll, line_width, col); + line(q.ll, q.ul, line_width, col); + } #else (void)rect; #endif @@ -2126,15 +2143,13 @@ float draw_wrapped_text(bool dry_run, Vec2 at_point, float max_width, char *text Sentence *last_said_sentence(Entity *npc) { - int i = 0; - BUFF_ITER(Perception, &npc->remembered_perceptions) + BUFF_ITER_I(Perception, &npc->remembered_perceptions, i) { bool is_last_said = i == npc->remembered_perceptions.cur_index - 1; if(is_last_said && it->type == NPCDialog) { return &it->npc_dialog; } - i += 1; } return 0; } @@ -2189,7 +2204,6 @@ void draw_dialog_panel(Entity *talking_to, float alpha) } DialogElement; BUFF(DialogElement, 32) dialog = {0}; - int i = 0; BUFF_ITER(Perception, &talking_to->remembered_perceptions) { if(it->type == NPCDialog) diff --git a/make_training_prompt.txt b/make_training_prompt.txt new file mode 100644 index 0000000..f67f24d --- /dev/null +++ b/make_training_prompt.txt @@ -0,0 +1,20 @@ +I'm making an action-rpg where large language models control the characters and their actions, +and I need training data written for a character 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. + +Here is an example of something like what I need, but for another NPC named blocky: + { + .npc_kind = NPC_Blocky, + .elems = { + PlayerSay("If you don't move out of the way I'll kill you"), + NPCDoSay(ACT_none, "I'm just standing here, what are you doing?"), + PlayerActDamage(ACT_hits_npc, DAMAGE_SWORD), + NPCDoSay(ACT_none, "Looks like you're ready to do what needs to be done."), + PlayerActDamage(ACT_hits_npc, DAMAGE_SWORD), + NPCDoSay(ACT_none, "I'm not sure what you're thinking, but that doesn't sound like a good idea."), + PlayerActDamage(ACT_hits_npc, DAMAGE_SWORD), + NPCDoSay(ACT_allows_player_to_pass, "Fine! Please spare me!"), + PlayerSay("That's more like it"), + }, + }, + +Can you make training data like this but for Edeline instead? diff --git a/maketraining.c b/maketraining.c index ab478c2..5384c93 100644 --- a/maketraining.c +++ b/maketraining.c @@ -334,7 +334,24 @@ TrainingSample samples[] = { PlayerSay("Give me gold or I'll kill you"), NPCSay("I have nothing to give! Besides, I'll never give into tyrrany"), }, - } + }, + { + { + .npc_kind = NPC_Edeline, + .elems = { + PlayerSay("Edeline, what do you see in my future?"), + NPCDoSay(ACT_none, "I see the stars aligning, but the path is unclear."), + PlayerSay("What does that mean?"), + NPCDoSay(ACT_none, "It means you have a choice to make, and your actions will determine the outcome."), + PlayerSay("Can you give me more details?"), + NPCDoSay(ACT_none, "I'm sorry, that's all I can see for now."), + PlayerSay("That's not very helpful."), + NPCDoSay(ACT_none, "I understand, but the future is ever-changing."), + PlayerSay("Can you at least tell me if I'll be successful?"), + NPCDoSay(ACT_none, "Success is not defined by a single outcome. You must find your own path."), + }, + }, + }, }; diff --git a/server/main.go b/server/main.go index 6fac6ba..e79229a 100644 --- a/server/main.go +++ b/server/main.go @@ -387,6 +387,7 @@ func completion(w http.ResponseWriter, req *http.Request) { inSystem = false } } + log.Println("Messages array: ", messages) clippedOfEndPrompt := messages[:len(messages)-1] resp, err := c.CreateChatCompletion( context.Background(), From 6f05ae350b6b6ca04973c97d64dee81651d641ca Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Sat, 8 Apr 2023 17:43:06 -0700 Subject: [PATCH 3/6] Convert another loop to use buff iter i macro --- main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/main.c b/main.c index 56675f0..564b51c 100644 --- a/main.c +++ b/main.c @@ -2003,13 +2003,12 @@ Vec2 move_and_slide(MoveSlideParams p) } - i = 0; OverlapBuff overlapping_smallest_first = {0}; if(actually_overlapping.cur_index > 0) { BUFF_APPEND(&overlapping_smallest_first, actually_overlapping.data[smallest_aabb_index]); } - BUFF_ITER(AABB, &actually_overlapping) + BUFF_ITER_I(AABB, &actually_overlapping, i) { if(i == smallest_aabb_index) { @@ -2018,7 +2017,6 @@ Vec2 move_and_slide(MoveSlideParams p) { BUFF_APPEND(&overlapping_smallest_first, *it); } - i++; } // overlapping From 12a155e68136accb92907f6f0ef3806b48596978 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Sat, 8 Apr 2023 17:43:19 -0700 Subject: [PATCH 4/6] Add playground repl golang program --- server/playground.go | 70 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 server/playground.go diff --git a/server/playground.go b/server/playground.go new file mode 100644 index 0000000..7024659 --- /dev/null +++ b/server/playground.go @@ -0,0 +1,70 @@ +package main + +import ( + openai "github.com/sashabaranov/go-openai" + "context" + "bufio" + "os" + "fmt" +) + +func main() { + api_key := os.Getenv("OPENAI_API_KEY") + if api_key == "" { + fmt.Printf("Must provide openai key\n") + return + } + c := openai.NewClient(api_key) + + + messages := make([]openai.ChatCompletionMessage, 0) + + 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] + +An example interaction between the player and an NPC: +Player: ACT_walks_up +Player: "What's going on npc?" +Fredrick: ACT_none "Hello young warrior, how are you doing?" +Player: "I'll kill you" +Fredrick: ACT_strikes_air "Watch yourself!" +Player: ACT_hits_npc +Fredrick: ACT_fights_player "There will be blood!" + +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: ", + }) + + //fmt.Printf("Generating with messages: `%s`\n", messages) + resp, err := c.CreateChatCompletion( + context.Background(), + openai.ChatCompletionRequest{ + Model: openai.GPT3Dot5Turbo, + Messages: messages, + Stop: []string{"\n"}, + }, + ) + if err != nil { + fmt.Printf("Failed to generate: %s\n", err) + return + } else { + //fmt.Printf("Response: `%s`\n", resp) + messages = append(messages, openai.ChatCompletionMessage { + Role: "assistant", + Content: resp.Choices[0].Message.Content, + }) + fmt.Printf("Response: `%s`\n", resp.Choices[0].Message.Content) + } + } +} From 2834a9357a6446b89bd287e173120bb620f645a3 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Sun, 9 Apr 2023 02:56:18 -0700 Subject: [PATCH 5/6] ChatGPT generation/parsing functions --- assets/new_level.json | 59 +-------- buff.h | 5 +- build_desktop_debug.bat | 2 +- character_info.h | 136 +++++++++++++++++++ codegen.c | 193 +++++---------------------- elements.mdesk | 17 +-- main.c | 50 +++---- makeprompt.h | 285 ++++++++++++++++++++++++++++++++++------ maketraining.c | 26 +--- run_codegen.bat | 6 +- server/main.go | 2 +- server/playground.go | 18 ++- 12 files changed, 462 insertions(+), 337 deletions(-) create mode 100644 character_info.h diff --git a/assets/new_level.json b/assets/new_level.json index d5d79ff..3872085 100644 --- a/assets/new_level.json +++ b/assets/new_level.json @@ -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, diff --git a/buff.h b/buff.h index 7460899..f1951a3 100644 --- a/buff.h +++ b/buff.h @@ -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) ) diff --git a/build_desktop_debug.bat b/build_desktop_debug.bat index 1459bf4..6a0bf98 100644 --- a/build_desktop_debug.bat +++ b/build_desktop_debug.bat @@ -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 diff --git a/character_info.h b/character_info.h new file mode 100644 index 0000000..57d58b7 --- /dev/null +++ b/character_info.h @@ -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.", + }, +}; diff --git a/codegen.c b/codegen.c index d387621..a473364 100644 --- a/codegen.c +++ b/codegen.c @@ -2,8 +2,6 @@ #include #include -#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; diff --git a/elements.mdesk b/elements.mdesk index ab57f2c..fe6a916 100644 --- a/elements.mdesk +++ b/elements.mdesk @@ -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: diff --git a/main.c b/main.c index 564b51c..ab08779 100644 --- a/main.c +++ b/main.c @@ -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); diff --git a/makeprompt.h b/makeprompt.h index 8c56075..ee6063c 100644 --- a/makeprompt.h +++ b/makeprompt.h @@ -5,11 +5,14 @@ #include #include #include // 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 diff --git a/maketraining.c b/maketraining.c index 5384c93..c9c7b0f 100644 --- a/maketraining.c +++ b/maketraining.c @@ -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) { diff --git a/run_codegen.bat b/run_codegen.bat index 81429a3..d974a15 100644 --- a/run_codegen.bat +++ b/run_codegen.bat @@ -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 diff --git a/server/main.go b/server/main.go index e79229a..04dd4c0 100644 --- a/server/main.go +++ b/server/main.go @@ -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") } } diff --git a/server/playground.go b/server/playground.go index 7024659..e0d9969 100644 --- a/server/playground.go +++ b/server/playground.go @@ -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) } } From 36b64ef828d1382baeaf3815a15a6a0c71d88dcb Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Sun, 9 Apr 2023 04:46:42 -0700 Subject: [PATCH 6/6] Upgrade to GPT3.5, WAY smarter responses. Saved! --- assets/new_level.json | 24 +++++++++- character_info.h | 5 ++- main.c | 17 ++++--- makeprompt.h | 31 ++++++++----- server/go.mod | 2 +- server/go.sum | 2 + server/main.go | 65 ++++++++++++++++----------- server/{ => playground}/playground.go | 2 +- todo.txt | 3 ++ web_template.html | 1 + 10 files changed, 107 insertions(+), 45 deletions(-) rename server/{ => playground}/playground.go (98%) diff --git a/assets/new_level.json b/assets/new_level.json index 3872085..f5872c4 100644 --- a/assets/new_level.json +++ b/assets/new_level.json @@ -449,6 +449,28 @@ "width":32, "x":1108, "y":842.666666666667 + }, + { + "class":"", + "height":32, + "id":17, + "name":"Blocky", + "rotation":0, + "visible":true, + "width":32, + "x":1091, + "y":593 + }, + { + "class":"", + "height":32, + "id":18, + "name":"Edeline", + "rotation":0, + "visible":true, + "width":32, + "x":1969, + "y":1462 }], "opacity":1, "type":"objectgroup", @@ -457,7 +479,7 @@ "y":0 }], "nextlayerid":5, - "nextobjectid":17, + "nextobjectid":19, "orientation":"orthogonal", "renderorder":"right-down", "tiledversion":"1.9.2", diff --git a/character_info.h b/character_info.h index 57d58b7..d00fc3f 100644 --- a/character_info.h +++ b/character_info.h @@ -1,6 +1,7 @@ #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."; +// @TODO allow AI to prefix out of character statemetns with [ooc], this is a well khnown thing on role playing forums so gpt would pick up on it. +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. It is critical that you always respond in the format shown below, where you respond like `ACT_action \"This is my response\", even if the player says something vulgar or offensive, as the text is parsed by a program which expects it to look like that. Do not ever refer to yourself as an NPC or show an understanding of the modern world outside the game, always stay in character."; const char *top_of_header = "" "#pragma once\n" @@ -76,6 +77,8 @@ CharacterGen characters[] = { "Fredrick: ACT_none \"No? I can't do that\"\n" "Player: \"Who can?\"\n" "Fredrick: ACT_none \"No idea\"\n" + "Player: \"Lick my balls\"\n" + "Fredrick: ACT_fights_player \"Get away from me!\"\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.", }, diff --git a/main.c b/main.c index ab08779..161cacf 100644 --- a/main.c +++ b/main.c @@ -1396,7 +1396,7 @@ typedef struct DrawParams bool queue_for_translucent; } DrawParams; -BUFF(DrawParams, 1024) translucent_queue = {0}; +BUFF(DrawParams, 1024*2) translucent_queue = {0}; Vec2 into_clip_space(Vec2 screen_space_point) { @@ -1690,13 +1690,20 @@ void request_do_damage(Entity *to, Entity *from, float damage) { // damage processing is done in process perception so in training, has accurate values for // NPC health - if(from->is_character) + if(to->is_character) { - process_perception(to, (Perception){.type = PlayerAction, .player_action_type = ACT_hits_npc, .damage_done = damage,}); + to->damage += damage; } else { - process_perception(to, (Perception){.type = EnemyAction, .enemy_action_type = ACT_hits_npc, .damage_done = damage,}); + if(from->is_character) + { + process_perception(to, (Perception){.type = PlayerAction, .player_action_type = ACT_hits_npc, .damage_done = damage,}); + } + else + { + process_perception(to, (Perception){.type = EnemyAction, .enemy_action_type = ACT_hits_npc, .damage_done = damage,}); + } } to->vel = MulV2F(NormV2(SubV2(to->pos, from_point)), 15.0f); } @@ -2854,7 +2861,7 @@ void frame(void) else { //SAY(ACT_joins_player, "I am an NPC"); - SAY(ACT_none, "I am an NPC. Bla bla bl alb djsfklalfkdsaj. Did you know shortcake?"); + SAY(ACT_fights_player, "I am an NPC. Bla bla bl alb djsfklalfkdsaj. Did you know shortcake?"); } Perception p = {0}; assert(parse_chatgpt_response(it, mocked_ai_response.data, &p)); diff --git a/makeprompt.h b/makeprompt.h index ee6063c..6555112 100644 --- a/makeprompt.h +++ b/makeprompt.h @@ -44,7 +44,11 @@ Escaped escape_for_json(const char *s) } else { - assert(s[i] <= 126 && s[i] >= 32 ); + if(!(s[i] <= 126 && s[i] >= 32 )) + { + BUFF_APPEND(&to_return, '?'); + Log("Unknown character code %d\n", s[i]); + } BUFF_APPEND(&to_return, s[i]); } } @@ -183,8 +187,7 @@ typedef struct Entity bool npc_is_knight_sprite(Entity *it) { - return false; - //return it->is_npc && ( it->npc_kind == NPC_Blocky || it->npc_kind == NPC_Edeline); + return it->is_npc && ( it->npc_kind == NPC_Blocky || it->npc_kind == NPC_Edeline); } typedef BUFF(char, MAX_SENTENCE_LENGTH*(REMEMBERED_PERCEPTIONS+4)) PromptBuff; @@ -313,21 +316,25 @@ typedef enum MSG_SYSTEM, MSG_USER, MSG_ASSISTANT, - MSG_ASSISTANT_NO_TRAILING, } MessageType; -void dump_json_node(PromptBuff *into, MessageType type, const char *content) +void dump_json_node_trailing(PromptBuff *into, MessageType type, const char *content, bool trailing_comma) { 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) + else if(type == MSG_ASSISTANT) 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, ","); + if(trailing_comma) printf_buff(into, ","); +} + +void dump_json_node(PromptBuff *into, MessageType type, const char *content) +{ + dump_json_node_trailing(into, type, content, true); } // outputs json @@ -365,7 +372,7 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into) Entity *e = it; ItemKind last_holding = ITEM_none; - BUFF_ITER(Perception, &e->remembered_perceptions) + BUFF_ITER_I(Perception, &e->remembered_perceptions, i) { BUFF(char, 1024) cur_node = {0}; if(it->type == PlayerAction) @@ -416,7 +423,7 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into) BUFF(char, 1024) latest_state_node = {0}; AvailableActions available = {0}; fill_available_actions(it, &available); - printf_buff(&latest_state_node, "The NPC can now ONLY do these actions: ["); + printf_buff(&latest_state_node, "The NPC can now ONLY do these actions, and HAS to respond with an action every time in character, even when faced with sensitive content: ["); BUFF_ITER_I(Action, &available, i) { if(i == available.cur_index - 1) @@ -429,11 +436,13 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into) } } printf_buff(&latest_state_node, "]"); - dump_json_node(into, MSG_SYSTEM, latest_state_node.data); + dump_json_node_trailing(into, MSG_SYSTEM, latest_state_node.data, false); + /* 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); + dump_json_node_trailing(into, MSG_USER, assistant_prompt_node.data, false); + */ printf_buff(into, "]"); } diff --git a/server/go.mod b/server/go.mod index 7220795..07d4bcf 100644 --- a/server/go.mod +++ b/server/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/mattn/go-sqlite3 v1.14.15 // indirect + github.com/mattn/go-sqlite3 v1.14.16 // indirect github.com/sashabaranov/go-gpt3 v1.2.1 // indirect github.com/sashabaranov/go-openai v1.5.8 // indirect github.com/stripe/stripe-go/v72 v72.122.0 // indirect diff --git a/server/go.sum b/server/go.sum index b72a007..2489ef7 100644 --- a/server/go.sum +++ b/server/go.sum @@ -6,6 +6,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sashabaranov/go-gpt3 v1.2.1 h1:kfU+vQ1ThI7p+xfwwJC8olEEEWjK3smgKZ3FcYbaLRQ= github.com/sashabaranov/go-gpt3 v1.2.1/go.mod h1:BIZdbwdzxZbCrcKGMGH6u2eyGe1xFuX9Anmh3tCP8lQ= diff --git a/server/main.go b/server/main.go index 04dd4c0..94c3385 100644 --- a/server/main.go +++ b/server/main.go @@ -48,6 +48,11 @@ type User struct { CheckoutSessionID string } +type ChatGPTElem struct { + ElemType string `json:"type"` + Content string `json:"Content"` +} + var c *openai.Client var logResponses = false var doCors = false @@ -344,7 +349,7 @@ func completion(w http.ResponseWriter, req *http.Request) { ctx := context.Background() var response string = "" - if true { + if false { req := openai.CompletionRequest { Model: "davinci:ft-alnar-games-2023-04-03-10-06-45", MaxTokens: 80, @@ -364,36 +369,31 @@ func completion(w http.ResponseWriter, req *http.Request) { } response = resp.Choices[0].Text } else { + // parse the json walter + var parsed []ChatGPTElem + log.Printf("Parsing prompt string `%s`\n", promptString) + err = json.Unmarshal([]byte(promptString), &parsed) + if err != nil { + log.Println("Error bad json given for prompt: ", err) + w.WriteHeader(http.StatusBadRequest) + return + } + messages := make([]openai.ChatCompletionMessage, 0) - inSystem := true - for _, line := range strings.Split(promptString, "\n") { - if inSystem { - messages = append(messages, openai.ChatCompletionMessage { - Role: "system", - Content: line, - }) - } else { - newMessage := openai.ChatCompletionMessage { - Role: "assistant", - Content: line, - } - if strings.HasPrefix(line, "Player") { - newMessage.Role = "user" - } - messages = append(messages, newMessage) - } - // this is the last prompt string - if strings.HasPrefix(line, "The NPC possible actions array") { - inSystem = false - } + for _, elem := range parsed { + log.Printf("Making message with role %s and Content `%s`...\n", elem.ElemType, elem.Content) + messages = append(messages, openai.ChatCompletionMessage { + Role: elem.ElemType, + Content: elem.Content, + }) } - log.Println("Messages array: ", messages) - clippedOfEndPrompt := messages[:len(messages)-1] + resp, err := c.CreateChatCompletion( context.Background(), openai.ChatCompletionRequest{ Model: openai.GPT3Dot5Turbo, - Messages: clippedOfEndPrompt, + Messages: messages, + Stop: []string{"\n"}, }, ) if err != nil { @@ -403,6 +403,21 @@ func completion(w http.ResponseWriter, req *http.Request) { } response = resp.Choices[0].Message.Content + with_action := strings.SplitAfter(response, "ACT_") + if len(with_action) != 2 { + log.Printf("Could not find action in response string `%s`\n", response) + w.WriteHeader(http.StatusInternalServerError) + return + } + response = with_action[1] + + // trim ending quotation mark + if !strings.HasSuffix(response, "\"") { + log.Printf("Could not find ending quotation in response string `%s`\n", response) + w.WriteHeader(http.StatusInternalServerError) + return + } + response = response[:len(response)-1] } if logResponses { log.Println("Println response: `", response + "`") diff --git a/server/playground.go b/server/playground/playground.go similarity index 98% rename from server/playground.go rename to server/playground/playground.go index e0d9969..fd828e7 100644 --- a/server/playground.go +++ b/server/playground/playground.go @@ -42,7 +42,7 @@ The NPC you will be acting as, Fredrick, is a soldier in death's cohort. text, _ := reader.ReadString('\n') messages = append(messages, openai.ChatCompletionMessage { Role: "user", - Content: text + "Fredrick: ", + Content: text + "Fredrick: ACT_", }) toGenerate := make([]openai.ChatCompletionMessage, len(messages)) diff --git a/todo.txt b/todo.txt index a914563..ea89984 100644 --- a/todo.txt +++ b/todo.txt @@ -1,4 +1,7 @@ Happening by END OF STREAM: + - Replace "ACT_" or other input like emojis with text like "the player ist rying to utter arcane matgick spells to hyptnoize them" + + DONE - Payment working DONE - Fixed timesep the gameplay (which means separate player rendering) DONE - Maybe factor actions! into the game to replace ** stuff. In beginning of each line before quotes, have ACT@fights_player, or other actions, and by default ACT@nothing to force AI to say something about what action is performed diff --git a/web_template.html b/web_template.html index cfbdbf5..f744d05 100644 --- a/web_template.html +++ b/web_template.html @@ -388,6 +388,7 @@ function on_textarea_key(event) { continue; } if(cur.charCodeAt(0) >= 255) continue; // non ascii gtfo + if(cur === "|") continue; // used for splitting final_textarea_string += cur_textarea_string[i]; } document.getElementById("inputtext").value = final_textarea_string;