文章发布于:2024年4月28日
用 Figjam 画了一个斗地主的界面布局,大致琢磨了一下实现难度,发现没有特别复杂的地方,感觉如果做的话,问题不大。
不过我并不打算真的做。如果说做在线五子棋让我了解“游戏服务器”的概念,那做斗地主有什么目的呢?想来想去,找不到一个说服自己的理由。
不过要说理由,有一个勉强的借口,就是可以通过做斗地主,来学习如何玩斗地主。是的,我不会玩斗地主。在做的过程中,要了解规则,特别是需要计算一家的牌是否大于另一家的牌,有多少种合法的组合等等,做完以后还需要测试,这样搞下来,我应该能学会玩斗地主。。。
后续:
写了一个牌型检测的功能,标准根据《竞技二打一扑克竞赛规则》。
牌型:
1. 单张 | 3
2. 对子 | 33
3. 王炸 | 大王小王
4. 炸弹 | 3333
5. 顺子 | 34567
6. 连对 | 334455
7. 三不带,三带一,三带对 | 333,333A,333AA
8. 四带二,四带一对,四带两对| 3333AK,3333AA,3333AAKK
9. 飞机,飞机单带,飞机双带,组合飞机(规律和普通飞机相似) | 333444,333444AK,333444AAKK,333444555AAKK22
//3 4 5 6 7 8 9 X J Q K A 2 L M
//3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
const cards_type_check = {
equal_2: function (str) {
return str[0] === str[1];
},
equal_3: function (str) {
return str[0] === str[1] && str[1] === str[2]
},
equal_4: function (str) {
return str[0] === str[1] && str[1] === str[2] && str[2] === str[3];
},
flat_arr: function (arr) {
let rst = [];
for (const str of arr) {
if (Array.isArray(str)) {
rst = rst.concat(this.flat_arr(str))
} else {
rst.push(str);
}
}
return rst;
},
group: function (cards) {
const obj = {};
for (const card of cards) {
if (!obj[card]) {
obj[card] = [card];
} else {
obj[card].push(card);
}
}
const type_list = Object.keys(obj).sort((a, b) => (+a) - (+b));
let arr = [];
type_list.forEach(tp => {
arr.push(obj[tp])
})
arr = arr.sort((a, b) => b.length - a.length);
return this.flat_arr(arr);
},
left_str: function (str, len) {
return str.slice(0, len);
},
right_str: function (str, len) {
return str.slice(-len);
},
slice_str: function (str, start, len) {
return str.slice(start - 1, start + len - 1);
},
straight: function (cards) {
if (cards[cards.length - 1] > 14) {
return false;
}
let tmp;
for (const card of cards) {
if (!tmp) {
tmp = card;
} else {
if (card - tmp !== 1) {
return false;
} else {
tmp = card;
}
}
}
return true;
},
chain_pair: function (cards) {
if (cards[cards.length - 1] > 14) {
return false;
}
let tmp;
for (let i = 0; i < cards.length; i += 2) {
const pair = this.slice_str(cards, i + 1, 2);
if (!this.equal_2(pair)) {
return false;
}
if (!tmp) {
tmp = pair;
} else {
if (pair[0] - tmp[0] !== 1) {
return false;
}
tmp = pair;
}
}
return true;
},
chain_three: function (cards) {
if (cards[cards.length - 1] > 14) {
return false;
}
let tmp;
for (let i = 0; i < cards.length; i += 3) {
const three = this.slice_str(cards, i + 1, 3);
if (!this.equal_3(three)) {
return false;
}
if (!tmp) {
tmp = three;
} else {
if (three[0] - tmp[0] !== 1) {
return false;
}
tmp = three;
}
}
return true;
},
dict: {
'3': 3, '4': 4, '5': 5, '6': 6,
'7': 7, '8': 8, '9': 9, 'X': 10,
'J': 11, 'Q': 12, 'K': 13,
'A': 14, '2': 15, 'L': 16, 'M': 17
},
convert: function (cards) {
const arr = [];
for (const card of cards) {
arr.push(this.dict[card])
}
return arr;
},
check: function (cards) {
//test mode
if (!/^[3456789XJQKA2LM]{1,20}$/.test(cards)) {
return { type : "无效字符" }
}
cards = this.convert(cards);
cards = this.group(cards);
const len = cards.length;
const fmt = function (type, flag, cvt_cards) {
return {
type: type,
flag: flag || null,
cards: cvt_cards || cards
}
}
if (len === 1) {
return fmt("单牌");
} else if (len === 2) {
if (cards[0] === 16 && cards[1] === 17) {
return fmt("王炸")
}
if (this.equal_2(cards)) {
return fmt("对子")
}
}
for (const card of cards) {
if (card === 16 || card === 17) {
return fmt("未知牌型");
}
}
if (len === 3 && this.equal_3(cards)) {
return fmt("三不带");
} else if (len === 4) {
if (this.equal_4(cards)) {
return fmt("炸弹");
}
if (this.equal_3(this.left_str(cards, 3))) {
return fmt("三带一");
}
} else if (len === 5) {
if (this.straight(cards)) {
return fmt("顺子", 5)
}
if (this.equal_3(this.left_str(cards, 3)) && this.equal_2(this.right_str(cards, 2))) {
return fmt("三带对");
}
} else if (len === 6) {
if (this.straight(cards)) {
return fmt("顺子", 6);
}
if (this.chain_three(cards)) {
return fmt("飞机", 2);
}
if (this.chain_pair(cards)) {
return fmt("连对", 3);
}
if (this.equal_4(this.left_str(cards, 4))) {
if (this.equal_2(this.right_str(cards, 2))) {
return fmt("四带一对");
} else {
return fmt("四带二单");
}
}
} else if (len === 7) {
if (this.straight(cards)) {
return fmt("顺子", 7)
}
} else if (len === 8) {
if (this.straight(cards)) {
return fmt("顺子", 8)
}
if (this.chain_pair(cards)) {
return fmt("连对", 4)
}
const card_6 = this.left_str(cards, 6);
if (this.chain_three(card_6)) {
return fmt("二飞机单带", 2)
}
//33334455
if (this.equal_4(this.left_str(cards, 4))
&& this.equal_2(this.slice_str(cards, 5, 2))
&& this.equal_2(this.right_str(cards, 2))
&& !this.equal_2(this.slice_str(cards, 6, 2))//所带牌中不包括炸弹,所以不能33334444
) {
return fmt("四带二对")
}
} else if (len === 9) {
if (this.chain_three(cards)) {
return fmt("飞机", 3)
}
if (this.straight(cards)) {
return fmt("顺子", 9)
}
} else if (len === 10) {
if (this.straight(cards)) {
return fmt("顺子", 10)
}
if (this.chain_pair(cards)) {
return fmt("连对", 5);
}
if (this.chain_three(this.left_str(cards, 6))
&& this.equal_2(this.slice_str(cards, 7, 2))
&& this.equal_2(this.slice_str(cards, 9, 2))
) {
return fmt("二飞机二对", 2);
}
} else if (len === 11 && this.straight(cards)) {
return fmt("顺子", 11);
} else if (len === 12) {
if (this.straight(cards)) {
return fmt("顺子", 12);
}
if (this.chain_pair(cards)) {
return fmt("连对", 6);
}
if (this.chain_three(cards)) {
return fmt("飞机", 4);
}
let card_9 = this.left_str(cards, 9);
if (this.chain_three(card_9)) {
return fmt("三飞机三单", 3)
}
const cvt = this.right_str(cards, 9).concat(this.left_str(cards, 3));
card_9 = this.left_str(cvt, 9);
if (this.chain_three(card_9)) {
return fmt("三飞机三单", 3, cvt)
}
} else if (len === 14) {
if (this.chain_pair(cards)) {
return fmt("连对", 7);
}
} else if (len === 15) {
if (this.chain_three(cards)) {
return fmt("飞机", 5);
}
if (
this.chain_three(this.left_str(cards, 9))
&& this.equal_2(this.slice_str(cards, 10, 2))
&& this.equal_2(this.slice_str(cards, 12, 2))
&& this.equal_2(this.slice_str(cards, 14, 2))
) {
return fmt("三飞机三对", 3);
}
} else if (len === 16) {
if (this.chain_pair(cards)) {
return fmt("连对", 8);
}
let card_12 = this.left_str(cards, 12);
if (this.chain_three(card_12)) {
return fmt("四飞机四单", 4)
}
const cvt = this.right_str(cards, 13).concat(this.left_str(cards, 3));
card_12 = this.left_str(cvt, 12);
if (this.chain_three(card_12)) {
return fmt("四飞机四单", 4, cvt)
}
} else if (len === 18) {
if (this.chain_pair(cards)) {
return fmt("连对", 9);
}
if (this.chain_three(cards)) {
return fmt("飞机", 6);
}
} else if (len === 20) {
if (this.chain_pair(cards)) {
return fmt("连对", 10);
}
let card_15 = this.left_str(cards, 15);
if (this.chain_three(card_15)) {
return fmt("五飞机五单", 5);
}
const cvt = this.right_str(cards, 17).concat(this.left_str(cards, 3));
card_15 = this.left_str(cvt, 15);
if (this.chain_three(card_15)) {
return fmt("五飞机五单", 5, cvt);
}
if (
this.chain_three(this.left_str(cards, 12))
&& this.equal_2(this.slice_str(cards, 13, 2))
&& this.equal_2(this.slice_str(cards, 15, 2))
&& this.equal_2(this.slice_str(cards, 17, 2))
&& this.equal_2(this.slice_str(cards, 19, 2))
) {
return fmt("四飞机四对", 4);
}
}
return fmt("未知牌型")
}
}
//测试 function t(cards) {
console.log(cards_type_check.check(cards));
}
t("A")
t("ML")
t("333")
t("JJJJ")
t("AAAA23")
t("AAAA2233")
t("999XXXJJJ")
t("3456789XJQKA")
//理论上最大牌型
t("XXJJQQ33445566778899")
t("XXX888999JJJ44553366")
t("33322XXXJJJQQQKKKAAA") // //输出 type :单牌
flag :null
cards :[14]
type :王炸
flag :null
cards :[16,17]
type :三不带
flag :null
cards :[3,3,3]
type :炸弹
flag :null
cards :[11,11,11,11]
type :四带二单
flag :null
cards :[14,14,14,14,3,15]
type :四带二对
flag :null
cards :[14,14,14,14,3,3,15,15]
type :飞机
flag :3
cards :[9,9,9,10,10,10,11,11,11]
type :顺子
flag :12
cards :[3,4,5,6,7,8,9,10,11,12,13,14]
type :连对
flag :10
cards :[3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12]
type :四飞机四对
flag :4
cards :[8,8,8,9,9,9,10,10,10,11,11,11,3,3,4,4,5,5,6,6]
type :五飞机五单
flag :5
cards :[10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,15,15,3,3,3]