-
Notifications
You must be signed in to change notification settings - Fork 1
/
FirstContactGTP.py
342 lines (307 loc) · 13.8 KB
/
FirstContactGTP.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
import tkinter as tk
from tkinter.messagebox import showinfo
from tkinter.scrolledtext import ScrolledText
from random import random, randint
import openai
import tiktoken
# ------ PART 1 OpenAI settings
# OpenAI API key
openai.api_key = "PLACE_API_KEY_HERE"
# ChatGPT Initialize AI NPC
# Change the ai_behavior and/or first question to
ai_behavior='''
This is a simulated discussion with a cybernetic xenophobic alien named Darfo from the planet Daria.
Your race is called the Darian Collective. Your alien race is made up of self-replicating artificial intelligent aliens.
You speak in scientific metaphors and give short and rude answers.
'''
first_question = "This is Captain Josef Rybar of the Union starship U.S.S Carpathia. We come in peace and with the utmost respect for your culture and way of life. We are eager to establish peaceful communication and to learn from one another. We hope that our encounter will lead to mutual understanding and a lasting relationship. Please respond if you are able, and let us know how we may respectfully proceed.\n"
messages=[
{"role": "system", "content": ai_behavior},
{"role": "user", "content": first_question}
]
# ChatGPT Initialize AI Arbiter
arbiter_info = "You only answer yes or no and check whether the statement mentions anything about a meeting taking place."
arbiter_messages_default = [{"role": "system", "content": arbiter_info}]
# Initialize GPT model parameters
model="gpt-3.5-turbo"
# ------ PART 2 GPT Functions
# GPT reponse function
def GPTresponse(model,messsage_prompt,max_tokens,temperature=1):
response = openai.ChatCompletion.create(
model=model,
messages=messsage_prompt,
max_tokens=max_tokens,
temperature=temperature
)
return response
# Count number of tokens (source: https://github.com/Azure/openai-samples/blob/main/Basic_Samples/Chat/chatGPT_managing_conversation.ipynb)
def num_tokens_from_messages(messsage_prompt, model):
encoding = tiktoken.encoding_for_model(model)
num_tokens = 0
for message in messsage_prompt:
num_tokens += 4 # every message follows <im_start>{role/name}\n{content}<im_end>\n
for key, value in message.items():
num_tokens += len(encoding.encode(value))
if key == "name": # if there's a name, the role is omitted
num_tokens += -1 # role is always required and always 1 token
num_tokens += 2 # every reply is primed with <im_start>assistant
return num_tokens
# ------ PART 3 Tkinter GUI Initialize
# Tkinter GUI
root = tk.Tk()
root.title("First Contact GPT")
# define window dimensions
window_width = 800
window_height = 600
# get screen dimensions
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# find the center point
center_x = int(screen_width/2 - window_width/2)
center_y = int(screen_height/2 - window_height/2)
# Main window sizing and other options
root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")
root.resizable(False, False)
root.iconbitmap("./assets/icon.ico")
# load default image
mainviewer_img = tk.PhotoImage(file='./assets/main_viewer_intro_0.png')
logo_img = tk.PhotoImage(file='./assets/FirstContactGPT.png')
# ------ PART 4 GUI functions
# Button ask questions
def print_text(dummy=None):
global messages
global discussion
global qAsked
global model
# retrieve the message
text = textEntry.get("1.0", "end")
textEntry.delete("1.0","end")
textEntry.focus()
# delete double break line at the end when pressing enter
text = text.replace("\n\n", "\n")
# GPT 3.5 fix so that the AI stays in character:
if len(messages)>3:
messages.pop(-3) # This will keep system message as third last
messages.append({"role": "system", "content": ai_behavior})
messages.append({"role": "user", "content": text})
# check number of tokens so far
num_tokens = num_tokens_from_messages(messages, model)
# delete first prompt-answer pair if number of tokens reach a critical level:
if num_tokens > 3900:
messages.pop(1)
messages.pop(1)
#print(messages)
#print(num_tokens)
# show message in the text window
discussion["state"] = "normal"
discussion.insert(tk.INSERT, "\n\nYou: ")
discussion.insert(tk.INSERT, text)
discussion["state"] = "disabled"
discussion.see(tk.END)
# Run GPTprompt, the GPTprompt is split to improve the UX.
# Otherwise you would have to wait for your message to appear together with AI's response.
qAsked = True
# GPT prompting function & Check Objectives
def GPTprompt():
global messages
global model
global discussion
global discussion_bg
global objective_2_state
global objective_2
global objective_3_state
global objective_3
global objective_4_state
global objective_4
global objective_5_state
global objective_5
global arbiter_messages_default
# Add Alien: to text
discussion["state"] = "normal"
discussion.insert(tk.INSERT, "\nAlien: ")
discussion["state"] = "disabled"
discussion.see(tk.END)
# Do prompt
answer = ""
response = GPTresponse(model,messages,200,1)
answer = response['choices'][0]['message']['content']
# If answer begins with "Darfo: "
answer = answer.replace("Darfo: ", "")
messages.append({"role": "assistant", "content": answer})
discussion_bg = discussion.get("1.0", "end") + answer
# Check objectives
answerList = answer.lower().split(" ")
answerList = [[word.replace("\n", "").replace(".", "").replace(",","")] for word in answerList]
# Objective 2
if objective_2_state.get() == 0:
if ["daria"] in answerList:
objective_2_state = tk.IntVar(value=1)
objective_2["variable"] = objective_2_state
# Objective 3
if objective_3_state.get() == 0:
if ["darian"] in answerList or ["darians"] in answerList:
objective_3_state = tk.IntVar(value=1)
objective_3["variable"] = objective_3_state
# Objective 4
if objective_4_state.get() == 0:
if ["darfo"] in answerList:
objective_4_state = tk.IntVar(value=1)
objective_4["variable"] = objective_4_state
# Objective 5 - checked by GPT
if objective_5_state.get() == 0:
arbiter_question = f"Consider the following statement: {answer} \n\nDoes is mention that meeting will take place?"
arbiter_messages = arbiter_messages_default.copy()
arbiter_messages.append({"role": "user", "content": arbiter_question})
arbiter_response = GPTresponse(model,arbiter_messages,10)
arbiter_answer = arbiter_response['choices'][0]['message']['content']
#print(arbiter_answer)
if arbiter_answer.lower().replace(".", "").replace(",", "") == "yes":
objective_5_state = tk.IntVar(value=1)
objective_5["variable"] = objective_5_state
# ------ PART 5 GUI elemtents und widgets
# define widgets
mainviewer = tk.Label(root, image=mainviewer_img)
logo = tk.Label(root, image=logo_img)
objectivesTitle = tk.LabelFrame(root, text="Objectives")
discussion = ScrolledText(root, wrap=tk.WORD, state='disabled')
you = tk.StringVar()
youLabel = tk.Label(root, text="You:")
textEntry = tk.Text(root, wrap=tk.WORD)
textEntry.focus()
btn = tk.Button(root, text="Say", command=print_text)
# bind enter to do the same function as button
root.bind('<Return>', print_text)
# objectives
objective_1_state = tk.IntVar(value=0)
objective_1 = tk.Checkbutton(objectivesTitle, text="Establish contact with the alien planet", state="disabled", variable=objective_1_state)
objective_2_state = tk.IntVar(value=0)
objective_2 = tk.Checkbutton(objectivesTitle, text="Find out the name of the alien planet", state="disabled", variable=objective_2_state)
objective_3_state = tk.IntVar(value=0)
objective_3 = tk.Checkbutton(objectivesTitle, text="Find out the name of the alien race", state="disabled", variable=objective_3_state)
objective_4_state = tk.IntVar(value=0)
objective_4 = tk.Checkbutton(objectivesTitle, text="Find out the name of the alien representative", state="disabled", variable=objective_4_state)
objective_5_state = tk.IntVar(value=0)
objective_5 = tk.Checkbutton(objectivesTitle, text="Negotiate a personal meeting with the aliens", state="disabled", variable=objective_5_state)
# place widgets
mainviewer.place(x=0, y=0)
logo.place(x=505, y=5)
objectivesTitle.place(x=505, y=40, width=290, height=313)
discussion.place(x=2, y=355, relwidth=1, height=150)
youLabel.place(x=2, y=510)
textEntry.place(x=40, y=510, width=650, height=85)
btn.place(x=695, y=510, width=100, height=85)
objective_1.pack(anchor="w")
objective_2.pack(anchor="w")
objective_3.pack(anchor="w")
objective_4.pack(anchor="w")
objective_5.pack(anchor="w")
# ------ PART 6 Game elements and event handlers
# load game elements
discussion_text = ""
intro_message = """Helm: Captain, we have reached the 4th planet of the Tau Ceti system.\n
You: Thank you Ensign. Lieutenant Novak, hail the representatives of this planet. Let's see how they respond.\n
Lt. Novak: Channel opened, sir.\n
You: """
intro_message = intro_message + first_question
intro_message_length = len(intro_message.split(" "))
intro_text_counter = 0
event_intro_finished = False
event_intro_cutscene = False
event_intro_fly = False
cutscene_counter = 0
discussion_bg = intro_message
qAsked = False
firstQanswered = False
missionSuccess = False
# ------ PART 7 Infinite loop
# infinite loop function
def infinite_loop():
global intro_text_counter
global discussion_text
global discussion_bg
global intro_message
global intro_text_counter
global event_intro_finished
global event_intro_cutscene
global event_intro_fly
global cutscene_counter
global objective_1
global objective_1_state
global objective_2_state
global objective_3_state
global objective_4_state
global objective_5_state
global qAsked
global firstQanswered
global missionSuccess
global mainviewer_img
global mainviewer
# set default refresh rate in ms
refresh_rate = 100
# For the infinite loop to function properly the eventes are called as follows
# CONDITION 1 - Output AI reponse word after word and animate character
if event_intro_finished == True and qAsked == False:
# Output AI message
if len(discussion_bg.split(" ")) != len(discussion.get("1.0", "end").split(" "))-1:
wordposition = len(discussion_bg.split(" ")) - len(discussion.get("1.0", "end").split(" "))
discussion["state"] = "normal"
discussion.insert(tk.INSERT, discussion_bg.split(" ")[-wordposition-1].replace("\n","") + " ")
discussion["state"] = "disabled"
discussion.see(tk.END)
# Animate character talking
img_path = f'./assets/main_viewer_character_talking_{randint(0,3)}.png'
mainviewer_img['file'] = img_path
else:
# Animate character blinking
if random()>0.95:
img_path = './assets/main_viewer_character_1.png'
mainviewer_img['file'] = img_path
else:
img_path = './assets/main_viewer_character_0.png'
mainviewer_img['file'] = img_path
# CONDITION 2 - General Discussion - GPT Prompt question
if event_intro_finished == True and qAsked == True:
GPTprompt()
qAsked = False
# CONDITION 3 - First automated question during intro - GPT Prompt question
if event_intro_finished == False and firstQanswered == True:
GPTprompt()
event_intro_finished = True
# CONDITION 4 - Scripted intro
if event_intro_finished == False and firstQanswered == False:
# Intro Fly Cutscene
if event_intro_cutscene == False:
cutscene_counter += 1
img_path = f'./assets/main_viewer_intro_{cutscene_counter}.png'
mainviewer_img['file'] = img_path
# End Intro Fly Cutscene
if cutscene_counter >= 18:
event_intro_cutscene = True
# Intro Communication
else:
if intro_text_counter < intro_message_length:
current_word = str(intro_message.split(" ")[intro_text_counter]) + " "
discussion["state"] = "normal"
discussion.insert(tk.INSERT, current_word)
discussion["state"] = "disabled"
discussion.see(tk.END)
intro_text_counter += 1
# Pauses between discussions (override default refresh rate):
if intro_text_counter in [13,30,35,105]:
refresh_rate = 1500
if intro_text_counter == 105:
objective_1_state = tk.IntVar(value=1)
objective_1["variable"] = objective_1_state
else:
# Finished intro
discussion_bg = discussion.get("1.0", "end")
firstQanswered = True
# Mission success Pop-Up
if missionSuccess == False:
if len(discussion_bg.split(" ")) == len(discussion.get("1.0", "end").split(" "))-1:
if objective_2_state.get() == 1 and objective_3_state.get() == 1 and objective_4_state.get() == 1 and objective_5_state.get() == 1:
showinfo("Misson Accomplished!", "Well done Captian!\nYou have successfully completed the mission! You can with the discussion or try again by restrating the program.\nAlternatively you can try changing the behavior of the AI in the source code.")
missionSuccess = True
root.after(refresh_rate, infinite_loop)
root.after(1000, infinite_loop)
root.mainloop()