File size: 31,832 Bytes
91eecdf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db58027
91eecdf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
#!/usr/bin/env python3

# Contest Entry for Nvidia / Langchain's Generative AI Agents Contest
# Submitted on Father's Day 2024
# Dedicated to all fathers and that special relationship with their children - this game is for you

import sys, random
import networkx as nx

# ==========================
# Mystery Game Object
# ==========================

class cMystery():

    # ===============================================
    # Game Constructor
    # ===============================================

    def __init__(self,
                NUMBER_OF_SUSPECTS  = 14, 
                NUMBER_OF_TIMESLOTS = 12,
                NUMBER_OF_ROOMS     = 10,
                NUMBER_OF_PAINTINGS = 13,
                HOURS_TO_SHIFT      = 9,
                CRIME_OCCURS        = 6,
                CRIME_WINDOW        = 4,
                 ):
        
        print("Initializing Mystery Game Object")

        # ===============
        # Game Constants
        # ===============

        self.suspects = {
            
        "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":[],
                                       "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."} ,
        "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":[],
                                       "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."} ,
        "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":[] ,
                                       "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."} ,
        "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":[] ,
                                       "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."} ,
        "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":[] ,
                                       "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."} ,
        "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":[] ,
                                       "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."} ,
        "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":[] ,
                                       "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."} , 
        "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":[] ,
                                       "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?"} , 
        "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":[] ,
                                       "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?"} ,  
        "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":[] ,
                                       "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."} ,
        "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":[] ,
                                       "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."} ,
        "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":[] ,
                                       "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."} ,
        "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":[] ,
                                       "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."} ,
        "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":[] ,      
                                       "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."} ,

        }

        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"]

        self.rooms = [
        {"name": "Dining Area", "activities": ["having a meal", "finishing up", "walking through"]},
        {"name": "Foyer", "activities": ["waiting for a guest", "taking a phone call"]},
        {"name": "Outdoor Pool Deck", "activities": ["swimming in the pool", "lounging on a deck chair", "having a drink"]},
        {"name": "Main Hall", "activities": ["looking at the art", "passing through", "having a drink","lounging on a deck chair"]},
        {"name": "Home Theater", "activities": ["watching a movie", "waiting for the movie to start", "lounging"]},
        {"name": "Library", "activities": ["looking at the art", "reading a book", "checking out a map of the area"]},
        {"name": "Mezzanine", "activities": ["looking at the art", "reading a book", "Exercising"]},
        {"name": "Art Gallery", "activities": ["looking at the art", "sat down and admired the paintings", "exploring"]},
        {"name": "Living Room", "activities": ["looking at the art", "talking with the other guests", "talking with the staff"]},
        {"name": "Grand Ballroom", "activities": ["getting questioned by the police", "don't know why we were summoned", "you summoned me to come here"]}
        ]

        self.paintings = [
        {"name": "Pop Art Poodles", "image": "1.jpg"},
        {"name": "Poodle with a Pearl Earring", "image": "2.jpg"},
        {"name": "Portrait of a Poodle", "image": "3.jpg"},
        {"name": "Starry Poodle", "image": "4.jpg"},
        {"name": "Mona Lisa with Poodle", "image": "5.jpg"},
        {"name": "Queen Elizabeth with Poodle", "image": "6.jpg"},
        {"name": "American Poodle", "image": "7.jpg"},
        {"name": "Whistler's Poodle", "image": "8.jpg"},
        {"name": "Night Poodles", "image": "9.jpg"},
        {"name": "Portrait of a Poodle", "image": "10.jpg"},
        {"name": "The Birth of Poodles", "image": "11.jpg"},
        {"name": "A Park Full of Parisian Poodles", "image": "12.jpg"},        
        ]

        self.sample_questions  = ["Where were you at the time of the crime?",          # Suggested questions for the QnA Chat interface
                                  "Where were you when the power went out?",
                                  "Who was with you at the time of the crime?"]

        # ===============
        # Game Variables
        # ===============

        # Initialization Parameters

        self.NUMBER_OF_SUSPECTS     = NUMBER_OF_SUSPECTS                             
        self.NUMBER_OF_TIMESLOTS    = NUMBER_OF_TIMESLOTS
        self.NUMBER_OF_ROOMS        = NUMBER_OF_ROOMS
        self.NUMBER_OF_PAINTINGS    = NUMBER_OF_PAINTINGS
        self.HOURS_TO_SHIFT         = HOURS_TO_SHIFT
        self.CRIME_OCCURS           = CRIME_OCCURS
        self.CRIME_WINDOW           = CRIME_WINDOW

        self.timeline               = {}                                             # Timeline for the crime scene

        self.current_suspect        = None                                           # Name of current suspect

        self.guilty_suspect_idx     = None                                           # Index of guilty suspect
        self.guilty_suspect         = None                                           # Name of guilty suspect
        self.prime_suspects         = None                                           # List of potential suspects (as indices)
        self.have_alibi             = None                                           # List of suspects with alibis (as indices)
        self.the_usual_suspects     = None                                           # Names of potential suspects

        self.power_cut_time_idx     = None                                           # Index to time when power was cut (occurs just before crime)
        self.power_cut_room_idx     = None                                           # Index to room where power was cut (occurs just before crime)
        self.crime_occurs_time_idx  = None                                           # Index to time when crime occurred 
        self.crime_occurs_room_idx  = None                                           # Index to room where crime occurred 

        self.power_cut_time_name    = None                                           # Time when power was cut (occurs just before crime)
        self.power_cut_room_name    = None                                           # Room where power was cut (occurs just before crime)
        self.crime_occurs_time_name = None                                           # Time when crime occurred 
        self.crime_occurs_room_name = None                                           # Room where crime occurred 

        self.stolen_painting        = None                                           # Reference to the stolen painting
        self.stolen_painting_img    = None 
        self.stolen_painting_name   = None 

        self.crime_description      = ""                                             # Text describing the crime
        self.crime_recap            = ""                                             # Text summarizing the responses from suspects and clues
        self.hints_given            = 0                                              # Number of hints given during the gaming session
        self.hints_recap            = []                                             # Recap of hints given during the gaming session
 
        # Creates a new game by default when an object is created

        self.new_game()

        return
        

    # ===============================================
    # End of Game Constructor
    # ===============================================

    # ===============================================
    # Game Functions - Information Retrieval
    # ===============================================

    def get_current_suspect(self):
        print("Current suspect is: ",self.current_suspect)
        return self.current_suspect

    def set_current_suspect(self, name):
        self.current_suspect = name
        print("Setting current suspect to: ", self.current_suspect)
        return

    def get_suspect(self, name):
        return self.suspects.get(name)

    def get_suspect_idx(self, name):
        suspects    = [suspect for suspect in self.suspects.keys()]
        suspect_idx = suspects.index(name)    
        return suspect_idx

    def get_suspect_name(self, suspect_idx):
        suspects    = [suspect for suspect in self.suspects.keys()]
        return suspects[suspect_idx]

    def get_guilty_suspect_name(self):
        return self.guilty_suspect

    def get_suspect_profile(self, name):
        selected = self.suspects.get(name)
        return selected["profile"]

    def get_suspect_proper_name(self, name):
        selected = self.suspects.get(name)
        return selected["name"]

    def get_suspect_background(self, name):
        selected = self.suspects.get(name)
        return selected["background"]

    def get_suspect_motive(self, name):
        selected = self.suspects.get(name)
        return selected["motive"]

    def get_suspect_images(self):
        suspect_images = [[suspect + ".jpg", suspect]  for suspect in self.suspects.keys()]
        return suspect_images

    def get_room_names(self):
        return [room["name"] for room in self.rooms]

    def get_room_name(self, room_idx):
        rooms = [room["name"] for room in self.rooms]
        return rooms[room_idx]

    def get_room_activities(self):
        return [room["activities"] for room in self.rooms]

    def get_random_room_activity(self, room):
        selected = self.rooms[room]
        return random.choice(selected["activities"])

    def get_random_painting(self):
        return random.choice(self.paintings)

    def get_stolen_painting(self):
        return self.stolen_painting_img

    def get_stolen_painting_name(self):
        return self.stolen_painting_name

    def get_power_off_room_idx(self):
        guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)     
        return guilty_timeline[self.power_cut_time_idx]

    def get_power_off_room_name(self):
        guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)     
        return self.rooms[guilty_timeline[self.power_cut_time_idx]]["name"]

    def get_crime_occurs_room_idx(self):
        guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)     
        return guilty_timeline[self.crime_occurs_time_idx]
        
    def get_crime_occurs_room_name(self):
        guilty_timeline = self.get_timeline_for_person(self.guilty_suspect_idx, self.timeline)     
        return self.rooms[guilty_timeline[self.crime_occurs_time_idx]]["name"]

    def round_up_the_usual_suspects(self):
        return ", ".join([self.get_suspect_name(suspect_idx) for suspect_idx in self.prime_suspects])

    def have_alibis(self):
        all_suspects = [x for x, suspect in enumerate(self.suspects)]
        have_alibis  = list(set(all_suspects).difference(set(self.prime_suspects)))
        return have_alibis

    def names_of_suspects_with_alibis(self):
        return ", ".join([self.get_suspect_name(suspect_idx) for suspect_idx in self.have_alibis()])

    def get_crime_text(self):     
        return f"Mystery Manor was showing its world-renowned collection of its finest poodle-themed art at a gala." + \
               f"\nA painting called ||{self.stolen_painting_name}|| was stolen from the ||{self.crime_occurs_room_name}||.  " + \
               f"Power to the security system was cut off in the ||{self.power_cut_room_name}|| just before the crime.  " + \
               f"Your job is to question the suspects and arrest the thief.  Good luck detective !!" + \
               f"\n ⬥ Select a suspect on the right" + \
               f"\n ⬥ Ask questions in the chat" + \
               f"\n ⬥ Get a hint (if you need it) with the 'Clue' button" + \
               f"\n ⬥ Hit 'Arrest' while questioning the suspect to convict"

    def give_hint(self):

        hints = [
            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",
            f"These suspects have valid alibis for the theft:\n{self.names_of_suspects_with_alibis()}",
            f"At the time of theft, these people were alone:\n{self.round_up_the_usual_suspects()}",
        ]

        if self.hints_given < len(hints):
            hint = hints[self.hints_given]
            self.hints_given += 1
            self.hints_recap += " ⬥ " + hint +"\n"
        else:
            hint = "Sorry no more hints are available"
        
        return hint

    def get_hints_recap(self):
        return "".join(self.hints_recap)

    def add_note_to_recap(self, note, current_suspect):
        if current_suspect is not None:
            self.crime_recap +=  current_suspect + " said the following delineated in square brackets\n"
        self.crime_recap += "[" + note + "]\n\n"
        return note

    def get_crime_recap(self):      
        return self.crime_recap

    def get_sample_questions(self):      
        return self.sample_questions
        

    # ========================================
    # Game Functions - Generate a New Game
    # ========================================

    def get_people_in_room(self, room, suspects_positions):                   # List people in a room
        return [person for person, position in suspects_positions.items() if position == room]

    def get_timeline_for_person(self, suspect_idx, timeline):
        return [suspects_locations[suspect_idx] for timeline, suspects_locations in timeline.items()]                

    def generate_new_game(self):

       # Local variables used to record the crime 

       timeline       = {}     # Log of all suspects and their movements over the day
       prime_suspects = []     # All potential criminals

       # Create a fully connected graph of ROOMS with rooms equal to NUMBER_OF_ROOMS 

       G = nx.Graph()
       G.add_nodes_from(range(self.NUMBER_OF_ROOMS))   

       for i in range(self.NUMBER_OF_ROOMS-1):
          for j in range(self.NUMBER_OF_ROOMS-1):
             G.add_edge(i, j)

       # Everyone starts in the DINING ROOM

       # suspects_positions = {i: random.choice(list(G.rooms)) for i in range(self.NUMBER_OF_SUSPECTS)}       # for random starting rooms for each character
       suspects_positions   = {i: 0 for i in range(self.NUMBER_OF_SUSPECTS)}        
       timeline[0]          = suspects_positions.copy()

       # Helper function that moves people to adjacent rooms

       def move_people(suspects_positions, G):
        
           for person, current_position in suspects_positions.items():
               neighbors = list(nx.neighbors(G, current_position))               # Get adjacent neighbouring rooms
               suspects_positions[person] = random.choice(neighbors)             # Move person to a random room

           return suspects_positions

       # Simulate movement of suspects over NUMBER_OF_TIMESLOTS steps

       for time_slot in range(1, self.NUMBER_OF_TIMESLOTS):
           suspects_positions  = move_people(suspects_positions, G)
           timeline[time_slot] = suspects_positions.copy()

       # All suspects end up in the GRAND BALLROOM 
       
       suspects_positions = {i: (self.NUMBER_OF_ROOMS-1) for i in range(self.NUMBER_OF_SUSPECTS)}
       timeline[self.NUMBER_OF_TIMESLOTS] = suspects_positions.copy()

       # Create a thief by checking the paths of all suspects
       # The thief is someone alone in a room during the crime window
       # defined as the time between CRIME_OCCURS plus CRIME_WINDOW
       # the power off room is where the thief was one step before

       for idx, time_slot in enumerate(timeline):     # Walk through whole day  

          # if we are within the crime window, see who is alone in a room

          if idx in range(self.CRIME_OCCURS,self.CRIME_OCCURS+self.CRIME_WINDOW):

             people_in_room  = [self.get_people_in_room(room, timeline[time_slot]) for room in range(self.NUMBER_OF_ROOMS)]
             counts_in_room  = [len(x) for x in people_in_room]
             prime_suspects  = []

             for room_idx, count in enumerate(counts_in_room):
                if count == 1:
                   prime_suspects.append(people_in_room[room_idx][0])

             if len(prime_suspects) >= 1:
                 return random.choice(prime_suspects), idx, prime_suspects, timeline

       # We did not generate a proper crime, return None for the suspect and None for the crime time

       return None, None, prime_suspects, timeline

    def new_game(self):
        
        guilty_suspect_idx = None
        
        while (guilty_suspect_idx is None):
            guilty_suspect_idx, crime_occurs_time, prime_suspects, timeline = self.generate_new_game()

        self.current_suspect        = None                                           # No suspect selected             

        self.guilty_suspect_idx     = guilty_suspect_idx                             # Index of guilty suspect
        self.guilty_suspect         = self.get_suspect_name(guilty_suspect_idx)      # Name of guilty suspect
        
        self.timeline               = timeline                                       # Timeline for the crime scene 
        self.prime_suspects         = prime_suspects                                 # List of potential suspects (as indices)
        self.have_alibi             = self.have_alibis()                             # List of suspects with alibis (as indices)
        self.the_usual_suspects     = self.round_up_the_usual_suspects()             # List of names of potential suspects

        self.power_cut_time_idx     = crime_occurs_time - 1                          # Index to time when power was cut (occurs just before crime)
        self.crime_occurs_time_idx  = crime_occurs_time                              # Index to time when crime occurred 
        self.power_cut_room_idx     = self.get_power_off_room_idx()                  # Index to room where power was cut (occurs just before crime)
        self.crime_occurs_room_idx  = self.get_crime_occurs_room_idx()               # Index to room where crime occurred

        self.times[self.power_cut_time_idx]     = "Power Outage"  
        self.times[self.crime_occurs_time_idx]  = "Time of Theft"
 
        self.power_cut_time_name    = self.times[self.power_cut_time_idx]            # Time when power was cut (occurs just before crime)
        self.power_cut_room_name    = self.get_power_off_room_name()                 # Room where power was cut (occurs just before crime)
        self.crime_occurs_time_name = self.times[self.crime_occurs_time_idx]         # Time when crime occurred 
        self.crime_occurs_room_name = self.get_crime_occurs_room_name()              # Room where crime occurred 

        self.stolen_painting        = self.get_random_painting()                     # Reference to the stolen painting
        self.stolen_painting_img    = self.stolen_painting["image"]
        self.stolen_painting_name   = self.stolen_painting["name"]

        self.crime_description      = self.get_crime_text()                          # Description of the crime
        self.crime_recap            = ""                                             # A summary of hints and notes from suspects
        self.hints_given            = 0                                              # Count of hints given
        self.hints_recap            = []                                             # Recap of hints given during the gaming session


    def print_game(self):

        print("Crime timeline: \n",           self.timeline,"\n")

        print("Name of current suspect: ",    self.current_suspect)

        print("Index of guilty suspect: ",    self.guilty_suspect_idx)
        print("Name of guilty suspect: ",     self.guilty_suspect)
        
        print("Prime Suspects: ",             self.prime_suspects)
        print("The usual suspects: ",         self.the_usual_suspects)
        print("Have alibis:        ",         self.names_of_suspects_with_alibis())

