progect106-1/jsinvn.py

727 lines
28 KiB
Python
Raw Normal View History

2026-01-08 23:41:05 +08:00
import streamlit as st
import random
# 页面配置
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 = []
# 词语描述库
word_descriptions = {
"苹果": [
"这是一种常见的东西",
"有特定的颜色",
"圆形的,口感不错",
"可以直接食用,也可以加工",
"富含营养",
"通常生长在高处",
"有外皮和内部结构",
"是一种健康的选择",
"在中国很常见",
"有些品种味道比较特别"
],
"橘子": [
"这是一种可以食用的东西",
"有特定的颜色,味道有点特别",
"圆形的,有外皮",
"内部结构是一瓣一瓣的",
"富含营养",
"通常生长在温暖的地方",
"需要剥皮后食用",
"有不同的品种",
"汁水很多",
"在某些季节比较常见"
],
"电脑": [
"这是一种电子设备",
"可以用来做很多事情",
"有屏幕和输入设备",
"需要电源才能使用",
"可以连接网络",
"有主机和显示部分",
"可以运行各种程序",
"现在很多家庭都有",
"有不同的类型",
"需要定期维护"
],
"手机": [
"这是一种便携式电子设备",
"可以用来沟通和获取信息",
"触摸操作,便于携带",
"需要充电才能使用",
"可以安装各种应用",
"有拍照功能",
"现在几乎人人都有",
"有不同的品牌和型号",
"可以用来娱乐",
"需要特定卡片才能通话"
],
"篮球": [
"这是一种球类用品",
"圆形的,有特定材质",
"需要用手操作",
"有特定的颜色",
"在特定的场地上使用",
"有不同的规格",
"可以用来进行比赛",
"需要充气才能使用",
"很多年轻人喜欢",
"有专业的赛事"
],
"足球": [
"这是一种球类用品",
"圆形的,有特定的颜色搭配",
"需要用脚操作",
"在特定的场地上使用",
"可以用来进行比赛",
"需要充气才能使用",
"是世界上很受欢迎的活动",
"有不同的规格",
"很多人喜欢观看相关比赛",
"有专业的赛事"
],
"牛奶": [
"这是一种饮品",
"白色的,有营养",
"来自动物",
"可以直接饮用",
"富含蛋白质和钙",
"有不同的品牌",
"可以作为早餐的一部分",
"需要特定条件保存",
"有不同的类型",
"很多人每天都会喝"
],
"豆浆": [
"这是一种饮品",
"乳白色的,有特殊香味",
"用植物制作",
"可以直接饮用",
"富含植物蛋白",
"有不同的口味",
"可以作为早餐的一部分",
"是传统饮品",
"适合特定人群",
"很多早餐店都有"
],
"米饭": [
"这是一种主食",
"白色的,颗粒状",
"用特定食材制作",
"很多人经常吃",
"可以搭配各种食物",
"需要加水烹饪",
"有不同的品种",
"可以有不同的做法",
"吃了会有饱腹感",
"很多地方的人都吃"
],
"面条": [
"这是一种主食",
"长条状的,需要烹饪",
"用特定食材制作",
"可以有各种口味",
"有不同的形状",
"需要水煮",
"可以有不同的做法",
"是传统食物",
"烹饪时间不长",
"很多人喜欢吃"
],
"": [
"这是一种动物",
"毛茸茸的,会发出特定的声音",
"喜欢抓小东西",
"可以作为宠物",
"有不同的品种和颜色",
"喜欢休息",
"比较独立",
"会自己清洁",
"有爪子和尾巴",
"很多人喜欢养"
],
"": [
"这是一种动物",
"毛茸茸的,会发出特定的声音",
"可以作为宠物",
"很忠诚",
"有不同的品种和大小",
"喜欢活动和玩耍",
"需要定期外出",
"可以训练各种技能",
"有爪子和尾巴",
"被很多人视为朋友"
],
"夏天": [
"这是一个季节",
"天气比较热",
"有很多新鲜的东西",
"适合在水上进行活动",
"白天的时间比较长",
"需要穿轻薄的衣服",
"可能会有雷雨天气",
"学生们都有假期",
"适合吃冷的东西",
"阳光比较强烈"
],
"冬天": [
"这是一个季节",
"天气比较冷",
"可能会有白色的东西从天上掉下来",
"需要穿厚厚的衣服",
"白天的时间比较短",
"适合在雪地上活动",
"很多节日在这个时候",
"需要注意保暖",
"水面可能会变硬",
"适合喝热的饮料"
],
"可乐": [
"这是一种饮料",
"有特定的颜色,有气泡",
"味道甜甜的",
"含有咖啡因",
"可以解渴",
"有不同的品牌",
"可以加冰饮用",
"很多人喜欢喝",
"适合搭配食物",
"开瓶后会有气体冒出"
],
"雪碧": [
"这是一种饮料",
"透明的,有气泡",
"味道甜甜的",
"有柠檬味",
"可以解渴",
"有不同的品牌",
"可以加冰饮用",
"很多人喜欢喝",
"适合搭配食物",
"开瓶后会有气体冒出"
],
"手机": [
"这是一种便携式电子设备",
"可以用来沟通和获取信息",
"触摸操作,便于携带",
"需要充电才能使用",
"可以安装各种应用",
"有拍照功能",
"现在几乎人人都有",
"有不同的品牌和型号",
"可以用来娱乐",
"需要特定卡片才能通话"
],
"平板": [
"这是一种便携式电子设备",
"介于两种电子设备之间",
"触摸操作,比手机大",
"需要充电才能使用",
"可以用来娱乐和工作",
"有拍照功能",
"可以安装各种应用",
"适合观看视频",
"比电脑轻便",
"有不同的品牌和型号"
],
"老师": [
"这是一种职业",
"在特定的场所工作",
"教授知识",
"需要准备课程",
"有不同的专业领域",
"需要批改作业",
"对他人有指导作用",
"需要有耐心",
"很多人尊敬",
"可以影响他人的成长"
],
"学生": [
"这是一种身份",
"在特定的场所学习",
"需要上课和完成作业",
"有不同的阶段",
"可以学到各种知识",
"需要参加考试",
"有同学和老师",
"可以参加各种活动",
"是未来的希望",
"很多人都很努力"
]
}
# 获取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 = 100
attempt = 0
while attempt < max_attempts:
# 按轮数递进的描述逻辑
# 每轮选择不同深度的描述
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 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}")
# 投票输入(只能在平票玩家中选择)
vote_name = st.selectbox(
"选择要投票的玩家",
tied_player_names
)
# 获取对应的玩家编号
vote = tied_players[tied_player_names.index(vote_name)]
# 提交第二轮投票
if st.button("提交第二轮投票"):
# 模拟所有玩家的第二轮投票
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:
# 进入下一轮
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 = []
# 等待用户确认后再刷新
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.rerun()
# 游戏规则
with st.sidebar:
st.header("游戏规则")
st.write("1. 每轮游戏会分配词语,大部分玩家是平民,拿到相同的词语")
st.write("2. 有一名玩家是卧底,拿到与平民不同但相似的词语")
st.write("3. 玩家通过讨论判断谁是卧底")
st.write("4. 每轮结束后进行投票,得票最多的玩家被淘汰")
st.write("5. 如果淘汰的是卧底,平民胜利")
st.write("6. 如果最后只剩下卧底,卧底胜利")