File size: 20,570 Bytes
b1e12f0
 
 
 
 
 
 
 
 
 
 
 
 
 
dcd204e
b1e12f0
 
 
 
 
 
 
24b9746
dcd204e
 
b1e12f0
 
82972eb
 
 
 
 
 
 
 
 
 
 
 
b1e12f0
 
82972eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24b9746
 
b1e12f0
 
 
 
 
 
dcd204e
5b8571f
 
 
 
d86774a
 
b1e12f0
 
 
24b9746
b1e12f0
 
 
 
 
dcd204e
5b8571f
 
 
 
d86774a
24b9746
5b8571f
 
82972eb
 
 
24b9746
 
82972eb
24b9746
 
 
5b8571f
82972eb
24b9746
6491daf
5b8571f
d86774a
 
 
24b9746
 
82972eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24b9746
82972eb
 
24b9746
5b8571f
 
 
 
d86774a
 
6491daf
24b9746
6491daf
d86774a
 
 
 
24b9746
 
d86774a
82972eb
 
24b9746
82972eb
b1e12f0
5b8571f
24b9746
 
 
 
 
b1e12f0
6491daf
 
 
 
 
82972eb
 
6491daf
82972eb
 
 
 
 
 
 
6491daf
 
4cf1475
 
 
 
 
 
 
b1e12f0
24b9746
5b8571f
d86774a
5b8571f
 
 
 
 
 
 
 
 
 
 
82972eb
 
 
d86774a
24b9746
6491daf
 
82972eb
 
d86774a
24b9746
6491daf
 
d86774a
82972eb
24b9746
 
d86774a
 
b1e12f0
 
 
 
 
 
 
aac7848
 
 
5b8571f
aac7848
 
 
c7203de
b1e12f0
 
dcd204e
 
b1e12f0
5b8571f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dcd204e
b1e12f0
 
 
 
c7203de
 
 
 
aac7848
 
 
 
c7203de
aac7848
 
b1e12f0
5b8571f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d86774a
5b8571f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4cf1475
 
 
 
 
5b8571f
b1e12f0
 
 
 
24b9746
 
 
b1e12f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dcd204e
b1e12f0
5b8571f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c7203de
 
b1e12f0
 
 
c7203de
b1e12f0
24b9746
 
 
 
 
 
 
 
 
 
5b8571f
24b9746
 
 
5b8571f
d86774a
5b8571f
d86774a
24b9746
 
 
 
b1e12f0
 
c7203de
b1e12f0
 
c7203de
24b9746
 
 
 
c7203de
24b9746
 
 
c7203de
c84a6e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b1e12f0
c7203de
 
24b9746
c7203de
b1e12f0
c7203de
 
b1e12f0
 
 
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
import random
import gradio as gr
from huggingface_hub import InferenceClient

model_name = "Qwen/Qwen2.5-72B-Instruct"
client = InferenceClient(model_name)

def llm_inference(messages):
    eos_token = "<|endoftext|>"
    output = client.chat.completions.create(
        messages=messages,
        stream=False,
        temperature=0.7,
        top_p=0.1,
        max_tokens=512,
        stop=[eos_token]
    )
    response = ''
    for choice in output.choices:
        response += choice['message']['content']
    return response

suspect_names = ["Colonel Mustard", "Miss Scarlett", "Professor Plum", "Mrs. Peacock", "Mr. Green", "Dr. Orchid"]
weapons = ["Candlestick", "Dagger", "Lead Pipe", "Revolver", "Rope", "Wrench", "Poison"]
locations = ["Kitchen", "Ballroom", "Conservatory", "Dining Room", "Library", "Study", "Billiard Room", "Lounge"]

possible_personalities = [
    {"description": "stern and suspicious", "hot_headed": False},
    {"description": "charming but evasive", "hot_headed": False},
    {"description": "intellectual and nervous", "hot_headed": False},
    {"description": "gracious but secretive", "hot_headed": False},
    {"description": "amiable yet deceptive", "hot_headed": False},
    {"description": "hot-headed and impulsive", "hot_headed": True},
    {"description": "calm and collected", "hot_headed": False},
    {"description": "mysterious and aloof", "hot_headed": False},
    {"description": "jovial but cunning", "hot_headed": False},
    {"description": "nervous and jittery", "hot_headed": False},
    {"description": "sarcastic and witty", "hot_headed": False},
    {"description": "arrogant and dismissive", "hot_headed": True}
]

