Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >展示斗地主扑克牌,支持按出牌规则排序!支持按大小排序!

展示斗地主扑克牌,支持按出牌规则排序!支持按大小排序!

原创
作者头像
HullQin
发布于 2023-06-27 16:00:16
发布于 2023-06-27 16:00:16
2.2K00
代码可运行
举报
文章被收录于专栏:教你做小游戏教你做小游戏
运行总次数:0
代码可运行

我是公众号「线下聚会游戏」的作者,开发了一些小游戏,包括UNO、斗地主、五子棋、卡坦岛、飞行棋等,总结了一些小游戏开发经验,分享给大家,欢迎关注。

问题描述

我们想做一个斗地主游戏,其中最重要的一点是,把扑克牌展示出来。

一副牌有54张,我们给每张牌1个编号(id),取值1-54。如果涉及到2副牌,就取id为1-108。

展示牌,其实就是给你一个id列表,按需展示列表中的牌即可。

而展示牌有3种排序方式:

  1. 不排序,列表是什么,就展示什么。(发牌、底牌常用)
  2. 按照大小排序。(手牌常用)
  3. 按照出牌规则排序。(出牌常用,规则比如顺子、连对、飞机、四带二、炸弹等)

今天,我们就来实现它们!

第1步,展示1张牌

准备素材

牌有54种,加上牌背面,有55种图案。我们先准备好素材:

如果要展示1张牌,以它为背景,使用background-positionwidthheight对整个大图片裁剪即可。

不要拆开这个大图,让用户一次性下载55张图片,那样速度会肉眼可见的慢。因为大多数浏览器不能并发55个请求下载图片,它可能一次最多建立8个TCP连接来下载,你可能需要8次RTT才能下载完。(55除以8向上取整=8)

所以,做Web开发,一定要尽量拼接多个小素材成为一个大图片,再去裁剪它展示素材。

写好css做裁剪

我们利用class,定义一个.poker写所有扑克牌共用的样式,再给每个扑克牌定义一个background-position(裁剪位置)即可。

代码语言:css
AI代码解释
复制
.poker {
  position: absolute;
  background-image: url('./card.png');
  background-clip: content-box;
  background-repeat: no-repeat;
  width: 116px;
  height: 159px;
  transform-origin: 0 0 0;
  transition: left .2s ease-out, top .2s ease-out;
}

例如,这是id为1的扑克牌的样式。每个扑克牌单独的样式很简单,只有1行,定义background-position即可。因为其它样式都是一模一样的,用.poker复用即可。

代码语言:css
AI代码解释
复制
.poker-1 {
  background-position: -238px -646px;
}

不再罗列了,可以参考style.css源码: github.com/HullQin/poker_fe

定义 扑克牌ID->图片ID 的映射

开头我们提到,可能有2幅牌,而他们的图片样式应该是一样的。所以需要通过取余数,把108个ID映射到54个值。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const mapPokerIdToCardId = (id) => {
  // 映射扑克id(可能有多幅牌)至卡片id(只有0-54)
  return (id - 1) % 54 + 1;
};

代码中,我用id = 0表示扑克牌的背面。

封装一个组件

你可以封装为React组件或Vue组件,或其它你采用框架支持的组件。

我代码使用了React,所以封装为React组件。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import cn from 'classnames';

const Poker = (props) => {
  const { id, className, ...otherProps } = props;
  if (typeof id !== 'number') return;
  const cardId = mapPokerIdToCardId(id);
  return (
    <div
      className={cn('poker', `poker-${cardId}`, className)}
      {...otherProps}
    />
  );
};

这是一个非常简洁的组件,只需要传入扑克牌的ID,就会展示这张扑克牌了。此外,可以传入className或者style,自定义样式。

至此,展示1张扑克牌,我们就完成啦!

第2步,不排序展示多张牌

目前还比较简单,只需要提供一个扑克牌ID列表,我们依次展示即可。我们用ids参数作为扑克牌ID列表,需要组件引用者传入。

