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. 如果最后只剩下卧底,卧底胜利")