anger_level_descriptions = {
    0: "calm",
    1: "slightly annoyed",
    2: "annoyed",
    3: "frustrated",
    4: "angry",
    5: "very angry",
    6: "furious"
}

def get_anger_description(anger_level):
    if anger_level <= 1:
        return "calm"
    elif anger_level == 2:
        return "slightly annoyed"
    elif anger_level == 3:
        return "annoyed"
    elif anger_level == 4:
        return "frustrated"
    elif anger_level == 5:
        return "angry"
    elif anger_level == 6:
        return "very angry"
    else:
        return "furious"

def get_trust_description(trust_level):
    if trust_level <= 2:
        return "distrustful"
    elif trust_level <= 4:
        return "untrusting"
    elif trust_level == 5:
        return "neutral"
    elif trust_level <= 7:
        return "trusting"
    elif trust_level <= 9:
        return "very trusting"
    else:
        return "completely trusting"

suspects = {}

game_state = {
    "murderer": "",
    "weapon": "",
    "location": "",
    "is_game_over": False,
    "clues": [],
    "history": [],
    "accused": False,
    "turns": 0,
    "searched_locations": [],
    "eavesdropped": False,
    "bluffed": False,
    "player_questions": []
}

def initialize_game():
    game_state["murderer"] = random.choice(suspect_names)
    game_state["weapon"] = random.choice(weapons)
    game_state["location"] = random.choice(locations)
    game_state["is_game_over"] = False
    game_state["clues"] = []
    game_state["history"] = []
    game_state["accused"] = False
    game_state["turns"] = 0
    game_state["searched_locations"] = []
    game_state["eavesdropped"] = False
    game_state["bluffed"] = False
    game_state["player_questions"] = []
    random.shuffle(possible_personalities)
    motives = ["inheritance", "jealousy", "revenge", "secret affair", "business rivalry", "blackmail"]
    relationships = ["friends", "colleagues", "family", "rivals", "acquaintances"]
    non_murderer_names = [name for name in suspect_names if name != game_state["murderer"]]
    non_murderer_alibi_locations = random.sample(locations, len(non_murderer_names))
    for i, suspect_name in enumerate(non_murderer_names):
        suspect = {
            "name": suspect_name,
            "is_murderer": False,
            "personality": possible_personalities[i % len(possible_personalities)]["description"],
            "hot_headed": possible_personalities[i % len(possible_personalities)]["hot_headed"],
            "anger_level": 0,
            "trust_level": 5,
            "alibi_location": non_murderer_alibi_locations[i],
            "alibi_with": [],
            "knowledge": {},
            "motive": random.choice(motives),
            "relationships": {},
            "backstory": "",
            "suspects": None
        }
        suspects[suspect_name] = suspect
    murderer_alibi_location = random.choice(non_murderer_alibi_locations)
    murderer = {
        "name": game_state["murderer"],
        "is_murderer": True,
        "personality": possible_personalities[len(non_murderer_names) % len(possible_personalities)]["description"],
        "hot_headed": possible_personalities[len(non_murderer_names) % len(possible_personalities)]["hot_headed"],
        "anger_level": 0,
        "trust_level": 5,
        "alibi_location": murderer_alibi_location,
        "alibi_with": [],
        "knowledge": {},
        "motive": random.choice(motives),
        "relationships": {},
        "backstory": "",
        "suspects": None
    }
    suspects[game_state["murderer"]] = murderer
    for suspect in suspects.values():
        others_in_same_location = [s["name"] for s in suspects.values()
                                   if s["alibi_location"] == suspect["alibi_location"] and s["name"] != suspect["name"]]
        suspect["alibi_with"] = others_in_same_location
    for suspect in suspects.values():
        other_suspects = [s["name"] for s in suspects.values() if s["name"] != suspect["name"]]
        for other in other_suspects:
            suspect["relationships"][other] = random.choice(relationships)
        suspect["suspects"] = random.choice([s for s in other_suspects if s not in suspect["alibi_with"]])
        suspect["backstory"] = generate_backstory(suspect)
        suspect["knowledge"]["others_locations"] = generate_others_locations_knowledge(suspect)
    for suspect in suspects.values():
        suspect["knowledge"]["self"] = generate_knowledge(suspect)

