You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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}")

关键点说明:

  1. asyncio.gather()会同时启动两个wait_for()任务,只要两个都完成(或其中一个超时),就会继续执行后续逻辑,不会阻塞。
  2. check函数确保只接收目标用户的DM消息,避免其他消息干扰。
  3. finally块保证无论游戏过程中发生什么错误(比如超时、输入无效),都会把用户从playing_users集合中移除,不会导致用户一直被锁定。

内容的提问来源于stack exchange,提问作者Nucleo __

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 21:39:08