mswhite commited on
Commit
91eecdf
1 Parent(s): 0755890

Update game_info.py

Browse files
Files changed (1) hide show
  1. game_info.py +530 -534
game_info.py CHANGED
@@ -1,534 +1,530 @@
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
-
 
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 = ["Where were you at the time of the crime?", # Suggested questions for the QnA Chat interface
100
+ "Where were you when the power went out?",
101
+ "Who was with you at the time of the crime?"]
102
+
103
+ # ===============
104
+ # Game Variables
105
+ # ===============
106
+
107
+ # Initialization Parameters
108
+
109
+ self.NUMBER_OF_SUSPECTS = NUMBER_OF_SUSPECTS
110
+ self.NUMBER_OF_TIMESLOTS = NUMBER_OF_TIMESLOTS
111
+ self.NUMBER_OF_ROOMS = NUMBER_OF_ROOMS
112
+ self.NUMBER_OF_PAINTINGS = NUMBER_OF_PAINTINGS
113
+ self.HOURS_TO_SHIFT = HOURS_TO_SHIFT
114
+ self.CRIME_OCCURS = CRIME_OCCURS
115
+ self.CRIME_WINDOW = CRIME_WINDOW
116
+
117
+ self.timeline = {} # Timeline for the crime scene
118
+
119
+ self.current_suspect = None # Name of current suspect
120
+
121
+ self.guilty_suspect_idx = None # Index of guilty suspect
122
+ self.guilty_suspect = None # Name of guilty suspect
123
+ self.prime_suspects = None # List of potential suspects (as indices)
124
+ self.have_alibi = None # List of suspects with alibis (as indices)
125
+ self.the_usual_suspects = None # Names of potential suspects
126
+
127
+ self.power_cut_time_idx = None # Index to time when power was cut (occurs just before crime)
128
+ self.power_cut_room_idx = None # Index to room where power was cut (occurs just before crime)
129
+ self.crime_occurs_time_idx = None # Index to time when crime occurred
130
+ self.crime_occurs_room_idx = None # Index to room where crime occurred
131
+
132
+ self.power_cut_time_name = None # Time when power was cut (occurs just before crime)
133
+ self.power_cut_room_name = None # Room where power was cut (occurs just before crime)
134
+ self.crime_occurs_time_name = None # Time when crime occurred
135
+ self.crime_occurs_room_name = None # Room where crime occurred
136
+
137
+ self.stolen_painting = None # Reference to the stolen painting
138
+ self.stolen_painting_img = None
139
+ self.stolen_painting_name = None
140
+
141
+ self.crime_description = "" # Text describing the crime
142
+ self.crime_recap = "" # Text summarizing the responses from suspects and clues
143
+ self.hints_given = 0 # Number of hints given during the gaming session
144
+ self.hints_recap = [] # Recap of hints given during the gaming session
145
+
146
+ # Creates a new game by default when an object is created
147
+
148
+ self.new_game()
149
+
150
+ return
151
+
152
+
153
+ # ===============================================
154
+ # End of Game Constructor
155
+ # ===============================================
156
+
157
+ # ===============================================
158
+ # Game Functions - Information Retrieval
159
+ # ===============================================
160
+
161
+ def get_current_suspect(self):
162
+ print("Current suspect is: ",self.current_suspect)
163
+ return self.current_suspect
164
+
165
+ def set_current_suspect(self, name):
166
+ self.current_suspect = name
167
+ print("Setting current suspect to: ", self.current_suspect)
168
+ return
169
+
170
+ def get_suspect(self, name):
171
+ return self.suspects.get(name)
172
+
173
+ def get_suspect_idx(self, name):
174
+ suspects = [suspect for suspect in self.suspects.keys()]
175
+ suspect_idx = suspects.index(name)
176
+ return suspect_idx
177
+
178
+ def get_suspect_name(self, suspect_idx):
179
+ suspects = [suspect for suspect in self.suspects.keys()]
180
+ return suspects[suspect_idx]
181
+
182
+ def get_guilty_suspect_name(self):
183
+ return self.guilty_suspect
184
+
185
+ def get_suspect_profile(self, name):
186
+ selected = self.suspects.get(name)
187
+ return selected["profile"]
188
+
189
+ def get_suspect_proper_name(self, name):
190
+ selected = self.suspects.get(name)
191
+ return selected["name"]
192
+
193
+ def get_suspect_background(self, name):
194
+ selected = self.suspects.get(name)
195
+ return selected["background"]
196
+
197
+ def get_suspect_motive(self, name):
198
+ selected = self.suspects.get(name)
199
+ return selected["motive"]
200
+
201
+ def get_suspect_images(self):
202
+ suspect_images = [[suspect + ".jpg", suspect] for suspect in self.suspects.keys()]
203
+ return suspect_images
204
+
205
+ def get_room_names(self):
206
+ return [room["name"] for room in self.rooms]
207
+
208
+ def get_room_name(self, room_idx):
209
+ rooms = [room["name"] for room in self.rooms]
210
+ return rooms[room_idx]
211
+
212
+ def get_room_activities(self):
213
+ return [room["activities"] for room in self.rooms]
214
+
215
+ def get_random_room_activity(self, room):
216
+ selected = self.rooms[room]
217
+ return random.choice(selected["activities"])
218
+
219
+ def get_random_painting(self):
220
+ return random.choice(self.paintings)
221
+
222
+ def get_stolen_painting(self):
223
+ return self.stolen_painting_img
224
+
225
+ def get_stolen_painting_name(self):
226
+ return self.stolen_painting_name
227
+
228
+ def get_power_off_room_idx(self):
229
+ guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)
230
+ return guilty_timeline[self.power_cut_time_idx]
231
+
232
+ def get_power_off_room_name(self):
233
+ guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)
234
+ return self.rooms[guilty_timeline[self.power_cut_time_idx]]["name"]
235
+
236
+ def get_crime_occurs_room_idx(self):
237
+ guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)
238
+ return guilty_timeline[self.crime_occurs_time_idx]
239
+
240
+ def get_crime_occurs_room_name(self):
241
+ guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)
242
+ return self.rooms[guilty_timeline[self.crime_occurs_time_idx]]["name"]
243
+
244
+ def round_up_the_usual_suspects(self):
245
+ return ", ".join([self.get_suspect_name(suspect_idx) for suspect_idx in self.prime_suspects])
246
+
247
+ def have_alibis(self):
248
+ all_suspects = [x for x, suspect in enumerate(self.suspects)]
249
+ have_alibis = list(set(all_suspects).difference(set(self.prime_suspects)))
250
+ return have_alibis
251
+
252
+ def names_of_suspects_with_alibis(self):
253
+ return ", ".join([self.get_suspect_name(suspect_idx) for suspect_idx in self.have_alibis()])
254
+
255
+ def get_crime_text(self):
256
+ return f"Mystery Manor was showing its world-renowned collection of its finest poodle-themed art at a gala." + \
257
+ f"\nA painting called ||{self.stolen_painting_name}|| was stolen from the ||{self.crime_occurs_room_name}||. " + \
258
+ f"Power to the security system was cut off in the ||{self.power_cut_room_name}|| just before the crime. " + \
259
+ f"Your job is to question the suspects and arrest the thief. Good luck detective !!" + \
260
+ f"\n Select a suspect on the right" + \
261
+ f"\n ⬥ Ask questions in the chat" + \
262
+ f"\n ⬥ Get a hint (if you need it) with the 'Clue' button" + \
263
+ f"\n ⬥ Hit 'Arrest' while questioning the suspect to convict"
264
+
265
+ def give_hint(self):
266
+
267
+ hints = [
268
+ 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",
269
+ f"These suspects have valid alibis for the theft:\n{self.names_of_suspects_with_alibis()}",
270
+ f"At the time of theft, these people were alone:\n{self.round_up_the_usual_suspects()}",
271
+ ]
272
+
273
+ if self.hints_given < len(hints):
274
+ hint = hints[self.hints_given]
275
+ self.hints_given += 1
276
+ self.hints_recap += " ⬥ " + hint +"\n"
277
+ else:
278
+ hint = "Sorry no more hints are available"
279
+
280
+ return hint
281
+
282
+ def get_hints_recap(self):
283
+ return "".join(self.hints_recap)
284
+
285
+ def add_note_to_recap(self, note, current_suspect):
286
+ if current_suspect is not None:
287
+ self.crime_recap += current_suspect + " said the following delineated in square brackets\n"
288
+ self.crime_recap += "[" + note + "]\n\n"
289
+ return note
290
+
291
+ def get_crime_recap(self):
292
+ return self.crime_recap
293
+
294
+ def get_sample_questions(self):
295
+ return self.sample_questions
296
+
297
+
298
+ # ========================================
299
+ # Game Functions - Generate a New Game
300
+ # ========================================
301
+
302
+ def get_people_in_room(self, room, suspects_positions): # List people in a room
303
+ return [person for person, position in suspects_positions.items() if position == room]
304
+
305
+ def get_timeline_for_person(self, suspect_idx, timeline):
306
+ return [suspects_locations[suspect_idx] for timeline, suspects_locations in timeline.items()]
307
+
308
+ def generate_new_game(self):
309
+
310
+ # Local variables used to record the crime
311
+
312
+ timeline = {} # Log of all suspects and their movements over the day
313
+ prime_suspects = [] # All potential criminals
314
+
315
+ # Create a fully connected graph of ROOMS with rooms equal to NUMBER_OF_ROOMS
316
+
317
+ G = nx.Graph()
318
+ G.add_nodes_from(range(self.NUMBER_OF_ROOMS))
319
+
320
+ for i in range(self.NUMBER_OF_ROOMS-1):
321
+ for j in range(self.NUMBER_OF_ROOMS-1):
322
+ G.add_edge(i, j)
323
+
324
+ # Everyone starts in the DINING ROOM
325
+
326
+ # suspects_positions = {i: random.choice(list(G.rooms)) for i in range(self.NUMBER_OF_SUSPECTS)} # for random starting rooms for each character
327
+ suspects_positions = {i: 0 for i in range(self.NUMBER_OF_SUSPECTS)}
328
+ timeline[0] = suspects_positions.copy()
329
+
330
+ # Helper function that moves people to adjacent rooms
331
+
332
+ def move_people(suspects_positions, G):
333
+
334
+ for person, current_position in suspects_positions.items():
335
+ neighbors = list(nx.neighbors(G, current_position)) # Get adjacent neighbouring rooms
336
+ suspects_positions[person] = random.choice(neighbors) # Move person to a random room
337
+
338
+ return suspects_positions
339
+
340
+ # Simulate movement of suspects over NUMBER_OF_TIMESLOTS steps
341
+
342
+ for time_slot in range(1, self.NUMBER_OF_TIMESLOTS):
343
+ suspects_positions = move_people(suspects_positions, G)
344
+ timeline[time_slot] = suspects_positions.copy()
345
+
346
+ # All suspects end up in the GRAND BALLROOM
347
+
348
+ suspects_positions = {i: (self.NUMBER_OF_ROOMS-1) for i in range(self.NUMBER_OF_SUSPECTS)}
349
+ timeline[self.NUMBER_OF_TIMESLOTS] = suspects_positions.copy()
350
+
351
+ # Create a thief by checking the paths of all suspects
352
+ # The thief is someone alone in a room during the crime window
353
+ # defined as the time between CRIME_OCCURS plus CRIME_WINDOW
354
+ # the power off room is where the thief was one step before
355
+
356
+ for idx, time_slot in enumerate(timeline): # Walk through whole day
357
+
358
+ # if we are within the crime window, see who is alone in a room
359
+
360
+ if idx in range(self.CRIME_OCCURS,self.CRIME_OCCURS+self.CRIME_WINDOW):
361
+
362
+ people_in_room = [self.get_people_in_room(room, timeline[time_slot]) for room in range(self.NUMBER_OF_ROOMS)]
363
+ counts_in_room = [len(x) for x in people_in_room]
364
+ prime_suspects = []
365
+
366
+ for room_idx, count in enumerate(counts_in_room):
367
+ if count == 1:
368
+ prime_suspects.append(people_in_room[room_idx][0])
369
+
370
+ if len(prime_suspects) >= 1:
371
+ return random.choice(prime_suspects), idx, prime_suspects, timeline
372
+
373
+ # We did not generate a proper crime, return None for the suspect and None for the crime time
374
+
375
+ return None, None, prime_suspects, timeline
376
+
377
+ def new_game(self):
378
+
379
+ guilty_suspect_idx = None
380
+
381
+ while (guilty_suspect_idx is None):
382
+ guilty_suspect_idx, crime_occurs_time, prime_suspects, timeline = self.generate_new_game()
383
+
384
+ self.current_suspect = None # No suspect selected
385
+
386
+ self.guilty_suspect_idx = guilty_suspect_idx # Index of guilty suspect
387
+ self.guilty_suspect = self.get_suspect_name(guilty_suspect_idx) # Name of guilty suspect
388
+
389
+ self.timeline = timeline # Timeline for the crime scene
390
+ self.prime_suspects = prime_suspects # List of potential suspects (as indices)
391
+ self.have_alibi = self.have_alibis() # List of suspects with alibis (as indices)
392
+ self.the_usual_suspects = self.round_up_the_usual_suspects() # List of names of potential suspects
393
+
394
+ self.power_cut_time_idx = crime_occurs_time - 1 # Index to time when power was cut (occurs just before crime)
395
+ self.crime_occurs_time_idx = crime_occurs_time # Index to time when crime occurred
396
+ self.power_cut_room_idx = self.get_power_off_room_idx() # Index to room where power was cut (occurs just before crime)
397
+ self.crime_occurs_room_idx = self.get_crime_occurs_room_idx() # Index to room where crime occurred
398
+
399
+ self.times[self.power_cut_time_idx] = "Power Outage"
400
+ self.times[self.crime_occurs_time_idx] = "Time of Theft"
401
+
402
+ self.power_cut_time_name = self.times[self.power_cut_time_idx] # Time when power was cut (occurs just before crime)
403
+ self.power_cut_room_name = self.get_power_off_room_name() # Room where power was cut (occurs just before crime)
404
+ self.crime_occurs_time_name = self.times[self.crime_occurs_time_idx] # Time when crime occurred
405
+ self.crime_occurs_room_name = self.get_crime_occurs_room_name() # Room where crime occurred
406
+
407
+ self.stolen_painting = self.get_random_painting() # Reference to the stolen painting
408
+ self.stolen_painting_img = self.stolen_painting["image"]
409
+ self.stolen_painting_name = self.stolen_painting["name"]
410
+
411
+ self.crime_description = self.get_crime_text() # Description of the crime
412
+ self.crime_recap = "" # A summary of hints and notes from suspects
413
+ self.hints_given = 0 # Count of hints given
414
+ self.hints_recap = [] # Recap of hints given during the gaming session
415
+
416
+
417
+ def print_game(self):
418
+
419
+ print("Crime timeline: \n", self.timeline,"\n")
420
+
421
+ print("Name of current suspect: ", self.current_suspect)
422
+
423
+ print("Index of guilty suspect: ", self.guilty_suspect_idx)
424
+ print("Name of guilty suspect: ", self.guilty_suspect)
425
+
426
+ print("Prime Suspects: ", self.prime_suspects)
427
+ print("The usual suspects: ", self.the_usual_suspects)
428
+ print("Have alibis: ", self.names_of_suspects_with_alibis())
429
+
430
+ ## print("power_cut_time_idx: ", self.power_cut_time_idx)
431
+ ## print("power_cut_room_idx: ", self.power_cut_room_idx)
432
+ ## print("self.crime_occurs_time_idx: ", self.crime_occurs_time_idx)
433
+ ## print("self.crime_occurs_room_idx: ", self.crime_occurs_room_idx)
434
+
435
+ print("Time when power was cut: ", self.power_cut_time_name)
436
+ print("Room where power was cut: ", self.power_cut_room_name)
437
+ print("Time when crime occurred : ", self.crime_occurs_time_name)
438
+ print("Room where crime occurred : ", self.crime_occurs_room_name)
439
+
440
+ print("stolen_painting_img: ", self.stolen_painting_img)
441
+ print("stolen_painting_name: ", self.stolen_painting_name)
442
+
443
+ print("Crime Description: \n", self.crime_description)
444
+ print("Hints Given: ", self.hints_given)
445
+
446
+
447
+ # ===============================================
448
+ # Game Function - Generate Script for Suspect
449
+ # ===============================================
450
+
451
+ def create_interview(self, suspect_name):
452
+
453
+ suspect_idx = self.get_suspect_idx(suspect_name)
454
+ whereabouts = self.get_timeline_for_person(suspect_idx, self.timeline)
455
+ witnesses = [self.get_people_in_room(room, self.timeline[idx]) for idx, room in enumerate(whereabouts)]
456
+
457
+ alibi = {
458
+ "name": self.get_suspect_proper_name(suspect_name),
459
+ "key suspect": suspect_idx in self.prime_suspects,
460
+ "have alibi for time of theft": suspect_idx in self.have_alibi,
461
+ "guilty": suspect_idx == self.guilty_suspect_idx,
462
+ "stole painting": suspect_idx == self.guilty_suspect_idx,
463
+ }
464
+
465
+ timeline = []
466
+
467
+ for idx,room in enumerate(whereabouts):
468
+
469
+ if len(witnesses[idx]) == self.NUMBER_OF_SUSPECTS:
470
+ witnesses_text = "Everyone"
471
+ elif len(witnesses[idx]) == 1:
472
+ witnesses_text = "No one. I was alone"
473
+ else:
474
+ witnesses_text = [self.get_suspect_name(witness) for witness in witnesses[idx] if witness != suspect_idx]
475
+
476
+ timeline_dict = {
477
+ "time": self.times[idx],
478
+ "location": self.get_room_name(room),
479
+ "activity": self.get_random_room_activity(room),
480
+ "witnesses": witnesses_text,
481
+ }
482
+
483
+ timeline.append(timeline_dict)
484
+
485
+ alibi["timeline"] = timeline
486
+
487
+ return str(alibi)
488
+
489
+ # ===============================================
490
+ # Game Destructor
491
+ # ===============================================
492
+
493
+ def __del__(self):
494
+ print("Game Destructor Called")
495
+ return
496
+
497
+ # ===============================================
498
+ # END OF Mystery Game Class Definition
499
+ # ===============================================
500
+
501
+ # END cMystery
502
+
503
+
504
+
505
+ # ===============================================
506
+ # MAIN PROGRAM - Testing Purposes
507
+ # ===============================================
508
+
509
+ if __name__ == '__main__':
510
+
511
+ print("Running game_info as main")
512
+
513
+ # Test game functions
514
+
515
+ #game = cMystery()
516
+ #game.print_game()
517
+ #print("\n",game.create_interview(game.guilty_suspect))
518
+
519
+
520
+
521
+
522
+
523
+
524
+
525
+
526
+
527
+
528
+
529
+
530
+