Spaces:
Running
Running
Upload 32 files
Browse files- 1.jpg +0 -0
- 10.jpg +0 -0
- 11.jpg +0 -0
- 12.jpg +0 -0
- 2.jpg +0 -0
- 3.jpg +0 -0
- 4.jpg +0 -0
- 5.jpg +0 -0
- 6.jpg +0 -0
- 7.jpg +0 -0
- 8.jpg +0 -0
- 9.jpg +0 -0
- Agnus the Art Gallery Owner.jpg +0 -0
- Ben the Butler.jpg +0 -0
- Bernice the Bartender.jpg +0 -0
- Buddy the Business Partner.jpg +0 -0
- Chef Chuck.jpg +0 -0
- Daughter-In-Law Daphne.jpg +0 -0
- Frank the Brother.jpg +0 -0
- Gary the Gardener.jpg +0 -0
- Larry the Lawyer.jpg +0 -0
- Madam Marmalade.jpg +0 -0
- Nick the Nephew.jpg +0 -0
- Question_Mark.jpg +0 -0
- README.md +4 -4
- Rebel the Niece.jpg +0 -0
- Samuel the Son.jpg +0 -0
- Sarah the Sister.jpg +0 -0
- app.py +236 -0
- game_info.py +534 -0
- mystery_manor.jpg +0 -0
- requirements.txt +6 -0
1.jpg
ADDED
![]() |
10.jpg
ADDED
![]() |
11.jpg
ADDED
![]() |
12.jpg
ADDED
![]() |
2.jpg
ADDED
![]() |
3.jpg
ADDED
![]() |
4.jpg
ADDED
![]() |
5.jpg
ADDED
![]() |
6.jpg
ADDED
![]() |
7.jpg
ADDED
![]() |
8.jpg
ADDED
![]() |
9.jpg
ADDED
![]() |
Agnus the Art Gallery Owner.jpg
ADDED
![]() |
Ben the Butler.jpg
ADDED
![]() |
Bernice the Bartender.jpg
ADDED
![]() |
Buddy the Business Partner.jpg
ADDED
![]() |
Chef Chuck.jpg
ADDED
![]() |
Daughter-In-Law Daphne.jpg
ADDED
![]() |
Frank the Brother.jpg
ADDED
![]() |
Gary the Gardener.jpg
ADDED
![]() |
Larry the Lawyer.jpg
ADDED
![]() |
Madam Marmalade.jpg
ADDED
![]() |
Nick the Nephew.jpg
ADDED
![]() |
Question_Mark.jpg
ADDED
![]() |
README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
---
|
2 |
title: Mystery Manor
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
sdk_version: 4.36.1
|
8 |
app_file: app.py
|
@@ -10,4 +10,4 @@ pinned: false
|
|
10 |
license: other
|
11 |
---
|
12 |
|
13 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
title: Mystery Manor
|
3 |
+
emoji: 🕵
|
4 |
+
colorFrom: gray
|
5 |
+
colorTo: indigo
|
6 |
sdk: gradio
|
7 |
sdk_version: 4.36.1
|
8 |
app_file: app.py
|
|
|
10 |
license: other
|
11 |
---
|
12 |
|
13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
Rebel the Niece.jpg
ADDED
![]() |
Samuel the Son.jpg
ADDED
![]() |
Sarah the Sister.jpg
ADDED
![]() |
app.py
ADDED
@@ -0,0 +1,236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
# ==========================
|
3 |
+
# Imports
|
4 |
+
# ==========================
|
5 |
+
|
6 |
+
import os, sys, random
|
7 |
+
import gradio as gr
|
8 |
+
from langchain_openai import ChatOpenAI
|
9 |
+
from langchain.schema import AIMessage, HumanMessage
|
10 |
+
from langchain.prompts import ChatPromptTemplate
|
11 |
+
from langchain.schema.output_parser import StrOutputParser
|
12 |
+
from langchain_core.runnables import RunnableLambda
|
13 |
+
from langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage, HumanMessageChunk
|
14 |
+
from game_info import *
|
15 |
+
|
16 |
+
# ==========================
|
17 |
+
# Constants / Globals
|
18 |
+
# ==========================
|
19 |
+
|
20 |
+
NVIDIA_API_KEY = os.environ["API_KEY"] # API Key for the LLM - Using NVIDIA's NIM to access an 8 Billion parameter Llama3 model
|
21 |
+
|
22 |
+
CHATBOT_HEIGHT = 700 # Height of Gradio Chatbot
|
23 |
+
LINES_PER_BOX = 20 # Number of text lines in a gradio text box
|
24 |
+
SUSPECTS_PER_PAGE = 10 # Number of suspects shown per page
|
25 |
+
|
26 |
+
game = cMystery() # New mystery game imported from game_info
|
27 |
+
|
28 |
+
# ==========================
|
29 |
+
# LLM Functions
|
30 |
+
# ==========================
|
31 |
+
|
32 |
+
# Using NVIDIA's NIM (NVIDIA Inference Microservices) to access an 8 Billion parameter Llama3 model
|
33 |
+
|
34 |
+
llm = ChatOpenAI(model = "meta/llama3-8b-instruct",temperature=0.5,max_tokens=1024,timeout=None,max_retries=2,
|
35 |
+
base_url = "https://integrate.api.nvidia.com/v1",
|
36 |
+
api_key = NVIDIA_API_KEY)
|
37 |
+
|
38 |
+
# ================================
|
39 |
+
# Custom LangChain Tools and Pipes
|
40 |
+
# ================================
|
41 |
+
|
42 |
+
def output_parser(ai_message: AIMessage) -> str:
|
43 |
+
|
44 |
+
global game
|
45 |
+
|
46 |
+
current_suspect = game.get_current_suspect()
|
47 |
+
game.add_note_to_recap(ai_message.content, current_suspect) # Save a record of the conversation with the suspect name in the recap
|
48 |
+
|
49 |
+
return ai_message.content
|
50 |
+
|
51 |
+
|
52 |
+
# ==========================
|
53 |
+
# UI Functions
|
54 |
+
# ==========================
|
55 |
+
|
56 |
+
|
57 |
+
# ============================================
|
58 |
+
# Feed the chat message to the LLM
|
59 |
+
# ============================================
|
60 |
+
|
61 |
+
def predict(message, history):
|
62 |
+
|
63 |
+
global game
|
64 |
+
global llm
|
65 |
+
|
66 |
+
current_suspect = game.get_current_suspect()
|
67 |
+
|
68 |
+
if current_suspect is None:
|
69 |
+
return "Please select a suspect."
|
70 |
+
|
71 |
+
AI_Instructions = f"You are a suspect in an art theft." + \
|
72 |
+
f"Your name is {game.get_suspect_proper_name(current_suspect)} " + \
|
73 |
+
f"and your background is {game.get_suspect_background(current_suspect)}. " + \
|
74 |
+
f"Your motive for stealing the painting is {game.get_suspect_motive(current_suspect)}. " + \
|
75 |
+
f"If you stole the painting do not admit it. " + \
|
76 |
+
f"Base all conversations on the following JSON text:{game.create_interview(current_suspect)}"
|
77 |
+
|
78 |
+
prompt = ChatPromptTemplate.from_messages([
|
79 |
+
("system", f"{AI_Instructions}"),
|
80 |
+
("human", f"{message}"),
|
81 |
+
])
|
82 |
+
|
83 |
+
history_langchain_format = []
|
84 |
+
history_langchain_format.append(AIMessage(content=AI_Instructions))
|
85 |
+
history_langchain_format.append(HumanMessage(content=message))
|
86 |
+
|
87 |
+
gpt_response = llm(history_langchain_format)
|
88 |
+
output_parser(gpt_response)
|
89 |
+
response = gpt_response.content
|
90 |
+
|
91 |
+
# LCEL configuration for future game expansion / options
|
92 |
+
|
93 |
+
# lcel_chain = prompt | llm | output_parser
|
94 |
+
# response = lcel_chain.invoke(message)
|
95 |
+
|
96 |
+
return response
|
97 |
+
|
98 |
+
# ==========================
|
99 |
+
# Generate recap of game
|
100 |
+
# ==========================
|
101 |
+
|
102 |
+
def get_recap():
|
103 |
+
|
104 |
+
global game
|
105 |
+
|
106 |
+
recap = ""
|
107 |
+
recap += "💡 Hints 💡 \n\n" + game.get_hints_recap() + "\n" # Add revealed hints
|
108 |
+
recap += "🕵 Suspect Interview Notes 🕵 \n\n" # Add notes from interviews
|
109 |
+
recap += game.get_crime_recap()
|
110 |
+
|
111 |
+
return gr.Textbox(lines=LINES_PER_BOX, label="Recap of the Mystery", value=recap)
|
112 |
+
|
113 |
+
# ==========================
|
114 |
+
# Question suspect
|
115 |
+
# ==========================
|
116 |
+
|
117 |
+
def question_suspect(image, name):
|
118 |
+
|
119 |
+
global game
|
120 |
+
|
121 |
+
game.set_current_suspect(name)
|
122 |
+
current_suspect = game.get_current_suspect()
|
123 |
+
|
124 |
+
return gr.Textbox(lines = LINES_PER_BOX,
|
125 |
+
label = "About the Suspect",
|
126 |
+
value = game.get_suspect_profile(current_suspect))
|
127 |
+
|
128 |
+
# ==========================
|
129 |
+
# Generate game hint
|
130 |
+
# ==========================
|
131 |
+
|
132 |
+
def get_hint():
|
133 |
+
|
134 |
+
global game
|
135 |
+
|
136 |
+
return gr.Textbox(lines = LINES_PER_BOX,
|
137 |
+
label = "Hint About the Mystery",
|
138 |
+
value = game.give_hint())
|
139 |
+
|
140 |
+
|
141 |
+
# ===============================
|
142 |
+
# Reset game for a new mystery
|
143 |
+
# ===============================
|
144 |
+
|
145 |
+
def new_crime():
|
146 |
+
|
147 |
+
global game
|
148 |
+
|
149 |
+
game = cMystery()
|
150 |
+
game.print_game()
|
151 |
+
|
152 |
+
return [gr.Image (value = game.get_stolen_painting(), height=500, width=500, label = game.get_stolen_painting_name(), interactive=False),
|
153 |
+
gr.Textbox(lines = LINES_PER_BOX, label="A Crime has been Committed", value = game.get_crime_text()),
|
154 |
+
gr.Image (value = "Question_Mark.jpg", height=500, width=500, label = "Suspect", interactive=False),
|
155 |
+
gr.Textbox(lines = LINES_PER_BOX, label="About the Suspect", value = ""),]
|
156 |
+
|
157 |
+
# ============================================
|
158 |
+
# Arrest attempt - Are you correct ?
|
159 |
+
# ============================================
|
160 |
+
|
161 |
+
def arrest():
|
162 |
+
|
163 |
+
global game
|
164 |
+
|
165 |
+
current_suspect = game.get_current_suspect()
|
166 |
+
guilty_suspect = game.get_guilty_suspect_name()
|
167 |
+
|
168 |
+
if current_suspect is None:
|
169 |
+
arrest_results = "Please select a suspect on the right to arrest."
|
170 |
+
|
171 |
+
elif current_suspect == guilty_suspect:
|
172 |
+
arrest_results = "👍👍 Congratulations 👍👍 \n\nYou caught " + guilty_suspect + " and recovered the painting.\n\n" + \
|
173 |
+
guilty_suspect + " confessed why: \n\n'" + game.get_suspect_motive(guilty_suspect) + "'" + \
|
174 |
+
"and said 'I would have gotten away with it too if it weren't for you meddling detectives'"
|
175 |
+
|
176 |
+
else:
|
177 |
+
arrest_results = "❌❌❌ You Failed Detective ❌❌❌\n\n" + guilty_suspect + " stole the painting and snuck away while you were arresting the wrong person"
|
178 |
+
|
179 |
+
return gr.Textbox(lines = LINES_PER_BOX,
|
180 |
+
label = "Results of Your Arrest",
|
181 |
+
value = arrest_results)
|
182 |
+
|
183 |
+
|
184 |
+
# ================================
|
185 |
+
# Main Code - Draw Gradio UI
|
186 |
+
# ================================
|
187 |
+
|
188 |
+
game.print_game() # DEBUG - Print out the game settings
|
189 |
+
|
190 |
+
with gr.Blocks() as demo:
|
191 |
+
|
192 |
+
image_manor = gr.Image(value = "mystery_manor.jpg", width=2400, interactive=False)
|
193 |
+
|
194 |
+
with gr.Row():
|
195 |
+
|
196 |
+
with gr.Column():
|
197 |
+
painting = gr.Image (value = game.get_stolen_painting(), height=500, width=500, label = game.get_stolen_painting_name(), interactive=False)
|
198 |
+
crime_desc = gr.Textbox(lines = LINES_PER_BOX, label="A Crime has been Committed", value = game.get_crime_text() )
|
199 |
+
with gr.Row():
|
200 |
+
btn_new_crime = gr.Button("⚡ New Crime ⚡")
|
201 |
+
btn_recap = gr.Button("💡 Recap 💡")
|
202 |
+
|
203 |
+
with gr.Column():
|
204 |
+
suspect_image = gr.Image (value = "Question_Mark.jpg", height=500,width=500, label="Suspect", interactive=False)
|
205 |
+
suspect_desc = gr.Textbox(lines = LINES_PER_BOX, label = "About the Suspect", value="")
|
206 |
+
with gr.Row():
|
207 |
+
btn_arrest= gr.Button("👮 Arrest 👮")
|
208 |
+
btn_hint = gr.Button("🕵 Clue 🕵")
|
209 |
+
|
210 |
+
with gr.Column():
|
211 |
+
suspect_list = gr.Examples(examples = game.get_suspect_images(), inputs = [suspect_image,suspect_desc], outputs=[suspect_desc], examples_per_page=SUSPECTS_PER_PAGE, fn=question_suspect, run_on_click=True, label="Suspect List", cache_examples=False)
|
212 |
+
|
213 |
+
with gr.Column():
|
214 |
+
QnA = gr.ChatInterface(fn=predict, examples=game.get_sample_questions(), title="❓"+" Question the Suspect "+"❓", fill_height=False, retry_btn=None, undo_btn=None, cache_examples=False)
|
215 |
+
|
216 |
+
btn_new_crime.click(new_crime, inputs=None, outputs=[painting, crime_desc, suspect_image, suspect_desc]) # Resets game to new one
|
217 |
+
btn_recap.click (get_recap, inputs=None, outputs=[crime_desc]) # Prints out summary of notes
|
218 |
+
btn_arrest.click (arrest, inputs=None, outputs=[suspect_desc]) # Arrest selected subject
|
219 |
+
btn_hint.click (get_hint, inputs=None, outputs=[suspect_desc]) # Ask for hint/clue
|
220 |
+
|
221 |
+
|
222 |
+
if __name__ == "__main__":
|
223 |
+
|
224 |
+
demo.launch()
|
225 |
+
|
226 |
+
|
227 |
+
|
228 |
+
|
229 |
+
|
230 |
+
|
231 |
+
|
232 |
+
|
233 |
+
|
234 |
+
|
235 |
+
|
236 |
+
|
game_info.py
ADDED
@@ -0,0 +1,534 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
|
3 |
+
# Contest Entry for Nvidia / Langchain's Generative AI Agents Contest
|
4 |
+
# Submitted on Father's Day 2024
|
5 |
+
# Dedicated to all fathers and that special relationship with their children - this game is for you
|
6 |
+
|
7 |
+
import sys, random
|
8 |
+
import networkx as nx
|
9 |
+
|
10 |
+
# ==========================
|
11 |
+
# Mystery Game Object
|
12 |
+
# ==========================
|
13 |
+
|
14 |
+
class cMystery():
|
15 |
+
|
16 |
+
# ===============================================
|
17 |
+
# Game Constructor
|
18 |
+
# ===============================================
|
19 |
+
|
20 |
+
def __init__(self,
|
21 |
+
NUMBER_OF_SUSPECTS = 14,
|
22 |
+
NUMBER_OF_TIMESLOTS = 12,
|
23 |
+
NUMBER_OF_ROOMS = 10,
|
24 |
+
NUMBER_OF_PAINTINGS = 13,
|
25 |
+
HOURS_TO_SHIFT = 9,
|
26 |
+
CRIME_OCCURS = 6,
|
27 |
+
CRIME_WINDOW = 4,
|
28 |
+
):
|
29 |
+
|
30 |
+
print("Initializing Mystery Game Object")
|
31 |
+
|
32 |
+
# ===============
|
33 |
+
# Game Constants
|
34 |
+
# ===============
|
35 |
+
|
36 |
+
self.suspects = {
|
37 |
+
|
38 |
+
"Agnus the Art Gallery Owner":{"name": "Agnus Gerhard","background":"owns local art gallery", "motive": "I sold all of the poodle art to the manor and only I know the true value of each painting", "did I steal the painting": "No", "timeline":[],
|
39 |
+
"profile": "Agnus Gerhard studied Art History at the University of Vienna which was her passion. After her internship at the MoMA in New York, Agnus was finally able to realize her dream and opened The Gallery of Eclectics Poodle Art.\n\nMOTIVE: She sold all of the poodle art to the manor and knows the high value of each painting."} ,
|
40 |
+
"Ben the Butler" :{"name": "Benjamin Atwood", "background": "butler for the manor","motive": "I have expensive tastes that cannot be funded with my pittance of a salary", "did I steal the painting": "No", "timeline":[],
|
41 |
+
"profile": "Benjamin Atwood has always been drawn to elegance and sophistication, fascinated with works of fine literature, art, and culture. Recognizing Ben's sharp mind and quick learning abilities, he soon ascended to the position of the manor's lead butler.\n\nMOTIVE: Ben's expensive tastes cannot be funded with his modest butler's salary."} ,
|
42 |
+
"Bernice the Bartender" :{"name": "Bernice Lo", "background": "bartender for the manor","motive": "I am angry that you keep falsely accusing me of stealing from the manor so why not make it real", "did I steal the painting": "No", "timeline":[] ,
|
43 |
+
"profile": "Bernice is more than just a bartender. She is a storehouse of stories, secrets, and emotions. Her compassionate listening and signature cocktails managed to give solace to many and catch the attention of the Head of the Manor.\n\nMOTIVE: Bernice has been angry ever since she was falsely accused of stealing a case of expensive wine."} ,
|
44 |
+
"Buddy the Business Partner" :{"name": "Buddy Bannister", "background": "business partner of the owner of the manor","motive": "Well I have been known to dabble in business deals others won't touch", "did I steal the painting": "No", "timeline":[] ,
|
45 |
+
"profile": "Hard-working Buddy Bannister always knows how to strike a deal. Good with numbers and an absolute shark at negotiating, he quickly climbed the business ladder to become a successful business partner.\n\nMOTIVE: Buddy is a man of secrets and questionable legal involvements with rumors of bribery, blackmail, even corporate espionage."} ,
|
46 |
+
"Chef Chuck" :{"name": "Charles Toussaint but people call me Chuck", "background": "chef for the manor","motive": "You may have heard on the street that I have some gambling debts. I'll pay them off you know", "did I steal the painting": "No", "timeline":[] ,
|
47 |
+
"profile": "Charles 'Chuck' Toussaint was born and raised in New Orleans. He is a cooking prodigy trained at an esteemed culinary school in Paris then returning home as an accomplished young chef and local celebrity.\n\nMOTIVE: The Chef is in a deep pot of trouble thanks to unpaid gambling debts."} ,
|
48 |
+
"Daughter-In-Law Daphne" :{"name": "Daphne Dartmouth", "background": "philanthopist and wife of son Samuel","motive": "I have no motive - how can I steal a painting that is rightfully mine", "did I steal the painting": "No", "timeline":[] ,
|
49 |
+
"profile": "Daphne met future husband Sam when they both lived in NYC. She was immediately attracted to the prospect of marrying into a wealthy family and saw Sam as an attractive ticket.\n\nMOTIVE: Daphne is always after the Bigger Better Thing including valuable paintings that will eventually be hers."} ,
|
50 |
+
"Frank the Brother" :{"name": "Franklin Edward Morris", "background": "brother of manor owner and not currently employed", "motive": "Well I have no money and no job", "did I steal the painting": "No", "timeline":[] ,
|
51 |
+
"profile": "Franklin Edward Morris, the youngest of three siblings including older sister Sarah, grew up in the city of New Chester. Frank was spoiled by money and largely struggled to achieve any meaningful accomplishments academically or in his career.\n\nMOTIVE: Frank holds a deep level of jealousy and will act out accordingly."} ,
|
52 |
+
"Gary the Gardener" :{"name": "Gary Eves", "background": "manor gardener","motive": "Once a thief always I thief I guess", "did I steal the painting": "No", "timeline":[] ,
|
53 |
+
"profile": "Gary grew up in a small town foster home and found himself in trouble with the law. In a life changing moment he became a dedicated gardener, taking up the mission to regenerate the green life of the city and eventually catching the attention of the manor.\n\nMOTIVE: Gary has a past rap sheet for burglary of luxury goods. Has he truly reformed?"} ,
|
54 |
+
"Larry the Lawyer" :{"name": "Lawrence Morington III", "background": "lawyer for the manor owner","motive": "Think of this as a down payment on my fees for my last legal settlement", "did I steal the painting": "No", "timeline":[] ,
|
55 |
+
"profile": "Lawrence Morington was born into a prestigious family known for its lineage of top-tier lawyers. Larry became a lawyer but his his passion for law and scheming blended into a deadly combo. With his intelligence he started to twist laws and bend cases to his will.\n\nMOTIVE: Larry thinks he deserved a bigger cut of that last legal settlement so why not take it?"} ,
|
56 |
+
"Madam Marmalade" :{"name": "Gillian Marmalade", "background": "","motive": "The proceeds from that painting could fund the retirement package the owners promised me", "did I steal the painting": "No", "timeline":[] ,
|
57 |
+
"profile": "Gillian Marmalade she found herself in the opulent manor as a housekeeper. With her diligent manner and uncanny knack for problem-solving, she quickly moved up the ranks eventually becoming the House Manager.\n\nMOTIVE: The manor is pushing her to retire and the proceeds from a painting would serve her nicely."} ,
|
58 |
+
"Nick the Nephew" :{"name": "Nick Wallace", "background": "investment banker and nephew of the manor's owner","motive": "Consider it a loan and I'll pay it back with a huge profit", "did I steal the painting": "No", "timeline":[] ,
|
59 |
+
"profile": "Nick Wallace, the ambitious nephew, is a top-notch graduate of a prestigious Ivy League business school. He is extremely eager to access the family fortune and invest it.\n\nMOTIVE: There is no money in dog art. Nick will generate better returns from this unofficial loan."} ,
|
60 |
+
"Rebel the Niece" :{"name": "Rachel Wallace niece of the manor's owner and Nick's sister", "background": "currently a student","motive": "Just boredom and to show everyone I am smarter than they think", "did I steal the painting": "No", "timeline":[] ,
|
61 |
+
"profile": "A childhood prodigy, Rachel 'Rebel' Wallace found at an early age she could hack into any electronic or mechanical system, which is a skill set she developed due to her parents' constant absence. Early successes fueled her rebellious streak only made her more determined to break more rules and gain forbidden knowledge.\n\nMOTIVE: To relieve the daily boredom of living in the manor."} ,
|
62 |
+
"Samuel the Son" :{"name": "Sam Morris", "background": "philanderer - I mean philanthopist and son of the manor's owner","motive": "My wife Daphne is very expensive and has threatened divorce", "did I steal the painting": "No", "timeline":[] ,
|
63 |
+
"profile": "Sam Morris, also known as “Silent Sam,” led a life that was quite unlike his other siblings. His dabbling in philanthropy led him to rub elbows with the rich and elite of NYC including Daphne who he later married.\n\nMOTIVE: Daphne is a really expensive wife to keep happy."} ,
|
64 |
+
"Sarah the Sister" :{"name": "Sarah Evelyn Morris", "background": "local judge and sister of manor's owner","motive": "Poodle art is a waste of the family's money", "did I steal the painting": "No", "timeline":[] ,
|
65 |
+
"profile": "Sarah Evelyn Morris always stood out among her siblings due to her serious and analytical demeanor even at a young age. After acing the bar and practicing as a lawyer for the prosecution she quickly rose to become one of the youngest appointed judge in the county.\n\nMOTIVE: Thinks that wasting family money on poodle art is a already a crime so no one can charge her thanks to double jeapardy."} ,
|
66 |
+
|
67 |
+
}
|
68 |
+
|
69 |
+
self.times = ["Breakfast Time","Early Morning","Mid Morning","Noon","Early Afternoon","Tea Time","Late Afternoon","Cocktail Hour","Dinner Time","After Dinner","Evening","Late Evening","Suspect Questioning"]
|
70 |
+
|
71 |
+
self.rooms = [
|
72 |
+
{"name": "Dining Area", "activities": ["having a meal", "finishing up", "walking through"]},
|
73 |
+
{"name": "Foyer", "activities": ["waiting for a guest", "taking a phone call"]},
|
74 |
+
{"name": "Outdoor Pool Deck", "activities": ["swimming in the pool", "lounging on a deck chair", "having a drink"]},
|
75 |
+
{"name": "Main Hall", "activities": ["looking at the art", "passing through", "having a drink","lounging on a deck chair"]},
|
76 |
+
{"name": "Home Theater", "activities": ["watching a movie", "waiting for the movie to start", "lounging"]},
|
77 |
+
{"name": "Library", "activities": ["looking at the art", "reading a book", "checking out a map of the area"]},
|
78 |
+
{"name": "Mezzanine", "activities": ["looking at the art", "reading a book", "Exercising"]},
|
79 |
+
{"name": "Art Gallery", "activities": ["looking at the art", "sat down and admired the paintings", "exploring"]},
|
80 |
+
{"name": "Living Room", "activities": ["looking at the art", "talking with the other guests", "talking with the staff"]},
|
81 |
+
{"name": "Grand Ballroom", "activities": ["getting questioned by the police", "don't know why we were summoned", "you summoned me to come here"]}
|
82 |
+
]
|
83 |
+
|
84 |
+
self.paintings = [
|
85 |
+
{"name": "Pop Art Poodles", "image": "1.jpg"},
|
86 |
+
{"name": "Poodle with a Pearl Earring", "image": "2.jpg"},
|
87 |
+
{"name": "Portrait of a Poodle", "image": "3.jpg"},
|
88 |
+
{"name": "Starry Poodle", "image": "4.jpg"},
|
89 |
+
{"name": "Mona Lisa with Poodle", "image": "5.jpg"},
|
90 |
+
{"name": "Queen Elizabeth with Poodle", "image": "6.jpg"},
|
91 |
+
{"name": "American Poodle", "image": "7.jpg"},
|
92 |
+
{"name": "Whistler's Poodle", "image": "8.jpg"},
|
93 |
+
{"name": "Night Poodles", "image": "9.jpg"},
|
94 |
+
{"name": "Portrait of a Poodle", "image": "10.jpg"},
|
95 |
+
{"name": "The Birth of Poodles", "image": "11.jpg"},
|
96 |
+
{"name": "A Park Full of Parisian Poodles", "image": "12.jpg"},
|
97 |
+
]
|
98 |
+
|
99 |
+
self.sample_questions = ["Provide a summary of your whereabouts.", # Suggested questions for the QnA Chat interface
|
100 |
+
"Where were you at the time of the crime?",
|
101 |
+
"Where were you when the power went out?",
|
102 |
+
"Who was with you at the time of the crime?"]
|
103 |
+
|
104 |
+
# ===============
|
105 |
+
# Game Variables
|
106 |
+
# ===============
|
107 |
+
|
108 |
+
# Initialization Parameters
|
109 |
+
|
110 |
+
self.NUMBER_OF_SUSPECTS = NUMBER_OF_SUSPECTS
|
111 |
+
self.NUMBER_OF_TIMESLOTS = NUMBER_OF_TIMESLOTS
|
112 |
+
self.NUMBER_OF_ROOMS = NUMBER_OF_ROOMS
|
113 |
+
self.NUMBER_OF_PAINTINGS = NUMBER_OF_PAINTINGS
|
114 |
+
self.HOURS_TO_SHIFT = HOURS_TO_SHIFT
|
115 |
+
self.CRIME_OCCURS = CRIME_OCCURS
|
116 |
+
self.CRIME_WINDOW = CRIME_WINDOW
|
117 |
+
|
118 |
+
self.timeline = {} # Timeline for the crime scene
|
119 |
+
|
120 |
+
self.current_suspect = None # Name of current suspect
|
121 |
+
|
122 |
+
self.guilty_suspect_idx = None # Index of guilty suspect
|
123 |
+
self.guilty_suspect = None # Name of guilty suspect
|
124 |
+
self.prime_suspects = None # List of potential suspects (as indices)
|
125 |
+
self.have_alibi = None # List of suspects with alibis (as indices)
|
126 |
+
self.the_usual_suspects = None # Names of potential suspects
|
127 |
+
|
128 |
+
self.power_cut_time_idx = None # Index to time when power was cut (occurs just before crime)
|
129 |
+
self.power_cut_room_idx = None # Index to room where power was cut (occurs just before crime)
|
130 |
+
self.crime_occurs_time_idx = None # Index to time when crime occurred
|
131 |
+
self.crime_occurs_room_idx = None # Index to room where crime occurred
|
132 |
+
|
133 |
+
self.power_cut_time_name = None # Time when power was cut (occurs just before crime)
|
134 |
+
self.power_cut_room_name = None # Room where power was cut (occurs just before crime)
|
135 |
+
self.crime_occurs_time_name = None # Time when crime occurred
|
136 |
+
self.crime_occurs_room_name = None # Room where crime occurred
|
137 |
+
|
138 |
+
self.stolen_painting = None # Reference to the stolen painting
|
139 |
+
self.stolen_painting_img = None
|
140 |
+
self.stolen_painting_name = None
|
141 |
+
|
142 |
+
self.crime_description = "" # Text describing the crime
|
143 |
+
self.crime_recap = "" # Text summarizing the responses from suspects and clues
|
144 |
+
self.hints_given = 0 # Number of hints given during the gaming session
|
145 |
+
self.hints_recap = [] # Recap of hints given during the gaming session
|
146 |
+
|
147 |
+
# Creates a new game by default when an object is created
|
148 |
+
|
149 |
+
self.new_game()
|
150 |
+
|
151 |
+
return
|
152 |
+
|
153 |
+
|
154 |
+
# ===============================================
|
155 |
+
# End of Game Constructor
|
156 |
+
# ===============================================
|
157 |
+
|
158 |
+
# ===============================================
|
159 |
+
# Game Functions - Information Retrieval
|
160 |
+
# ===============================================
|
161 |
+
|
162 |
+
def get_current_suspect(self):
|
163 |
+
print("Current suspect is: ",self.current_suspect)
|
164 |
+
return self.current_suspect
|
165 |
+
|
166 |
+
def set_current_suspect(self, name):
|
167 |
+
self.current_suspect = name
|
168 |
+
print("Setting current suspect to: ", self.current_suspect)
|
169 |
+
return
|
170 |
+
|
171 |
+
def get_suspect(self, name):
|
172 |
+
return self.suspects.get(name)
|
173 |
+
|
174 |
+
def get_suspect_idx(self, name):
|
175 |
+
suspects = [suspect for suspect in self.suspects.keys()]
|
176 |
+
suspect_idx = suspects.index(name)
|
177 |
+
return suspect_idx
|
178 |
+
|
179 |
+
def get_suspect_name(self, suspect_idx):
|
180 |
+
suspects = [suspect for suspect in self.suspects.keys()]
|
181 |
+
return suspects[suspect_idx]
|
182 |
+
|
183 |
+
def get_guilty_suspect_name(self):
|
184 |
+
return self.guilty_suspect
|
185 |
+
|
186 |
+
def get_suspect_profile(self, name):
|
187 |
+
selected = self.suspects.get(name)
|
188 |
+
return selected["profile"]
|
189 |
+
|
190 |
+
def get_suspect_proper_name(self, name):
|
191 |
+
selected = self.suspects.get(name)
|
192 |
+
return selected["name"]
|
193 |
+
|
194 |
+
def get_suspect_background(self, name):
|
195 |
+
selected = self.suspects.get(name)
|
196 |
+
return selected["background"]
|
197 |
+
|
198 |
+
def get_suspect_motive(self, name):
|
199 |
+
selected = self.suspects.get(name)
|
200 |
+
return selected["motive"]
|
201 |
+
|
202 |
+
def get_suspect_images(self):
|
203 |
+
suspect_images = [[suspect + ".jpg", suspect] for suspect in self.suspects.keys()]
|
204 |
+
return suspect_images
|
205 |
+
|
206 |
+
def get_room_names(self):
|
207 |
+
return [room["name"] for room in self.rooms]
|
208 |
+
|
209 |
+
def get_room_name(self, room_idx):
|
210 |
+
rooms = [room["name"] for room in self.rooms]
|
211 |
+
return rooms[room_idx]
|
212 |
+
|
213 |
+
def get_room_activities(self):
|
214 |
+
return [room["activities"] for room in self.rooms]
|
215 |
+
|
216 |
+
def get_random_room_activity(self, room):
|
217 |
+
selected = self.rooms[room]
|
218 |
+
return random.choice(selected["activities"])
|
219 |
+
|
220 |
+
def get_random_painting(self):
|
221 |
+
return random.choice(self.paintings)
|
222 |
+
|
223 |
+
def get_stolen_painting(self):
|
224 |
+
return self.stolen_painting_img
|
225 |
+
|
226 |
+
def get_stolen_painting_name(self):
|
227 |
+
return self.stolen_painting_name
|
228 |
+
|
229 |
+
def get_power_off_room_idx(self):
|
230 |
+
guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)
|
231 |
+
return guilty_timeline[self.power_cut_time_idx]
|
232 |
+
|
233 |
+
def get_power_off_room_name(self):
|
234 |
+
guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)
|
235 |
+
return self.rooms[guilty_timeline[self.power_cut_time_idx]]["name"]
|
236 |
+
|
237 |
+
def get_crime_occurs_room_idx(self):
|
238 |
+
guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)
|
239 |
+
return guilty_timeline[self.crime_occurs_time_idx]
|
240 |
+
|
241 |
+
def get_crime_occurs_room_name(self):
|
242 |
+
guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)
|
243 |
+
return self.rooms[guilty_timeline[self.crime_occurs_time_idx]]["name"]
|
244 |
+
|
245 |
+
def round_up_the_usual_suspects(self):
|
246 |
+
return ", ".join([self.get_suspect_name(suspect_idx) for suspect_idx in self.prime_suspects])
|
247 |
+
|
248 |
+
def have_alibis(self):
|
249 |
+
all_suspects = [x for x, suspect in enumerate(self.suspects)]
|
250 |
+
have_alibis = list(set(all_suspects).difference(set(self.prime_suspects)))
|
251 |
+
return have_alibis
|
252 |
+
|
253 |
+
def names_of_suspects_with_alibis(self):
|
254 |
+
return ", ".join([self.get_suspect_name(suspect_idx) for suspect_idx in self.have_alibis()])
|
255 |
+
|
256 |
+
def get_crime_text(self):
|
257 |
+
return f"Mystery Manor was showing its world-renowned collection of its finest poodle-themed art at a gala." + \
|
258 |
+
f"\nA painting called ||{self.stolen_painting_name}|| was stolen from the ||{self.crime_occurs_room_name}||. " + \
|
259 |
+
f"Power to the security system was cut off in the ||{self.power_cut_room_name}|| just before the crime. " + \
|
260 |
+
f"Your job is to question the suspects and arrest the thief. Good luck detective !!" + \
|
261 |
+
f"\n\nInstructions: \n" + \
|
262 |
+
f"\n ⬥ Select a suspect on the right" + \
|
263 |
+
f"\n ⬥ Ask questions in the chat" + \
|
264 |
+
f"\n ⬥ Get a hint (if you need it) with the 'Clue' button" + \
|
265 |
+
f"\n ⬥ Deduce who is the thief" + \
|
266 |
+
f"\n ⬥ Hit the 'Arrest' button while questioning the" + \
|
267 |
+
f"\n suspect to make an arrest"
|
268 |
+
|
269 |
+
def give_hint(self):
|
270 |
+
|
271 |
+
hints = [
|
272 |
+
f"Your suspect was in the {self.power_cut_room_name} during the power outage and the {self.crime_occurs_room_name} when the theft occurred",
|
273 |
+
f"These suspects have valid alibis for the theft:\n{self.names_of_suspects_with_alibis()}",
|
274 |
+
f"At the time of theft, these people were alone:\n{self.round_up_the_usual_suspects()}",
|
275 |
+
]
|
276 |
+
|
277 |
+
if self.hints_given < len(hints):
|
278 |
+
hint = hints[self.hints_given]
|
279 |
+
self.hints_given += 1
|
280 |
+
self.hints_recap += " ⬥ " + hint +"\n"
|
281 |
+
else:
|
282 |
+
hint = "Sorry no more hints are available"
|
283 |
+
|
284 |
+
return hint
|
285 |
+
|
286 |
+
def get_hints_recap(self):
|
287 |
+
return "".join(self.hints_recap)
|
288 |
+
|
289 |
+
def add_note_to_recap(self, note, current_suspect):
|
290 |
+
if current_suspect is not None:
|
291 |
+
self.crime_recap += current_suspect + " said the following delineated in square brackets\n"
|
292 |
+
self.crime_recap += "[" + note + "]\n\n"
|
293 |
+
return note
|
294 |
+
|
295 |
+
def get_crime_recap(self):
|
296 |
+
return self.crime_recap
|
297 |
+
|
298 |
+
def get_sample_questions(self):
|
299 |
+
return self.sample_questions
|
300 |
+
|
301 |
+
|
302 |
+
# ========================================
|
303 |
+
# Game Functions - Generate a New Game
|
304 |
+
# ========================================
|
305 |
+
|
306 |
+
def get_people_in_room(self, room, suspects_positions): # List people in a room
|
307 |
+
return [person for person, position in suspects_positions.items() if position == room]
|
308 |
+
|
309 |
+
def get_timeline_for_person(self, suspect_idx, timeline):
|
310 |
+
return [suspects_locations[suspect_idx] for timeline, suspects_locations in timeline.items()]
|
311 |
+
|
312 |
+
def generate_new_game(self):
|
313 |
+
|
314 |
+
# Local variables used to record the crime
|
315 |
+
|
316 |
+
timeline = {} # Log of all suspects and their movements over the day
|
317 |
+
prime_suspects = [] # All potential criminals
|
318 |
+
|
319 |
+
# Create a fully connected graph of ROOMS with rooms equal to NUMBER_OF_ROOMS
|
320 |
+
|
321 |
+
G = nx.Graph()
|
322 |
+
G.add_nodes_from(range(self.NUMBER_OF_ROOMS))
|
323 |
+
|
324 |
+
for i in range(self.NUMBER_OF_ROOMS-1):
|
325 |
+
for j in range(self.NUMBER_OF_ROOMS-1):
|
326 |
+
G.add_edge(i, j)
|
327 |
+
|
328 |
+
# Everyone starts in the DINING ROOM
|
329 |
+
|
330 |
+
# suspects_positions = {i: random.choice(list(G.rooms)) for i in range(self.NUMBER_OF_SUSPECTS)} # for random starting rooms for each character
|
331 |
+
suspects_positions = {i: 0 for i in range(self.NUMBER_OF_SUSPECTS)}
|
332 |
+
timeline[0] = suspects_positions.copy()
|
333 |
+
|
334 |
+
# Helper function that moves people to adjacent rooms
|
335 |
+
|
336 |
+
def move_people(suspects_positions, G):
|
337 |
+
|
338 |
+
for person, current_position in suspects_positions.items():
|
339 |
+
neighbors = list(nx.neighbors(G, current_position)) # Get adjacent neighbouring rooms
|
340 |
+
suspects_positions[person] = random.choice(neighbors) # Move person to a random room
|
341 |
+
|
342 |
+
return suspects_positions
|
343 |
+
|
344 |
+
# Simulate movement of suspects over NUMBER_OF_TIMESLOTS steps
|
345 |
+
|
346 |
+
for time_slot in range(1, self.NUMBER_OF_TIMESLOTS):
|
347 |
+
suspects_positions = move_people(suspects_positions, G)
|
348 |
+
timeline[time_slot] = suspects_positions.copy()
|
349 |
+
|
350 |
+
# All suspects end up in the GRAND BALLROOM
|
351 |
+
|
352 |
+
suspects_positions = {i: (self.NUMBER_OF_ROOMS-1) for i in range(self.NUMBER_OF_SUSPECTS)}
|
353 |
+
timeline[self.NUMBER_OF_TIMESLOTS] = suspects_positions.copy()
|
354 |
+
|
355 |
+
# Create a thief by checking the paths of all suspects
|
356 |
+
# The thief is someone alone in a room during the crime window
|
357 |
+
# defined as the time between CRIME_OCCURS plus CRIME_WINDOW
|
358 |
+
# the power off room is where the thief was one step before
|
359 |
+
|
360 |
+
for idx, time_slot in enumerate(timeline): # Walk through whole day
|
361 |
+
|
362 |
+
# if we are within the crime window, see who is alone in a room
|
363 |
+
|
364 |
+
if idx in range(self.CRIME_OCCURS,self.CRIME_OCCURS+self.CRIME_WINDOW):
|
365 |
+
|
366 |
+
people_in_room = [self.get_people_in_room(room, timeline[time_slot]) for room in range(self.NUMBER_OF_ROOMS)]
|
367 |
+
counts_in_room = [len(x) for x in people_in_room]
|
368 |
+
prime_suspects = []
|
369 |
+
|
370 |
+
for room_idx, count in enumerate(counts_in_room):
|
371 |
+
if count == 1:
|
372 |
+
prime_suspects.append(people_in_room[room_idx][0])
|
373 |
+
|
374 |
+
if len(prime_suspects) >= 1:
|
375 |
+
return random.choice(prime_suspects), idx, prime_suspects, timeline
|
376 |
+
|
377 |
+
# We did not generate a proper crime, return None for the suspect and None for the crime time
|
378 |
+
|
379 |
+
return None, None, prime_suspects, timeline
|
380 |
+
|
381 |
+
def new_game(self):
|
382 |
+
|
383 |
+
guilty_suspect_idx = None
|
384 |
+
|
385 |
+
while (guilty_suspect_idx is None):
|
386 |
+
guilty_suspect_idx, crime_occurs_time, prime_suspects, timeline = self.generate_new_game()
|
387 |
+
|
388 |
+
self.current_suspect = None # No suspect selected
|
389 |
+
|
390 |
+
self.guilty_suspect_idx = guilty_suspect_idx # Index of guilty suspect
|
391 |
+
self.guilty_suspect = self.get_suspect_name(guilty_suspect_idx) # Name of guilty suspect
|
392 |
+
|
393 |
+
self.timeline = timeline # Timeline for the crime scene
|
394 |
+
self.prime_suspects = prime_suspects # List of potential suspects (as indices)
|
395 |
+
self.have_alibi = self.have_alibis() # List of suspects with alibis (as indices)
|
396 |
+
self.the_usual_suspects = self.round_up_the_usual_suspects() # List of names of potential suspects
|
397 |
+
|
398 |
+
self.power_cut_time_idx = crime_occurs_time - 1 # Index to time when power was cut (occurs just before crime)
|
399 |
+
self.crime_occurs_time_idx = crime_occurs_time # Index to time when crime occurred
|
400 |
+
self.power_cut_room_idx = self.get_power_off_room_idx() # Index to room where power was cut (occurs just before crime)
|
401 |
+
self.crime_occurs_room_idx = self.get_crime_occurs_room_idx() # Index to room where crime occurred
|
402 |
+
|
403 |
+
self.times[self.power_cut_time_idx] = "Power Outage"
|
404 |
+
self.times[self.crime_occurs_time_idx] = "Time of Theft"
|
405 |
+
|
406 |
+
self.power_cut_time_name = self.times[self.power_cut_time_idx] # Time when power was cut (occurs just before crime)
|
407 |
+
self.power_cut_room_name = self.get_power_off_room_name() # Room where power was cut (occurs just before crime)
|
408 |
+
self.crime_occurs_time_name = self.times[self.crime_occurs_time_idx] # Time when crime occurred
|
409 |
+
self.crime_occurs_room_name = self.get_crime_occurs_room_name() # Room where crime occurred
|
410 |
+
|
411 |
+
self.stolen_painting = self.get_random_painting() # Reference to the stolen painting
|
412 |
+
self.stolen_painting_img = self.stolen_painting["image"]
|
413 |
+
self.stolen_painting_name = self.stolen_painting["name"]
|
414 |
+
|
415 |
+
self.crime_description = self.get_crime_text() # Description of the crime
|
416 |
+
self.crime_recap = "" # A summary of hints and notes from suspects
|
417 |
+
self.hints_given = 0 # Count of hints given
|
418 |
+
self.hints_recap = [] # Recap of hints given during the gaming session
|
419 |
+
|
420 |
+
|
421 |
+
def print_game(self):
|
422 |
+
|
423 |
+
print("Crime timeline: \n", self.timeline,"\n")
|
424 |
+
|
425 |
+
print("Name of current suspect: ", self.current_suspect)
|
426 |
+
|
427 |
+
print("Index of guilty suspect: ", self.guilty_suspect_idx)
|
428 |
+
print("Name of guilty suspect: ", self.guilty_suspect)
|
429 |
+
|
430 |
+
print("Prime Suspects: ", self.prime_suspects)
|
431 |
+
print("The usual suspects: ", self.the_usual_suspects)
|
432 |
+
print("Have alibis: ", self.names_of_suspects_with_alibis())
|
433 |
+
|
434 |
+
## print("power_cut_time_idx: ", self.power_cut_time_idx)
|
435 |
+
## print("power_cut_room_idx: ", self.power_cut_room_idx)
|
436 |
+
## print("self.crime_occurs_time_idx: ", self.crime_occurs_time_idx)
|
437 |
+
## print("self.crime_occurs_room_idx: ", self.crime_occurs_room_idx)
|
438 |
+
|
439 |
+
print("Time when power was cut: ", self.power_cut_time_name)
|
440 |
+
print("Room where power was cut: ", self.power_cut_room_name)
|
441 |
+
print("Time when crime occurred : ", self.crime_occurs_time_name)
|
442 |
+
print("Room where crime occurred : ", self.crime_occurs_room_name)
|
443 |
+
|
444 |
+
print("stolen_painting_img: ", self.stolen_painting_img)
|
445 |
+
print("stolen_painting_name: ", self.stolen_painting_name)
|
446 |
+
|
447 |
+
print("Crime Description: \n", self.crime_description)
|
448 |
+
print("Hints Given: ", self.hints_given)
|
449 |
+
|
450 |
+
|
451 |
+
# ===============================================
|
452 |
+
# Game Function - Generate Script for Suspect
|
453 |
+
# ===============================================
|
454 |
+
|
455 |
+
def create_interview(self, suspect_name):
|
456 |
+
|
457 |
+
suspect_idx = self.get_suspect_idx(suspect_name)
|
458 |
+
whereabouts = self.get_timeline_for_person(suspect_idx, self.timeline)
|
459 |
+
witnesses = [self.get_people_in_room(room, self.timeline[idx]) for idx, room in enumerate(whereabouts)]
|
460 |
+
|
461 |
+
alibi = {
|
462 |
+
"name": self.get_suspect_proper_name(suspect_name),
|
463 |
+
"key suspect": suspect_idx in self.prime_suspects,
|
464 |
+
"have alibi for time of theft": suspect_idx in self.have_alibi,
|
465 |
+
"guilty": suspect_idx == self.guilty_suspect_idx,
|
466 |
+
"stole painting": suspect_idx == self.guilty_suspect_idx,
|
467 |
+
}
|
468 |
+
|
469 |
+
timeline = []
|
470 |
+
|
471 |
+
for idx,room in enumerate(whereabouts):
|
472 |
+
|
473 |
+
if len(witnesses[idx]) == self.NUMBER_OF_SUSPECTS:
|
474 |
+
witnesses_text = "Everyone"
|
475 |
+
elif len(witnesses[idx]) == 1:
|
476 |
+
witnesses_text = "No one. I was alone"
|
477 |
+
else:
|
478 |
+
witnesses_text = [self.get_suspect_name(witness) for witness in witnesses[idx] if witness != suspect_idx]
|
479 |
+
|
480 |
+
timeline_dict = {
|
481 |
+
"time": self.times[idx],
|
482 |
+
"location": self.get_room_name(room),
|
483 |
+
"activity": self.get_random_room_activity(room),
|
484 |
+
"witnesses": witnesses_text,
|
485 |
+
}
|
486 |
+
|
487 |
+
timeline.append(timeline_dict)
|
488 |
+
|
489 |
+
alibi["timeline"] = timeline
|
490 |
+
|
491 |
+
return str(alibi)
|
492 |
+
|
493 |
+
# ===============================================
|
494 |
+
# Game Destructor
|
495 |
+
# ===============================================
|
496 |
+
|
497 |
+
def __del__(self):
|
498 |
+
print("Game Destructor Called")
|
499 |
+
return
|
500 |
+
|
501 |
+
# ===============================================
|
502 |
+
# END OF Mystery Game Class Definition
|
503 |
+
# ===============================================
|
504 |
+
|
505 |
+
# END cMystery
|
506 |
+
|
507 |
+
|
508 |
+
|
509 |
+
# ===============================================
|
510 |
+
# MAIN PROGRAM - Testing Purposes
|
511 |
+
# ===============================================
|
512 |
+
|
513 |
+
if __name__ == '__main__':
|
514 |
+
|
515 |
+
print("Running game_info as main")
|
516 |
+
|
517 |
+
# Test game functions
|
518 |
+
|
519 |
+
#game = cMystery()
|
520 |
+
#game.print_game()
|
521 |
+
#print("\n",game.create_interview(game.guilty_suspect))
|
522 |
+
|
523 |
+
|
524 |
+
|
525 |
+
|
526 |
+
|
527 |
+
|
528 |
+
|
529 |
+
|
530 |
+
|
531 |
+
|
532 |
+
|
533 |
+
|
534 |
+
|
mystery_manor.jpg
ADDED
![]() |
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
langchain
|
2 |
+
langchain_core
|
3 |
+
langchain_openai
|
4 |
+
networkx
|
5 |
+
|
6 |
+
|