629 lines
22 KiB
JavaScript
629 lines
22 KiB
JavaScript
/**
|
|
* 掼蛋游戏核心逻辑
|
|
* 简化版:4人2v2,108张牌,27张/人,有百搭,无进贡,无亮主
|
|
*/
|
|
|
|
// ========== 牌组定义 ==========
|
|
const SUITS = ['♠', '♥', '♣', '♦'];
|
|
const RANKS = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
|
|
|
|
// 生成两副牌共108张
|
|
function generateDeck() {
|
|
const deck = [];
|
|
let id = 0;
|
|
|
|
// 两副牌
|
|
for (let deckNum = 0; deckNum < 2; deckNum++) {
|
|
for (const rank of RANKS) {
|
|
for (const suit of SUITS) {
|
|
deck.push({
|
|
id: id++,
|
|
rank: rank,
|
|
suit: suit,
|
|
value: getCardValue(rank),
|
|
isJoker: false,
|
|
deckNum: deckNum
|
|
});
|
|
}
|
|
}
|
|
// 大小王(每副牌2张)
|
|
deck.push({ id: id++, rank: '小王', suit: '🃏', value: 15, isJoker: true, deckNum: deckNum });
|
|
deck.push({ id: id++, rank: '大王', suit: '🃏', value: 16, isJoker: true, deckNum: deckNum });
|
|
}
|
|
|
|
return deck;
|
|
}
|
|
|
|
// 获取牌点数(用于比较大小)
|
|
function getCardValue(rank) {
|
|
const values = {
|
|
'2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10,
|
|
'J': 11, 'Q': 12, 'K': 13, 'A': 14
|
|
};
|
|
return values[rank] || 0;
|
|
}
|
|
|
|
// 洗牌
|
|
function shuffleDeck(deck) {
|
|
const shuffled = [...deck];
|
|
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
}
|
|
return shuffled;
|
|
}
|
|
|
|
// ========== 牌型定义 ==========
|
|
const CARD_TYPES = {
|
|
SINGLE: 'single', // 单张
|
|
PAIR: 'pair', // 对子
|
|
TRIPLE: 'triple', // 三张
|
|
TRIPLE_WITH_PAIR: 'triple_pair', // 三带二
|
|
STRAIGHT: 'straight', // 顺子(5+连续)
|
|
CONSECUTIVE_PAIRS: 'consecutive_pairs', // 三连对(3+连续对子)
|
|
STEEL_PLATE: 'steel_plate', // 钢板(2个连续三张)
|
|
BOMB_4: 'bomb4', // 4张炸
|
|
BOMB_5: 'bomb5', // 5张炸
|
|
BOMB_6: 'bomb6', // 6张炸
|
|
BOMB_7: 'bomb7', // 7张炸
|
|
BOMB_8: 'bomb8', // 8张炸
|
|
JOKER_BOMB: 'joker_bomb' // 天王炸(4王)
|
|
};
|
|
|
|
// ========== 游戏状态 ==========
|
|
class GuandanGame {
|
|
constructor() {
|
|
this.players = [
|
|
{ id: 0, cards: [], team: 0, name: '玩家', isAI: false, rank: null },
|
|
{ id: 1, cards: [], team: 1, name: '对手甲', isAI: true, rank: null },
|
|
{ id: 2, cards: [], team: 0, name: '队友', isAI: true, rank: null },
|
|
{ id: 3, cards: [], team: 1, name: '对手乙', isAI: true, rank: null }
|
|
];
|
|
this.currentLevel = '2'; // 当前级牌
|
|
this.wildCard = null; // 百搭牌(红桃级牌)
|
|
this.currentPlayer = 0; // 当前出牌玩家
|
|
this.lastPlay = null; // 上轮出牌
|
|
this.lastPlayer = null; // 上轮出牌玩家
|
|
this.passCount = 0; // 连续不出计数
|
|
this.gameState = 'waiting'; // waiting/dealing/playing/ended
|
|
this.roundPlays = []; // 本轮出牌记录
|
|
this.finishedPlayers = []; // 已出完牌的玩家
|
|
this.teamLevels = { 0: '2', 1: '2' }; // 两队当前级数
|
|
}
|
|
|
|
// 开始新游戏
|
|
startGame() {
|
|
this.currentLevel = this.teamLevels[0]; // 使用我方当前级数
|
|
this.wildCard = { suit: '♥', rank: this.currentLevel };
|
|
|
|
// 生成并洗牌
|
|
const deck = shuffleDeck(generateDeck());
|
|
|
|
// 发牌,每人27张
|
|
for (let i = 0; i < 4; i++) {
|
|
this.players[i].cards = deck.slice(i * 27, (i + 1) * 27);
|
|
this.players[i].cards.sort((a, b) => this.compareCards(a, b));
|
|
this.players[i].rank = null;
|
|
}
|
|
|
|
this.currentPlayer = Math.floor(Math.random() * 4); // 随机起始
|
|
this.lastPlay = null;
|
|
this.lastPlayer = null;
|
|
this.passCount = 0;
|
|
this.gameState = 'playing';
|
|
this.roundPlays = [];
|
|
this.finishedPlayers = [];
|
|
|
|
return {
|
|
success: true,
|
|
firstPlayer: this.currentPlayer,
|
|
wildCard: this.wildCard
|
|
};
|
|
}
|
|
|
|
// 比较两张牌(用于排序)
|
|
compareCards(a, b) {
|
|
// 王最大
|
|
if (a.isJoker && b.isJoker) return b.value - a.value;
|
|
if (a.isJoker) return -1;
|
|
if (b.isJoker) return 1;
|
|
|
|
// 级牌次之
|
|
const aIsLevel = a.rank === this.currentLevel;
|
|
const bIsLevel = b.rank === this.currentLevel;
|
|
if (aIsLevel && bIsLevel) {
|
|
// 红桃级牌(百搭)最大
|
|
if (a.suit === '♥') return -1;
|
|
if (b.suit === '♥') return 1;
|
|
return 0;
|
|
}
|
|
if (aIsLevel) return -1;
|
|
if (bIsLevel) return 1;
|
|
|
|
// 然后按点数
|
|
if (b.value !== a.value) {
|
|
return b.value - a.value;
|
|
}
|
|
|
|
// 同点数按花色
|
|
const suitOrder = { '♠': 0, '♥': 1, '♣': 2, '♦': 3 };
|
|
return suitOrder[a.suit] - suitOrder[b.suit];
|
|
}
|
|
|
|
// 识别牌型(核心逻辑)
|
|
identifyCardType(cards) {
|
|
if (!cards || cards.length === 0) return null;
|
|
|
|
const count = cards.length;
|
|
|
|
// 统计点数和百搭数量
|
|
let wildCount = 0;
|
|
const rankCounts = {};
|
|
|
|
for (const card of cards) {
|
|
if (this.isWildCard(card)) {
|
|
wildCount++;
|
|
} else {
|
|
rankCounts[card.rank] = (rankCounts[card.rank] || 0) + 1;
|
|
}
|
|
}
|
|
|
|
const ranks = Object.keys(rankCounts);
|
|
|
|
// 天王炸(4张王)
|
|
if (count === 4 && cards.every(c => c.isJoker)) {
|
|
return { type: CARD_TYPES.JOKER_BOMB, value: 100, length: 4 };
|
|
}
|
|
|
|
// 炸弹(4-8张相同点数,可配百搭)
|
|
if (count >= 4 && count <= 8) {
|
|
// 检查是否能组成炸弹
|
|
for (const rank of ranks) {
|
|
const need = count - rankCounts[rank];
|
|
if (need <= wildCount && need >= 0) {
|
|
return {
|
|
type: CARD_TYPES[`BOMB_${count}`],
|
|
value: getCardValue(rank) + (count - 4) * 20,
|
|
length: count,
|
|
mainRank: rank
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// 单张
|
|
if (count === 1) {
|
|
return { type: CARD_TYPES.SINGLE, value: this.getCardPower(cards[0]), length: 1 };
|
|
}
|
|
|
|
// 对子
|
|
if (count === 2) {
|
|
// 两张真牌同点数
|
|
if (ranks.length === 1 && wildCount === 0) {
|
|
return { type: CARD_TYPES.PAIR, value: getCardValue(ranks[0]), length: 2, mainRank: ranks[0] };
|
|
}
|
|
// 一真一百搭
|
|
if (ranks.length === 1 && wildCount === 1) {
|
|
return { type: CARD_TYPES.PAIR, value: getCardValue(ranks[0]), length: 2, mainRank: ranks[0] };
|
|
}
|
|
// 两张百搭
|
|
if (ranks.length === 0 && wildCount === 2) {
|
|
return { type: CARD_TYPES.PAIR, value: getCardValue(this.currentLevel), length: 2, mainRank: this.currentLevel };
|
|
}
|
|
}
|
|
|
|
// 三张
|
|
if (count === 3) {
|
|
// 全真
|
|
if (ranks.length === 1 && wildCount === 0) {
|
|
return { type: CARD_TYPES.TRIPLE, value: getCardValue(ranks[0]), length: 3, mainRank: ranks[0] };
|
|
}
|
|
// 配百搭
|
|
if (ranks.length === 1 && wildCount > 0) {
|
|
return { type: CARD_TYPES.TRIPLE, value: getCardValue(ranks[0]), length: 3, mainRank: ranks[0] };
|
|
}
|
|
}
|
|
|
|
// 三带二(5张)
|
|
if (count === 5) {
|
|
// 找三张部分
|
|
for (const rank of ranks) {
|
|
const tripleWild = Math.max(0, 3 - rankCounts[rank]);
|
|
const remainingWild = wildCount - tripleWild;
|
|
const remainingCards = count - rankCounts[rank] - tripleWild;
|
|
|
|
// 检查剩余2张能否组成对子
|
|
const remainingRanks = ranks.filter(r => r !== rank);
|
|
if (remainingRanks.length === 1 && rankCounts[remainingRanks[0]] + remainingWild === 2) {
|
|
return {
|
|
type: CARD_TYPES.TRIPLE_WITH_PAIR,
|
|
value: getCardValue(rank),
|
|
length: 5,
|
|
mainRank: rank
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// 顺子(5+连续单张,不可含级牌、王)
|
|
if (count >= 5) {
|
|
const straightResult = this.checkStraight(cards, rankCounts, wildCount);
|
|
if (straightResult) {
|
|
return {
|
|
type: CARD_TYPES.STRAIGHT,
|
|
value: straightResult.minValue,
|
|
length: count,
|
|
minRank: straightResult.minRank
|
|
};
|
|
}
|
|
}
|
|
|
|
// 三连对(6张=3对连续)
|
|
if (count === 6) {
|
|
const result = this.checkConsecutivePairs(cards, rankCounts, wildCount, 3);
|
|
if (result) {
|
|
return { type: CARD_TYPES.CONSECUTIVE_PAIRS, value: result.minValue, length: 6, minRank: result.minRank };
|
|
}
|
|
}
|
|
|
|
// 钢板(6张=2个连续三张)
|
|
if (count === 6) {
|
|
const result = this.checkSteelPlate(cards, rankCounts, wildCount);
|
|
if (result) {
|
|
return { type: CARD_TYPES.STEEL_PLATE, value: result.minValue, length: 6, minRank: result.minRank };
|
|
}
|
|
}
|
|
|
|
return null; // 非法牌型
|
|
}
|
|
|
|
// 检查是否为百搭牌
|
|
isWildCard(card) {
|
|
return card.suit === '♥' && card.rank === this.currentLevel;
|
|
}
|
|
|
|
// 获取牌的实际威力值
|
|
getCardPower(card) {
|
|
if (card.isJoker) return 100 + card.value; // 王
|
|
if (this.isWildCard(card)) return 90; // 百搭
|
|
if (card.rank === this.currentLevel) return 80; // 其他级牌
|
|
return card.value;
|
|
}
|
|
|
|
// 检查顺子
|
|
checkStraight(cards, rankCounts, wildCount) {
|
|
const count = cards.length;
|
|
const validRanks = ['3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
|
|
|
|
// 顺子不能含级牌和王
|
|
for (const card of cards) {
|
|
if (card.isJoker) return null;
|
|
if (card.rank === this.currentLevel && !this.isWildCard(card)) return null;
|
|
}
|
|
|
|
// 尝试找到连续序列
|
|
const rankValues = {};
|
|
for (const rank of validRanks) {
|
|
rankValues[rank] = getCardValue(rank);
|
|
}
|
|
|
|
// 收集已有的牌点数
|
|
const hasRanks = new Set();
|
|
for (const rank of Object.keys(rankCounts)) {
|
|
if (rankCounts[rank] > 0) hasRanks.add(rank);
|
|
}
|
|
|
|
// 尝试每个可能的起始点
|
|
for (let i = 0; i <= validRanks.length - count; i++) {
|
|
const sequence = validRanks.slice(i, i + count);
|
|
let needWild = 0;
|
|
for (const rank of sequence) {
|
|
if (!hasRanks.has(rank)) needWild++;
|
|
}
|
|
if (needWild <= wildCount) {
|
|
return { minValue: getCardValue(sequence[0]), minRank: sequence[0] };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// 检查三连对
|
|
checkConsecutivePairs(cards, rankCounts, wildCount, pairCount) {
|
|
const validRanks = ['3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
|
|
const count = pairCount * 2;
|
|
|
|
// 不能含级牌和王
|
|
for (const card of cards) {
|
|
if (card.isJoker) return null;
|
|
if (card.rank === this.currentLevel && !this.isWildCard(card)) return null;
|
|
}
|
|
|
|
for (let i = 0; i <= validRanks.length - pairCount; i++) {
|
|
const sequence = validRanks.slice(i, i + pairCount);
|
|
let needWild = 0;
|
|
for (const rank of sequence) {
|
|
const has = rankCounts[rank] || 0;
|
|
needWild += Math.max(0, 2 - has);
|
|
}
|
|
if (needWild <= wildCount) {
|
|
return { minValue: getCardValue(sequence[0]), minRank: sequence[0] };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// 检查钢板(2个连续三张)
|
|
checkSteelPlate(cards, rankCounts, wildCount) {
|
|
const validRanks = ['3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
|
|
|
|
for (let i = 0; i <= validRanks.length - 2; i++) {
|
|
const rank1 = validRanks[i];
|
|
const rank2 = validRanks[i + 1];
|
|
|
|
const need1 = Math.max(0, 3 - (rankCounts[rank1] || 0));
|
|
const need2 = Math.max(0, 3 - (rankCounts[rank2] || 0));
|
|
const totalNeed = need1 + need2;
|
|
|
|
if (totalNeed <= wildCount) {
|
|
return { minValue: getCardValue(rank1), minRank: rank1 };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// 验证出牌是否合法
|
|
validatePlay(playerId, cardIds) {
|
|
const player = this.players[playerId];
|
|
const cards = cardIds.map(id => player.cards.find(c => c.id === id)).filter(Boolean);
|
|
|
|
if (cards.length !== cardIds.length) {
|
|
return { success: false, message: '牌不存在' };
|
|
}
|
|
|
|
const cardType = this.identifyCardType(cards);
|
|
if (!cardType) {
|
|
return { success: false, message: '非法牌型' };
|
|
}
|
|
|
|
// 如果是新一轮或没人出牌,任意合法牌型都可出
|
|
if (!this.lastPlay || this.passCount >= 3) {
|
|
return { success: true, cardType, cards };
|
|
}
|
|
|
|
// 必须牌型相同、长度相同
|
|
if (cardType.type !== this.lastPlay.type || cardType.length !== this.lastPlay.length) {
|
|
// 炸弹可以管任何牌
|
|
if (this.isBomb(cardType.type)) {
|
|
return { success: true, cardType, cards, isBomb: true };
|
|
}
|
|
return { success: false, message: '牌型或长度不匹配' };
|
|
}
|
|
|
|
// 比较大小
|
|
if (cardType.value > this.lastPlay.value) {
|
|
return { success: true, cardType, cards };
|
|
}
|
|
|
|
return { success: false, message: '牌不够大' };
|
|
}
|
|
|
|
// 判断是否为炸弹
|
|
isBomb(type) {
|
|
return [CARD_TYPES.BOMB_4, CARD_TYPES.BOMB_5, CARD_TYPES.BOMB_6,
|
|
CARD_TYPES.BOMB_7, CARD_TYPES.BOMB_8, CARD_TYPES.JOKER_BOMB].includes(type);
|
|
}
|
|
|
|
// 执行出牌
|
|
playCards(playerId, cardIds) {
|
|
const validation = this.validatePlay(playerId, cardIds);
|
|
if (!validation.success) {
|
|
return validation;
|
|
}
|
|
|
|
const player = this.players[playerId];
|
|
|
|
// 从手牌移除
|
|
player.cards = player.cards.filter(c => !cardIds.includes(c.id));
|
|
|
|
// 更新游戏状态
|
|
this.lastPlay = validation.cardType;
|
|
this.lastPlayer = playerId;
|
|
this.passCount = 0;
|
|
this.roundPlays.push({ player: playerId, cards: validation.cards, type: validation.cardType });
|
|
|
|
// 检查是否出完
|
|
if (player.cards.length === 0 && !this.finishedPlayers.includes(playerId)) {
|
|
this.finishedPlayers.push(playerId);
|
|
player.rank = this.finishedPlayers.length;
|
|
}
|
|
|
|
// 检查游戏是否结束
|
|
if (this.finishedPlayers.length >= 3) {
|
|
this.endGame();
|
|
} else {
|
|
// 下一个玩家
|
|
this.currentPlayer = (playerId + 1) % 4;
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
cardType: validation.cardType,
|
|
cards: validation.cards,
|
|
isFinished: player.cards.length === 0,
|
|
nextPlayer: this.currentPlayer
|
|
};
|
|
}
|
|
|
|
// 不出
|
|
pass(playerId) {
|
|
if (playerId !== this.currentPlayer) {
|
|
return { success: false, message: '不是你的回合' };
|
|
}
|
|
|
|
// 如果是首轮或自己上轮出的牌,不能不出
|
|
if (this.lastPlayer === playerId || !this.lastPlay) {
|
|
return { success: false, message: '必须出牌' };
|
|
}
|
|
|
|
this.passCount++;
|
|
|
|
// 连续3人不出,本轮结束
|
|
if (this.passCount >= 3) {
|
|
this.roundPlays = [];
|
|
this.passCount = 0;
|
|
// 最后出牌者获得出牌权
|
|
this.currentPlayer = this.lastPlayer;
|
|
return { success: true, roundEnd: true, nextPlayer: this.currentPlayer };
|
|
}
|
|
|
|
this.currentPlayer = (playerId + 1) % 4;
|
|
return { success: true, nextPlayer: this.currentPlayer };
|
|
}
|
|
|
|
// AI决策
|
|
makeAIDecision(playerId) {
|
|
const player = this.players[playerId];
|
|
|
|
// 如果是新一轮,出最小的牌
|
|
if (!this.lastPlay || this.passCount >= 3) {
|
|
return this.findSmallestPlay(player);
|
|
}
|
|
|
|
// 尝试找能管上的牌
|
|
const play = this.findPlayToBeat(player, this.lastPlay);
|
|
if (play) {
|
|
return { action: 'play', cards: play };
|
|
}
|
|
|
|
// 不出
|
|
return { action: 'pass' };
|
|
}
|
|
|
|
// 找最小的出牌(AI用)
|
|
findSmallestPlay(player) {
|
|
// 优先出单张、对子
|
|
const cards = player.cards;
|
|
|
|
// 尝试出单张(最小的非级牌非王)
|
|
const normalCards = cards.filter(c => !c.isJoker && c.rank !== this.currentLevel);
|
|
if (normalCards.length > 0) {
|
|
const minCard = normalCards.reduce((min, c) =>
|
|
this.getCardPower(c) < this.getCardPower(min) ? c : min
|
|
);
|
|
return { action: 'play', cards: [minCard] };
|
|
}
|
|
|
|
// 出级牌单张
|
|
const levelCards = cards.filter(c => c.rank === this.currentLevel && !this.isWildCard(c));
|
|
if (levelCards.length > 0) {
|
|
return { action: 'play', cards: [levelCards[0]] };
|
|
}
|
|
|
|
// 出百搭
|
|
const wildCards = cards.filter(c => this.isWildCard(c));
|
|
if (wildCards.length > 0) {
|
|
return { action: 'play', cards: [wildCards[0]] };
|
|
}
|
|
|
|
// 出对子
|
|
// ...简化处理,出第一张
|
|
return { action: 'play', cards: [cards[0]] };
|
|
}
|
|
|
|
// 找能管上的牌(AI用)- 简化版
|
|
findPlayToBeat(player, lastPlay) {
|
|
// 这里简化处理,只检查是否有炸弹
|
|
const cards = player.cards;
|
|
|
|
// 检查是否有更大的炸弹
|
|
const rankCounts = {};
|
|
let wildCount = 0;
|
|
for (const card of cards) {
|
|
if (this.isWildCard(card)) {
|
|
wildCount++;
|
|
} else {
|
|
rankCounts[card.rank] = (rankCounts[card.rank] || 0) + 1;
|
|
}
|
|
}
|
|
|
|
// 找4张炸弹
|
|
for (const rank of Object.keys(rankCounts)) {
|
|
if (rankCounts[rank] + wildCount >= 4) {
|
|
const needWild = Math.max(0, 4 - rankCounts[rank]);
|
|
const playCards = cards.filter(c => c.rank === rank && !this.isWildCard(c)).slice(0, 4 - needWild);
|
|
// 添加百搭
|
|
if (needWild > 0) {
|
|
playCards.push(...cards.filter(c => this.isWildCard(c)).slice(0, needWild));
|
|
}
|
|
return playCards;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// 结束游戏
|
|
endGame() {
|
|
this.gameState = 'ended';
|
|
|
|
// 给最后一个玩家排名
|
|
for (const player of this.players) {
|
|
if (player.rank === null) {
|
|
player.rank = 4;
|
|
}
|
|
}
|
|
|
|
// 计算升级
|
|
const team0Ranks = this.players.filter(p => p.team === 0).map(p => p.rank).sort((a, b) => a - b);
|
|
const team1Ranks = this.players.filter(p => p.team === 1).map(p => p.rank).sort((a, b) => a - b);
|
|
|
|
let upgrade = 0;
|
|
let winner = null;
|
|
|
|
// 判断哪队获胜(有头游的队伍)
|
|
const headPlayer = this.players.find(p => p.rank === 1);
|
|
winner = headPlayer.team;
|
|
|
|
const winnerRanks = winner === 0 ? team0Ranks : team1Ranks;
|
|
|
|
// 计算升级数
|
|
if (winnerRanks[0] === 1 && winnerRanks[1] === 2) {
|
|
upgrade = 3; // 双上
|
|
} else if (winnerRanks[0] === 1 && winnerRanks[1] === 3) {
|
|
upgrade = 2; // 单上
|
|
} else {
|
|
upgrade = 1; // 险胜
|
|
}
|
|
|
|
return {
|
|
winner: winner,
|
|
upgrade: upgrade,
|
|
ranks: this.players.map(p => ({ name: p.name, rank: p.rank, team: p.team }))
|
|
};
|
|
}
|
|
|
|
// 开始下一局
|
|
nextRound(winner, upgrade) {
|
|
// 更新级数
|
|
const currentLevelIndex = RANKS.indexOf(this.teamLevels[winner]);
|
|
const newLevelIndex = Math.min(currentLevelIndex + upgrade, RANKS.length - 1);
|
|
this.teamLevels[winner] = RANKS[newLevelIndex];
|
|
|
|
// 检查是否打过A
|
|
if (this.teamLevels[winner] === 'A' && upgrade === 3) {
|
|
return { gameOver: true, finalWinner: winner };
|
|
}
|
|
|
|
// 重新开始
|
|
return this.startGame();
|
|
}
|
|
}
|
|
|
|
// 导出
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = { GuandanGame, CARD_TYPES, generateDeck, shuffleDeck };
|
|
}
|