progect106-1/jsinvn.py
2026-01-08 23:41:05 +08:00

727 lines
28 KiB
Python
Raw 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
# 页面配置
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. 如果最后只剩下卧底,卧底胜利")