我是HullQin,公众号线下团聚游戏的作者(欢送关注公众号,发送加微信,交个敌人),转发本文前需取得作者HullQin受权。我独立开发了《联机桌游合集》,是个网页,能够很不便的跟敌人联机玩斗地主、五子棋等游戏,不免费没广告。还开发了《Dice Crush》加入Game Jam 2022。喜爱能够关注我 HullQin 噢~我有空了会分享做游戏的相干技术。
问题形容
咱们想做一个斗地主游戏,其中最重要的一点是,把扑克牌展现进去。
一副牌有54张,咱们给每张牌1个编号(id),取值1-54。如果波及到2副牌,就取id为1-108。
展现牌,其实就是给你一个id列表,按需展现列表中的牌即可。
而展现牌有3种排序形式:
- 不排序,列表是什么,就展现什么。(发牌、底牌罕用)
- 依照大小排序。(手牌罕用)
- 依照出牌规定排序。(出牌罕用,规定比方顺子、连对、飞机、四带二、炸弹等)
明天,咱们就来实现它们!
第1步,展现1张牌
筹备素材
牌有54种,加上牌反面,有55种图案。咱们先筹备好素材:
如果要展现1张牌,以它为背景,应用background-position
和width
、height
对整个大图片裁剪即可。
不要拆开这个大图,让用户一次性下载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,按出牌规定排好序的列表。
- 统计每个数字的呈现次数。
- 依照呈现次数排序,呈现频次高的,放在后面。
- 如果频次雷同,依照数字大小排序。数字小的,放在在前。
- 同样的数字,要依照固定花色程序排序,保障好看。
验证算法正确性:
- 顺子: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 噢~我有空了会分享做游戏的相干技术。