|
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 have no idea where they were." |
|
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"]] = f"you are not sure where they were, but they might have been in the {random.choice(locations)}." |
|
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. |
|
""") |
|
markdown_output = "### List of Locations:\n\n" + "\n".join(f"- {location}" for location in locations) |
|
gr.Markdown("## Game Locations") |
|
gr.Markdown(markdown_output) |
|
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() |
|
|