Discord.py Rewrite:双人剪刀石头布命令的用户占用检测及多wait_for()使用问题
解决Discord.py双人剪刀石头布游戏的两个核心问题
我来帮你搞定这两个问题,这在做Discord多人互动命令时是很典型的场景,咱们一步步拆解解决:
一、用户占用检测逻辑实现
要阻止正在游戏的用户被其他人打扰,咱们只需要一个全局的集合来跟踪当前正在游戏中的用户ID。每次触发命令时,先检查发起者和被提及的用户是否在这个集合里,只要有一个在,就直接提示并终止命令执行;游戏开始时把两人加入集合,不管游戏正常结束、超时还是取消,都要确保把他们从集合里移除(用try...finally块来保证这点)。
示例代码:
import discord import asyncio from discord.ext import commands bot = commands.Bot(command_prefix="!", intents=discord.Intents.all()) # 全局集合,存储正在游戏中的用户ID playing_users = set() @bot.command(name="rps") async def rock_paper_scissors(ctx, opponent: discord.Member): # 检查自己或对手是否正在进行游戏 if ctx.author.id in playing_users or opponent.id in playing_users: await ctx.send("你或对方正在进行游戏,请稍后再试!") return # 基础校验:不能和机器人或自己玩 if opponent.bot or opponent == ctx.author: await ctx.send("不能和机器人或者自己玩哦!") return # 将两名用户加入游戏跟踪集合 playing_users.add(ctx.author.id) playing_users.add(opponent.id) try: # 后续游戏逻辑(包含获取输入、判断胜负) # 下面会补全这部分实现 finally: # 无论游戏成功还是失败,都从集合中移除用户 playing_users.discard(ctx.author.id) playing_users.discard(opponent.id)
二、并行使用两个wait_for()获取DM输入
因为要同时等待两名用户在DM里提交选择,不能串行等待(不然先提交的用户要等另一个人,体验很差),所以咱们用asyncio.gather()来并发执行两个wait_for()任务,这样就能同时监听两个人的消息了。
把这部分逻辑补到上面的try块里:
try: # 给两名用户发送DM提示 await ctx.author.send("请输入你的选择:石头/剪刀/布(输入`cancel`可取消游戏)") await opponent.send("请输入你的选择:石头/剪刀/布(输入`cancel`可取消游戏)") # 定义消息过滤函数:只接收两名用户的DM消息 def check(msg): return msg.author.id in [ctx.author.id, opponent.id] and isinstance(msg.channel, discord.DMChannel) # 并行等待两个用户的消息,超时时间设为60秒 user1_msg, user2_msg = await asyncio.gather( bot.wait_for("message", check=check, timeout=60), bot.wait_for("message", check=check, timeout=60) ) except asyncio.TimeoutError: # 处理超时情况 await ctx.send("游戏超时,已自动结束!") await ctx.author.send("游戏超时啦,下次要快点哦~") await opponent.send("游戏超时啦,下次要快点哦~") return # 处理用户输入 choice1 = user1_msg.content.strip().lower() choice2 = user2_msg.content.strip().lower() # 检查是否有用户取消游戏 if choice1 == "cancel" or choice2 == "cancel": await ctx.send("有用户取消了游戏,已结束!") await ctx.author.send("游戏已取消") await opponent.send("游戏已取消") return # 校验输入是否有效 valid_choices = ["石头", "剪刀", "布"] if choice1 not in valid_choices or choice2 not in valid_choices: await ctx.send("有用户输入了无效内容,游戏结束!") await ctx.author.send("你输入的内容无效哦,游戏结束啦") await opponent.send("对方输入了无效内容,游戏结束啦") return # 判断胜负逻辑 win_relations = { "石头": "剪刀", "剪刀": "布", "布": "石头" } if choice1 == choice2: result = "平局!" elif win_relations[choice1] == choice2: result = f"{ctx.author.mention}获胜!" else: result = f"{opponent.mention}获胜!" # 发送结果到服务器和用户DM await ctx.send(f"游戏结果:{ctx.author}选择了**{choice1}**,{opponent}选择了**{choice2}**,{result}") await ctx.author.send(f"游戏结果:你选择了**{choice1}**,{opponent}选择了**{choice2}**,{result}") await opponent.send(f"游戏结果:{ctx.author}选择了**{choice1}**,你选择了**{choice2}**,{result}")
关键点说明:
asyncio.gather()会同时启动两个wait_for()任务,只要两个都完成(或其中一个超时),就会继续执行后续逻辑,不会阻塞。check函数确保只接收目标用户的DM消息,避免其他消息干扰。finally块保证无论游戏过程中发生什么错误(比如超时、输入无效),都会把用户从playing_users集合中移除,不会导致用户一直被锁定。
内容的提问来源于stack exchange,提问作者Nucleo __
相关产品推荐
相关产品推荐

