起因
上周五部门聚餐,有同事提出一个酒桌纸牌游戏,给予输的人一定的惩罚,好巧不巧轮到了我,但我以百般借口推脱,逃过一劫。
事后回想此事,感觉这个游戏逻辑也不复杂,就用vue将其实现。
游戏规则
1.游戏开始时,每人有一张起始牌,选择大、小按钮并点击猜牌按钮,出现结果牌。
2.当起始牌和结果牌数字大小相同时,根据花型(红桃>黑桃>梅花>方块)
进行比较。
3.当结果牌为7,8时则需要重新猜牌
,若结果牌大于起始牌则获胜,反之同理。
4.当结果牌为大王,小王时,游戏规则更改为猜颜色,若结果牌与起始牌同色,则获胜,反之同理。
5.其中红桃A最大,方块2最小。
游戏体验
考虑到有些小伙伴不愿意仔细看规则,可以先体验几局回过头再来看这个规则。
游戏实现
我分析一下比较关键的几个点,重要的是思路,源码戳这就可以看了。
初始化纸牌数组
一张扑克牌具有大小、花色、颜色(红/黑)、花色所占的权重值。那么类型数组如下
const typeList = [
{
typeName: "红桃",
weight: 4,
color: "red",
typeValue: "hearts",
number: 2,
},
{
typeName: "黑桃",
weight: 3,
color: "black",
typeValue: "spades",
number: 4,
},
{
typeName: "梅花",
weight: 2,
color: "black",
typeValue: "clubs",
},
{
typeName: "方块",
weight: 1,
color: "red",
typeValue: "diamonds",
},
];
因为还有大小王,大小王额外进行定义
const ghostCardList = [
{ typeName: "小王", number: 9999, weight: 99999, color: "black" },
{ typeName: "大王", number: 9999, weight: 99999, color: "red" },
];
其中规则中定义A的值最大,因此循环由2-14,其中14作为A
的数值
到此为止,卡牌初始化的代码如下
//初始化卡牌
const initCard = () => {
const typeList = [
{
typeName: "红桃",
weight: 4,
color: "red",
typeValue: "hearts",
number: 2,
},
{
typeName: "黑桃",
weight: 3,
color: "black",
typeValue: "spades",
number: 4,
},
{
typeName: "梅花",
weight: 2,
color: "black",
typeValue: "clubs",
},
{
typeName: "方块",
weight: 1,
color: "red",
typeValue: "diamonds",
},
];
const ghostCardList = [
{ typeName: "小王", number: 9999, weight: 99999, color: "black" },
{ typeName: "大王", number: 9999, weight: 99999, color: "red" },
];
for (let i = 2; i <= 14; i++) {
typeList.forEach((item) => {
state.cardList.push({
number: i,
typeName: item.typeName,
weight: item.weight,
color: item.color,
typeValue: item.typeValue,
});
});
}
state.cardList = [...state.cardList, ...ghostCardList];
console.log("list", state.cardList);
};
但因为这样生成的纸牌是按顺序的,于是我们使用洗牌算法
,模仿人的洗牌行为.
洗牌算法的核心倒序输出,随机交换
//洗牌算法
const shuffle = (list) => {
let len = list.length;
for (let i = len - 1; i >= 0; i--) {
let randomIndex = Math.floor(Math.random() * i);
let temp = list[i];
list[i] = list[randomIndex];
list[randomIndex] = temp;
}
return list;
};
到这一步纸牌的初始化算的上彻底完成。
猜牌
猜牌的本质是将下一张牌与当前这张牌进行规则比较
,倘若是比大小,就是进行数字比较。要是猜黑红,就是颜色比较。
其中数字规则下,7/8需要重新猜牌,遇到大小王时,游戏规则变为猜黑红,猜黑红结束,游戏规则变回猜数字。
游戏规则是数字的处理
//猜数字的规则
if (state.gameRule == "num") {
//如果猜到7,8,重新发牌,并且将拿到的牌更改为7,8
if (state.guessCardRes.number == 7 || state.guessCardRes.number == 8) {
showModal("平");
console.log("因为您抽到7/8了,重新发牌");
return;
}
const compareResult = compareCardNumRes(
state.firstCard,
state.guessCardRes
);
if (
status == "big" &&
state.guessCardRes.typeName != "大王" &&
state.guessCardRes.typeName != "小王"
) {
state.gameRes = compareResult < 0 ? "赢" : "输";
showModal(state.gameRes);
}
if (
status == "small" &&
state.guessCardRes.typeName != "大王" &&
state.guessCardRes.typeName != "小王"
) {
state.gameRes = compareResult > 0 ? "赢" : "输";
showModal(state.gameRes);
}
}
比较数字
//先比较数字大小
//再比较花色大小
const compareCardNumRes = (firstCard, secondCard) => {
console.log("第一张牌", firstCard);
console.log("第二张牌", secondCard);
console.log("比较", firstCard.number - secondCard.number);
if (firstCard.number == secondCard.number) {
return firstCard.weight - secondCard.weight;
}
return firstCard.number - secondCard.number;
};
因为比较颜色大致和比较数字是同理,有兴趣的小伙伴直接看源码就好了。
计算赢的概率
举例:比如我猜比2大,只需要计算出剩下的全部纸牌中,比2大的元素有多少项/剩下纸牌的数组长度即可。
我的思路是将起始牌与剩下纸牌的数组进行运算,得到一个胜率数组,再胜率数组中
1. 元素>0表示比起始牌大。
2. 元素<0表示比起始牌小。
3. 元素=0表示平局。
最终根据是猜大还是猜小,对应算出获胜的几率是多少即可。
//计算数字游戏的赢,输,平的概率
const calcNumRate = (firstCard, secondCard) => {
const paceList = [7, 8, 9999]; //7,8,大小王都是再来一次的机会
if (paceList.includes(secondCard.number)) {
return 0;
}
if (firstCard.number == secondCard.number) {
return firstCard.weight - secondCard.weight;
}
return firstCard.number - secondCard.number;
};
//计算数字规则的概率
const calcWinRate = () => {
console.log("state.firstCard.number)", state.firstCard.number);
const rateList = state.cardList.map((item) => {
return calcNumRate(state.firstCard, item);
});
const bigWinRate = Math.round(
(rateList.filter((item) => item < 0).length / state.cardList.length) *
100
);
const smallWinRate = Math.round(
(rateList.filter((item) => item > 0).length / state.cardList.length) *
100
);
state.numBtnList[0].rate = bigWinRate;
state.numBtnList[1].rate = smallWinRate;
console.log("卡组", state.cardList.length);
};
其他
不同结果对应不同的弹窗.
这段代码逻辑上没啥问题,但并不简洁,不推荐小伙们这样写
//弹窗提示
const showModal = (status) => {
setTimeout(() => {
if (status == "赢") {
ElMessageBox.confirm("恭喜您获得胜利,有两把刷子继续不?", "结果", {
confirmButtonText: "奥利给,冲",
cancelButtonText: "不了不了",
})
.then(() => {
console.log("是否继续");
resetCardStatus();
})
.catch(() => {
//进入下一轮
resetCardStatus();
state.gameNumber += 1;
});
}
if (status == "输") {
ElMessageBox.alert("小伙子你寄了", "结果", {
confirmButtonText: "拜拜了您",
callback: () => {
resetCardStatus();
state.gameNumber += 1;
},
});
}
if (status == "平") {
ElMessageBox.alert("7的意志来咯", "结果", {
confirmButtonText: "再来一次",
callback: () => {
resetCardStatus();
},
});
}
if (status == "王") {
ElMessageBox.alert("幸运女神朝你微笑哦", "结果", {
confirmButtonText: "再来一次",
callback: () => {
calcColorRate();
resetCardStatus("color");
},
});
}
}, 1000);
纸牌花色样式
为了尽可能的让卡牌像扑克牌,通过使用伪类元素:after,:before
实现不同的花色
.diamonds:before,
.diamonds:after {
content: "♦";
color: #ff0000;
}
.hearts:before,
.hearts:after {
content: "♥";
color: #ff0000;
}
.clubs:before,
.clubs:after {
content: "♣";
color: #000;
}
.spades:before,
.spades:after {
content: "♠";
color: #000;
}
div[class*="suit"]:before {
position: absolute;
font-size: 35px;
left: 5px;
top: 5px;
}
div[class*="suit"]:after {
position: absolute;
font-size: 35px;
right: 5px;
bottom: 5px;
}
成品展示
末尾
最后,并不是每个人的脸皮实力都和作者一样,可以推脱。俗话说得好,要想在职场上如鱼得水,少不了几条为人处事的原则,作为在职场上摸爬滚打了2年多的社畜,给大家分享几条我的原则。
- 领导敬酒我不喝,领导夹菜我转桌
- 领导开门我上车,领导隐私我乱说
- 领导听牌我自摸,领导唱K我切歌 最后,祝大家升官发财。