zhangjian/game.js

2179 lines
80 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const CHARACTERS = [
{ name: '刘备', hp: 4, color: '#4169E1', skill: '仁德', skillDesc: '可以将任意手牌赠予其他角色' },
{ name: '曹操', hp: 4, color: '#8B4513', skill: '奸雄', skillDesc: '受到伤害后,可以获得造成伤害的牌' },
{ name: '孙权', hp: 4, color: '#228B22', skill: '制衡', skillDesc: '可以弃置任意张手牌,然后摸等量的牌' },
{ name: '关羽', hp: 4, color: '#DC143C', skill: '武圣', skillDesc: '可以将红色手牌当杀使用' },
{ name: '张飞', hp: 4, color: '#FF4500', skill: '咆哮', skillDesc: '出牌阶段可以使用任意张杀' },
{ name: '赵云', hp: 4, color: '#1E90FF', skill: '龙胆', skillDesc: '可以将杀当闪使用,或将闪当杀使用' },
{ name: '诸葛亮', hp: 3, color: '#9370DB', skill: '观星', skillDesc: '回合开始时可以查看牌堆顶的牌' },
{ name: '黄月英', hp: 3, color: '#FF69B4', skill: '集智', skillDesc: '使用锦囊牌后可以摸一张牌' },
{ name: '吕布', hp: 4, color: '#8B0000', skill: '无双', skillDesc: '使用杀时,目标需要使用两张闪' },
{ name: '貂蝉', hp: 3, color: '#FF1493', skill: '离间', skillDesc: '可以让两名男性角色决斗' }
];
const IDENTITIES = {
lord: { name: '主公', class: 'lord' },
loyalist: { name: '忠臣', class: 'loyalist' },
rebel: { name: '反贼', class: 'rebel' },
spy: { name: '内奸', class: 'spy' }
};
const EQUIPMENT_EFFECTS = {
'诸葛连弩': { range: 1, effect: '每回合可以使用任意张杀' },
'青龙偃月刀': { range: 3, effect: '攻击范围3' },
'丈八蛇矛': { range: 3, effect: '攻击范围3' },
'贯石斧': { range: 3, effect: '攻击范围3' },
'方天画戟': { range: 4, effect: '攻击范围4' },
'八卦阵': { effect: '需要判定是否受到伤害' },
'仁王盾': { effect: '黑色杀无效' },
'藤甲': { effect: '普通杀无效,火杀伤害+1' },
'的卢': { effect: '其他角色计算距离+1' },
'赤兔': { effect: '计算与其他角色距离-1' }
};
const CARD_IMAGES = {
'杀': {
background: 'linear-gradient(135deg, #8B0000 0%, #DC143C 50%, #FF6347 100%)',
icon: '⚔️',
pattern: 'attack'
},
'闪': {
background: 'linear-gradient(135deg, #006400 0%, #228B22 50%, #32CD32 100%)',
icon: '🛡️',
pattern: 'dodge'
},
'桃': {
background: 'linear-gradient(135deg, #FF69B4 0%, #FF1493 50%, #C71585 100%)',
icon: '🍑',
pattern: 'peach'
},
'决斗': {
background: 'linear-gradient(135deg, #4B0082 0%, #8A2BE2 50%, #9370DB 100%)',
icon: '⚡',
pattern: 'duel'
},
'过河拆桥': {
background: 'linear-gradient(135deg, #8B4513 0%, #A0522D 50%, #CD853F 100%)',
icon: '🌉',
pattern: 'destroy'
},
'顺手牵羊': {
background: 'linear-gradient(135deg, #DAA520 0%, #FFD700 50%, #FFA500 100%)',
icon: '🐑',
pattern: 'steal'
},
'万箭齐发': {
background: 'linear-gradient(135deg, #B22222 0%, #DC143C 50%, #FF4500 100%)',
icon: '🏹',
pattern: 'arrow'
},
'南蛮入侵': {
background: 'linear-gradient(135deg, #2F4F4F 0%, #008080 50%, #20B2AA 100%)',
icon: '🏹',
pattern: 'barbarian'
},
'无中生有': {
background: 'linear-gradient(135deg, #FF8C00 0%, #FFA500 50%, #FFD700 100%)',
icon: '✨',
pattern: 'create'
},
'借刀杀人': {
background: 'linear-gradient(135deg, #800080 0%, #9370DB 50%, #BA55D3 100%)',
icon: '🗡️',
pattern: 'borrow'
},
'无懈可击': {
background: 'linear-gradient(135deg, #191970 0%, #4169E1 50%, #6495ED 100%)',
icon: '🚫',
pattern: 'nullify'
},
'桃园结义': {
background: 'linear-gradient(135deg, #FF69B4 0%, #FFB6C1 50%, #FFC0CB 100%)',
icon: '🌸',
pattern: 'peachGarden'
},
'诸葛连弩': {
background: 'linear-gradient(135deg, #FF4500 0%, #FF6347 50%, #FF7F50 100%)',
icon: '🏹',
pattern: 'weapon'
},
'青龙偃月刀': {
background: 'linear-gradient(135deg, #006400 0%, #228B22 50%, #32CD32 100%)',
icon: '🗡️',
pattern: 'weapon'
},
'丈八蛇矛': {
background: 'linear-gradient(135deg, #8B0000 0%, #A52A2A 50%, #CD5C5C 100%)',
icon: '⚔️',
pattern: 'weapon'
},
'贯石斧': {
background: 'linear-gradient(135deg, #4B0082 0%, #6A5ACD 50%, #7B68EE 100%)',
icon: '🪓',
pattern: 'weapon'
},
'方天画戟': {
background: 'linear-gradient(135deg, #FFD700 0%, #FFA500 50%, #FF8C00 100%)',
icon: '🔱',
pattern: 'weapon'
},
'八卦阵': {
background: 'linear-gradient(135deg, #000080 0%, #4169E1 50%, #6495ED 100%)',
icon: '☯️',
pattern: 'armor'
},
'仁王盾': {
background: 'linear-gradient(135deg, #8B4513 0%, #A0522D 50%, #CD853F 100%)',
icon: '🛡️',
pattern: 'armor'
},
'藤甲': {
background: 'linear-gradient(135deg, #556B2F 0%, #6B8E23 50%, #8FBC8F 100%)',
icon: '🌿',
pattern: 'armor'
},
'的卢': {
background: 'linear-gradient(135deg, #808080 0%, #A9A9A9 50%, #C0C0C0 100%)',
icon: '🐎',
pattern: 'horse'
},
'赤兔': {
background: 'linear-gradient(135deg, #DC143C 0%, #FF6347 50%, #FF7F50 100%)',
icon: '🐎',
pattern: 'horse'
},
'五谷丰登': {
background: 'linear-gradient(135deg, #228B22 0%, #32CD32 50%, #90EE90 100%)',
icon: '🌾',
pattern: 'harvest'
},
'闪电': {
background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)',
icon: '⚡',
pattern: 'lightning'
},
'乐不思蜀': {
background: 'linear-gradient(135deg, #e94560 0%, #ff6b6b 50%, #feca57 100%)',
icon: '🎵',
pattern: 'happy'
},
'兵粮寸断': {
background: 'linear-gradient(135deg, #5f27cd 0%, #341f97 50%, #222f3e 100%)',
icon: '🍚',
pattern: 'starvation'
},
'铁索连环': {
background: 'linear-gradient(135deg, #2c3e50 0%, #34495e 50%, #7f8c8d 100%)',
icon: '⛓️',
pattern: 'chain'
},
'火攻': {
background: 'linear-gradient(135deg, #e74c3c 0%, #c0392b 50%, #922b21 100%)',
icon: '🔥',
pattern: 'fireAttack'
},
'火杀': {
background: 'linear-gradient(135deg, #ff4500 0%, #ff6347 50%, #ff7f50 100%)',
icon: '🔥',
pattern: 'fireAttack'
},
'雷杀': {
background: 'linear-gradient(135deg, #4169e1 0%, #6495ed 50%, #87ceeb 100%)',
icon: '⚡',
pattern: 'thunderAttack'
}
};
class Card {
constructor(suit, rank, name, type, category = 'basic') {
this.suit = suit;
this.rank = rank;
this.name = name;
this.type = type;
this.category = category;
}
getSuitSymbol() {
const suits = {
'spade': '♠',
'heart': '♥',
'club': '♣',
'diamond': '♦'
};
return suits[this.suit] || '';
}
getDisplayRank() {
const ranks = {
'A': 'A', '2': '2', '3': '3', '4': '4', '5': '5',
'6': '6', '7': '7', '8': '8', '9': '9', '10': '10',
'J': 'J', 'Q': 'Q', 'K': 'K'
};
return ranks[this.rank] || this.rank;
}
isRed() {
return this.suit === 'heart' || this.suit === 'diamond';
}
isBlack() {
return this.suit === 'spade' || this.suit === 'club';
}
}
class Player {
constructor(index, character, identity, isHuman = false) {
this.index = index;
this.character = character;
this.identity = identity;
this.maxHp = character.hp;
this.currentHp = character.hp;
this.hand = [];
this.equipment = { weapon: null, armor: null, horsePlus: null, horseMinus: null };
this.isHuman = isHuman;
this.isAlive = true;
this.attackRange = 1;
this.statusEffects = {
lightning: false,
happy: false,
starvation: false,
chain: false
};
}
takeDamage(amount) {
this.currentHp = Math.max(0, this.currentHp - amount);
if (this.currentHp === 0) {
this.isAlive = false;
}
return this.currentHp;
}
heal(amount) {
this.currentHp = Math.min(this.maxHp, this.currentHp + amount);
return this.currentHp;
}
showDamageEffect() {
const playerCard = document.getElementById(`player-${this.index}`);
const damageEffect = document.createElement('div');
damageEffect.className = 'damage-effect';
damageEffect.textContent = '-1';
playerCard.appendChild(damageEffect);
setTimeout(() => {
damageEffect.remove();
}, 500);
}
showHealEffect() {
const playerCard = document.getElementById(`player-${this.index}`);
const healEffect = document.createElement('div');
healEffect.className = 'heal-effect';
healEffect.textContent = '+1';
playerCard.appendChild(healEffect);
setTimeout(() => {
healEffect.remove();
}, 500);
}
addCard(card) {
this.hand.push(card);
}
removeCard(index) {
return this.hand.splice(index, 1)[0];
}
equipCard(card) {
if (card.type === 'weapon') {
this.equipment.weapon = card;
const weaponRanges = {
'诸葛连弩': 1,
'青龙偃月刀': 3,
'丈八蛇矛': 3,
'贯石斧': 3,
'方天画戟': 4
};
this.attackRange = weaponRanges[card.name] || 1;
} else if (card.type === 'armor') {
this.equipment.armor = card;
} else if (card.type === 'horsePlus') {
this.equipment.horsePlus = card;
} else if (card.type === 'horseMinus') {
this.equipment.horseMinus = card;
}
}
}
class Game {
constructor() {
this.deck = [];
this.discardPile = [];
this.players = [];
this.currentPlayerIndex = 0;
this.phase = 'ready';
this.turnCount = 1;
this.selectedCardIndex = -1;
this.selectedTargetIndex = -1;
this.hasDrawn = false;
this.hasPlayed = false;
this.attackCount = 0;
this.gameOver = false;
this.initDeck();
this.initPlayers();
}
initDeck() {
const suits = ['spade', 'heart', 'club', 'diamond'];
const ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
for (let suit of suits) {
for (let rank of ranks) {
this.deck.push(new Card(suit, rank, '杀', 'attack', 'basic'));
this.deck.push(new Card(suit, rank, '闪', 'dodge', 'basic'));
}
}
for (let suit of suits) {
for (let i = 0; i < 5; i++) {
this.deck.push(new Card(suit, ranks[i], '火杀', 'attack', 'basic'));
this.deck.push(new Card(suit, ranks[i], '雷杀', 'attack', 'basic'));
}
}
for (let suit of suits) {
for (let i = 0; i < 3; i++) {
this.deck.push(new Card(suit, ranks[i], '桃', 'peach', 'basic'));
}
}
const scrollCards = [
{ name: '过河拆桥', type: 'dismantlement' },
{ name: '顺手牵羊', type: 'steal' },
{ name: '决斗', type: 'duel' },
{ name: '借刀杀人', type: 'borrow' },
{ name: '无中生有', type: 'draw' },
{ name: '万箭齐发', type: 'arrow' },
{ name: '南蛮入侵', type: 'barbarian' },
{ name: '无懈可击', type: 'nullify' },
{ name: '桃园结义', type: 'peachGarden' },
{ name: '五谷丰登', type: 'harvest' },
{ name: '闪电', type: 'lightning' },
{ name: '乐不思蜀', type: 'happy' },
{ name: '兵粮寸断', type: 'starvation' },
{ name: '铁索连环', type: 'chain' },
{ name: '火攻', type: 'fireAttack' }
];
for (let card of scrollCards) {
for (let suit of suits) {
for (let i = 0; i < 2; i++) {
this.deck.push(new Card(suit, ranks[i], card.name, card.type, 'scroll'));
}
}
}
const equipCards = [
{ name: '诸葛连弩', type: 'weapon', range: 1 },
{ name: '青龙偃月刀', type: 'weapon', range: 3 },
{ name: '丈八蛇矛', type: 'weapon', range: 3 },
{ name: '贯石斧', type: 'weapon', range: 3 },
{ name: '方天画戟', type: 'weapon', range: 4 },
{ name: '八卦阵', type: 'armor' },
{ name: '仁王盾', type: 'armor' },
{ name: '藤甲', type: 'armor' },
{ name: '的卢', type: 'horsePlus' },
{ name: '绝影', type: 'horsePlus' },
{ name: '赤兔', type: 'horseMinus' },
{ name: '紫骍', type: 'horseMinus' }
];
for (let card of equipCards) {
for (let suit of suits) {
this.deck.push(new Card(suit, 'A', card.name, card.type, 'equip'));
}
}
this.shuffleDeck();
}
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]];
}
}
initPlayers() {
const availableCharacters = [...CHARACTERS];
const shuffledCharacters = availableCharacters.sort(() => Math.random() - 0.5);
const identities = ['lord', 'loyalist', 'rebel', 'rebel', 'spy'];
const shuffledIdentities = identities.sort(() => Math.random() - 0.5);
for (let i = 0; i < 5; i++) {
const isHuman = (i === 0);
const character = shuffledCharacters[i];
const identity = shuffledIdentities[i];
this.players.push(new Player(i, character, identity, isHuman));
if (identity === 'lord') {
this.players[i].maxHp++;
this.players[i].currentHp++;
}
}
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 4; j++) {
const card = this.drawCard();
if (card) {
this.players[i].addCard(card);
}
}
}
const lordIndex = this.players.findIndex(p => p.identity === 'lord');
if (lordIndex !== -1) {
this.currentPlayerIndex = lordIndex;
}
}
drawCard() {
if (this.deck.length === 0) {
if (this.discardPile.length === 0) {
return null;
}
this.deck = [...this.discardPile];
this.discardPile = [];
this.shuffleDeck();
}
return this.deck.pop();
}
discardCard(card) {
this.discardPile.push(card);
}
getCurrentPlayer() {
return this.players[this.currentPlayerIndex];
}
getNextAlivePlayer(startIndex, direction = 1) {
let index = startIndex;
do {
index = (index + direction + 5) % 5;
if (this.players[index].isAlive) {
return index;
}
} while (index !== startIndex);
return startIndex;
}
startTurn() {
this.phase = 'draw';
this.hasDrawn = false;
this.hasPlayed = false;
this.attackCount = 0;
this.selectedCardIndex = -1;
this.selectedTargetIndex = -1;
const player = this.getCurrentPlayer();
this.addLog(`${player.character.name} 的回合开始`, true);
this.handleStatusEffects(player);
}
handleStatusEffects(player) {
if (!player.isAlive) return;
if (player.statusEffects.lightning) {
this.handleLightningEffect(player);
}
if (player.statusEffects.happy) {
this.handleHappyEffect(player);
}
if (player.statusEffects.starvation) {
this.handleStarvationEffect(player);
}
}
handleLightningEffect(player) {
const judgmentCard = this.drawCard();
this.addLog(`${player.character.name} 进行闪电判定,判定牌为 ${judgmentCard.getSuitSymbol()}${judgmentCard.getDisplayRank()}`);
if (judgmentCard.suit === 'spade' && ['2', '3', '4', '5', '6', '7', '8', '9'].includes(judgmentCard.rank)) {
player.takeDamage(3);
player.showDamageEffect();
this.addLog(`${player.character.name} 受到3点雷属性伤害`);
this.checkDeath(player);
player.statusEffects.lightning = false;
} else {
this.addLog('闪电判定未通过,闪电传递给下家');
player.statusEffects.lightning = false;
const nextPlayerIndex = this.getNextAlivePlayer(player.index);
this.players[nextPlayerIndex].statusEffects.lightning = true;
this.addLog(`${this.players[nextPlayerIndex].character.name} 被闪电标记`);
}
this.discardCard(judgmentCard);
}
handleHappyEffect(player) {
const judgmentCard = this.drawCard();
this.addLog(`${player.character.name} 进行乐不思蜀判定,判定牌为 ${judgmentCard.getSuitSymbol()}${judgmentCard.getDisplayRank()}`);
if (judgmentCard.suit !== 'heart') {
this.addLog(`${player.character.name} 乐不思蜀判定未通过,本回合不能使用牌`);
player.statusEffects.happy = true;
} else {
this.addLog(`${player.character.name} 乐不思蜀判定通过,可以正常使用牌`);
player.statusEffects.happy = false;
}
this.discardCard(judgmentCard);
}
handleStarvationEffect(player) {
const judgmentCard = this.drawCard();
this.addLog(`${player.character.name} 进行兵粮寸断判定,判定牌为 ${judgmentCard.getSuitSymbol()}${judgmentCard.getDisplayRank()}`);
if (judgmentCard.suit !== 'club') {
this.addLog(`${player.character.name} 兵粮寸断判定未通过,本回合不能摸牌`);
player.statusEffects.starvation = true;
} else {
this.addLog(`${player.character.name} 兵粮寸断判定通过,可以正常摸牌`);
player.statusEffects.starvation = false;
}
this.discardCard(judgmentCard);
}
drawPhase() {
if (this.hasDrawn) return;
const player = this.getCurrentPlayer();
if (player.statusEffects.starvation) {
this.addLog(`${player.character.name} 兵粮寸断生效,本回合不能摸牌`);
player.statusEffects.starvation = false;
} else {
const drawCount = 2;
for (let i = 0; i < drawCount; i++) {
const card = this.drawCard();
if (card) {
player.addCard(card);
this.addLog(`${player.character.name} 摸了一张牌`);
}
}
}
this.hasDrawn = true;
this.phase = 'play';
}
useSkill() {
const player = this.getCurrentPlayer();
switch (player.character.name) {
case '刘备':
this.useLiuBeiSkill();
break;
case '曹操':
this.addLog('奸雄技能在受到伤害后自动触发');
break;
case '孙权':
this.useSunQuanSkill();
break;
case '关羽':
this.addLog('武圣技能:出牌时红色手牌可以当作杀使用');
break;
case '张飞':
this.addLog('咆哮技能:出牌阶段可以使用任意张杀');
break;
case '赵云':
this.addLog('龙胆技能:杀可以当闪使用,闪可以当杀使用');
break;
case '诸葛亮':
this.useZhugeLiangSkill();
break;
case '黄月英':
this.addLog('集智技能:使用锦囊牌后自动摸牌');
break;
case '吕布':
this.addLog('无双技能:使用杀时目标需要两张闪');
break;
case '貂蝉':
this.useDiaoChanSkill();
break;
default:
this.addLog('该角色没有主动技能');
}
}
useLiuBeiSkill() {
const player = this.getCurrentPlayer();
if (player.hand.length === 0) {
this.addLog('没有手牌可以赠送');
return;
}
const targetIndex = this.selectedTargetIndex;
if (targetIndex === -1 || targetIndex === player.index) {
this.addLog('请先选择一个目标');
return;
}
const target = this.players[targetIndex];
if (!target.isAlive) {
this.addLog('目标已阵亡');
return;
}
const cardIndex = this.selectedCardIndex;
if (cardIndex === -1) {
this.addLog('请先选择一张手牌');
return;
}
const card = player.removeCard(cardIndex);
target.addCard(card);
this.addLog(`${player.character.name} 将【${card.name}】赠予 ${target.character.name}`);
this.selectedCardIndex = -1;
this.selectedTargetIndex = -1;
}
useSunQuanSkill() {
const player = this.getCurrentPlayer();
if (player.hand.length === 0) {
this.addLog('没有手牌可以弃置');
return;
}
const cardIndex = this.selectedCardIndex;
if (cardIndex === -1) {
this.addLog('请先选择要弃置的手牌');
return;
}
const card = player.removeCard(cardIndex);
this.discardCard(card);
this.addLog(`${player.character.name} 弃置了【${card.name}`);
const newCard = this.drawCard();
if (newCard) {
player.addCard(newCard);
this.addLog(`${player.character.name} 摸了一张牌`);
}
this.selectedCardIndex = -1;
}
useZhugeLiangSkill() {
if (this.deck.length === 0) {
this.addLog('牌堆已空');
return;
}
const topCard = this.deck[this.deck.length - 1];
this.addLog(`牌堆顶的牌是:【${topCard.name}`);
}
useDiaoChanSkill() {
const player = this.getCurrentPlayer();
const malePlayers = this.players.filter(p => p.isAlive && p.index !== player.index && p.character.name !== '貂蝉');
if (malePlayers.length < 2) {
this.addLog('场上男性角色不足');
return;
}
const target1Index = this.selectedTargetIndex;
if (target1Index === -1) {
this.addLog('请先选择第一个决斗目标');
return;
}
const target1 = this.players[target1Index];
if (!target1.isAlive || target1.character.name === '貂蝉') {
this.addLog('第一个目标无效');
return;
}
const target2 = malePlayers.find(p => p.index !== target1Index);
if (!target2) {
this.addLog('请选择第二个决斗目标');
return;
}
this.addLog(`${player.character.name} 发动【离间】,让 ${target1.character.name}${target2.character.name} 决斗`);
this.startDuel(target1, target2);
}
playCard(cardIndex, targetIndex = -1) {
const player = this.getCurrentPlayer();
const card = player.hand[cardIndex];
if (player.statusEffects.happy) {
this.addLog('乐不思蜀生效,本回合不能使用牌');
return false;
}
if (targetIndex === -1 && this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
}
if (card.category === 'basic') {
return this.playBasicCard(cardIndex, targetIndex);
} else if (card.category === 'scroll') {
return this.playScrollCard(cardIndex, targetIndex);
} else if (card.category === 'equip') {
return this.playEquipCard(cardIndex);
}
return false;
}
playBasicCard(cardIndex, targetIndex = -1) {
const player = this.getCurrentPlayer();
const card = player.hand[cardIndex];
if (card.type === 'attack') {
if (this.attackCount >= 1 && !this.hasWeaponWithEffect('zhugeliannu') && player.character.name !== '张飞') {
this.addLog('每回合只能使用一张杀');
return false;
}
if (targetIndex === -1) {
if (this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
} else {
targetIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
}
}
const target = this.players[targetIndex];
if (!target.isAlive) {
this.addLog('目标已阵亡');
return false;
}
if (!this.isInRange(targetIndex)) {
this.addLog('目标不在攻击范围内');
return false;
}
const attackType = card.name === '火杀' ? '火' : (card.name === '雷杀' ? '雷' : '');
this.addLog(`${player.character.name}${target.character.name} 使用了【${card.name}`);
this.showAttackAnimation(targetIndex);
let damage = 1;
let needsDodge = true;
if (target.equipment.armor && target.equipment.armor.name === '藤甲') {
if (card.name === '火杀') {
damage = 2;
this.addLog(`${target.character.name} 的藤甲受到火杀伤害+1`);
} else if (card.name !== '雷杀') {
needsDodge = false;
this.addLog(`${target.character.name} 的藤甲使普通杀无效`);
}
}
if (needsDodge && this.useDodge(target, player)) {
this.addLog(`${target.character.name} 使用了【闪】`);
} else {
target.takeDamage(damage);
target.showDamageEffect();
this.addLog(`${target.character.name} 受到${damage}${attackType}属性伤害`);
if (target.statusEffects.chain) {
this.handleChainDamage(target, attackType);
}
this.checkDeath(target);
}
this.attackCount++;
} else if (card.type === 'peach') {
if (player.currentHp >= player.maxHp) {
this.addLog('体力已满,无法使用桃');
return false;
}
player.heal(1);
player.showHealEffect();
this.addLog(`${player.character.name} 使用了【桃】回复1点体力`);
} else if (card.type === 'dodge') {
if (player.character.name === '赵云') {
if (this.attackCount >= 1 && !this.hasWeaponWithEffect('zhugeliannu')) {
this.addLog('每回合只能使用一张杀');
return false;
}
if (targetIndex === -1) {
if (this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
} else {
targetIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
}
}
const target = this.players[targetIndex];
if (!target.isAlive) {
this.addLog('目标已阵亡');
return false;
}
if (!this.isInRange(targetIndex)) {
this.addLog('目标不在攻击范围内');
return false;
}
this.addLog(`${player.character.name} 发动【龙胆】,将【闪】当作【杀】使用`);
this.addLog(`${player.character.name}${target.character.name} 使用了【杀】`);
this.showAttackAnimation(targetIndex);
if (this.useDodge(target, player)) {
this.addLog(`${target.character.name} 使用了【闪】`);
} else {
target.takeDamage(1);
target.showDamageEffect();
this.addLog(`${target.character.name} 受到1点伤害`);
if (target.statusEffects.chain) {
this.handleChainDamage(target, '');
}
this.checkDeath(target);
}
this.attackCount++;
} else {
this.addLog('闪只能在对方使用杀时使用');
return false;
}
} else if (player.character.name === '关羽' && card.isRed()) {
if (this.attackCount >= 1 && !this.hasWeaponWithEffect('zhugeliannu')) {
this.addLog('每回合只能使用一张杀');
return false;
}
if (targetIndex === -1) {
if (this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
} else {
targetIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
}
}
const target = this.players[targetIndex];
if (!target.isAlive) {
this.addLog('目标已阵亡');
return false;
}
if (!this.isInRange(targetIndex)) {
this.addLog('目标不在攻击范围内');
return false;
}
this.addLog(`${player.character.name} 发动【武圣】,将【${card.name}】当作【杀】使用`);
this.addLog(`${player.character.name}${target.character.name} 使用了【杀】`);
this.showAttackAnimation(targetIndex);
let damage = 1;
let needsDodge = true;
if (target.equipment.armor && target.equipment.armor.name === '藤甲') {
needsDodge = false;
this.addLog(`${target.character.name} 的藤甲使普通杀无效`);
}
if (needsDodge && this.useDodge(target, player)) {
this.addLog(`${target.character.name} 使用了【闪】`);
} else {
target.takeDamage(damage);
target.showDamageEffect();
this.addLog(`${target.character.name} 受到${damage}点伤害`);
if (target.statusEffects.chain) {
this.handleChainDamage(target, '');
}
this.checkDeath(target);
}
this.attackCount++;
}
const playedCard = player.removeCard(cardIndex);
this.discardCard(playedCard);
return true;
}
playScrollCard(cardIndex, targetIndex = -1) {
const player = this.getCurrentPlayer();
const card = player.hand[cardIndex];
if (card.type === 'dismantlement') {
if (targetIndex === -1) {
if (this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
} else {
targetIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
}
}
const target = this.players[targetIndex];
if (!target.isAlive || targetIndex === this.currentPlayerIndex) {
this.addLog('无效的目标');
return false;
}
this.addLog(`${player.character.name}${target.character.name} 使用了【过河拆桥】`);
this.showCardUseAnimation();
if (target.hand.length > 0) {
const randomIndex = Math.floor(Math.random() * target.hand.length);
const removedCard = target.removeCard(randomIndex);
this.discardCard(removedCard);
this.addLog(`${target.character.name} 的一张牌被拆掉了`);
}
} else if (card.type === 'steal') {
if (targetIndex === -1) {
if (this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
} else {
targetIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
}
}
const target = this.players[targetIndex];
if (!target.isAlive || targetIndex === this.currentPlayerIndex) {
this.addLog('无效的目标');
return false;
}
this.addLog(`${player.character.name}${target.character.name} 使用了【顺手牵羊】`);
this.showCardUseAnimation();
if (target.hand.length > 0) {
const randomIndex = Math.floor(Math.random() * target.hand.length);
const stolenCard = target.removeCard(randomIndex);
player.addCard(stolenCard);
this.addLog(`${player.character.name} 获得了 ${target.character.name} 的一张牌`);
}
} else if (card.type === 'draw') {
this.addLog(`${player.character.name} 使用了【无中生有】`);
this.showCardUseAnimation();
for (let i = 0; i < 2; i++) {
const newCard = this.drawCard();
if (newCard) {
player.addCard(newCard);
this.addLog(`${player.character.name} 摸了一张牌`);
}
}
} else if (card.type === 'duel') {
if (targetIndex === -1) {
if (this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
} else {
targetIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
}
}
const target = this.players[targetIndex];
if (!target.isAlive || targetIndex === this.currentPlayerIndex) {
this.addLog('无效的目标');
return false;
}
this.addLog(`${player.character.name}${target.character.name} 发起了【决斗】`);
this.showCardUseAnimation();
this.handleDuel(player, target);
} else if (card.type === 'peachGarden') {
this.addLog(`${player.character.name} 使用了【桃园结义】`);
this.showCardUseAnimation();
for (let p of this.players) {
if (p.isAlive && p.currentHp < p.maxHp) {
p.heal(1);
p.showHealEffect();
this.addLog(`${p.character.name} 回复1点体力`);
}
}
} else if (card.type === 'barbarian') {
this.addLog(`${player.character.name} 使用了【南蛮入侵】`);
this.showCardUseAnimation();
this.handleBarbarian(player);
} else if (card.type === 'arrow') {
this.addLog(`${player.character.name} 使用了【万箭齐发】`);
this.showCardUseAnimation();
this.handleArrow(player);
} else if (card.type === 'nullify') {
this.addLog('无懈可击需要在锦囊牌使用时使用');
return false;
} else if (card.type === 'lightning') {
if (targetIndex === -1) {
if (this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
} else {
targetIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
}
}
const target = this.players[targetIndex];
if (!target.isAlive || targetIndex === this.currentPlayerIndex) {
this.addLog('无效的目标');
return false;
}
this.addLog(`${player.character.name}${target.character.name} 使用了【闪电】`);
this.showCardUseAnimation();
target.statusEffects.lightning = true;
this.addLog(`${target.character.name} 被闪电标记`);
} else if (card.type === 'happy') {
if (targetIndex === -1) {
if (this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
} else {
targetIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
}
}
const target = this.players[targetIndex];
if (!target.isAlive || targetIndex === this.currentPlayerIndex) {
this.addLog('无效的目标');
return false;
}
this.addLog(`${player.character.name}${target.character.name} 使用了【乐不思蜀】`);
this.showCardUseAnimation();
target.statusEffects.happy = true;
this.addLog(`${target.character.name} 被乐不思蜀标记`);
} else if (card.type === 'starvation') {
if (targetIndex === -1) {
if (this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
} else {
targetIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
}
}
const target = this.players[targetIndex];
if (!target.isAlive || targetIndex === this.currentPlayerIndex) {
this.addLog('无效的目标');
return false;
}
if (!this.isInRange(targetIndex)) {
this.addLog('目标不在攻击范围内');
return false;
}
this.addLog(`${player.character.name}${target.character.name} 使用了【兵粮寸断】`);
this.showCardUseAnimation();
target.statusEffects.starvation = true;
this.addLog(`${target.character.name} 被兵粮寸断标记`);
} else if (card.type === 'chain') {
if (targetIndex === -1) {
if (this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
} else {
targetIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
}
}
const target = this.players[targetIndex];
if (!target.isAlive || targetIndex === this.currentPlayerIndex) {
this.addLog('无效的目标');
return false;
}
this.addLog(`${player.character.name}${target.character.name} 使用了【铁索连环】`);
this.showCardUseAnimation();
target.statusEffects.chain = true;
this.addLog(`${target.character.name} 被铁索连环标记`);
} else if (card.type === 'fireAttack') {
if (targetIndex === -1) {
if (this.selectedTargetIndex !== -1) {
targetIndex = this.selectedTargetIndex;
} else {
targetIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
}
}
const target = this.players[targetIndex];
if (!target.isAlive || targetIndex === this.currentPlayerIndex) {
this.addLog('无效的目标');
return false;
}
this.addLog(`${player.character.name}${target.character.name} 使用了【火攻】`);
this.showCardUseAnimation();
if (target.hand.length === 0) {
this.addLog('目标没有手牌,火攻无效');
return false;
}
const randomCardIndex = Math.floor(Math.random() * target.hand.length);
const randomCard = target.hand[randomCardIndex];
this.addLog(`${target.character.name} 展示了一张 ${randomCard.name}`);
if (randomCard.suit === 'heart' || randomCard.suit === 'diamond') {
target.takeDamage(1);
target.showDamageEffect();
this.addLog(`${target.character.name} 受到1点火属性伤害`);
this.checkDeath(target);
} else {
this.addLog('火攻未造成伤害');
}
}
const playedCard = player.removeCard(cardIndex);
this.discardCard(playedCard);
if (player.character.name === '黄月英') {
const newCard = this.drawCard();
if (newCard) {
player.addCard(newCard);
this.addLog(`${player.character.name} 发动【集智】,摸了一张牌`);
}
}
this.showCardUseAnimation();
return true;
}
showCardUseAnimation() {
const cardElements = document.querySelectorAll('.card.selected');
cardElements.forEach(card => {
card.classList.add('use-animation');
setTimeout(() => {
card.classList.remove('use-animation');
}, 500);
});
}
playEquipCard(cardIndex) {
const player = this.getCurrentPlayer();
const card = player.hand[cardIndex];
player.equipCard(card);
this.addLog(`${player.character.name} 装备了【${card.name}`);
const playedCard = player.removeCard(cardIndex);
this.discardCard(playedCard);
this.showCardUseAnimation();
return true;
}
useDodge(target, attacker) {
let dodgeCount = 1;
if (attacker && attacker.character.name === '吕布') {
dodgeCount = 2;
}
let dodgesUsed = 0;
for (let i = 0; i < dodgeCount; i++) {
let dodgeIndex = target.hand.findIndex(c => c.type === 'dodge');
if (target.character.name === '赵云' && dodgeIndex === -1) {
dodgeIndex = target.hand.findIndex(c => c.type === 'attack');
if (dodgeIndex !== -1) {
this.addLog(`${target.character.name} 发动【龙胆】,将【杀】当作【闪】使用`);
}
}
if (dodgeIndex !== -1) {
if (target.isHuman) {
dodgesUsed++;
} else {
const dodgeCard = target.removeCard(dodgeIndex);
this.discardCard(dodgeCard);
dodgesUsed++;
}
this.showShieldAnimation(target.index);
}
}
return dodgesUsed >= dodgeCount;
}
showShieldAnimation(playerIndex) {
const playerCard = document.getElementById(`player-${playerIndex}`);
const shieldAnimation = document.createElement('div');
shieldAnimation.className = 'shield-animation';
shieldAnimation.textContent = '🛡️';
playerCard.appendChild(shieldAnimation);
setTimeout(() => {
shieldAnimation.remove();
}, 800);
}
handleDuel(attacker, defender) {
let currentAttacker = attacker;
let currentDefender = defender;
while (true) {
const attackIndex = currentAttacker.hand.findIndex(c => c.type === 'attack');
if (attackIndex !== -1) {
const attackCard = currentAttacker.removeCard(attackIndex);
this.discardCard(attackCard);
this.showAttackAnimation(currentDefender.index);
const defendIndex = currentDefender.hand.findIndex(c => c.type === 'attack');
if (defendIndex !== -1) {
const defendCard = currentDefender.removeCard(defendIndex);
this.discardCard(defendCard);
[currentAttacker, currentDefender] = [currentDefender, currentAttacker];
} else {
currentDefender.takeDamage(1);
currentDefender.showDamageEffect();
this.addLog(`${currentDefender.character.name} 受到1点伤害`);
this.checkDeath(currentDefender);
break;
}
} else {
break;
}
}
}
handleBarbarian(attacker) {
const targets = this.players.filter(p => p.isAlive && p.index !== attacker.index);
for (const target of targets) {
let attackIndex = target.hand.findIndex(c => c.type === 'attack');
if (attackIndex === -1 && target.character.name === '关羽') {
attackIndex = target.hand.findIndex(c => c.isRed());
if (attackIndex !== -1) {
this.addLog(`${target.character.name} 发动【武圣】,将【${target.hand[attackIndex].name}】当作【杀】使用`);
}
}
if (attackIndex === -1 && target.character.name === '赵云') {
attackIndex = target.hand.findIndex(c => c.type === 'dodge');
if (attackIndex !== -1) {
this.addLog(`${target.character.name} 发动【龙胆】,将【闪】当作【杀】使用`);
}
}
if (attackIndex !== -1) {
const attackCard = target.removeCard(attackIndex);
this.discardCard(attackCard);
this.addLog(`${target.character.name} 打出了【杀】`);
} else {
target.takeDamage(1);
target.showDamageEffect();
this.addLog(`${target.character.name} 受到1点伤害`);
this.checkDeath(target);
}
}
}
handleArrow(attacker) {
const targets = this.players.filter(p => p.isAlive && p.index !== attacker.index);
for (const target of targets) {
let dodgeIndex = target.hand.findIndex(c => c.type === 'dodge');
if (dodgeIndex === -1 && target.character.name === '赵云') {
dodgeIndex = target.hand.findIndex(c => c.type === 'attack');
if (dodgeIndex !== -1) {
this.addLog(`${target.character.name} 发动【龙胆】,将【杀】当作【闪】使用`);
}
}
if (dodgeIndex !== -1) {
const dodgeCard = target.removeCard(dodgeIndex);
this.discardCard(dodgeCard);
this.addLog(`${target.character.name} 打出了【闪】`);
} else {
target.takeDamage(1);
target.showDamageEffect();
this.addLog(`${target.character.name} 受到1点伤害`);
this.checkDeath(target);
}
}
}
startDuel(player1, player2) {
this.handleDuel(player1, player2);
}
isInRange(targetIndex) {
const player = this.getCurrentPlayer();
const target = this.players[targetIndex];
let range = player.attackRange;
if (player.equipment.horseMinus) {
range++;
}
let distance = 0;
let currentIndex = this.currentPlayerIndex;
while (currentIndex !== targetIndex) {
currentIndex = this.getNextAlivePlayer(currentIndex);
distance++;
if (this.players[currentIndex].equipment.horsePlus && currentIndex !== targetIndex) {
distance++;
}
}
if (target.equipment.horsePlus) {
distance++;
}
return distance <= range;
}
hasWeaponWithEffect(effect) {
const player = this.getCurrentPlayer();
return player.equipment.weapon && player.equipment.weapon.name === '诸葛连弩';
}
checkDeath(player) {
if (!player.isAlive) {
this.showKillEffect(player);
this.addLog(`${player.character.name} 阵亡!`, true);
const playerCard = document.getElementById(`player-${player.index}`);
playerCard.classList.add('dead');
if (player.identity === 'lord') {
this.gameOver = true;
this.checkWinCondition();
} else {
const lord = this.players.find(p => p.identity === 'lord');
if (lord) {
if (player.identity === 'rebel') {
lord.addCard(this.drawCard());
lord.addCard(this.drawCard());
lord.addCard(this.drawCard());
this.addLog(`${lord.character.name} 摸了3张牌`);
}
}
this.checkWinCondition();
}
}
}
showKillEffect(player) {
const killEffect = document.createElement('div');
killEffect.className = 'kill-effect';
killEffect.innerHTML = '<div class="kill-text">击杀!</div>';
document.body.appendChild(killEffect);
setTimeout(() => {
killEffect.remove();
}, 1000);
this.showDeathAnimation(player.index);
}
showDeathAnimation(playerIndex) {
const playerCard = document.getElementById(`player-${playerIndex}`);
const deathAnimation = document.createElement('div');
deathAnimation.className = 'death-animation';
deathAnimation.innerHTML = '💀';
playerCard.appendChild(deathAnimation);
setTimeout(() => {
deathAnimation.remove();
}, 800);
}
showAttackAnimation(targetIndex) {
const targetCard = document.getElementById(`player-${targetIndex}`);
const attackAnimation = document.createElement('div');
attackAnimation.className = 'attack-animation';
attackAnimation.textContent = '⚔️';
targetCard.appendChild(attackAnimation);
setTimeout(() => {
attackAnimation.remove();
}, 400);
}
handleChainDamage(sourcePlayer, damageType) {
if (!sourcePlayer.statusEffects.chain) return;
sourcePlayer.statusEffects.chain = false;
this.addLog(`${sourcePlayer.character.name} 的铁索连环传导伤害`);
const chainedPlayers = this.players.filter(p => p.isAlive && p.statusEffects.chain);
for (const player of chainedPlayers) {
player.takeDamage(1);
player.showDamageEffect();
this.addLog(`${player.character.name} 受到1点${damageType}属性传导伤害`);
player.statusEffects.chain = false;
this.checkDeath(player);
}
}
checkWinCondition() {
const alivePlayers = this.players.filter(p => p.isAlive);
const lord = this.players.find(p => p.identity === 'lord');
if (!lord || !lord.isAlive) {
const rebels = alivePlayers.filter(p => p.identity === 'rebel');
const spy = alivePlayers.find(p => p.identity === 'spy');
if (rebels.length > 0) {
this.addLog('反贼获胜!', true);
} else if (spy && alivePlayers.length === 1 && alivePlayers[0].identity === 'spy') {
this.addLog('内奸获胜!', true);
} else {
this.addLog('反贼获胜!', true);
}
this.gameOver = true;
} else {
const rebels = alivePlayers.filter(p => p.identity === 'rebel');
const loyalists = alivePlayers.filter(p => p.identity === 'loyalist');
const spy = alivePlayers.find(p => p.identity === 'spy');
if (alivePlayers.length === 1 && alivePlayers[0].identity === 'spy') {
this.addLog('内奸获胜!', true);
this.gameOver = true;
} else if (rebels.length === 0 && (!spy || !spy.isAlive)) {
this.addLog('主公和忠臣获胜!', true);
this.gameOver = true;
}
}
}
endTurn() {
if (this.gameOver) return;
const player = this.getCurrentPlayer();
this.addLog(`${player.character.name} 结束回合`);
this.currentPlayerIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
if (this.currentPlayerIndex === 0) {
this.turnCount++;
}
this.startTurn();
if (!this.getCurrentPlayer().isHuman) {
this.aiTurn();
}
}
aiTurn() {
const player = this.getCurrentPlayer();
setTimeout(() => {
this.drawPhase();
updateUI();
setTimeout(() => {
this.aiPlayCards(player);
}, 300);
}, 300);
}
aiPlayCards(player) {
const canUseMultipleAttacks = this.hasWeaponWithEffect('zhugeliannu') || player.character.name === '张飞';
const cardPriority = [
{ type: 'peach', condition: () => player.currentHp < player.maxHp, target: null },
{ type: 'attack', condition: () => this.attackCount === 0 || canUseMultipleAttacks, target: 'aiSelectTarget', skillCheck: true },
{ type: 'duel', condition: () => true, target: 'aiSelectDuelTarget' },
{ type: 'barbarian', condition: () => true, target: null },
{ type: 'arrow', condition: () => true, target: null },
{ type: 'fireAttack', condition: () => true, target: 'aiSelectFireAttackTarget' },
{ type: 'dismantlement', condition: () => true, target: 'aiSelectDismantleTarget' },
{ type: 'steal', condition: () => true, target: 'aiSelectStealTarget' },
{ type: 'chain', condition: () => true, target: 'aiSelectChainTarget' },
{ type: 'happy', condition: () => true, target: 'aiSelectHappyTarget' },
{ type: 'starvation', condition: () => true, target: 'aiSelectStarvationTarget' },
{ type: 'lightning', condition: () => true, target: 'aiSelectLightningTarget' },
{ type: 'draw', condition: () => true, target: null },
{ type: 'equip', condition: () => true, target: 'aiSelectEquipCard', isEquip: true }
];
const playNextCard = () => {
if (this.gameOver || this.getCurrentPlayer().index !== player.index) {
return;
}
const targets = this.players.filter(p => p.isAlive && p.index !== player.index);
let cardsUsed = false;
if (player.character.name === '刘备' && player.hand.length >= 2) {
const ally = targets.find(t => t.identity === 'loyalist' || t.identity === 'lord');
if (ally && ally.currentHp < ally.maxHp) {
const cardIndex = player.hand.findIndex(c => c.type === 'peach');
if (cardIndex !== -1) {
this.useLiuBeiSkillAI(player, ally, cardIndex);
cardsUsed = true;
}
}
}
if (!cardsUsed && player.character.name === '孙权' && player.hand.length >= 2) {
const uselessCards = player.hand.filter(c => c.type !== 'attack' && c.type !== 'peach' && c.type !== 'dodge');
if (uselessCards.length > 0) {
this.useSunQuanSkillAI(player, uselessCards[0]);
cardsUsed = true;
}
}
if (!cardsUsed && player.character.name === '貂蝉') {
const malePlayers = targets.filter(t => t.character.name !== '貂蝉');
if (malePlayers.length >= 2) {
const target1 = malePlayers[0];
const target2 = malePlayers[1];
this.useDiaoChanSkillAI(player, target1, target2);
cardsUsed = true;
}
}
if (!cardsUsed) {
for (const priority of cardPriority) {
if (cardsUsed) break;
let cardIndex = -1;
if (priority.isEquip) {
cardIndex = this[priority.target](player);
} else {
cardIndex = player.hand.findIndex(c => c.type === priority.type);
if (priority.skillCheck && cardIndex === -1) {
if (player.character.name === '关羽') {
cardIndex = player.hand.findIndex(c => c.isRed());
if (cardIndex !== -1) {
this.addLog(`${player.character.name} 发动【武圣】,将【${player.hand[cardIndex].name}】当作【杀】使用`);
}
} else if (player.character.name === '赵云') {
cardIndex = player.hand.findIndex(c => c.type === 'dodge');
if (cardIndex !== -1) {
this.addLog(`${player.character.name} 发动【龙胆】,将【闪】当作【杀】使用`);
}
}
}
}
if (cardIndex !== -1 && priority.condition()) {
let targetIndex = -1;
if (priority.target && !priority.isEquip) {
targetIndex = this[priority.target](player, targets);
}
if (targetIndex !== -1 || !priority.target || priority.isEquip) {
const result = this.playCard(cardIndex, targetIndex);
if (result) {
cardsUsed = true;
}
}
}
}
}
if (cardsUsed) {
updateUI();
setTimeout(playNextCard, 300);
} else {
updateUI();
setTimeout(() => {
this.endTurn();
updateUI();
}, 300);
}
};
playNextCard();
}
aiSelectTarget(player, targets) {
const inRangeTargets = targets.filter(t => this.isInRange(t.index));
if (inRangeTargets.length === 0) {
return -1;
}
return this.selectTargetByIdentity(player, inRangeTargets);
}
useLiuBeiSkillAI(player, ally, cardIndex) {
const card = player.removeCard(cardIndex);
ally.addCard(card);
this.addLog(`${player.character.name} 发动【仁德】,将【${card.name}】赠予 ${ally.character.name}`);
}
useSunQuanSkillAI(player, card) {
const cardIndex = player.hand.indexOf(card);
if (cardIndex !== -1) {
player.removeCard(cardIndex);
this.discardCard(card);
this.addLog(`${player.character.name} 发动【制衡】,弃置了【${card.name}`);
const newCard = this.drawCard();
if (newCard) {
player.addCard(newCard);
this.addLog(`${player.character.name} 摸了一张牌`);
}
}
}
useDiaoChanSkillAI(player, target1, target2) {
this.addLog(`${player.character.name} 发动【离间】,让 ${target1.character.name}${target2.character.name} 决斗`);
this.startDuel(target1, target2);
}
aiSelectDuelTarget(player, targets) {
const weakTargets = targets.filter(t => t.hand.length < 2);
if (weakTargets.length > 0) {
return this.aiSelectTarget(player, weakTargets);
}
return this.aiSelectTarget(player, targets);
}
aiSelectFireAttackTarget(player, targets) {
const target = this.aiSelectTarget(player, targets);
if (target !== -1) {
const targetPlayer = this.players[target];
const hasRedCard = targetPlayer.hand.some(c => c.suit === 'heart' || c.suit === 'diamond');
if (!hasRedCard) {
return target;
}
}
return this.aiSelectTarget(player, targets);
}
aiSelectDismantleTarget(player, targets) {
const targetWithCards = targets.filter(t => t.hand.length > 0 || t.equipment.weapon || t.equipment.armor || t.equipment.horsePlus || t.equipment.horseMinus);
if (targetWithCards.length > 0) {
return this.selectTargetByIdentity(player, targetWithCards);
}
return this.selectTargetByIdentity(player, targets);
}
aiSelectStealTarget(player, targets) {
const targetWithCards = targets.filter(t => t.hand.length > 0);
if (targetWithCards.length > 0) {
return this.selectTargetByIdentity(player, targetWithCards);
}
return this.selectTargetByIdentity(player, targets);
}
aiSelectChainTarget(player, targets) {
const unchainedTargets = targets.filter(t => !t.statusEffects.chain);
if (unchainedTargets.length > 0) {
return this.selectTargetByIdentity(player, unchainedTargets);
}
return this.selectTargetByIdentity(player, targets);
}
aiSelectHappyTarget(player, targets) {
const dangerousTargets = targets.filter(t => t.hand.length >= 3);
if (dangerousTargets.length > 0) {
return this.selectTargetByIdentity(player, dangerousTargets);
}
return this.selectTargetByIdentity(player, targets);
}
aiSelectStarvationTarget(player, targets) {
const inRangeTargets = targets.filter(t => this.isInRange(t.index));
if (inRangeTargets.length > 0) {
return this.selectTargetByIdentity(player, inRangeTargets);
}
return this.selectTargetByIdentity(player, targets);
}
aiSelectLightningTarget(player, targets) {
const enemyTargets = targets.filter(t => {
if (player.identity === 'lord' || player.identity === 'loyalist') {
return t.identity === 'rebel' || t.identity === 'spy';
} else if (player.identity === 'rebel') {
return t.identity === 'lord' || t.identity === 'loyalist';
} else {
return true;
}
});
if (enemyTargets.length > 0) {
return this.selectTargetByIdentity(player, enemyTargets);
}
return this.selectTargetByIdentity(player, targets);
}
selectTargetByIdentity(player, targets) {
if (targets.length === 0) return -1;
if (player.identity === 'lord') {
const rebel = targets.find(t => t.identity === 'rebel');
if (rebel) return rebel.index;
const spy = targets.find(t => t.identity === 'spy');
if (spy) return spy.index;
} else if (player.identity === 'loyalist') {
const rebel = targets.find(t => t.identity === 'rebel');
if (rebel) return rebel.index;
} else if (player.identity === 'rebel') {
const lord = targets.find(t => t.identity === 'lord');
if (lord) return lord.index;
const loyalist = targets.find(t => t.identity === 'loyalist');
if (loyalist) return loyalist.index;
} else if (player.identity === 'spy') {
const weakTarget = targets.reduce((weakest, t) =>
t.currentHp < weakest.currentHp ? t : weakest, targets[0]);
if (weakTarget) return weakTarget.index;
}
return targets.length > 0 ? targets[0].index : -1;
}
aiSelectEquipCard(player) {
const equipCards = player.hand.filter(c => c.category === 'equip');
if (equipCards.length === 0) return -1;
for (const card of equipCards) {
const cardIndex = player.hand.indexOf(card);
if (card.type === 'weapon') {
if (!player.equipment.weapon ||
(player.equipment.weapon.name !== '诸葛连弩' && card.name === '诸葛连弩')) {
return cardIndex;
}
} else if (card.type === 'armor') {
if (!player.equipment.armor) {
return cardIndex;
}
} else if (card.type === 'horseMinus') {
if (!player.equipment.horseMinus) {
return cardIndex;
}
} else if (card.type === 'horsePlus') {
if (!player.equipment.horsePlus) {
return cardIndex;
}
}
}
return player.hand.indexOf(equipCards[0]);
}
addLog(message, highlight = false) {
const logContainer = document.getElementById('game-log');
const logEntry = document.createElement('div');
logEntry.className = 'log-entry' + (highlight ? ' highlight' : '');
logEntry.textContent = message;
logContainer.appendChild(logEntry);
logContainer.scrollTop = logContainer.scrollHeight;
}
}
let game;
function startGame() {
document.getElementById('start-screen').style.display = 'none';
document.getElementById('game-container').style.display = 'block';
game = new Game();
updateUI();
setupEventListeners();
game.addLog('游戏初始化完成', true);
const currentPlayer = game.getCurrentPlayer();
if (!currentPlayer.isHuman) {
game.addLog(`${currentPlayer.character.name} 的回合开始`, true);
setTimeout(() => {
game.drawPhase();
updateUI();
setTimeout(() => {
game.aiPlayCards(currentPlayer);
}, 300);
}, 300);
} else {
game.addLog('点击"准备"开始游戏', true);
}
}
function initGame() {
document.getElementById('start-btn').addEventListener('click', startGame);
}
let updateUIPending = false;
let updateUITimer = null;
function updateUI() {
if (updateUIPending) {
return;
}
updateUIPending = true;
if (updateUITimer) {
clearTimeout(updateUITimer);
}
updateUITimer = setTimeout(() => {
updateUIPending = false;
renderUI();
}, 50);
}
function renderUI() {
const player = game.getCurrentPlayer();
document.getElementById('current-phase').textContent = `阶段: ${game.phase === 'ready' ? '准备' : game.phase === 'draw' ? '摸牌' : game.phase === 'play' ? '出牌' : '结束'}`;
document.getElementById('turn-count').textContent = `回合: ${game.turnCount}`;
document.getElementById('deck-count').textContent = game.deck.length;
const attackRange = calculateAttackRange(player);
document.getElementById('attack-range').textContent = `攻击距离: ${attackRange}`;
const isPlayPhase = game.phase === 'play';
const hasSelectedCard = game.selectedCardIndex !== -1 && player.isHuman && isPlayPhase;
const selectedCard = hasSelectedCard ? player.hand[game.selectedCardIndex] : null;
const isAttackOrScroll = selectedCard && (selectedCard.type === 'attack' || selectedCard.category === 'scroll');
for (let i = 0; i < 5; i++) {
const p = game.players[i];
if (!playerInfoCache[i]) {
playerInfoCache[i] = {
name: null,
identity: null,
skill: null
};
}
const infoCache = playerInfoCache[i];
const playerCard = document.getElementById(`player-${i}`);
playerCard.classList.remove('active', 'targetable', 'targeted', 'out-of-range');
if (i === game.currentPlayerIndex) {
playerCard.classList.add('active');
}
if (hasSelectedCard && isAttackOrScroll && i !== game.currentPlayerIndex && p.isAlive) {
if (game.isInRange(i)) {
playerCard.classList.add('targetable');
} else {
playerCard.classList.add('out-of-range');
}
}
if (game.selectedTargetIndex === i) {
playerCard.classList.add('targeted');
}
if (infoCache.name !== p.character.name) {
const avatar = document.getElementById(`avatar-${i}`);
avatar.src = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60'%3E%3Crect width='60' height='60' fill='${p.character.color}'/%3E%3Ctext x='30' y='35' text-anchor='middle' fill='white' font-size='12'%3E${p.character.name}%3C/text%3E%3C/svg%3E`;
document.getElementById(`name-${i}`).textContent = p.character.name;
infoCache.name = p.character.name;
}
const identityEl = document.getElementById(`identity-${i}`);
const identityInfo = IDENTITIES[p.identity];
const showIdentity = p.isHuman || p.identity === 'lord' || !p.isAlive;
const identityText = showIdentity ? identityInfo.name : '???';
const identityClass = showIdentity ? `identity ${identityInfo.class}` : 'identity hidden';
if (infoCache.identity !== identityText) {
identityEl.textContent = identityText;
identityEl.className = identityClass;
infoCache.identity = identityText;
}
const hpFill = document.getElementById(`hp-bar-${i}`).querySelector('.hp-fill');
if (!hpCache[i]) {
hpCache[i] = {
currentHp: null,
maxHp: null,
handLength: null
};
}
const hpData = hpCache[i];
if (hpData.currentHp !== p.currentHp || hpData.maxHp !== p.maxHp) {
hpFill.style.width = `${(p.currentHp / p.maxHp) * 100}%`;
hpFill.classList.remove('damaged');
void hpFill.offsetWidth;
hpFill.classList.add('damaged');
document.getElementById(`hp-value-${i}`).textContent = `${p.currentHp}/${p.maxHp}`;
hpData.currentHp = p.currentHp;
hpData.maxHp = p.maxHp;
}
if (hpData.handLength !== p.hand.length) {
document.getElementById(`cards-${i}`).textContent = p.hand.length;
hpData.handLength = p.hand.length;
}
renderEquipment(i);
const skillEl = document.getElementById(`skill-${i}`);
const skillText = p.character.skill || '';
if (infoCache.skill !== skillText) {
skillEl.textContent = skillText;
skillEl.title = p.character.skillDesc || '';
infoCache.skill = skillText;
}
renderHand(i);
}
const drawBtn = document.getElementById('draw-btn');
const playBtn = document.getElementById('play-btn');
const endTurnBtn = document.getElementById('end-turn-btn');
const readyBtn = document.getElementById('ready-btn');
const skillBtn = document.getElementById('skill-btn');
readyBtn.disabled = game.phase !== 'ready' || !player.isHuman || game.gameOver;
drawBtn.disabled = game.phase !== 'draw' || !player.isHuman || game.hasDrawn || game.gameOver;
skillBtn.disabled = game.phase !== 'play' || !player.isHuman || game.gameOver;
playBtn.disabled = game.phase !== 'play' || !player.isHuman || game.selectedCardIndex === -1 || game.gameOver;
endTurnBtn.disabled = game.gameOver || !player.isHuman || game.phase === 'ready';
}
const handCache = {};
const equipmentCache = {};
const playerInfoCache = {};
const hpCache = {};
function renderHand(playerIndex) {
const player = game.players[playerIndex];
const handContainer = document.getElementById(`hand-${playerIndex}`);
if (!handCache[playerIndex]) {
handCache[playerIndex] = {
handLength: 0,
cards: [],
selectedCardIndex: -1
};
}
const cache = handCache[playerIndex];
const currentHandLength = player.hand.length;
const currentSelectedIndex = player.isHuman ? game.selectedCardIndex : -1;
if (currentHandLength === cache.handLength && currentSelectedIndex === cache.selectedCardIndex) {
return;
}
if (currentHandLength === cache.handLength && player.isHuman) {
const existingCards = handContainer.querySelectorAll('.card');
const changedIndex = currentSelectedIndex !== cache.selectedCardIndex ? currentSelectedIndex : -1;
if (changedIndex !== -1) {
existingCards.forEach((cardElement, index) => {
cardElement.classList.toggle('selected', index === currentSelectedIndex);
});
}
cache.selectedCardIndex = currentSelectedIndex;
return;
}
handContainer.innerHTML = '';
cache.handLength = currentHandLength;
cache.cards = [];
cache.selectedCardIndex = currentSelectedIndex;
if (player.isHuman) {
player.hand.forEach((card, index) => {
const cardElement = document.createElement('div');
const cardImage = CARD_IMAGES[card.name] || { background: '#fff', icon: '🃏', pattern: 'default' };
cardElement.className = `card ${card.category}` + (index === currentSelectedIndex ? ' selected' : '');
cardElement.style.background = cardImage.background;
cardElement.innerHTML = `
<div class="card-icon">${cardImage.icon}</div>
<div class="card-suit">${card.getSuitSymbol()}</div>
<div class="card-rank">${card.getDisplayRank()}</div>
<div class="card-name">${card.name}</div>
<div class="card-type">${card.category === 'basic' ? '基本' : card.category === 'scroll' ? '锦囊' : '装备'}</div>
`;
cardElement.onclick = () => selectCard(index);
handContainer.appendChild(cardElement);
cache.cards.push(card.name);
});
} else {
for (let i = 0; i < player.hand.length; i++) {
const cardElement = document.createElement('div');
cardElement.className = 'card-back';
handContainer.appendChild(cardElement);
}
}
}
function renderEquipment(playerIndex) {
const player = game.players[playerIndex];
const equipmentEl = document.getElementById(`equipment-${playerIndex}`);
if (!equipmentCache[playerIndex]) {
equipmentCache[playerIndex] = {
weapon: null,
armor: null,
horsePlus: null,
horseMinus: null
};
}
const cache = equipmentCache[playerIndex];
if (cache.weapon === player.equipment.weapon?.name &&
cache.armor === player.equipment.armor?.name &&
cache.horsePlus === player.equipment.horsePlus?.name &&
cache.horseMinus === player.equipment.horseMinus?.name) {
return;
}
equipmentEl.innerHTML = '';
cache.weapon = player.equipment.weapon?.name || null;
cache.armor = player.equipment.armor?.name || null;
cache.horsePlus = player.equipment.horsePlus?.name || null;
cache.horseMinus = player.equipment.horseMinus?.name || null;
const equipmentTypes = [
{ key: 'weapon', class: 'weapon' },
{ key: 'armor', class: 'armor' },
{ key: 'horsePlus', class: 'horse' },
{ key: 'horseMinus', class: 'horse' }
];
equipmentTypes.forEach(({ key, class: className }) => {
const equip = player.equipment[key];
if (equip) {
const equipCard = document.createElement('div');
equipCard.className = `equipment-card ${className}`;
equipCard.textContent = equip.name.substring(0, 2);
const effect = EQUIPMENT_EFFECTS[equip.name];
if (effect) {
equipCard.title = `${equip.name}\n效果: ${effect.effect}`;
}
equipmentEl.appendChild(equipCard);
}
});
}
function selectCard(index) {
if (game.phase !== 'play' || !game.getCurrentPlayer().isHuman) return;
if (game.selectedCardIndex === index) {
game.selectedCardIndex = -1;
game.selectedTargetIndex = -1;
} else {
game.selectedCardIndex = index;
game.selectedTargetIndex = -1;
}
updateUI();
}
function selectTarget(targetIndex) {
const player = game.getCurrentPlayer();
if (!player.isHuman || game.phase !== 'play' || game.selectedCardIndex === -1) return;
const target = game.players[targetIndex];
if (!target.isAlive || targetIndex === game.currentPlayerIndex) return;
const selectedCard = player.hand[game.selectedCardIndex];
if (!selectedCard) return;
if (!game.isInRange(targetIndex)) {
game.addLog('目标不在攻击范围内');
return;
}
if (game.selectedTargetIndex === targetIndex) {
game.selectedTargetIndex = -1;
} else {
game.selectedTargetIndex = targetIndex;
}
updateUI();
}
function calculateAttackRange(player) {
let range = player.attackRange;
if (player.equipment.horseMinus) {
range++;
}
return Math.max(1, range);
}
function setupEventListeners() {
document.getElementById('ready-btn').onclick = () => {
if (game.getCurrentPlayer().isHuman && game.phase === 'ready') {
game.drawPhase();
updateUI();
}
};
document.getElementById('draw-btn').onclick = () => {
if (game.getCurrentPlayer().isHuman && game.phase === 'draw') {
game.drawPhase();
updateUI();
}
};
document.getElementById('skill-btn').onclick = () => {
if (game.getCurrentPlayer().isHuman && game.phase === 'play') {
game.useSkill();
updateUI();
}
};
document.getElementById('play-btn').onclick = () => {
if (game.selectedCardIndex !== -1 && game.getCurrentPlayer().isHuman) {
if (game.playCard(game.selectedCardIndex)) {
game.selectedCardIndex = -1;
game.selectedTargetIndex = -1;
updateUI();
}
}
};
document.getElementById('end-turn-btn').onclick = () => {
if (game.getCurrentPlayer().isHuman) {
game.endTurn();
updateUI();
}
};
const modal = document.getElementById('card-modal');
const closeBtn = document.querySelector('.close');
closeBtn.onclick = () => {
modal.style.display = 'none';
};
window.onclick = (event) => {
if (event.target === modal) {
modal.style.display = 'none';
}
};
}
window.onload = initGame;