zhangjian/game.js

2179 lines
80 KiB
JavaScript
Raw Normal View History

2026-01-08 15:10:40 +08:00
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';
}
2026-01-08 15:10:40 +08:00
}
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('武圣技能:出牌时红色手牌可以当作杀使用');
2026-01-08 15:10:40 +08:00
break;
case '张飞':
this.addLog('咆哮技能:出牌阶段可以使用任意张杀');
break;
case '赵云':
this.addLog('龙胆技能:杀可以当闪使用,闪可以当杀使用');
2026-01-08 15:10:40 +08:00
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++;
2026-01-08 15:10:40 +08:00
}
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);
2026-01-08 15:10:40 +08:00
} 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;
}
2026-01-08 15:10:40 +08:00
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} 发动【龙胆】,将【杀】当作【闪】使用`);
}
}
2026-01-08 15:10:40 +08:00
if (dodgeIndex !== -1) {
if (target.isHuman) {
dodgesUsed++;
} else {
const dodgeCard = target.removeCard(dodgeIndex);
this.discardCard(dodgeCard);
2026-01-08 15:10:40 +08:00
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);
2026-01-08 15:10:40 +08:00
this.showAttackAnimation(currentDefender.index);
const defendIndex = currentDefender.hand.findIndex(c => c.type === 'attack');
if (defendIndex !== -1) {
const defendCard = currentDefender.removeCard(defendIndex);
this.discardCard(defendCard);
2026-01-08 15:10:40 +08:00
[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);
}
}
}
2026-01-08 15:10:40 +08:00
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;
}
2026-01-08 15:10:40 +08:00
}
}
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;
}
2026-01-08 15:10:40 +08:00
}
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 {
2026-01-08 15:10:40 +08:00
updateUI();
setTimeout(() => {
this.endTurn();
updateUI();
}, 300);
2026-01-08 15:10:40 +08:00
}
};
2026-01-08 15:10:40 +08:00
playNextCard();
2026-01-08 15:10:40 +08:00
}
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]);
2026-01-08 15:10:40 +08:00
}
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);
2026-01-08 15:10:40 +08:00
}
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');
2026-01-08 15:10:40 +08:00
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');
2026-01-08 15:10:40 +08:00
}
}
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;
2026-01-08 15:10:40 +08:00
}
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;
}
2026-01-08 15:10:40 +08:00
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;
2026-01-08 15:10:40 +08:00
}
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 = {};
2026-01-08 15:10:40 +08:00
function renderHand(playerIndex) {
const player = game.players[playerIndex];
const handContainer = document.getElementById(`hand-${playerIndex}`);
if (!handCache[playerIndex]) {
handCache[playerIndex] = {
handLength: 0,
cards: [],
selectedCardIndex: -1
2026-01-08 15:10:40 +08:00
};
}
const cache = handCache[playerIndex];
const currentHandLength = player.hand.length;
const currentSelectedIndex = player.isHuman ? game.selectedCardIndex : -1;
2026-01-08 15:10:40 +08:00
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);
2026-01-08 15:10:40 +08:00
});
}
cache.selectedCardIndex = currentSelectedIndex;
2026-01-08 15:10:40 +08:00
return;
}
handContainer.innerHTML = '';
cache.handLength = currentHandLength;
cache.cards = [];
cache.selectedCardIndex = currentSelectedIndex;
2026-01-08 15:10:40 +08:00
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' : '');
2026-01-08 15:10:40 +08:00
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);
}
});
2026-01-08 15:10:40 +08:00
}
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;