##        print("power_cut_time_idx: ",         self.power_cut_time_idx)
##        print("power_cut_room_idx: ",         self.power_cut_room_idx)
##        print("self.crime_occurs_time_idx: ", self.crime_occurs_time_idx)
##        print("self.crime_occurs_room_idx: ", self.crime_occurs_room_idx)

        print("Time when power was cut: ",    self.power_cut_time_name)    
        print("Room where power was cut: ",   self.power_cut_room_name)    
        print("Time when crime occurred : ",  self.crime_occurs_time_name)
        print("Room where crime occurred : ", self.crime_occurs_room_name)
        
        print("stolen_painting_img:  ",       self.stolen_painting_img)   
        print("stolen_painting_name: ",       self.stolen_painting_name)   

        print("Crime Description: \n",        self.crime_description)
        print("Hints Given: ",                self.hints_given)
        
        
    # ===============================================
    # Game Function - Generate Script for Suspect
    # ===============================================

    def create_interview(self, suspect_name): 

        suspect_idx = self.get_suspect_idx(suspect_name)                         
        whereabouts = self.get_timeline_for_person(suspect_idx, self.timeline)
        witnesses   = [self.get_people_in_room(room, self.timeline[idx]) for idx, room in enumerate(whereabouts)]

        alibi = {
                "name":           self.get_suspect_proper_name(suspect_name),
                "key suspect":    suspect_idx in self.prime_suspects,
                "have alibi for time of theft":    suspect_idx in self.have_alibi,
                "guilty":         suspect_idx == self.guilty_suspect_idx,
                "stole painting": suspect_idx == self.guilty_suspect_idx,
                 }

        timeline = []

        for idx,room in enumerate(whereabouts):

            if len(witnesses[idx]) == self.NUMBER_OF_SUSPECTS:
                witnesses_text = "Everyone"
            elif len(witnesses[idx]) == 1:
                witnesses_text = "No one. I was alone"
            else:
                witnesses_text = [self.get_suspect_name(witness) for witness in witnesses[idx] if witness != suspect_idx]

            timeline_dict = {
                             "time":      self.times[idx],
                             "location":  self.get_room_name(room),
                             "activity":  self.get_random_room_activity(room),
                             "witnesses": witnesses_text,
                             }
                             
            timeline.append(timeline_dict)

        alibi["timeline"] = timeline        
        
        return str(alibi)

    # ===============================================
    # Game Destructor
    # ===============================================

    def __del__(self):
        print("Game Destructor Called")
        return

    # ===============================================
    # END OF Mystery Game Class Definition
    # ===============================================

# END cMystery    


 
# ===============================================
# MAIN PROGRAM - Testing Purposes
# ===============================================

if __name__ == '__main__':

    print("Running game_info as main")

    # Test game functions

    #game = cMystery()
    #game.print_game()
    #print("\n",game.create_interview(game.guilty_suspect))