我们需要关注一下扑克牌图片的高度,我们定义一个默认高度(159),此外也允许引用组件者通过height新设置高度。

我们还需要关注扑克牌之间的间隔:如果是底牌,那么间隔大一些;如果是手牌、或者出牌,牌会比较多,间隔应该是负数,有重叠的效果。我们用overlap参数,表示是否需要重叠。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const StaticPokerList = (props) => {
  const { ids, overlap, height = 159, className, style, ...otherProps } = props;
  const gap = (overlap ? 48 : 116) * height / 159;
  return (
    <div className={cn('static-poker-list', className)} style={{ height, ...style }} {...otherProps}>
      {ids.map((id, index) => (
        <Poker
          key={index}
          id={id}
          style={{ left: index * gap, transform: `scale(${height / 159})` }}
        />
      ))}
    </div>
  );
};

可以看到,我们引用了Poker组件,并控制了每一个扑克牌的left属性,让它们等间距排列。

你可能会问:啊!你为什么用列表的index做Key呢?为什么不用扑克牌ID做Key呢?

因为我们这个列表非常小,不超过108,不会有性能问题,所以采用了最稳妥的方式,以index作Key,是独一无二的,绝不会出错。如果你能保证你传入的扑克牌ID唯一,也可以使用扑克牌ID作Key。

第3步,按照大小排序

扑克牌是有大小的,顺序是:大王、小王、2、A、K、Q、J、10、9、8、7、6、5、4、3。

此外,为了美观,我们也期望同样大小的数字的花色,也是有顺序的。例如按照♥️、♦️、♠️、♣️的顺序排列,当你有很多炸弹时,会非常漂亮,令玩家舒适。

所以,我们要按数字大小排列,数字相同时,按固定花色顺序排列。

只要修改一下StaticPokerList,对它的ids参数做一个排序即可。

排序依据是什么呢?需要手写函数嘛?

答案是:当然不需要!只要我们把54个ID映射到54个数字,再按数字排序,就大功告成了!这是效率非常高的方式!

这定义了映射,传入ID为1-54,即可映射到牌的具体大小。规定四个花色的小数部分不一样,分别为.2 .4 .6 .8,这样数字相同时,就按花色排序啦。

定义好每张牌的数字,再根据大小数值排序即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const pokerMap = [0, 14.2, 15.2, 3.2, 4.2, 5.2, 6.2, 7.2, 8.2, 9.2, 10.2, 11.2, 12.2, 13.2, 14.4, 15.4, 3.4, 4.4, 5.4, 6.4, 7.4, 8.4, 9.4, 10.4, 11.4, 12.4, 13.4, 14.6, 15.6, 3.6, 4.6, 5.6, 6.6, 7.6, 8.6, 9.6, 10.6, 11.6, 12.6, 13.6, 14.8, 15.8, 3.8, 4.8, 5.8, 6.8, 7.8, 8.8, 9.8, 10.8, 11.8, 12.8, 13.8, 54, 53];

这就是排序函数,传入ids,输出排好序的ids

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const sortPokersById = (ids) => {
  return ids.sort((a, b) => pokerMap[mapPokerIdToCardId(b)] - pokerMap[mapPokerIdToCardId(a)]);
};

当然,这调用了mapPokerIdToCardId,是考虑到2幅牌的情况,先把1-108映射到1-54,再映射到具体数值。

第4步,按照规则排序

上面按大小排序还是太简单,只有结合了游戏规则的排序,才是最难的!

我根据斗地主规则,总结了这样的排序算法:

输入:ids,即你出的牌的列表(前提:是符合斗地主规则的一串牌)。

输出:sortedIds,按出牌规则排好序的列表。

  1. 统计每个数字的出现次数。
  2. 按照出现次数排序,出现频次高的,放在前面。
  3. 如果频次相同,按照数字大小排序。数字小的,放在在前。
  4. 同样的数字,要按照固定花色顺序排序,保证美观。

