progect106-1/jsinvn.py

619 lines
28 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import streamlit as st
import random
import os
import requests
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 获取Deepseek API配置
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com")
# 页面配置
st.set_page_config(
page_title="谁是卧底",
page_icon="🕵️",
layout="centered"
)
# 动画片角色名字列表
cartoon_characters = [
"熊大", "熊二", "光头强", "开心超人", "樱桃小丸子", "蜡笔小新",
"黑猫警长", "喜羊羊", "美羊羊", "懒羊羊", "沸羊羊", "暖羊羊",
"灰太狼", "红太狼", "孙悟空", "猪八戒", "沙僧", "唐僧",
"哪吒", "敖丙", "姜子牙", "葫芦娃", "爷爷", "蛇精",
"蝎子精", "大头儿子", "小头爸爸", "围裙妈妈", "大耳朵图图", "图图妈妈"
]
# 初始化会话状态
if "game_state" not in st.session_state:
st.session_state.game_state = "setup"
if "num_players" not in st.session_state:
st.session_state.num_players = 4
if "word_pairs" not in st.session_state:
st.session_state.word_pairs = [
("苹果", "橘子"),
("电脑", "手机"),
("篮球", "足球"),
("牛奶", "豆浆"),
("米饭", "面条"),
("", ""),
("夏天", "冬天"),
("可乐", "雪碧"),
("手机", "平板"),
("老师", "学生")
]
if "selected_pair" not in st.session_state:
st.session_state.selected_pair = st.session_state.word_pairs[0]
if "player_words" not in st.session_state:
st.session_state.player_words = []
if "spy_index" not in st.session_state:
st.session_state.spy_index = -1
if "current_round" not in st.session_state:
st.session_state.current_round = 1
if "eliminated_players" not in st.session_state:
st.session_state.eliminated_players = []
if "votes" not in st.session_state:
st.session_state.votes = {}
if "game_result" not in st.session_state:
st.session_state.game_result = ""
if "speeches" not in st.session_state:
st.session_state.speeches = []
if "player_speech" not in st.session_state:
st.session_state.player_speech = ""
if "player_speech_saved" not in st.session_state:
st.session_state.player_speech_saved = False
if "used_speeches" not in st.session_state:
st.session_state.used_speeches = {}
if "player_names" not in st.session_state:
st.session_state.player_names = []
if "runoff" not in st.session_state:
st.session_state.runoff = False
if "tied_players" not in st.session_state:
st.session_state.tied_players = []
if "max_rounds" not in st.session_state:
st.session_state.max_rounds = 4
if "runoff_speeches" not in st.session_state:
st.session_state.runoff_speeches = []
if "player_runoff_speech" not in st.session_state:
st.session_state.player_runoff_speech = ""
if "player_runoff_speech_saved" not in st.session_state:
st.session_state.player_runoff_speech_saved = False
# 调用Deepseek API生成AI发言
def call_deepseek_api(prompt):
try:
url = f"{DEEPSEEK_BASE_URL}/chat/completions"
headers = {
"Authorization": f"Bearer {DEEPSEEK_API_KEY}",
"Content-Type": "application/json"
}
data = {
"model": "deepseek-chat",
"messages": [
{
"role": "system",
"content": "你是一个谁是卧底游戏的玩家,需要根据你拿到的词语生成自然的发言,不能直接说出词语本身,发言要符合当前轮数的特点。"
},
{
"role": "user",
"content": prompt
}
],
"max_tokens": 100,
"temperature": 0.8
}
response = requests.post(url, headers=headers, json=data, timeout=10)
response.raise_for_status()
result = response.json()
return result["choices"][0]["message"]["content"].strip()
except Exception as e:
st.error(f"API调用失败: {str(e)}")
return None
# 获取AI发言
def get_ai_speech(player_index, round_num, used_speeches):
is_spy = player_index == st.session_state.spy_index
player_word = st.session_state.player_words[player_index]
num_players = st.session_state.num_players
max_attempts = 5
attempt = 0
while attempt < max_attempts:
# 构建API提示词
if round_num == 1:
# 第一轮:基础描述,不要太详细
prompt = f"你在玩谁是卧底游戏,你拿到的词语是 '{player_word}'。请生成第一轮的发言,只做基础描述,不要直接说出词语,发言要自然简洁。"
if is_spy:
prompt += " 你是卧底,但不要暴露自己的身份。"
elif round_num == 2:
# 第二轮:更详细的描述,加一些分析
prompt = f"你在玩谁是卧底游戏,你拿到的词语是 '{player_word}'。请生成第二轮的发言,在第一轮的基础上增加一些细节分析,不要直接说出词语,发言要自然。"
if is_spy:
prompt += " 你是卧底,但不要暴露自己的身份。"
else:
# 第三轮及以后:详细描述 + 合理怀疑
suspect_player = random.randint(1, num_players) if num_players > 1 else 1
prompt = f"你在玩谁是卧底游戏,你拿到的词语是 '{player_word}'。请生成第{round_num}轮的发言,包括详细描述和对其他玩家的合理怀疑(比如玩家{suspect_player}),不要直接说出词语,发言要自然。"
if is_spy:
prompt += " 你是卧底,要隐藏自己的身份,同时可以适当引导怀疑其他玩家。"
# 调用API生成发言
speech = call_deepseek_api(prompt)
# 如果API调用失败或返回空使用传统方式生成
if not speech:
# 备用方案:使用传统方式生成发言
if player_word in word_descriptions:
all_descriptions = word_descriptions[player_word]
max_descriptions = min(round_num, len(all_descriptions))
if len(all_descriptions) >= max_descriptions:
selected_descriptions = random.sample(all_descriptions, max_descriptions)
else:
selected_descriptions = all_descriptions[:max_descriptions]
else:
base_descriptions = [
f"这是一个{len(player_word)}字的词语",
"这个词很常见",
"和日常生活相关",
"大家应该都知道"
]
max_descriptions = min(round_num, len(base_descriptions))
selected_descriptions = random.sample(base_descriptions, max_descriptions)
description_part = " ".join(selected_descriptions)
if round_num == 1:
openings = ["我抽到的词", "我的词", "这个词", "我拿到的词", "这个东西"]
speech = f"{random.choice(openings)}{description_part}"
elif round_num == 2:
analysis_phrases = [
"这个东西大家应该都接触过",
"日常生活中经常能看到",
"很多地方都能找到",
"比较常见的一种",
"大家应该都不陌生",
"随处可见的一种"
]
speech = f"{description_part}{random.choice(analysis_phrases)}"
else:
detail_phrases = [
"仔细想想,它还有一些特点",
"其实它还有其他方面",
"从不同角度看,它有更多特征",
"再补充一点关于它的信息",
"进一步说,它还有",
"另外,它还有一个特点"
]
suspicion_phrases = [
f"我发现玩家{random.randint(1, num_players)}的描述好像不太对",
f"玩家{random.randint(1, num_players)}的发言有点可疑",
"对比大家的发言,有些人的描述和我的不太一样",
"根据这些特点,我觉得有人在隐藏什么",
"这些特征应该很明显,不符合的人可能有问题",
"我注意到有些人的描述和这些特征不符"
]
speech = f"{description_part}{random.choice(detail_phrases)}{random.choice(suspicion_phrases)}"
# 确保发言不直接包含词语本身
if player_word in speech:
# 如果直接说出了词语,尝试替换或修改
speech = speech.replace(player_word, "这个词")
# 检查发言是否唯一
if speech not in used_speeches:
return speech
attempt += 1
# 如果尝试多次后仍无法生成唯一发言,返回一个默认发言
return f"{player_word}的相关描述"
# 标题和介绍
st.title("谁是卧底 🕵️")
st.write("一个有趣的多人推理游戏,找出隐藏在平民中的卧底!")
# 游戏设置阶段
if st.session_state.game_state == "setup":
st.header("游戏设置")
# 选择玩家数量
st.session_state.num_players = st.slider(
"玩家数量",
min_value=3,
max_value=10,
value=st.session_state.num_players
)
# 系统自动选择词语对(玩家不可见)
st.session_state.selected_pair = random.choice(st.session_state.word_pairs)
# 开始游戏按钮
if st.button("开始游戏"):
# 分配词语
citizen_word, spy_word = st.session_state.selected_pair
st.session_state.player_words = [citizen_word] * st.session_state.num_players
st.session_state.spy_index = random.randint(0, st.session_state.num_players - 1)
st.session_state.player_words[st.session_state.spy_index] = spy_word
# 分配动画片角色名字,用户默认是村长
if st.session_state.num_players > 1:
# 为AI玩家随机选择角色名字
ai_names = random.sample(cartoon_characters, st.session_state.num_players - 1)
# 用户始终是村长
st.session_state.player_names = ["村长"] + ai_names
else:
# 只有一个玩家时,直接使用村长
st.session_state.player_names = ["村长"]
# 重置游戏状态
st.session_state.current_round = 1
st.session_state.eliminated_players = []
st.session_state.votes = {}
st.session_state.game_result = ""
st.session_state.speeches = []
st.session_state.game_state = "playing"
st.rerun()
# 游戏进行阶段
elif st.session_state.game_state == "playing":
st.header(f"{st.session_state.current_round}")
# 默认用户名字为村长
if "player_id" not in st.session_state:
st.session_state.player_id = 1
player_index = st.session_state.player_id - 1
if player_index not in st.session_state.eliminated_players:
st.subheader("你的词语")
st.info(f"{st.session_state.player_words[player_index]}")
else:
st.error("你已经被淘汰了!")
# 检查是否需要进行第二轮投票runoff
if st.session_state.get('runoff', False):
st.subheader("第二轮投票")
st.write("由于出现平票,需要在平票玩家中进行第二轮投票:")
# 显示平票玩家
tied_players = st.session_state.tied_players
tied_player_names = [st.session_state.player_names[p-1] for p in tied_players]
st.write("平票玩家:")
for name in tied_player_names:
st.write(f"- {name}")
# 生成AI玩家第二轮发言
current_runoff_speeches = []
# 检查是否已经为当前第二轮投票生成了发言
runoff_speeches = st.session_state.runoff_speeches
if not runoff_speeches:
# 为当前第二轮投票创建一个空的已使用发言集合
used_speeches = set()
# 为每个AI玩家生成发言
for i in range(st.session_state.num_players):
if i != player_index and i not in st.session_state.eliminated_players:
# 生成唯一的发言,使用当前轮数+0.5表示第二轮投票
speech = get_ai_speech(i, st.session_state.current_round + 0.5, used_speeches)
# 将生成的发言添加到已使用集合中
used_speeches.add(speech)
current_runoff_speeches.append({
'player': i + 1,
'speech': speech,
'type': 'ai'
})
# 保存当前第二轮投票的发言
st.session_state.runoff_speeches = current_runoff_speeches
else:
# 使用已有的发言
current_runoff_speeches = runoff_speeches
# 显示AI玩家第二轮发言
st.subheader("第二轮发言")
for speech in current_runoff_speeches:
st.write(f"**{st.session_state.player_names[speech['player']-1]}**{speech['speech']}")
# 用户第二轮发言输入
if player_index not in st.session_state.eliminated_players:
st.write(f"\n### 你的第二轮发言({st.session_state.player_names[player_index]}")
st.session_state.player_runoff_speech = st.text_area(
"请输入你的第二轮发言",
value=st.session_state.player_runoff_speech,
height=100,
placeholder="在这里输入你的第二轮发言..."
)
# 保存用户第二轮发言
if st.button("确认第二轮发言"):
if st.session_state.player_runoff_speech.strip():
st.session_state.player_runoff_speech_saved = True
st.success("第二轮发言已保存!")
else:
st.error("请输入第二轮发言内容!")
# 投票输入(只能在平票玩家中选择)
vote_name = st.selectbox(
"选择要投票的玩家",
tied_player_names
)
# 获取对应的玩家编号
vote = tied_players[tied_player_names.index(vote_name)]
# 提交第二轮投票
if st.button("提交第二轮投票"):
# 检查用户是否已完成第二轮发言
if player_index not in st.session_state.eliminated_players and not st.session_state.player_runoff_speech_saved:
st.error("请先完成第二轮发言!")
else:
# 模拟所有玩家的第二轮投票
st.session_state.votes = {}
st.session_state.votes[vote] = 1
# 模拟AI玩家的第二轮投票
for ai_player in [i for i in range(st.session_state.num_players) if i not in st.session_state.eliminated_players]:
if ai_player != player_index:
# AI在平票玩家中随机投票
ai_vote = random.choice(tied_players)
st.session_state.votes[ai_vote] = st.session_state.votes.get(ai_vote, 0) + 1
# 确定最终被淘汰的玩家
max_votes = max(st.session_state.votes.values())
final_tied_players = [p for p, v in st.session_state.votes.items() if v == max_votes]
# 如果仍然平票,随机选择一个
if len(final_tied_players) > 1:
eliminated_player = random.choice(final_tied_players)
else:
eliminated_player = final_tied_players[0]
eliminated_player_index = eliminated_player - 1
eliminated_player_name = st.session_state.player_names[eliminated_player_index]
# 显示第二轮投票结果
st.subheader("第二轮投票结果")
for p, v in st.session_state.votes.items():
st.write(f"{st.session_state.player_names[p-1]}{v}")
st.write(f"\n**{eliminated_player_name} 被淘汰!**")
# 记录被淘汰的玩家
st.session_state.eliminated_players.append(eliminated_player_index)
# 检查游戏是否结束
if eliminated_player_index == st.session_state.spy_index:
st.session_state.game_result = "平民胜利!卧底被找出。"
st.session_state.game_state = "ended"
st.success(st.session_state.game_result)
elif len(st.session_state.eliminated_players) == st.session_state.num_players - 1:
st.session_state.game_result = "卧底胜利!平民全部被淘汰。"
st.session_state.game_state = "ended"
st.success(st.session_state.game_result)
else:
# 检查是否超过最大轮数
if st.session_state.current_round >= st.session_state.max_rounds:
st.session_state.game_result = "游戏结束已达到最大投票次数4次"
st.session_state.game_state = "ended"
st.success(st.session_state.game_result)
else:
# 进入下一轮
st.write("\n游戏继续,进入下一轮!")
st.session_state.current_round += 1
st.session_state.votes = {}
st.session_state.player_speech = ""
st.session_state.player_speech_saved = False
# 重置 runoff 状态和第二轮发言相关变量
st.session_state.runoff = False
st.session_state.tied_players = []
st.session_state.runoff_speeches = []
st.session_state.player_runoff_speech = ""
st.session_state.player_runoff_speech_saved = False
# 等待用户确认后再刷新
if st.button("进入下一轮"):
st.rerun()
else:
# 讨论环节
st.subheader("讨论环节")
# 生成AI玩家发言
current_round_speeches = []
# 检查是否已经为当前轮生成了发言
round_speeches = [s for s in st.session_state.speeches if s.get('round') == st.session_state.current_round]
if not round_speeches:
# 为当前轮创建一个空的已使用发言集合
used_speeches = set()
# 为每个AI玩家生成发言
for i in range(st.session_state.num_players):
if i != player_index and i not in st.session_state.eliminated_players:
# 生成唯一的发言
speech = get_ai_speech(i, st.session_state.current_round, used_speeches)
# 将生成的发言添加到已使用集合中
used_speeches.add(speech)
current_round_speeches.append({
'player': i + 1,
'speech': speech,
'type': 'ai'
})
# 保存当前轮的发言
st.session_state.speeches.append({
'round': st.session_state.current_round,
'speeches': current_round_speeches
})
else:
# 使用已有的发言
current_round_speeches = round_speeches[0]['speeches']
# 显示AI玩家发言
st.write("### 玩家发言")
for speech in current_round_speeches:
st.write(f"**{st.session_state.player_names[speech['player']-1]}**{speech['speech']}")
# 用户发言输入
if player_index not in st.session_state.eliminated_players:
st.write(f"\n### 你的发言({st.session_state.player_names[player_index]}")
st.session_state.player_speech = st.text_area(
"请输入你的发言",
value=st.session_state.player_speech,
height=100,
placeholder="在这里输入你的发言..."
)
# 保存用户发言
if st.button("确认发言"):
if st.session_state.player_speech.strip():
st.session_state.player_speech_saved = True
st.success("发言已保存!")
else:
st.error("请输入发言内容!")
# 投票环节
st.subheader("投票环节")
st.write("请选择你认为是卧底的玩家:")
# 可投票的玩家(使用名字)
available_players = []
available_player_indices = []
for i in range(st.session_state.num_players):
if i not in st.session_state.eliminated_players:
available_players.append(st.session_state.player_names[i])
available_player_indices.append(i+1)
# 投票输入(显示名字,实际存储编号)
if available_players:
vote_name = st.selectbox(
"选择要投票的玩家",
available_players
)
# 获取对应的玩家编号
vote = available_player_indices[available_players.index(vote_name)]
# 提交投票
if st.button("提交投票"):
# 检查用户是否已发言
if player_index not in st.session_state.eliminated_players and not st.session_state.player_speech_saved:
st.error("请先完成发言!")
else:
# 模拟所有玩家的投票
# 1. 记录用户的投票
st.session_state.votes = {}
st.session_state.votes[vote] = 1
# 2. 模拟AI玩家的投票
active_players = [i for i in range(st.session_state.num_players) if i not in st.session_state.eliminated_players]
for ai_player in active_players:
if ai_player != player_index:
# AI投票逻辑优先怀疑发言与自己不同的人或随机投票
possible_votes = [p+1 for p in active_players if p != ai_player]
# 70%概率随机投票30%概率针对可能的卧底
if random.random() < 0.7:
ai_vote = random.choice(possible_votes)
else:
# 优先投给与自己词语不同的人(如果能判断)
if ai_player == st.session_state.spy_index:
# 卧底优先投给平民
civilian_players = [p+1 for p in active_players if p != st.session_state.spy_index]
ai_vote = random.choice(civilian_players) if civilian_players else random.choice(possible_votes)
else:
# 平民随机投票
ai_vote = random.choice(possible_votes)
st.session_state.votes[ai_vote] = st.session_state.votes.get(ai_vote, 0) + 1
# 3. 检查是否存在平票
max_votes = max(st.session_state.votes.values())
tied_players = [player for player, votes in st.session_state.votes.items() if votes == max_votes]
# 4. 显示投票结果公告
st.subheader("投票结果公告")
st.write("本轮投票情况:")
for player_num, count in st.session_state.votes.items():
player_name = st.session_state.player_names[player_num-1]
st.write(f"{player_name}{count}")
# 5. 处理平票情况
if len(tied_players) > 1:
st.write("\n**出现平票!需要进行第二轮投票。**")
st.write("平票玩家:")
for player_num in tied_players:
player_name = st.session_state.player_names[player_num-1]
st.write(f"- {player_name}")
# 进入 runoff 投票状态
st.session_state.runoff = True
st.session_state.tied_players = tied_players
# 等待用户确认后进入 runoff 投票
if st.button("进入第二轮投票"):
st.rerun()
else:
# 没有平票,正常淘汰
eliminated_player = tied_players[0]
eliminated_player_index = eliminated_player - 1
eliminated_player_name = st.session_state.player_names[eliminated_player_index]
st.write(f"\n**{eliminated_player_name} 被淘汰!**")
# 记录被淘汰的玩家
st.session_state.eliminated_players.append(eliminated_player_index)
# 检查游戏是否结束
if eliminated_player_index == st.session_state.spy_index:
st.session_state.game_result = "平民胜利!卧底被找出。"
st.session_state.game_state = "ended"
st.success(st.session_state.game_result)
elif len(st.session_state.eliminated_players) == st.session_state.num_players - 1:
st.session_state.game_result = "卧底胜利!平民全部被淘汰。"
st.session_state.game_state = "ended"
st.success(st.session_state.game_result)
else:
# 进入下一轮
st.write("\n游戏继续,进入下一轮!")
st.session_state.current_round += 1
st.session_state.votes = {}
st.session_state.player_speech = ""
st.session_state.player_speech_saved = False
# 等待用户确认后再刷新
if st.button("进入下一轮"):
st.rerun()
# 游戏结束阶段
elif st.session_state.game_state == "ended":
st.header("游戏结束")
st.success(st.session_state.game_result)
# 显示游戏结果
st.subheader("游戏详情")
# 只显示用户抽到的词语和卧底信息
user_word = st.session_state.player_words[st.session_state.player_id - 1]
st.write(f"你抽到的词语: {user_word}")
st.write(f"卧底是: {st.session_state.player_names[st.session_state.spy_index]}")
# 重新开始按钮
if st.button("重新开始"):
st.session_state.game_state = "setup"
st.session_state.speeches = []
st.session_state.player_speech = ""
st.session_state.player_speech_saved = False
st.session_state.runoff_speeches = []
st.session_state.player_runoff_speech = ""
st.session_state.player_runoff_speech_saved = False
st.rerun()
# 游戏规则
with st.sidebar:
st.header("游戏规则")
st.write("1. 每轮游戏会分配词语,大部分玩家是平民,拿到相同的词语")
st.write("2. 有一名玩家是卧底,拿到与平民不同但相似的词语")
st.write("3. 玩家通过讨论判断谁是卧底")
st.write("4. 每轮结束后进行投票,得票最多的玩家被淘汰")
st.write("5. 如果淘汰的是卧底,平民胜利")
st.write("6. 如果最后只剩下卧底,卧底胜利")
st.write("7. 游戏最多进行4轮投票超过次数则游戏结束")