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 = '
击杀!
'; 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 = `
${cardImage.icon}
${card.getSuitSymbol()}
${card.getDisplayRank()}
${card.name}
${card.category === 'basic' ? '基本' : card.category === 'scroll' ? '锦囊' : '装备'}
`; 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;