601 lines
20 KiB
JavaScript
601 lines
20 KiB
JavaScript
// 卡片类
|
||
class Card {
|
||
constructor(suit, rank, value) {
|
||
this.suit = suit; // 花色:spades, hearts, clubs, diamonds, joker
|
||
this.rank = rank; // 点数:A, 2, 3, ..., 10, J, Q, K, 小王, 大王
|
||
this.value = value; // 牌值:用于比较大小
|
||
this.selected = false;
|
||
}
|
||
|
||
// 获取卡片的HTML表示
|
||
getHTML(selected = false) {
|
||
const cardDiv = document.createElement('div');
|
||
cardDiv.className = `card ${this.suit} ${selected ? 'selected' : ''}`;
|
||
cardDiv.dataset.suit = this.suit;
|
||
cardDiv.dataset.rank = this.rank;
|
||
cardDiv.dataset.value = this.value;
|
||
|
||
// 花色符号
|
||
const suitSymbol = {
|
||
spades: '♠',
|
||
hearts: '♥',
|
||
clubs: '♣',
|
||
diamonds: '♦'
|
||
}[this.suit] || this.rank;
|
||
|
||
// 创建卡片内容
|
||
const rankTop = document.createElement('div');
|
||
rankTop.className = 'rank-top';
|
||
rankTop.textContent = this.rank;
|
||
|
||
const suitMiddle = document.createElement('div');
|
||
suitMiddle.className = 'suit-middle';
|
||
suitMiddle.textContent = suitSymbol;
|
||
|
||
const rankBottom = document.createElement('div');
|
||
rankBottom.className = 'rank-bottom';
|
||
rankBottom.textContent = this.rank;
|
||
|
||
// 组装卡片
|
||
cardDiv.appendChild(rankTop);
|
||
cardDiv.appendChild(suitMiddle);
|
||
cardDiv.appendChild(rankBottom);
|
||
|
||
return cardDiv;
|
||
}
|
||
|
||
// 获取背面HTML
|
||
static getBackHTML() {
|
||
const cardDiv = document.createElement('div');
|
||
cardDiv.className = 'card card-back';
|
||
return cardDiv;
|
||
}
|
||
}
|
||
|
||
// 游戏类
|
||
class LandlordGame {
|
||
constructor() {
|
||
this.players = [
|
||
{ id: 0, name: '我', cards: [], role: '', lastPlay: [] },
|
||
{ id: 1, name: '电脑玩家1', cards: [], role: '', lastPlay: [] },
|
||
{ id: 2, name: '电脑玩家2', cards: [], role: '', lastPlay: [] }
|
||
];
|
||
this.deck = [];
|
||
this.landlordCards = [];
|
||
this.currentPlayer = 0;
|
||
this.gameStatus = 'waiting'; // waiting, calling, playing, ended
|
||
this.landlord = -1;
|
||
this.lastPlayPlayer = -1;
|
||
this.lastPlayCards = [];
|
||
this.score = 0;
|
||
this.callLandlordCount = 0;
|
||
|
||
this.initializeElements();
|
||
this.initializeEventListeners();
|
||
this.initializeDeck();
|
||
}
|
||
|
||
// 初始化DOM元素
|
||
initializeElements() {
|
||
this.elements = {
|
||
startGame: document.getElementById('start-game'),
|
||
gameStatus: document.getElementById('game-status'),
|
||
currentPlayer: document.getElementById('current-player'),
|
||
landlordCardsArea: document.getElementById('landlord-cards-area'),
|
||
landlordButtons: document.querySelector('.landlord-buttons'),
|
||
callLandlord: document.getElementById('call-landlord'),
|
||
noCall: document.getElementById('no-call'),
|
||
playButtons: document.querySelector('.play-buttons'),
|
||
playCards: document.getElementById('play-cards'),
|
||
pass: document.getElementById('pass'),
|
||
currentPlayCards: document.getElementById('current-play-cards'),
|
||
currentPlayInfo: document.getElementById('current-play-info'),
|
||
playerCards: [
|
||
document.getElementById('self-cards'),
|
||
document.getElementById('player-1-cards'),
|
||
document.getElementById('player-2-cards')
|
||
],
|
||
playerCardsCount: [
|
||
document.getElementById('self-cards-count'),
|
||
document.getElementById('player-1-cards-count'),
|
||
document.getElementById('player-2-cards-count')
|
||
],
|
||
playerRoles: [
|
||
document.getElementById('self-role'),
|
||
document.getElementById('player-1-role'),
|
||
document.getElementById('player-2-role')
|
||
],
|
||
lastPlay: [
|
||
document.getElementById('self-last-play'),
|
||
document.getElementById('player-1-last-play'),
|
||
document.getElementById('player-2-last-play')
|
||
],
|
||
score: document.getElementById('score')
|
||
};
|
||
}
|
||
|
||
// 初始化事件监听器
|
||
initializeEventListeners() {
|
||
this.elements.startGame.addEventListener('click', () => this.startGame());
|
||
this.elements.callLandlord.addEventListener('click', () => this.callLandlord());
|
||
this.elements.noCall.addEventListener('click', () => this.noCall());
|
||
this.elements.playCards.addEventListener('click', () => this.playSelectedCards());
|
||
this.elements.pass.addEventListener('click', () => this.pass());
|
||
}
|
||
|
||
// 初始化牌组
|
||
initializeDeck() {
|
||
this.deck = [];
|
||
const suits = ['spades', 'hearts', 'clubs', 'diamonds'];
|
||
const ranks = ['3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A', '2'];
|
||
const values = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
|
||
|
||
// 添加普通牌
|
||
for (let i = 0; i < ranks.length; i++) {
|
||
for (const suit of suits) {
|
||
this.deck.push(new Card(suit, ranks[i], values[i]));
|
||
}
|
||
}
|
||
|
||
// 添加大小王
|
||
this.deck.push(new Card('joker', '小王', 16));
|
||
this.deck.push(new Card('joker', '大王', 17));
|
||
}
|
||
|
||
// 洗牌算法
|
||
shuffleDeck() {
|
||
for (let i = this.deck.length - 1; i > 0; i--) {
|
||
const j = Math.floor(Math.random() * (i + 1));
|
||
[this.deck[i], this.deck[j]] = [this.deck[j], this.deck[i]];
|
||
}
|
||
}
|
||
|
||
// 开始游戏
|
||
startGame() {
|
||
this.gameStatus = 'calling';
|
||
this.currentPlayer = 0;
|
||
this.landlord = -1;
|
||
this.lastPlayPlayer = -1;
|
||
this.lastPlayCards = [];
|
||
this.callLandlordCount = 0;
|
||
|
||
// 重置玩家数据
|
||
for (const player of this.players) {
|
||
player.cards = [];
|
||
player.role = '';
|
||
player.lastPlay = [];
|
||
}
|
||
|
||
// 洗牌发牌
|
||
this.initializeDeck();
|
||
this.shuffleDeck();
|
||
this.dealCards();
|
||
|
||
// 更新UI
|
||
this.updateGameStatus('开始叫地主');
|
||
this.updateCurrentPlayer();
|
||
this.renderAllCards();
|
||
this.elements.startGame.style.display = 'none';
|
||
this.elements.landlordButtons.style.display = 'flex';
|
||
this.elements.playButtons.style.display = 'none';
|
||
this.clearCurrentPlay();
|
||
}
|
||
|
||
// 发牌
|
||
dealCards() {
|
||
// 发牌给三个玩家
|
||
for (let i = 0; i < 17; i++) {
|
||
this.players[0].cards.push(this.deck.pop());
|
||
this.players[1].cards.push(this.deck.pop());
|
||
this.players[2].cards.push(this.deck.pop());
|
||
}
|
||
|
||
// 剩余的3张作为地主牌
|
||
this.landlordCards = [...this.deck];
|
||
this.deck = [];
|
||
|
||
// 排序玩家的牌
|
||
for (const player of this.players) {
|
||
this.sortCards(player.cards);
|
||
}
|
||
}
|
||
|
||
// 排序卡片
|
||
sortCards(cards) {
|
||
cards.sort((a, b) => a.value - b.value);
|
||
}
|
||
|
||
// 叫地主
|
||
callLandlord() {
|
||
this.landlord = this.currentPlayer;
|
||
this.players[this.currentPlayer].role = '地主';
|
||
this.players[(this.currentPlayer + 1) % 3].role = '农民';
|
||
this.players[(this.currentPlayer + 2) % 3].role = '农民';
|
||
|
||
// 将地主牌加入地主手中
|
||
this.players[this.currentPlayer].cards.push(...this.landlordCards);
|
||
this.sortCards(this.players[this.currentPlayer].cards);
|
||
|
||
// 开始游戏
|
||
this.gameStatus = 'playing';
|
||
this.updateGameStatus('游戏开始,地主出牌');
|
||
this.updatePlayerRoles();
|
||
this.renderAllCards();
|
||
this.elements.landlordButtons.style.display = 'none';
|
||
this.elements.playButtons.style.display = 'flex';
|
||
|
||
// 如果是电脑地主,自动出牌
|
||
if (this.currentPlayer !== 0) {
|
||
setTimeout(() => this.computerPlay(), 1000);
|
||
}
|
||
}
|
||
|
||
// 不叫地主
|
||
noCall() {
|
||
this.callLandlordCount++;
|
||
this.currentPlayer = (this.currentPlayer + 1) % 3;
|
||
this.updateCurrentPlayer();
|
||
|
||
// 如果三个玩家都不叫,重新开始
|
||
if (this.callLandlordCount === 3) {
|
||
this.updateGameStatus('无人叫地主,重新开始');
|
||
setTimeout(() => this.startGame(), 1500);
|
||
return;
|
||
}
|
||
|
||
// 如果是电脑玩家,自动选择
|
||
if (this.currentPlayer !== 0) {
|
||
setTimeout(() => this.computerCallLandlord(), 1000);
|
||
}
|
||
}
|
||
|
||
// 电脑叫地主
|
||
computerCallLandlord() {
|
||
// 简单AI:如果有王或多张大牌,叫地主
|
||
const hasBigCards = this.players[this.currentPlayer].cards.some(card =>
|
||
card.value >= 16 ||
|
||
(card.value >= 14 && this.players[this.currentPlayer].cards.filter(c => c.value >= 14).length >= 3)
|
||
);
|
||
|
||
if (hasBigCards) {
|
||
this.callLandlord();
|
||
} else {
|
||
this.noCall();
|
||
}
|
||
}
|
||
|
||
// 选择卡片
|
||
toggleCardSelection(cardElement) {
|
||
if (this.gameStatus !== 'playing' || this.currentPlayer !== 0) return;
|
||
|
||
cardElement.classList.toggle('selected');
|
||
const selected = cardElement.classList.contains('selected');
|
||
const suit = cardElement.dataset.suit;
|
||
const rank = cardElement.dataset.rank;
|
||
const value = parseInt(cardElement.dataset.value);
|
||
|
||
// 更新卡片对象的选中状态
|
||
const card = this.players[0].cards.find(c =>
|
||
c.suit === suit && c.rank === rank && c.value === value
|
||
);
|
||
if (card) {
|
||
card.selected = selected;
|
||
}
|
||
}
|
||
|
||
// 出牌
|
||
playSelectedCards() {
|
||
if (this.gameStatus !== 'playing' || this.currentPlayer !== 0) return;
|
||
|
||
// 获取选中的卡片
|
||
const selectedCards = this.players[0].cards.filter(card => card.selected);
|
||
if (selectedCards.length === 0) return;
|
||
|
||
// 验证出牌是否合法
|
||
if (this.isValidPlay(selectedCards)) {
|
||
// 更新游戏状态
|
||
this.players[0].lastPlay = selectedCards;
|
||
this.lastPlayPlayer = 0;
|
||
this.lastPlayCards = selectedCards;
|
||
|
||
// 从手中移除出的牌
|
||
this.players[0].cards = this.players[0].cards.filter(card => !card.selected);
|
||
|
||
// 检查是否获胜
|
||
if (this.players[0].cards.length === 0) {
|
||
this.endGame(0);
|
||
return;
|
||
}
|
||
|
||
// 更新UI
|
||
this.renderPlayerCards(0);
|
||
this.updateCardsCount(0);
|
||
this.updateCurrentPlay(0, selectedCards);
|
||
this.updateSelfLastPlay(selectedCards);
|
||
|
||
// 切换到下一个玩家
|
||
this.currentPlayer = 1;
|
||
this.updateCurrentPlayer();
|
||
|
||
// 电脑玩家自动出牌
|
||
setTimeout(() => this.computerPlay(), 1000);
|
||
} else {
|
||
alert('出牌不合法,请重新选择');
|
||
}
|
||
}
|
||
|
||
// 不出牌
|
||
pass() {
|
||
if (this.gameStatus !== 'playing' || this.currentPlayer !== 0) return;
|
||
|
||
// 更新游戏状态
|
||
this.players[0].lastPlay = [];
|
||
this.updateGameStatus('玩家不出牌');
|
||
|
||
// 切换到下一个玩家
|
||
this.currentPlayer = 1;
|
||
this.updateCurrentPlayer();
|
||
|
||
// 电脑玩家自动出牌
|
||
setTimeout(() => this.computerPlay(), 1000);
|
||
}
|
||
|
||
// 电脑出牌
|
||
computerPlay() {
|
||
const player = this.players[this.currentPlayer];
|
||
const playableCards = this.findPlayableCards(player.cards);
|
||
|
||
if (playableCards.length > 0) {
|
||
// 选择最合适的牌组
|
||
const selectedCards = this.chooseBestCards(playableCards);
|
||
|
||
// 更新游戏状态
|
||
player.lastPlay = selectedCards;
|
||
this.lastPlayPlayer = this.currentPlayer;
|
||
this.lastPlayCards = selectedCards;
|
||
|
||
// 从手中移除出的牌
|
||
player.cards = player.cards.filter(card => !selectedCards.includes(card));
|
||
|
||
// 检查是否获胜
|
||
if (player.cards.length === 0) {
|
||
this.endGame(this.currentPlayer);
|
||
return;
|
||
}
|
||
|
||
// 更新UI
|
||
this.renderPlayerCards(this.currentPlayer);
|
||
this.updateCardsCount(this.currentPlayer);
|
||
this.updateCurrentPlay(this.currentPlayer, selectedCards);
|
||
this.updateComputerLastPlay(this.currentPlayer, selectedCards);
|
||
|
||
this.updateGameStatus(`${player.name} 出牌`);
|
||
} else {
|
||
// 不出牌
|
||
player.lastPlay = [];
|
||
this.updateGameStatus(`${player.name} 不出牌`);
|
||
}
|
||
|
||
// 切换到下一个玩家
|
||
this.currentPlayer = (this.currentPlayer + 1) % 3;
|
||
this.updateCurrentPlayer();
|
||
|
||
// 如果下一个是电脑,继续自动出牌
|
||
if (this.currentPlayer !== 0) {
|
||
setTimeout(() => this.computerPlay(), 1000);
|
||
}
|
||
}
|
||
|
||
// 查找可出的牌组
|
||
findPlayableCards(cards) {
|
||
const playable = [];
|
||
|
||
// 如果是第一个出牌,可以出任何牌组
|
||
if (this.lastPlayPlayer === -1) {
|
||
// 简单实现:找出所有可能的单牌、对子、顺子等
|
||
// 这里简化处理,只考虑单牌
|
||
for (const card of cards) {
|
||
playable.push([card]);
|
||
}
|
||
} else {
|
||
// 必须出比上一次大的同类型牌
|
||
const lastType = this.getCardType(this.lastPlayCards);
|
||
const lastMaxValue = Math.max(...this.lastPlayCards.map(c => c.value));
|
||
|
||
if (lastType === 'single') {
|
||
// 找比lastMaxValue大的单牌
|
||
const singles = cards.filter(card => card.value > lastMaxValue);
|
||
for (const card of singles) {
|
||
playable.push([card]);
|
||
}
|
||
}
|
||
}
|
||
|
||
return playable;
|
||
}
|
||
|
||
// 选择最好的牌组
|
||
chooseBestCards(playableCards) {
|
||
// 简单AI:选择最小的可出牌组
|
||
if (playableCards.length === 0) return [];
|
||
|
||
// 由于只允许单牌,直接选择最小的可出牌
|
||
return playableCards[0];
|
||
}
|
||
|
||
// 验证出牌是否合法
|
||
isValidPlay(cards) {
|
||
if (cards.length === 0) return false;
|
||
|
||
// 简化版:只允许出单牌
|
||
if (cards.length !== 1) {
|
||
return false;
|
||
}
|
||
|
||
// 如果是第一个出牌
|
||
if (this.lastPlayPlayer === -1) {
|
||
return true;
|
||
}
|
||
|
||
// 检查牌型是否相同(都是单牌)
|
||
const currentType = this.getCardType(cards);
|
||
const lastType = this.getCardType(this.lastPlayCards);
|
||
|
||
if (currentType !== 'single' || lastType !== 'single') {
|
||
return false;
|
||
}
|
||
|
||
// 检查大小
|
||
const currentMax = Math.max(...cards.map(c => c.value));
|
||
const lastMax = Math.max(...this.lastPlayCards.map(c => c.value));
|
||
|
||
return currentMax > lastMax;
|
||
}
|
||
|
||
// 获取牌型
|
||
getCardType(cards) {
|
||
if (cards.length === 0) return 'empty';
|
||
if (cards.length === 1) return 'single';
|
||
if (cards.length === 2) {
|
||
if (cards[0].value === cards[1].value) return 'pair';
|
||
if (cards[0].value === 16 && cards[1].value === 17) return 'rocket';
|
||
return 'invalid';
|
||
}
|
||
// 这里可以扩展更多牌型判断
|
||
return 'invalid';
|
||
}
|
||
|
||
// 结束游戏
|
||
endGame(winner) {
|
||
this.gameStatus = 'ended';
|
||
const winnerPlayer = this.players[winner];
|
||
const isLandlordWin = winnerPlayer.role === '地主';
|
||
|
||
if (isLandlordWin) {
|
||
this.score += 200;
|
||
this.updateGameStatus(`地主 ${winnerPlayer.name} 获胜!`);
|
||
} else {
|
||
this.score -= 100;
|
||
this.updateGameStatus(`农民 ${winnerPlayer.name} 获胜!`);
|
||
}
|
||
|
||
// 更新UI
|
||
this.elements.landlordButtons.style.display = 'none';
|
||
this.elements.playButtons.style.display = 'none';
|
||
this.elements.startGame.style.display = 'block';
|
||
this.elements.startGame.textContent = '再来一局';
|
||
this.updateScore();
|
||
}
|
||
|
||
// 更新游戏状态
|
||
updateGameStatus(status) {
|
||
this.elements.gameStatus.textContent = status;
|
||
}
|
||
|
||
// 更新当前玩家
|
||
updateCurrentPlayer() {
|
||
this.elements.currentPlayer.textContent = `当前玩家:${this.players[this.currentPlayer].name}`;
|
||
}
|
||
|
||
// 更新玩家角色
|
||
updatePlayerRoles() {
|
||
for (let i = 0; i < 3; i++) {
|
||
this.elements.playerRoles[i].textContent = this.players[i].role;
|
||
}
|
||
}
|
||
|
||
// 渲染所有卡片
|
||
renderAllCards() {
|
||
for (let i = 0; i < 3; i++) {
|
||
this.renderPlayerCards(i);
|
||
this.updateCardsCount(i);
|
||
}
|
||
this.renderLandlordCards();
|
||
}
|
||
|
||
// 渲染玩家卡片
|
||
renderPlayerCards(playerIndex) {
|
||
const container = this.elements.playerCards[playerIndex];
|
||
container.innerHTML = '';
|
||
|
||
const player = this.players[playerIndex];
|
||
|
||
if (playerIndex === 0) {
|
||
// 自己的卡片,可以选择
|
||
for (const card of player.cards) {
|
||
const cardElement = card.getHTML(card.selected);
|
||
cardElement.addEventListener('click', () => this.toggleCardSelection(cardElement));
|
||
container.appendChild(cardElement);
|
||
}
|
||
} else {
|
||
// 电脑玩家的卡片,显示背面
|
||
for (let i = 0; i < player.cards.length; i++) {
|
||
const cardElement = Card.getBackHTML();
|
||
container.appendChild(cardElement);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 渲染地主牌
|
||
renderLandlordCards() {
|
||
this.elements.landlordCardsArea.innerHTML = '';
|
||
for (const card of this.landlordCards) {
|
||
const cardElement = card.getHTML();
|
||
this.elements.landlordCardsArea.appendChild(cardElement);
|
||
}
|
||
}
|
||
|
||
// 更新牌数
|
||
updateCardsCount(playerIndex) {
|
||
this.elements.playerCardsCount[playerIndex].textContent = this.players[playerIndex].cards.length;
|
||
}
|
||
|
||
// 更新当前出牌
|
||
updateCurrentPlay(playerIndex, cards) {
|
||
this.elements.currentPlayInfo.textContent = `${this.players[playerIndex].name} 出牌:`;
|
||
this.elements.currentPlayCards.innerHTML = '';
|
||
|
||
for (const card of cards) {
|
||
const cardElement = card.getHTML();
|
||
this.elements.currentPlayCards.appendChild(cardElement);
|
||
}
|
||
}
|
||
|
||
// 清空当前出牌
|
||
clearCurrentPlay() {
|
||
this.elements.currentPlayInfo.textContent = '当前出牌:';
|
||
this.elements.currentPlayCards.innerHTML = '';
|
||
}
|
||
|
||
// 更新自己的上次出牌
|
||
updateSelfLastPlay(cards) {
|
||
const container = this.elements.lastPlay[0];
|
||
container.innerHTML = '';
|
||
|
||
for (const card of cards) {
|
||
const cardElement = card.getHTML();
|
||
container.appendChild(cardElement);
|
||
}
|
||
}
|
||
|
||
// 更新电脑玩家的上次出牌
|
||
updateComputerLastPlay(playerIndex, cards) {
|
||
const container = this.elements.lastPlay[playerIndex];
|
||
container.innerHTML = '';
|
||
|
||
for (const card of cards) {
|
||
const cardElement = card.getHTML();
|
||
container.appendChild(cardElement);
|
||
}
|
||
}
|
||
|
||
// 更新分数
|
||
updateScore() {
|
||
this.elements.score.textContent = this.score;
|
||
}
|
||
}
|
||
|
||
// 初始化游戏
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
window.game = new LandlordGame();
|
||
}); |