def generate_backstory(suspect):
    backstory = f"{suspect['name']} has a backstory involving {suspect['motive']}."
    return backstory

def generate_knowledge(suspect):
    details = f"You have a motive of {suspect['motive']}. {suspect['backstory']}"
    relationship_details = ". ".join([f"You are {relation} with {other}"
                                      for other, relation in suspect["relationships"].items()])
    if suspect["is_murderer"]:
        knowledge = f"You are the murderer. You claim you were in the {suspect['alibi_location']} during the murder, but you were actually committing the crime in the {game_state['location']}. You need to lie about your alibi and deflect suspicion. {details} {relationship_details}"
    else:
        knowledge = f"You were in the {suspect['alibi_location']} with {', '.join(suspect['alibi_with'])} during the murder. {details} {relationship_details}"
        if random.choice([True, False]):
            knowledge += f" You know that the weapon is the {game_state['weapon']}."
        if random.choice([True, False]):
            knowledge += f" You know that the location of the murder was the {game_state['location']}."
    return knowledge

def generate_others_locations_knowledge(suspect):
    knowledge = {}
    for other in suspects.values():
        if other["name"] == suspect["name"]:
            continue
        elif other["name"] == game_state["murderer"]:
            knowledge[other["name"]] = "You know that they were not in your location."
        else:
            chance = random.random()
            if chance < 0.5:
                knowledge[other["name"]] = f"was in the {other['alibi_location']}."
            elif chance < 0.75:
                knowledge[other["name"]] = "you are not sure where they were."
            else:
                knowledge[other["name"]] = "you have no idea where they were."
    return knowledge

def analyze_tone(player_input):
    accusatory_words = ["did you", "murderer", "kill", "guilty", "crime", "weapon", "suspect"]
    if any(word in player_input.lower() for word in accusatory_words):
        return "accusatory"
    else:
        return "neutral"

def get_ai_response(suspect_name, player_input):
    suspect = suspects[suspect_name]
    game_state["turns"] += 1
    game_state["player_questions"].append(player_input)
    tone = analyze_tone(player_input)
    if "accuse" in player_input.lower() or any(word in player_input.lower() for word in ["murderer", "kill", "guilty"]):
        suspect["trust_level"] -= 1
    else:
        suspect["trust_level"] += 1
    suspect["trust_level"] = max(0, min(10, suspect["trust_level"]))
    if tone == "accusatory":
        suspect["anger_level"] += 2
        suspect["trust_level"] -= 1
    else:
        suspect["anger_level"] += 1
    if suspect["anger_level"] > 6:
        suspect["anger_level"] = 6
    if suspect["anger_level"] >= 3 and suspect["hot_headed"]:
        suspect["personality"] = "agitated and defensive"
    personality = suspect["personality"]
    knowledge = suspect["knowledge"]["self"]
    others_knowledge = suspect["knowledge"]["others_locations"]
    anger_description = get_anger_description(suspect["anger_level"])
    trust_description = get_trust_description(suspect["trust_level"])
    previous_questions = " ".join(game_state["player_questions"][-3:])
    system_prompt = f"You are {suspect_name}, who is {personality}. {knowledge}"
    for other_name, info in others_knowledge.items():
        system_prompt += f" You know that {other_name} {info}"
    system_prompt += f" You suspect that {suspect['suspects']} might be involved due to {suspect['motive']}."
    system_prompt += f" Your current emotional state is {anger_description}, and your level of trust towards the detective is {trust_description}."
    if suspect["is_murderer"]:
        system_prompt += " You are the murderer and will lie to protect yourself."
    system_prompt += f" Previously, the detective asked: {previous_questions}"
    user_message = f"The detective asks: \"{player_input}\" As {suspect_name}, respond in first person, staying in character. Provide a detailed response, and consider any previous interactions. If you lie, include subtle hints like hesitations or contradictions."
    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_message}
    ]
    response = llm_inference(messages)
    return response.strip()

def get_group_response(suspect_names_list, player_input):
    responses = []
    for suspect_name in suspect_names_list:
        response = get_ai_response(suspect_name, player_input)
        responses.append(f"**{suspect_name}:** {response.strip()}")
    return "\n\n".join(responses)

