UNO游戏设计(III):优化/功能牌实现
UNO游戏设计(III):优化/功能牌实现
流程优化
本次解决两个优化:
- 在群里公布时,展示出手牌的数量。
- 轮到某玩家时私发提醒,并提醒该玩家需要应对哪张牌或什么状态。
展示手牌数量
在玩家名称后面以数字提醒即可:
len(game.hands[game.playerids.index(user_id)]) #上家 |
对于上家,因为只有user_id
已知(发送信息时其他变量已经更新到下家了),所以需要先检索到玩家的索引,再找到手牌的数量;
对于下家,current_player_index
即为所求。
此时手牌的数量为已经出过牌的数量!
选择合适的时机保存
- 在宣布胜利之前保存,否则牌局将保存不到最后一步。
轮到某玩家时私发提醒
由于上家出的牌中可能有黑色万能牌,由上家指定颜色。所以上家指定的颜色会将状态更新,因此不如将当前的状态发给该玩家。
await bot.send_private_msg(user_id=game.playerids[game.current_player_index], message=f"轮到你出牌了!当前牌顶是 {game.now_stat[0]}{game.now_stat[1]}") |
测试优化
再玩一局进行测试:


功能牌实现
功能牌有如下几种:
- 功能牌:同样有红、黄、蓝、绿四种颜色,包含“跳过”、“反转”、“+2”三种功能,每种功能牌各有8张。
- 跳过牌:下家无法出牌,轮至下下家出牌。
- 反转牌:游戏顺序反转,由下家(原上家)出牌。
- +2牌:下家摸2张牌,不能出牌,轮至下下家出牌。
首先需要在牌堆里更新这些牌。
for color in colors: |
洗牌前结果如下:
['红0', '红1', '红2', '红3', '红4', '红5', '红6', '红7', '红8', '红9', '红1', '红2', '红3', '红4', '红5', '红6', '红7', '红8', '红9', '黄0', '黄1', '黄2', '黄3', '黄4', '黄5', '黄6', '黄7', '黄8', '黄9', '黄1', '黄2', '黄3', '黄4', '黄5', '黄6', '黄7', '黄8', '黄9', '绿0', '绿1', '绿2', '绿3', '绿4', '绿5', '绿6', '绿7', '绿8', '绿9', '绿1', '绿2', '绿3', '绿4', '绿5', '绿6', '绿7', '绿8', '绿9', '蓝0', '蓝1', '蓝2', '蓝3', '蓝4', '蓝5', '蓝6', '蓝7', '蓝8', '蓝9', '蓝1', '蓝2', '蓝3', '蓝4', '蓝5', '蓝6', '蓝7', '蓝8', '蓝9', '红跳过', '红反转', '红+2', '红跳过', '红反转', '红+2', '黄跳过', '黄反转', '黄+2', '黄跳过', '黄反转', '黄+2', '绿跳过', '绿反转', '绿+2', '绿跳过', '绿反转', '绿+2', '蓝跳过', '蓝反转', '蓝+2', '蓝跳过', '蓝反转', '蓝+2'] |
每张牌各2张,每种功能各8张。
每添加一张牌,需要增改以下内容:
- 出牌条件
- 当前状态逻辑(出一张牌会导致当前状态如何更新,一般只要把颜色和功能分离即可)
- 牌本身的功能
跳过牌
跳过牌会跳过下家。
为了方便检索,令old_index
为被跳过的下家,new_index
为该出牌的下下家。当然,user_id
为本轮出跳过牌的玩家。
# 跳过牌 |
当然,出牌和更新状态也需设置:
# UNO |
此处一并将功能牌全部设置完成。
反转牌
反转牌会反转当前顺序。因为之前已经设置了出牌方向self.direction: int = 1
,且定义了顺时针是1,因此只需取反即可,游戏继续;
需要注意的是,在前面已经切换了下家,因此反转牌需要无视切换。user_id
是出牌的玩家,基于此来寻找下一索引可规避之前对下家的定义。
elif card[1:] == "反转": |
+2牌
此牌较为棘手,不仅要下家摸两张牌,而且如果下家有+2,可以不摸牌而继续出+2,将牌叠加。为此,当下家接收到+2时,可以选择:
- 私聊输入“摸”,即决定摸相应数量的牌;
- 私聊输入“出”,需要出一张+2牌。
这一切的前提是该玩家需要面临被+2的情况。因此需要先设置一个标签self.plus_2
,这个标签的第一位是谁被出了+2,第二位是+2累积到了几。首先,一般情况下,应该是[-1, 0]
。即如果是-1,不需要面临应对+2牌的问题。
出牌处
在出牌处,需要首先将plus_2
设置为下家,并且先叠加两张。在群里公布:下家需要摸几张牌,或者继续叠加。接下来在私聊中告诉下家选择一项。
# +2 牌 |
在这里需要return
,因为不需要后面普通的私聊提醒流程了。
出牌条件处
出牌条件的位置,需要设置一个强优先的判断:如果该玩家是被+2的玩家(不是只需要接+2牌的牌面即可,而是处于被罚牌的状态),那么是不能走常规的出牌流程的。
# 这里加入特殊牌发生效果 |
注意:这里只需要提供一层过滤即可,通过条件判断之后自会在出牌处更改
plus_2
的状态。
更新状态处
更新状态的位置不需要设置,因为牌堆顶就是正常的+2牌。
摸牌处
摸牌处也是强优先判断,摸完牌之后重置plus_2
的值。
# 特殊摸牌流程 |
到这里,三大功能牌以及+2的叠加功能就做完了。但是随着抽牌越来越频繁,牌堆不够的问题越来越显著。所以当因为牌堆牌不够导致无法摸牌时,需要一位成员在群里喊“洗牌”。
洗牌功能
洗牌就是将弃牌堆里除了顶上的一张牌之外的其他牌都加入到牌堆中,再shuffle()
一下。
def reshuffle_deck(self): # 洗牌 |
并且在群聊命令中加入洗牌命令。当然,只有弃牌堆牌不太少时才能洗牌(3张)。
reshuffle_deck = on_command("洗牌") # 洗牌 |
测试功能
接下来测试功能是否完善。
游戏流程为:
- 创建游戏:群聊使用
创建uno房间
命令创建一个新的游戏房间。 - 加入游戏:玩家群聊使用
加入uno房间
命令加入游戏。 - 开始游戏:当有至少两名玩家加入后,群聊使用
一起uno
命令开始游戏。 - 出牌:玩家轮流私聊使用
出 [牌名]
命令出牌。 - 摸牌:私聊使用
摸
命令摸牌。 - 洗牌:当牌堆牌数不够时,群聊使用
洗牌
命令重新洗牌。 结束:当主动放弃时,群聊使用结束uno
命令结束游戏。




继续优化
优化方向有:
- 加入7-0规则
- 加入抢牌规则
- 加入强制出牌规则
- 在创建房间时设置房间参数
- 游戏中途如果放弃,可以切断游戏
- 游戏结束后可以计算分值
- 如果询问状态,可以展示当前的牌局状态,包括:
- 当前回合的玩家
- 牌局顺序以及座次
- 当前牌堆数和弃牌堆数
- 当前弃牌堆顶牌
- 各玩家的手牌数量
- 如果询问帮助,可以查看以下帮助:
- UNO的游戏规则
- 命令指南
- 单个牌的规则