2026-01-08 23:41:05 +08:00
|
|
|
|
import streamlit as st
|
|
|
|
|
|
import random
|
2026-01-09 09:18:48 +08:00
|
|
|
|
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")
|
2026-01-08 23:41:05 +08:00
|
|
|
|
|
|
|
|
|
|
# 页面配置
|
|
|
|
|
|
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 = []
|
2026-01-09 09:18:48 +08:00
|
|
|
|
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
|
2026-01-08 23:41:05 +08:00
|
|
|
|
|
2026-01-09 09:18:48 +08:00
|
|
|
|
# 调用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
|
2026-01-08 23:41:05 +08:00
|
|
|
|
|
|
|
|
|
|
# 获取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
|
|
|
|
|
|
|
2026-01-09 09:18:48 +08:00
|
|
|
|
max_attempts = 5
|
2026-01-08 23:41:05 +08:00
|
|
|
|
attempt = 0
|
|
|
|
|
|
|
|
|
|
|
|
while attempt < max_attempts:
|
2026-01-09 09:18:48 +08:00
|
|
|
|
# 构建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 += " 你是卧底,但不要暴露自己的身份。"
|
2026-01-08 23:41:05 +08:00
|
|
|
|
else:
|
2026-01-09 09:18:48 +08:00
|
|
|
|
# 第三轮及以后:详细描述 + 合理怀疑
|
|
|
|
|
|
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 += " 你是卧底,要隐藏自己的身份,同时可以适当引导怀疑其他玩家。"
|
2026-01-08 23:41:05 +08:00
|
|
|
|
|
2026-01-09 09:18:48 +08:00
|
|
|
|
# 调用API生成发言
|
|
|
|
|
|
speech = call_deepseek_api(prompt)
|
2026-01-08 23:41:05 +08:00
|
|
|
|
|
2026-01-09 09:18:48 +08:00
|
|
|
|
# 如果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)}"
|
2026-01-08 23:41:05 +08:00
|
|
|
|
|
2026-01-09 09:18:48 +08:00
|
|
|
|
# 确保发言不直接包含词语本身
|
|
|
|
|
|
if player_word in speech:
|
|
|
|
|
|
# 如果直接说出了词语,尝试替换或修改
|
|
|
|
|
|
speech = speech.replace(player_word, "这个词")
|
2026-01-08 23:41:05 +08:00
|
|
|
|
|
|
|
|
|
|
# 检查发言是否唯一
|
|
|
|
|
|
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}")
|
|
|
|
|
|
|
2026-01-09 09:18:48 +08:00
|
|
|
|
# 生成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("请输入第二轮发言内容!")
|
|
|
|
|
|
|
2026-01-08 23:41:05 +08:00
|
|
|
|
# 投票输入(只能在平票玩家中选择)
|
|
|
|
|
|
vote_name = st.selectbox(
|
|
|
|
|
|
"选择要投票的玩家",
|
|
|
|
|
|
tied_player_names
|
|
|
|
|
|
)
|
|
|
|
|
|
# 获取对应的玩家编号
|
|
|
|
|
|
vote = tied_players[tied_player_names.index(vote_name)]
|
|
|
|
|
|
|
|
|
|
|
|
# 提交第二轮投票
|
|
|
|
|
|
if st.button("提交第二轮投票"):
|
2026-01-09 09:18:48 +08:00
|
|
|
|
# 检查用户是否已完成第二轮发言
|
|
|
|
|
|
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
|
2026-01-08 23:41:05 +08:00
|
|
|
|
|
|
|
|
|
|
# 模拟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:
|
2026-01-09 09:18:48 +08:00
|
|
|
|
# 检查是否超过最大轮数
|
|
|
|
|
|
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
|
2026-01-08 23:41:05 +08:00
|
|
|
|
|
2026-01-09 09:18:48 +08:00
|
|
|
|
# 重置 runoff 状态和第二轮发言相关变量
|
2026-01-08 23:41:05 +08:00
|
|
|
|
st.session_state.runoff = False
|
|
|
|
|
|
st.session_state.tied_players = []
|
2026-01-09 09:18:48 +08:00
|
|
|
|
st.session_state.runoff_speeches = []
|
|
|
|
|
|
st.session_state.player_runoff_speech = ""
|
|
|
|
|
|
st.session_state.player_runoff_speech_saved = False
|
2026-01-08 23:41:05 +08:00
|
|
|
|
|
|
|
|
|
|
# 等待用户确认后再刷新
|
|
|
|
|
|
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
|
2026-01-09 09:18:48 +08:00
|
|
|
|
st.session_state.runoff_speeches = []
|
|
|
|
|
|
st.session_state.player_runoff_speech = ""
|
|
|
|
|
|
st.session_state.player_runoff_speech_saved = False
|
2026-01-08 23:41:05 +08:00
|
|
|
|
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
# 游戏规则
|
|
|
|
|
|
with st.sidebar:
|
|
|
|
|
|
st.header("游戏规则")
|
|
|
|
|
|
st.write("1. 每轮游戏会分配词语,大部分玩家是平民,拿到相同的词语")
|
|
|
|
|
|
st.write("2. 有一名玩家是卧底,拿到与平民不同但相似的词语")
|
|
|
|
|
|
st.write("3. 玩家通过讨论判断谁是卧底")
|
|
|
|
|
|
st.write("4. 每轮结束后进行投票,得票最多的玩家被淘汰")
|
|
|
|
|
|
st.write("5. 如果淘汰的是卧底,平民胜利")
|
2026-01-09 09:18:48 +08:00
|
|
|
|
st.write("6. 如果最后只剩下卧底,卧底胜利")
|
|
|
|
|
|
st.write("7. 游戏最多进行4轮投票,超过次数则游戏结束")
|
|
|
|
|
|
|