和朋友正在尝试编写一个斗地主的游戏(联机版本),改文章还会继续更新
斗地主由52张卡片组成,分别是梅花1K,方块1K,红心1K,黑桃1K,再加上大小王,我们把 J, Q, K 记为 11, 12, 13, 我们作如下定义
type PockerType =
'SPADE' | // 黑桃
'HEART' | // 红心
'DUAMOND' | // 方块
'CLUB' | // 梅花
'RED_JOKER' | // 大王
'BLACK_JOKER' // 小王
interface Pocker{
type: PockerType,
value: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | null
}
牌型
我们把每种出牌规则成为牌型 (type),一般的不同的牌型不能混出(炸弹除外),同种牌型有大小之分 (value),在斗地主中满足这样的规则
- 每次出牌必须和上个牌型一致
- 如果前面没有牌型,则可以任意出一个牌型
- 炸弹可以拦截任意一种牌型,只有更价值(value)的炸弹能拦截炸弹
- 王炸可以拦截所有的牌
斗地主中牌型主要有
- 单张
- 对子
- 连对 - 2连对
- 连对 - 3连对
- 连对 - 4连对
- 连对 - 5连对
- 连对 - 6连对
- 3带1
- 3带2
- 飞机带一对
- 飞机带两张
- 顺子
- 炸弹
- 连炸
- 三连炸
- 王炸
我们给不同的排至一个权重值(typeValue),进行决策判断时,在不考虑炸弹的情况下,易得出当 oldTypeValue != typeValue 时,无法出牌,当 oldTypeValue -= typeValue 时,我们还需计算新的牌是否比旧的牌大
因此,我们的一个出牌动作里面,应该宝行两个变量
interface PockerValue {
typeValue: string,
value: number
}
不同牌型的 value 值计算方式不一定相同,同时,在判牌阶段,相同牌型,value 值更高的大过 value 低的
牌型判断
前面讲了牌型,对于任意序列,我们还应该有一定的机制分析出他是何种牌型,不同的判刑的判断规则并不一定相同,另外,上述的规则里面可能还有遗漏,或者在未来可能会有新的规则补充,因此,我们设计一个流水线性的牌型判断器,流水线上面挂着各种牌型的判断器,牌从流水线的一端流入,依次经过流水线处理,当处理单元成功命中牌型时,则流水线工作停止,结果抛出
需要注意的是,由于部分牌可能会满足多个牌型结果,因此再挂载处理单元的时候,要考虑到优先级的顺序关系,比如,炸弹的判断顺序会大于其他顺序,他应该在流水线的开头
import KingBOOM from './process/KingBOOM'
import SingleProcess from './process/SingleProcess'
const pipeline = [
KingBOOM, // 王炸判断在前
SingleProcess
]
export default function getPockerType(list: Pocker[]): PockerValue{
for(let process of pipeline){
const item = process.judge(list)
if(item){
return item
}
}
throw new Error('错误的排列组合')
}
牌值判断
在牌型判断器的处理单元内部,处理单元除了要处理是否匹配牌型,还要准确输出该序列的value值,比如,一个处理器单元可能要这么编写
export default new class TypeSingle{
judge(list: PockerType[]): PockerValue | false {
if(list.length != 1){
return false
}
const pocker = list[0]
let val = pocker.value
if(pocker.type == 'RED_JOKER'){
val = 15
}else if(pocker.type == 'BLACK_JOKER'){
val = 14
}
return {
typeValue: 'SINGLE',
value: val
}
}
test(oldPockerValue: PockerValue, newPockerValue: PockerValue){
return oldPockerValue.value < newPockerValue.value;
}
}
对于顺子,使用AABB方式记录,AA表示起始的扑克卡片,BB表示连张个数
export default new class TypeSingle{
judge(list: PockerType[]): PockerValue | false {
if(list.length < 5 || list.some(item => item.value == null)){
return false
}
const temp = list.map(item => item.value)
.sort((a, b) => a - b)
for(let i=1; i<temp.length; i++){
if(temp[i-1] + 1 != temp[i]){
return false
}
}
return {
typeValue: 'SUNZI',
value: temp[0] * 100 + temp.length - 1
}
}
test(oldPockerValue: PockerValue, newPockerValue: PockerValue){
return oldPockerValue.value < newPockerValue.value &&
oldPockerValue.value % 100 == newPockerValue % 100;
}
}
对于连对, 依然使用AABB
export default new class TypeSingle{
judge(list: PockerType[]): PockerValue | false {
if(list.length % 2 == 1 || list.some(item => item.value == null)){
return false
}
const temp = list.map(item => item.value)
.sort((a, b) => a - b)
for(let i=0; i<temp.length; i+=2){
if(temp[i] != temp[i+1]){
return false
}
}
return {
typeValue: 'LIANDUI',
value: temp[0] * 100 + temp.length - 1
}
}
test(oldPockerValue: PockerValue, newPockerValue: PockerValue){
return oldPockerValue.value < newPockerValue.value &&
oldPockerValue.value % 100 == newPockerValue % 100;
}
}
出牌判断
出牌时,将新的排序列分析出牌型后,然后调用对应牌型的test方法,进行大小值的判断,如果返回真,则说明牌有小