写点什么

[教你做小游戏] 展示斗地主扑克牌,支持按出牌规则排序!支持按大小排序!

作者:HullQin
  • 2022 年 9 月 01 日
    广东
  • 本文字数:3793 字

    阅读完需:约 12 分钟

[教你做小游戏] 展示斗地主扑克牌,支持按出牌规则排序!支持按大小排序!

我是 HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者 HullQin 授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加 Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。

问题描述

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


一副牌有 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(裁剪位置)即可。


.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复用即可。


.poker-1 {  background-position: -238px -646px;}
复制代码


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

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

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


const mapPokerIdToCardId = (id) => {  // 映射扑克id(可能有多幅牌)至卡片id(只有0-54)  return (id - 1) % 54 + 1;};
复制代码


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

封装一个组件

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


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


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参数,表示是否需要重叠。


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,这样数字相同时,就按花色排序啦。


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


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


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同样有小数部分,是为了同数字时按花色排序。


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 个数字谁出现频次更高,高的靠前。


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 授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加 Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。

发布于: 2 小时前阅读数: 11
用户头像

HullQin

关注

公众号【线下聚会游戏】 2020.10.07 加入

game.hullqin.cn 我做了一些联机桌游网页:支持2-10人联机的UNO、2-4人联机的斗地主、2人联机的五子棋。无需下载,点开即玩!叫上朋友,即刻开局!不看广告,不做任务,享受「纯粹」的游戏!

评论

发布
暂无评论
[教你做小游戏] 展示斗地主扑克牌,支持按出牌规则排序!支持按大小排序!_CSS_HullQin_InfoQ写作社区