验证算法正确性:

  • 顺子:3、4、5、6、7。频次都是1,排序结果是3、4、5、6、7。
  • 连对:QQ、KK、AA。频次都是2,排序结果是QQ、KK、AA。
  • 三带一:KKK2。K频次是3,2频次是1。排序结果是KKK、2。
  • 四带两对:44443322。4频次是4,3和2频次是2。排序结果是44443322。
  • 王炸:大王、小王。频次都是1,规定大王数字更小,那么排序结果是大王、小王。

这里定义pokerNumberMap为数字大小,为了让王炸时大王在前小王在后,我们规定大王=53、小王=54即可。

pokerRuleMap同样有小数部分,是为了同数字时按花色排序。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const pokerRuleMap = [0, 14.2, 15.2, 3.2, 4.2, 5.2, 6.2, 7.2, 8.2, 9.2, 10.2, 11.2, 12.2, 13.2, 14.4, 15.4, 3.4, 4.4, 5.4, 6.4, 7.4, 8.4, 9.4, 10.4, 11.4, 12.4, 13.4, 14.6, 15.6, 3.6, 4.6, 5.6, 6.6, 7.6, 8.6, 9.6, 10.6, 11.6, 12.6, 13.6, 14.8, 15.8, 3.8, 4.8, 5.8, 6.8, 7.8, 8.8, 9.8, 10.8, 11.8, 12.8, 13.8, 53, 54];
const pokerNumberMap = [0, 14, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 54, 53];

下面是排序的函数,可以看到,我们先统计了frequency,对ids排序时,判断了2个数字谁出现频次更高,高的靠前。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const sortPokersByRule = (ids) => {
  const frequency = {};
  for (const id of ids) {
    const cardNumber = pokerNumberMap[mapPokerIdToCardId(id)];
    if (cardNumber in frequency) {
      frequency[cardNumber] += 1;
    } else {
      frequency[cardNumber] = 1;
    }
  }
  return ids.sort((a, b) => {
    a = mapPokerIdToCardId(a);
    b = mapPokerIdToCardId(b);
    const frequencyA = frequency[pokerNumberMap[a]];
    const frequencyB = frequency[pokerNumberMap[b]];
    if (frequencyA === frequencyB) {
      return pokerRuleMap[a] - pokerRuleMap[b];
    }
    return frequencyB - frequencyA;
  });
};

写在最后