def process_input(player_input, selected_suspects):
    if game_state["is_game_over"]:
        return "The game is over. Please restart to play again.", game_state["history"]
    if game_state["accused"]:
        return "You have already made an accusation. Please restart to play again.", game_state["history"]
    game_state["history"].append(("Detective", player_input))
    if player_input.lower().startswith("search"):
        location = player_input[7:].strip()
        result = search_location(location)
        game_state["history"].append(("System", result))
        return result, game_state["history"]
    elif player_input.lower().startswith("eavesdrop"):
        result = eavesdrop()
        game_state["history"].append(("System", result))
        return result, game_state["history"]
    elif player_input.lower().startswith("bluff"):
        result = bluff(player_input)
        game_state["history"].append(("System", result))
        return result, game_state["history"]
    elif player_input.lower().startswith("analyze"):
        result = analyze_response()
        game_state["history"].append(("System", result))
        return result, game_state["history"]
    elif player_input.lower().startswith("reveal"):
        result = endgame_reveal(player_input)
        game_state["history"].append(("System", result))
        return result, game_state["history"]
    elif "accuse" in player_input.lower():
        game_state["accused"] = True
        result = handle_accusation(player_input)
        game_state["history"].append(("System", result))
        return result, game_state["history"]
    else:
        if not selected_suspects:
            return "Please select at least one suspect to question.", game_state["history"]
        elif len(selected_suspects) == 1:
            suspect_name = selected_suspects[0]
            ai_response = get_ai_response(suspect_name, player_input)
            game_state["history"].append((suspect_name, ai_response))
            return ai_response, game_state["history"]
        else:
            ai_response = get_group_response(selected_suspects, player_input)
            game_state["history"].append(("Group", ai_response))
            return ai_response, game_state["history"]

def search_location(location):
    if location not in locations:
        return "That location does not exist."
    if location in game_state["searched_locations"]:
        return "You have already searched this location."
    game_state["searched_locations"].append(location)
    game_state["turns"] += 1
    if location == game_state["location"]:
        found_weapon = f"You found the {game_state['weapon']}!"
        return f"You search the {location} and {found_weapon}"
    else:
        return f"You search the {location} but find nothing of interest."

def eavesdrop():
    if game_state["eavesdropped"]:
        return "You have already eavesdropped once."
    game_state["eavesdropped"] = True
    game_state["turns"] += 1
    suspect1, suspect2 = random.sample(suspect_names, 2)
    conversation = f"{suspect1} whispers to {suspect2}: 'I think {suspects[suspect1]['suspects']} might be involved. They had a motive because of {suspects[suspects[suspect1]['suspects']]['motive']}.'"
    return f"You overhear a conversation: {conversation}"

def bluff(player_input):
    game_state["bluffed"] = True
    game_state["turns"] += 1
    mentioned_suspects = [suspect_name for suspect_name in suspect_names if suspect_name.lower() in player_input.lower()]
    if not mentioned_suspects:
        return "You need to specify a suspect to bluff."
    suspect_name = mentioned_suspects[0]
    suspect = suspects[suspect_name]
    suspect["trust_level"] -= 2
    if suspect["trust_level"] <= 3:
        return f"{suspect_name} doesn't believe your bluff and now trusts you even less."
    else:
        response = f"{suspect_name} seems unsettled by your claim and might reveal more information."
        return response

def analyze_response():
    game_state["turns"] += 1
    success = random.choice([True, False])
    if success:
        return "You successfully detect that the suspect is lying!"
    else:
        return "You fail to detect any lies."

def handle_accusation(player_input):
    suspect_guess = None
    weapon_guess = None
    location_guess = None
    for suspect_name in suspect_names:
        if suspect_name.lower() in player_input.lower():
            suspect_guess = suspect_name
            break
    for weapon in weapons:
        if weapon.lower() in player_input.lower():
            weapon_guess = weapon
            break
    for location in locations:
        if location.lower() in player_input.lower():
            location_guess = location
            break
    if (suspect_guess == game_state["murderer"] and
        weapon_guess == game_state["weapon"] and
        location_guess == game_state["location"]):
        game_state["is_game_over"] = True
        return f"Congratulations! You solved the case. It was {suspect_guess} with the {weapon_guess} in the {location_guess}."
    else:
        game_state["is_game_over"] = True
        return f"Incorrect accusation! You lose. The real murderer was {game_state['murderer']} with the {game_state['weapon']} in the {game_state['location']}."

