私は公式アカウント「 OfflinePartyGames 」の作者です。いくつかのオンラインボードゲームページ(Doudizhu、Gobangなど)を開発し、いくつかの小さなゲーム開発経験を要約し、「 TeachYoutoMake」の列にまとめました。あなたと共有する小さなゲーム」。、フォローすることを歓迎します。
問題の説明
家主のゲームを作りたいのですが、一番大事なのはトランプを見せることです。
デッキには54枚のカードがあり、各カードに1から54の範囲のID(id)を付けます。カードのデッキが2枚含まれている場合は、IDを1-108とします。
カードを表示すると、実際にはIDのリストが表示され、必要に応じてリストにカードを表示できます。
ディスプレイカードには3つの並べ替え方法があります。
- 並べ替えはなく、リストが何であるかを表示するだけです。(一般的にカードやホールカードを配るために使用されます)
- サイズで並べ替えます。(一般的に手で使用されます)
- カードルールで並べ替えます。(トランプ、ストレート、ペア、プレーン、4と2、爆弾などのルールに一般的に使用されます)
今日、それらを実現させましょう!
ステップ1、1枚のカードを公開する
資料を準備する
54種類のカードとカードの裏側に55種類のパターンがあります。最初に資料を準備しましょう:
カードを1枚表示する場合は、それを背景として使用し、とを使用background-position
しwidth
てheight
大きな画像全体をトリミングします。
この全体像を分解して、一度に55枚の画像をダウンロードできるようにしないでください。肉眼では速度が遅くなります。ほとんどのブラウザは55の同時リクエストで画像をダウンロードできないため、一度にダウンロードするTCP接続を最大8つ確立する可能性があり、ダウンロードを完了するには8つのRTTが必要になる場合があります。
したがって、Web開発を行うときは、複数の小さな素材を大きな画像につなぎ合わせてから、それをカットして素材を表示する必要があります。
トリミング用のCSSを書く
クラスを使用して.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;
}
たとえば、これはID1のトランプのスタイルです。各トランプの個々のスタイルは非常にシンプルで、1行だけで定義するだけbackground-position
です。他のスタイルはまったく同じなので、.poker
再利用できます。
.poker-1 {
background-position: -238px -646px;
}
不再罗列了,可以参考style.css源码: github.com/HullQin/pok…
定义 扑克牌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
。IDをソートするときに、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;
});
};
最後に書く
私の名前はハルキンです。「オンラインボードゲームコレクション」を独自に開発しました。これは、闘地主、ゴバン、UNOなどのゲームを友達とオンラインで簡単にプレイできるWebページです。また、独自に開発した「合成ビッグウォーターメロンリメイク」。よろしければフォローしてください〜時間があれば、ゲーム制作に関連する技術を共有し、「小さなゲームの作り方を教えて」と「エクストリームユーザーエクスペリエンス」の2つのコラムで共有します。
ナゲッツテクノロジーコミュニティのクリエイター署名プログラムの募集に参加しています。リンクをクリックして登録し、送信してください。