我是HullQin,公众号线下聚会游戏的作者(欢迎关注我,交个朋友)。转发本文前需获得作者HullQin授权。我独立开发了一些小游戏,是个网页,可以很方便的跟朋友联机玩UNO、飞行棋、斗地主、五子棋、一夜狼、狼人杀、象棋、德国心脏病、达芬奇密码等游戏,不收费无广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这个专栏里分享:《教你做小游戏》

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JDK1.9-模拟斗地主洗牌发牌
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
cwl_java
2019/12/03
7360
iOS 斗地主发牌排序
很多人都玩过斗地主,也有很多人没玩过,或者像我一样是个菜B,不太懂怎么玩,好,没关系,这篇文章不是教你斗地主,是要根据斗地主这个游戏做些技术分享: 目的:随机发牌,发的牌按牌大小排序(花色与数字)
清墨
2018/05/07
1.3K0
iOS 斗地主发牌排序
扑克牌例题与Collections工具类
我们需要创建四个类,一个封装对象的属性,一个封装牌的花色和大小也就是牌的类型,一个实现发牌,排序,洗牌功能,也就是封装对象的行为,最后一个实现图形化界面。
端碗吹水
2020/09/23
3200
这是一道算法题:如何比较徳州扑克牌大小?(附带python实现)
每个玩家有2张牌,公共牌有5张牌,共计7张牌。比牌时,每个玩家要找到自己组成最大牌型的5张,跟其他玩家的最大牌型比大小。
HullQin
2023/07/26
3K0
用扑克牌演示 Python 数据分析
扑克牌是我们常见一种娱乐工具,玩法千变万化,为了提高学习 Python 知识的趣味性,我构建了一个扑克牌的数据框,将用它来演示一些 Python 数据分析的功能。
张俊红
2019/09/30
1.4K0
【Java】Java基础 使用集合实现斗地主分牌
我们到时候分的牌都存储在这里,但你可能会有疑问,因为存储的泛型是Integer,但扑克牌是有花色的,这该如何实现?
哈__
2024/04/25
1400
【Java】Java基础 使用集合实现斗地主分牌
P2668 斗地主 贪心+深搜
题目描述 牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由n张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。 现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。 需要注意的是,本
attack
2018/04/13
2K0
P2668 斗地主 贪心+深搜
2106. [NOIP2015] 斗地主
2106. [NOIP2015] 斗地主 ★★★☆   输入文件:landlords.in   输出文件:landlords.out 简单对比 时间限制:2 s   内存限制:1025 MB 【题目描述】 牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由
attack
2018/04/13
1.6K0
【剑指Offer】61.扑克牌中的顺子
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
瑞新
2020/12/07
2810
剑指Offer-扑克牌顺子
题目描述 LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买
武培轩
2018/04/19
5900
【心碎篇】冲刺CSP-J/S第2轮倒计时5天:斗地主,栽了
这道题:斗地主,在小码匠让我给她准备食材的时候就开始搞,下午继续搞了2.5个小时,最终也没搞定。
小码匠
2023/10/24
2900
【心碎篇】冲刺CSP-J/S第2轮倒计时5天:斗地主,栽了
剑指offer 扑克牌顺子
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…..LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何。为了方便起见,你可以认为大小王是0。
vincentbbli
2021/08/18
2270
剑指offer——扑克牌顺子
题目描述 LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…..LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
AI那点小事
2020/04/18
2870
如何优雅地给扑克牌排序?(一)——排序算法的数学本质
不知平常各位打牌时候是否遇到过这样的场景:四人打完升级后,面对两幅混乱的扑克牌,走了一人后想打斗地主,现在要把他们分出一副来,于是打算先排序后分离,然后各种花色,数字,摆满一桌子,乱成一团,等排好了,5分钟过去了……
magic2728
2019/09/27
2K0
如何优雅地给扑克牌排序?(一)——排序算法的数学本质
剑指offer 扑克牌顺子
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
week
2019/03/29
3080
每天一道剑指offer-扑克牌顺子
考试结束,班级平均分只拿到了年级第二,班主任于是问道:大家都知道世界第一高峰珠穆朗玛峰,有人知道世界第二高峰是什么吗?正当班主任要继续发话,只听到角落默默响起来一个声音:”乔戈里峰” 题目 每天一道剑
乔戈里
2019/01/28
4850
用斗地主的实例学会使用java Collections工具类
最近在学习数据结构和算法的过程中频繁用到了Collections工具类,这是开发中的一把利器,简化了许多涉及集合的编码,该文将通过实例对此工具类进入深入剖析。
智慧zhuhuix
2020/08/14
7051
用斗地主的实例学会使用java Collections工具类
斗地主之洗牌发牌----Java篇
区别: 上面是用一个字符串数组来存储所有牌的组合,并且也是对字符串数组进行洗牌操作,较为麻烦,而用哈希表之后,我们可以对每一张牌对应的索引进行洗牌操作。
大忽悠爱学习
2021/11/15
8560
扑克牌大小
题目描述 扑克牌游戏大家应该都比较熟悉了,一副牌由54张组成,含3~A,2各4张,小王1张,大王1张。牌面从小到大用如下字符和字符串表示(其中,小写joker表示小王,大写JOKER表示大王):) 3 4 5 6 7 8 9 10 J Q K A 2 joker JOKER 输入两手牌,两手牌之间用“-”连接,每手牌的每张牌以空格分隔,“-”两边没有空格,如:4 4 4 4-joker JOKER 请比较两手牌大小,输出较大的牌,如果不存在比较关系则输出ERROR
AI那点小事
2020/04/20
7450
[剑指offer] 扑克牌顺子
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…..LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
尾尾部落
2018/09/04
7170
相关推荐
JDK1.9-模拟斗地主洗牌发牌
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验