def endgame_reveal(player_input):
    game_state["turns"] += 1
    correctness = 0
    if game_state["murderer"].lower() in player_input.lower():
        correctness += 1
    if game_state["weapon"].lower() in player_input.lower():
        correctness += 1
    if game_state["location"].lower() in player_input.lower():
        correctness += 1
    if correctness == 3:
        game_state["is_game_over"] = True
        return "Your deductions are spot on! The murderer confesses and the case is closed."
    else:
        game_state["is_game_over"] = True
        return "Your deductions have inaccuracies. The murderer denies your claims and escapes justice."

def chat_interface(player_input, selected_suspects):
    response, history = process_input(player_input, selected_suspects)
    chat_history = ""
    for speaker, text in history:
        chat_history += f"**{speaker}:** {text}\n\n"
    return chat_history

def get_debug_info():
    debug_info = ""
    debug_info += f"Murderer: {game_state['murderer']}\n"
    debug_info += f"Weapon: {game_state['weapon']}\n"
    debug_info += f"Location: {game_state['location']}\n\n"
    for suspect in suspects.values():
        info = f"Name: {suspect['name']}\n"
        info += f"Personality: {suspect['personality']}\n"
        info += f"Hot-headed: {suspect['hot_headed']}\n"
        info += f"Anger Level: {suspect['anger_level']}\n"
        info += f"Trust Level: {suspect['trust_level']}\n"
        info += f"Is Murderer: {suspect['is_murderer']}\n"
        info += f"Alibi Location: {suspect['alibi_location']}\n"
        info += f"Alibi With: {', '.join(suspect['alibi_with'])}\n"
        info += f"Motive: {suspect['motive']}\n"
        info += f"Suspects: {suspect['suspects']}\n"
        info += f"Relationships: {suspect['relationships']}\n"
        info += f"Backstory: {suspect['backstory']}\n"
        info += f"Knowledge: {suspect['knowledge']}\n\n"
        debug_info += info
    return debug_info

def restart_game():
    initialize_game()
    return "", "", []

with gr.Blocks() as demo:
    gr.Markdown("# Cluedo")
    with gr.Tabs():
        with gr.TabItem("Game"):
            with gr.Row():
                with gr.Column():
                    suspect_selection = gr.CheckboxGroup(choices=suspect_names, label="Select Suspects to Question")
                    player_input = gr.Textbox(lines=1, label="Your Input")
                    send_button = gr.Button("Send")
                    restart_button = gr.Button("Restart Game")
                    chatbox = gr.Textbox(lines=20, label="Chat History", interactive=False)
        with gr.TabItem("Instructions"):
            gr.Markdown("## How to Play")
            gr.Markdown("""
- **Ask a Question**: Type your question in the input box.
- **Select Suspects**: Choose one or more suspects to question by selecting them.
    - If you select multiple suspects, they will be questioned as a group.
- **Other Actions**:
    - **Search a Location**: Type `"Search [Location]"` to search a room for clues.
    - **Eavesdrop on Suspects**: Type `"Eavesdrop"` to overhear conversations.
    - **Bluff a Suspect**: Type `"Bluff [Suspect]"` to try and trick a suspect into revealing information.
    - **Analyze a Response**: Type `"Analyze"` to attempt to detect lies.
    - **Reveal Your Deductions**: Type `"Reveal: [Your deductions]"` to present your findings before accusing.
    - **Make an Accusation**: Include the word `"accuse"` in your statement to accuse a suspect.
- **Note**: Be careful with your actions; they may affect suspects' trust and anger levels.
""")
        with gr.TabItem("Debug"):
            debug_info = gr.Textbox(lines=30, label="Debug Information", interactive=False)
            update_debug_button = gr.Button("Update Debug Info")
            update_debug_button.click(fn=lambda: get_debug_info(), inputs=None, outputs=debug_info)

    def send_message(player_input, selected_suspects):
        chat_history = chat_interface(player_input, selected_suspects)
        debug_information = get_debug_info()
        return chat_history, "", [], debug_information

    send_button.click(send_message, inputs=[player_input, suspect_selection], outputs=[chatbox, player_input, suspect_selection, debug_info])
    restart_button.click(fn=restart_game, inputs=None, outputs=[chatbox, player_input, suspect_selection, debug_info])

initialize_game()
demo.launch()