2162 lines
79 KiB
JavaScript
2162 lines
79 KiB
JavaScript
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();
|
||
|
||
const randomCardIndex = Math.floor(Math.random() * target.hand.length);
|
||
const randomCard = target.hand[randomCardIndex];
|
||
this.addLog(`${target.character.name} 展示了一张 ${randomCard.name}`);
|
||
|
||
if (randomCard.suit === 'heart' || randomCard.suit === 'diamond') {
|
||
target.takeDamage(1);
|
||
target.showDamageEffect();
|
||
this.addLog(`${target.character.name} 受到1点火属性伤害`);
|
||
this.checkDeath(target);
|
||
} else {
|
||
this.addLog('火攻未造成伤害');
|
||
}
|
||
}
|
||
|
||
const playedCard = player.removeCard(cardIndex);
|
||
this.discardCard(playedCard);
|
||
|
||
if (player.character.name === '黄月英') {
|
||
const newCard = this.drawCard();
|
||
if (newCard) {
|
||
player.addCard(newCard);
|
||
this.addLog(`${player.character.name} 发动【集智】,摸了一张牌`);
|
||
}
|
||
}
|
||
|
||
this.showCardUseAnimation();
|
||
return true;
|
||
}
|
||
|
||
showCardUseAnimation() {
|
||
const cardElements = document.querySelectorAll('.card.selected');
|
||
cardElements.forEach(card => {
|
||
card.classList.add('use-animation');
|
||
setTimeout(() => {
|
||
card.classList.remove('use-animation');
|
||
}, 500);
|
||
});
|
||
}
|
||
|
||
playEquipCard(cardIndex) {
|
||
const player = this.getCurrentPlayer();
|
||
const card = player.hand[cardIndex];
|
||
|
||
player.equipCard(card);
|
||
this.addLog(`${player.character.name} 装备了【${card.name}】`);
|
||
|
||
const playedCard = player.removeCard(cardIndex);
|
||
this.discardCard(playedCard);
|
||
|
||
this.showCardUseAnimation();
|
||
return true;
|
||
}
|
||
|
||
useDodge(target, attacker) {
|
||
let dodgeCount = 1;
|
||
|
||
if (attacker && attacker.character.name === '吕布') {
|
||
dodgeCount = 2;
|
||
}
|
||
|
||
let dodgesUsed = 0;
|
||
for (let i = 0; i < dodgeCount; i++) {
|
||
let dodgeIndex = target.hand.findIndex(c => c.type === 'dodge');
|
||
|
||
if (target.character.name === '赵云' && dodgeIndex === -1) {
|
||
dodgeIndex = target.hand.findIndex(c => c.type === 'attack');
|
||
if (dodgeIndex !== -1) {
|
||
this.addLog(`${target.character.name} 发动【龙胆】,将【杀】当作【闪】使用`);
|
||
}
|
||
}
|
||
|
||
if (dodgeIndex !== -1) {
|
||
if (target.isHuman) {
|
||
dodgesUsed++;
|
||
} else {
|
||
const dodgeCard = target.removeCard(dodgeIndex);
|
||
this.discardCard(dodgeCard);
|
||
dodgesUsed++;
|
||
}
|
||
this.showShieldAnimation(target.index);
|
||
}
|
||
}
|
||
|
||
return dodgesUsed >= dodgeCount;
|
||
}
|
||
|
||
showShieldAnimation(playerIndex) {
|
||
const playerCard = document.getElementById(`player-${playerIndex}`);
|
||
const shieldAnimation = document.createElement('div');
|
||
shieldAnimation.className = 'shield-animation';
|
||
shieldAnimation.textContent = '🛡️';
|
||
playerCard.appendChild(shieldAnimation);
|
||
|
||
setTimeout(() => {
|
||
shieldAnimation.remove();
|
||
}, 800);
|
||
}
|
||
|
||
handleDuel(attacker, defender) {
|
||
let currentAttacker = attacker;
|
||
let currentDefender = defender;
|
||
|
||
while (true) {
|
||
const attackIndex = currentAttacker.hand.findIndex(c => c.type === 'attack');
|
||
if (attackIndex !== -1) {
|
||
const attackCard = currentAttacker.removeCard(attackIndex);
|
||
this.discardCard(attackCard);
|
||
this.showAttackAnimation(currentDefender.index);
|
||
|
||
const defendIndex = currentDefender.hand.findIndex(c => c.type === 'attack');
|
||
if (defendIndex !== -1) {
|
||
const defendCard = currentDefender.removeCard(defendIndex);
|
||
this.discardCard(defendCard);
|
||
[currentAttacker, currentDefender] = [currentDefender, currentAttacker];
|
||
} else {
|
||
currentDefender.takeDamage(1);
|
||
currentDefender.showDamageEffect();
|
||
this.addLog(`${currentDefender.character.name} 受到1点伤害`);
|
||
this.checkDeath(currentDefender);
|
||
break;
|
||
}
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
handleBarbarian(attacker) {
|
||
const targets = this.players.filter(p => p.isAlive && p.index !== attacker.index);
|
||
for (const target of targets) {
|
||
let attackIndex = target.hand.findIndex(c => c.type === 'attack');
|
||
|
||
if (attackIndex === -1 && target.character.name === '关羽') {
|
||
attackIndex = target.hand.findIndex(c => c.isRed());
|
||
if (attackIndex !== -1) {
|
||
this.addLog(`${target.character.name} 发动【武圣】,将【${target.hand[attackIndex].name}】当作【杀】使用`);
|
||
}
|
||
}
|
||
|
||
if (attackIndex === -1 && target.character.name === '赵云') {
|
||
attackIndex = target.hand.findIndex(c => c.type === 'dodge');
|
||
if (attackIndex !== -1) {
|
||
this.addLog(`${target.character.name} 发动【龙胆】,将【闪】当作【杀】使用`);
|
||
}
|
||
}
|
||
|
||
if (attackIndex !== -1) {
|
||
const attackCard = target.removeCard(attackIndex);
|
||
this.discardCard(attackCard);
|
||
this.addLog(`${target.character.name} 打出了【杀】`);
|
||
} else {
|
||
target.takeDamage(1);
|
||
target.showDamageEffect();
|
||
this.addLog(`${target.character.name} 受到1点伤害`);
|
||
this.checkDeath(target);
|
||
}
|
||
}
|
||
}
|
||
|
||
handleArrow(attacker) {
|
||
const targets = this.players.filter(p => p.isAlive && p.index !== attacker.index);
|
||
for (const target of targets) {
|
||
let dodgeIndex = target.hand.findIndex(c => c.type === 'dodge');
|
||
|
||
if (dodgeIndex === -1 && target.character.name === '赵云') {
|
||
dodgeIndex = target.hand.findIndex(c => c.type === 'attack');
|
||
if (dodgeIndex !== -1) {
|
||
this.addLog(`${target.character.name} 发动【龙胆】,将【杀】当作【闪】使用`);
|
||
}
|
||
}
|
||
|
||
if (dodgeIndex !== -1) {
|
||
const dodgeCard = target.removeCard(dodgeIndex);
|
||
this.discardCard(dodgeCard);
|
||
this.addLog(`${target.character.name} 打出了【闪】`);
|
||
} else {
|
||
target.takeDamage(1);
|
||
target.showDamageEffect();
|
||
this.addLog(`${target.character.name} 受到1点伤害`);
|
||
this.checkDeath(target);
|
||
}
|
||
}
|
||
}
|
||
|
||
startDuel(player1, player2) {
|
||
this.handleDuel(player1, player2);
|
||
}
|
||
|
||
isInRange(targetIndex) {
|
||
const player = this.getCurrentPlayer();
|
||
const target = this.players[targetIndex];
|
||
|
||
let range = player.attackRange;
|
||
if (player.equipment.horseMinus) {
|
||
range++;
|
||
}
|
||
|
||
let distance = 0;
|
||
let currentIndex = this.currentPlayerIndex;
|
||
|
||
while (currentIndex !== targetIndex) {
|
||
currentIndex = this.getNextAlivePlayer(currentIndex);
|
||
distance++;
|
||
if (this.players[currentIndex].equipment.horsePlus && currentIndex !== targetIndex) {
|
||
distance++;
|
||
}
|
||
}
|
||
|
||
if (target.equipment.horsePlus) {
|
||
distance++;
|
||
}
|
||
|
||
return distance <= range;
|
||
}
|
||
|
||
hasWeaponWithEffect(effect) {
|
||
const player = this.getCurrentPlayer();
|
||
return player.equipment.weapon && player.equipment.weapon.name === '诸葛连弩';
|
||
}
|
||
|
||
checkDeath(player) {
|
||
if (!player.isAlive) {
|
||
this.showKillEffect(player);
|
||
this.addLog(`${player.character.name} 阵亡!`, true);
|
||
|
||
const playerCard = document.getElementById(`player-${player.index}`);
|
||
playerCard.classList.add('dead');
|
||
|
||
if (player.identity === 'lord') {
|
||
this.gameOver = true;
|
||
this.checkWinCondition();
|
||
} else {
|
||
const lord = this.players.find(p => p.identity === 'lord');
|
||
if (lord) {
|
||
if (player.identity === 'rebel') {
|
||
lord.addCard(this.drawCard());
|
||
lord.addCard(this.drawCard());
|
||
lord.addCard(this.drawCard());
|
||
this.addLog(`${lord.character.name} 摸了3张牌`);
|
||
}
|
||
}
|
||
this.checkWinCondition();
|
||
}
|
||
}
|
||
}
|
||
|
||
showKillEffect(player) {
|
||
const killEffect = document.createElement('div');
|
||
killEffect.className = 'kill-effect';
|
||
killEffect.innerHTML = '<div class="kill-text">击杀!</div>';
|
||
document.body.appendChild(killEffect);
|
||
|
||
setTimeout(() => {
|
||
killEffect.remove();
|
||
}, 1000);
|
||
|
||
this.showDeathAnimation(player.index);
|
||
}
|
||
|
||
showDeathAnimation(playerIndex) {
|
||
const playerCard = document.getElementById(`player-${playerIndex}`);
|
||
const deathAnimation = document.createElement('div');
|
||
deathAnimation.className = 'death-animation';
|
||
deathAnimation.innerHTML = '💀';
|
||
playerCard.appendChild(deathAnimation);
|
||
|
||
setTimeout(() => {
|
||
deathAnimation.remove();
|
||
}, 800);
|
||
}
|
||
|
||
showAttackAnimation(targetIndex) {
|
||
const targetCard = document.getElementById(`player-${targetIndex}`);
|
||
const attackAnimation = document.createElement('div');
|
||
attackAnimation.className = 'attack-animation';
|
||
attackAnimation.textContent = '⚔️';
|
||
targetCard.appendChild(attackAnimation);
|
||
|
||
setTimeout(() => {
|
||
attackAnimation.remove();
|
||
}, 400);
|
||
}
|
||
|
||
handleChainDamage(sourcePlayer, damageType) {
|
||
if (!sourcePlayer.statusEffects.chain) return;
|
||
|
||
sourcePlayer.statusEffects.chain = false;
|
||
this.addLog(`${sourcePlayer.character.name} 的铁索连环传导伤害`);
|
||
|
||
const chainedPlayers = this.players.filter(p => p.isAlive && p.statusEffects.chain);
|
||
|
||
for (const player of chainedPlayers) {
|
||
player.takeDamage(1);
|
||
player.showDamageEffect();
|
||
this.addLog(`${player.character.name} 受到1点${damageType}属性传导伤害`);
|
||
player.statusEffects.chain = false;
|
||
this.checkDeath(player);
|
||
}
|
||
}
|
||
|
||
checkWinCondition() {
|
||
const alivePlayers = this.players.filter(p => p.isAlive);
|
||
const lord = this.players.find(p => p.identity === 'lord');
|
||
|
||
if (!lord || !lord.isAlive) {
|
||
const rebels = alivePlayers.filter(p => p.identity === 'rebel');
|
||
const spy = alivePlayers.find(p => p.identity === 'spy');
|
||
|
||
if (rebels.length > 0) {
|
||
this.addLog('反贼获胜!', true);
|
||
} else if (spy && alivePlayers.length === 1 && alivePlayers[0].identity === 'spy') {
|
||
this.addLog('内奸获胜!', true);
|
||
} else {
|
||
this.addLog('反贼获胜!', true);
|
||
}
|
||
this.gameOver = true;
|
||
} else {
|
||
const rebels = alivePlayers.filter(p => p.identity === 'rebel');
|
||
const loyalists = alivePlayers.filter(p => p.identity === 'loyalist');
|
||
const spy = alivePlayers.find(p => p.identity === 'spy');
|
||
|
||
if (alivePlayers.length === 1 && alivePlayers[0].identity === 'spy') {
|
||
this.addLog('内奸获胜!', true);
|
||
this.gameOver = true;
|
||
} else if (rebels.length === 0 && (!spy || !spy.isAlive)) {
|
||
this.addLog('主公和忠臣获胜!', true);
|
||
this.gameOver = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
endTurn() {
|
||
if (this.gameOver) return;
|
||
|
||
const player = this.getCurrentPlayer();
|
||
this.addLog(`${player.character.name} 结束回合`);
|
||
|
||
this.currentPlayerIndex = this.getNextAlivePlayer(this.currentPlayerIndex);
|
||
|
||
if (this.currentPlayerIndex === 0) {
|
||
this.turnCount++;
|
||
}
|
||
|
||
this.startTurn();
|
||
|
||
if (!this.getCurrentPlayer().isHuman) {
|
||
this.aiTurn();
|
||
}
|
||
}
|
||
|
||
aiTurn() {
|
||
const player = this.getCurrentPlayer();
|
||
|
||
setTimeout(() => {
|
||
this.drawPhase();
|
||
updateUI();
|
||
|
||
setTimeout(() => {
|
||
this.aiPlayCards(player);
|
||
}, 300);
|
||
}, 300);
|
||
}
|
||
|
||
aiPlayCards(player) {
|
||
const targets = this.players.filter(p => p.isAlive && p.index !== player.index);
|
||
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: 'selectTarget', skillCheck: true },
|
||
{ type: 'duel', condition: () => true, target: 'selectDuelTarget' },
|
||
{ type: 'barbarian', condition: () => true, target: null },
|
||
{ type: 'arrow', condition: () => true, target: null },
|
||
{ type: 'fireAttack', condition: () => true, target: 'selectFireAttackTarget' },
|
||
{ type: 'dismantlement', condition: () => true, target: 'selectDismantleTarget' },
|
||
{ type: 'steal', condition: () => true, target: 'selectStealTarget' },
|
||
{ type: 'chain', condition: () => true, target: 'selectChainTarget' },
|
||
{ type: 'happy', condition: () => true, target: 'selectHappyTarget' },
|
||
{ type: 'starvation', condition: () => true, target: 'selectStarvationTarget' },
|
||
{ type: 'lightning', condition: () => true, target: 'selectLightningTarget' },
|
||
{ type: 'draw', condition: () => true, target: null },
|
||
{ type: 'equip', condition: () => true, target: 'selectEquipCard', isEquip: true }
|
||
];
|
||
|
||
const playNextCard = () => {
|
||
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) {
|
||
this.playCard(cardIndex, targetIndex);
|
||
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;
|
||
}
|
||
|
||
if (player.identity === 'lord') {
|
||
const rebel = inRangeTargets.find(t => t.identity === 'rebel');
|
||
if (rebel) return rebel.index;
|
||
const spy = inRangeTargets.find(t => t.identity === 'spy');
|
||
if (spy) return spy.index;
|
||
} else if (player.identity === 'loyalist') {
|
||
const rebel = inRangeTargets.find(t => t.identity === 'rebel');
|
||
if (rebel) return rebel.index;
|
||
} else if (player.identity === 'rebel') {
|
||
const lord = inRangeTargets.find(t => t.identity === 'lord');
|
||
if (lord) return lord.index;
|
||
const loyalist = inRangeTargets.find(t => t.identity === 'loyalist');
|
||
if (loyalist) return loyalist.index;
|
||
} else if (player.identity === 'spy') {
|
||
const weakTarget = inRangeTargets.reduce((weakest, t) =>
|
||
t.currentHp < weakest.currentHp ? t : weakest, inRangeTargets[0]);
|
||
if (weakTarget) return weakTarget.index;
|
||
}
|
||
|
||
return inRangeTargets.length > 0 ? inRangeTargets[0].index : -1;
|
||
}
|
||
|
||
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);
|
||
if (targetWithCards.length > 0) {
|
||
return this.aiSelectTarget(player, targetWithCards);
|
||
}
|
||
return this.aiSelectTarget(player, targets);
|
||
}
|
||
|
||
aiSelectStealTarget(player, targets) {
|
||
const targetWithCards = targets.filter(t => t.hand.length > 0);
|
||
if (targetWithCards.length > 0) {
|
||
return this.aiSelectTarget(player, targetWithCards);
|
||
}
|
||
return this.aiSelectTarget(player, targets);
|
||
}
|
||
|
||
aiSelectChainTarget(player, targets) {
|
||
const unchainedTargets = targets.filter(t => !t.statusEffects.chain);
|
||
if (unchainedTargets.length > 0) {
|
||
return this.aiSelectTarget(player, unchainedTargets);
|
||
}
|
||
return this.aiSelectTarget(player, targets);
|
||
}
|
||
|
||
aiSelectHappyTarget(player, targets) {
|
||
const dangerousTargets = targets.filter(t => t.hand.length >= 3);
|
||
if (dangerousTargets.length > 0) {
|
||
return this.aiSelectTarget(player, dangerousTargets);
|
||
}
|
||
return this.aiSelectTarget(player, targets);
|
||
}
|
||
|
||
aiSelectStarvationTarget(player, targets) {
|
||
const inRangeTargets = targets.filter(t => this.isInRange(t.index));
|
||
if (inRangeTargets.length > 0) {
|
||
return this.aiSelectTarget(player, inRangeTargets);
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
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.aiSelectTarget(player, enemyTargets);
|
||
}
|
||
return this.aiSelectTarget(player, targets);
|
||
}
|
||
|
||
aiSelectEquipCard(player) {
|
||
const equipCards = player.hand.filter(c => c.category === 'equip');
|
||
if (equipCards.length === 0) return -1;
|
||
|
||
for (const card of equipCards) {
|
||
const cardIndex = player.hand.indexOf(card);
|
||
|
||
if (card.type === 'weapon') {
|
||
if (!player.equipment.weapon ||
|
||
(player.equipment.weapon.name !== '诸葛连弩' && card.name === '诸葛连弩')) {
|
||
return cardIndex;
|
||
}
|
||
} else if (card.type === 'armor') {
|
||
if (!player.equipment.armor) {
|
||
return cardIndex;
|
||
}
|
||
} else if (card.type === 'horseMinus') {
|
||
if (!player.equipment.horseMinus) {
|
||
return cardIndex;
|
||
}
|
||
} else if (card.type === 'horsePlus') {
|
||
if (!player.equipment.horsePlus) {
|
||
return cardIndex;
|
||
}
|
||
}
|
||
}
|
||
|
||
return player.hand.indexOf(equipCards[0]);
|
||
}
|
||
|
||
addLog(message, highlight = false) {
|
||
const logContainer = document.getElementById('game-log');
|
||
const logEntry = document.createElement('div');
|
||
logEntry.className = 'log-entry' + (highlight ? ' highlight' : '');
|
||
logEntry.textContent = message;
|
||
logContainer.appendChild(logEntry);
|
||
logContainer.scrollTop = logContainer.scrollHeight;
|
||
}
|
||
}
|
||
|
||
let game;
|
||
|
||
function startGame() {
|
||
document.getElementById('start-screen').style.display = 'none';
|
||
document.getElementById('game-container').style.display = 'block';
|
||
game = new Game();
|
||
updateUI();
|
||
setupEventListeners();
|
||
game.addLog('游戏初始化完成', true);
|
||
|
||
const currentPlayer = game.getCurrentPlayer();
|
||
if (!currentPlayer.isHuman) {
|
||
game.addLog(`${currentPlayer.character.name} 的回合开始`, true);
|
||
setTimeout(() => {
|
||
game.drawPhase();
|
||
updateUI();
|
||
setTimeout(() => {
|
||
game.aiPlayCards(currentPlayer);
|
||
}, 300);
|
||
}, 300);
|
||
} else {
|
||
game.addLog('点击"准备"开始游戏', true);
|
||
}
|
||
}
|
||
|
||
function initGame() {
|
||
document.getElementById('start-btn').addEventListener('click', startGame);
|
||
}
|
||
|
||
let updateUIPending = false;
|
||
let updateUITimer = null;
|
||
|
||
function updateUI() {
|
||
if (updateUIPending) {
|
||
return;
|
||
}
|
||
|
||
updateUIPending = true;
|
||
|
||
if (updateUITimer) {
|
||
clearTimeout(updateUITimer);
|
||
}
|
||
|
||
updateUITimer = setTimeout(() => {
|
||
updateUIPending = false;
|
||
renderUI();
|
||
}, 50);
|
||
}
|
||
|
||
function renderUI() {
|
||
const player = game.getCurrentPlayer();
|
||
|
||
document.getElementById('current-phase').textContent = `阶段: ${game.phase === 'ready' ? '准备' : game.phase === 'draw' ? '摸牌' : game.phase === 'play' ? '出牌' : '结束'}`;
|
||
document.getElementById('turn-count').textContent = `回合: ${game.turnCount}`;
|
||
document.getElementById('deck-count').textContent = game.deck.length;
|
||
|
||
const attackRange = calculateAttackRange(player);
|
||
document.getElementById('attack-range').textContent = `攻击距离: ${attackRange}`;
|
||
|
||
const isPlayPhase = game.phase === 'play';
|
||
const hasSelectedCard = game.selectedCardIndex !== -1 && player.isHuman && isPlayPhase;
|
||
const selectedCard = hasSelectedCard ? player.hand[game.selectedCardIndex] : null;
|
||
const isAttackOrScroll = selectedCard && (selectedCard.type === 'attack' || selectedCard.category === 'scroll');
|
||
|
||
for (let i = 0; i < 5; i++) {
|
||
const p = game.players[i];
|
||
|
||
if (!playerInfoCache[i]) {
|
||
playerInfoCache[i] = {
|
||
name: null,
|
||
identity: null,
|
||
skill: null
|
||
};
|
||
}
|
||
|
||
const infoCache = playerInfoCache[i];
|
||
|
||
const playerCard = document.getElementById(`player-${i}`);
|
||
|
||
playerCard.classList.remove('active', 'targetable', 'targeted', 'out-of-range');
|
||
if (i === game.currentPlayerIndex) {
|
||
playerCard.classList.add('active');
|
||
}
|
||
|
||
if (hasSelectedCard && isAttackOrScroll && i !== game.currentPlayerIndex && p.isAlive) {
|
||
if (game.isInRange(i)) {
|
||
playerCard.classList.add('targetable');
|
||
} else {
|
||
playerCard.classList.add('out-of-range');
|
||
}
|
||
}
|
||
|
||
if (game.selectedTargetIndex === i) {
|
||
playerCard.classList.add('targeted');
|
||
}
|
||
|
||
if (infoCache.name !== p.character.name) {
|
||
const avatar = document.getElementById(`avatar-${i}`);
|
||
avatar.src = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60'%3E%3Crect width='60' height='60' fill='${p.character.color}'/%3E%3Ctext x='30' y='35' text-anchor='middle' fill='white' font-size='12'%3E${p.character.name}%3C/text%3E%3C/svg%3E`;
|
||
document.getElementById(`name-${i}`).textContent = p.character.name;
|
||
infoCache.name = p.character.name;
|
||
}
|
||
|
||
const identityEl = document.getElementById(`identity-${i}`);
|
||
const identityInfo = IDENTITIES[p.identity];
|
||
const showIdentity = p.isHuman || p.identity === 'lord' || !p.isAlive;
|
||
const identityText = showIdentity ? identityInfo.name : '???';
|
||
const identityClass = showIdentity ? `identity ${identityInfo.class}` : 'identity hidden';
|
||
|
||
if (infoCache.identity !== identityText) {
|
||
identityEl.textContent = identityText;
|
||
identityEl.className = identityClass;
|
||
infoCache.identity = identityText;
|
||
}
|
||
|
||
const hpFill = document.getElementById(`hp-bar-${i}`).querySelector('.hp-fill');
|
||
|
||
if (!hpCache[i]) {
|
||
hpCache[i] = {
|
||
currentHp: null,
|
||
maxHp: null,
|
||
handLength: null
|
||
};
|
||
}
|
||
|
||
const hpData = hpCache[i];
|
||
|
||
if (hpData.currentHp !== p.currentHp || hpData.maxHp !== p.maxHp) {
|
||
hpFill.style.width = `${(p.currentHp / p.maxHp) * 100}%`;
|
||
hpFill.classList.remove('damaged');
|
||
void hpFill.offsetWidth;
|
||
hpFill.classList.add('damaged');
|
||
document.getElementById(`hp-value-${i}`).textContent = `${p.currentHp}/${p.maxHp}`;
|
||
hpData.currentHp = p.currentHp;
|
||
hpData.maxHp = p.maxHp;
|
||
}
|
||
|
||
if (hpData.handLength !== p.hand.length) {
|
||
document.getElementById(`cards-${i}`).textContent = p.hand.length;
|
||
hpData.handLength = p.hand.length;
|
||
}
|
||
|
||
renderEquipment(i);
|
||
|
||
const skillEl = document.getElementById(`skill-${i}`);
|
||
const skillText = p.character.skill || '';
|
||
if (infoCache.skill !== skillText) {
|
||
skillEl.textContent = skillText;
|
||
skillEl.title = p.character.skillDesc || '';
|
||
infoCache.skill = skillText;
|
||
}
|
||
|
||
renderHand(i);
|
||
}
|
||
|
||
const drawBtn = document.getElementById('draw-btn');
|
||
const playBtn = document.getElementById('play-btn');
|
||
const endTurnBtn = document.getElementById('end-turn-btn');
|
||
const readyBtn = document.getElementById('ready-btn');
|
||
const skillBtn = document.getElementById('skill-btn');
|
||
|
||
readyBtn.disabled = game.phase !== 'ready' || !player.isHuman || game.gameOver;
|
||
drawBtn.disabled = game.phase !== 'draw' || !player.isHuman || game.hasDrawn || game.gameOver;
|
||
skillBtn.disabled = game.phase !== 'play' || !player.isHuman || game.gameOver;
|
||
playBtn.disabled = game.phase !== 'play' || !player.isHuman || game.selectedCardIndex === -1 || game.gameOver;
|
||
endTurnBtn.disabled = game.gameOver || !player.isHuman || game.phase === 'ready';
|
||
}
|
||
|
||
const handCache = {};
|
||
const equipmentCache = {};
|
||
const playerInfoCache = {};
|
||
const hpCache = {};
|
||
|
||
function renderHand(playerIndex) {
|
||
const player = game.players[playerIndex];
|
||
const handContainer = document.getElementById(`hand-${playerIndex}`);
|
||
|
||
if (!handCache[playerIndex]) {
|
||
handCache[playerIndex] = {
|
||
handLength: 0,
|
||
cards: [],
|
||
selectedCardIndex: -1
|
||
};
|
||
}
|
||
|
||
const cache = handCache[playerIndex];
|
||
const currentHandLength = player.hand.length;
|
||
const currentSelectedIndex = player.isHuman ? game.selectedCardIndex : -1;
|
||
|
||
if (currentHandLength === cache.handLength && currentSelectedIndex === cache.selectedCardIndex) {
|
||
return;
|
||
}
|
||
|
||
if (currentHandLength === cache.handLength && player.isHuman) {
|
||
const existingCards = handContainer.querySelectorAll('.card');
|
||
const changedIndex = currentSelectedIndex !== cache.selectedCardIndex ? currentSelectedIndex : -1;
|
||
|
||
if (changedIndex !== -1) {
|
||
existingCards.forEach((cardElement, index) => {
|
||
cardElement.classList.toggle('selected', index === currentSelectedIndex);
|
||
});
|
||
}
|
||
cache.selectedCardIndex = currentSelectedIndex;
|
||
return;
|
||
}
|
||
|
||
handContainer.innerHTML = '';
|
||
cache.handLength = currentHandLength;
|
||
cache.cards = [];
|
||
cache.selectedCardIndex = currentSelectedIndex;
|
||
|
||
if (player.isHuman) {
|
||
player.hand.forEach((card, index) => {
|
||
const cardElement = document.createElement('div');
|
||
const cardImage = CARD_IMAGES[card.name] || { background: '#fff', icon: '🃏', pattern: 'default' };
|
||
|
||
cardElement.className = `card ${card.category}` + (index === currentSelectedIndex ? ' selected' : '');
|
||
cardElement.style.background = cardImage.background;
|
||
cardElement.innerHTML = `
|
||
<div class="card-icon">${cardImage.icon}</div>
|
||
<div class="card-suit">${card.getSuitSymbol()}</div>
|
||
<div class="card-rank">${card.getDisplayRank()}</div>
|
||
<div class="card-name">${card.name}</div>
|
||
<div class="card-type">${card.category === 'basic' ? '基本' : card.category === 'scroll' ? '锦囊' : '装备'}</div>
|
||
`;
|
||
cardElement.onclick = () => selectCard(index);
|
||
handContainer.appendChild(cardElement);
|
||
cache.cards.push(card.name);
|
||
});
|
||
} else {
|
||
for (let i = 0; i < player.hand.length; i++) {
|
||
const cardElement = document.createElement('div');
|
||
cardElement.className = 'card-back';
|
||
handContainer.appendChild(cardElement);
|
||
}
|
||
}
|
||
}
|
||
|
||
function renderEquipment(playerIndex) {
|
||
const player = game.players[playerIndex];
|
||
const equipmentEl = document.getElementById(`equipment-${playerIndex}`);
|
||
|
||
if (!equipmentCache[playerIndex]) {
|
||
equipmentCache[playerIndex] = {
|
||
weapon: null,
|
||
armor: null,
|
||
horsePlus: null,
|
||
horseMinus: null
|
||
};
|
||
}
|
||
|
||
const cache = equipmentCache[playerIndex];
|
||
|
||
if (cache.weapon === player.equipment.weapon?.name &&
|
||
cache.armor === player.equipment.armor?.name &&
|
||
cache.horsePlus === player.equipment.horsePlus?.name &&
|
||
cache.horseMinus === player.equipment.horseMinus?.name) {
|
||
return;
|
||
}
|
||
|
||
equipmentEl.innerHTML = '';
|
||
cache.weapon = player.equipment.weapon?.name || null;
|
||
cache.armor = player.equipment.armor?.name || null;
|
||
cache.horsePlus = player.equipment.horsePlus?.name || null;
|
||
cache.horseMinus = player.equipment.horseMinus?.name || null;
|
||
|
||
const equipmentTypes = [
|
||
{ key: 'weapon', class: 'weapon' },
|
||
{ key: 'armor', class: 'armor' },
|
||
{ key: 'horsePlus', class: 'horse' },
|
||
{ key: 'horseMinus', class: 'horse' }
|
||
];
|
||
|
||
equipmentTypes.forEach(({ key, class: className }) => {
|
||
const equip = player.equipment[key];
|
||
if (equip) {
|
||
const equipCard = document.createElement('div');
|
||
equipCard.className = `equipment-card ${className}`;
|
||
equipCard.textContent = equip.name.substring(0, 2);
|
||
const effect = EQUIPMENT_EFFECTS[equip.name];
|
||
if (effect) {
|
||
equipCard.title = `${equip.name}\n效果: ${effect.effect}`;
|
||
}
|
||
equipmentEl.appendChild(equipCard);
|
||
}
|
||
});
|
||
}
|
||
|
||
function selectCard(index) {
|
||
if (game.phase !== 'play' || !game.getCurrentPlayer().isHuman) return;
|
||
|
||
if (game.selectedCardIndex === index) {
|
||
game.selectedCardIndex = -1;
|
||
game.selectedTargetIndex = -1;
|
||
} else {
|
||
game.selectedCardIndex = index;
|
||
game.selectedTargetIndex = -1;
|
||
}
|
||
|
||
updateUI();
|
||
}
|
||
|
||
function selectTarget(targetIndex) {
|
||
const player = game.getCurrentPlayer();
|
||
if (!player.isHuman || game.phase !== 'play' || game.selectedCardIndex === -1) return;
|
||
|
||
const target = game.players[targetIndex];
|
||
if (!target.isAlive || targetIndex === game.currentPlayerIndex) return;
|
||
|
||
const selectedCard = player.hand[game.selectedCardIndex];
|
||
if (!selectedCard) return;
|
||
|
||
if (!game.isInRange(targetIndex)) {
|
||
game.addLog('目标不在攻击范围内');
|
||
return;
|
||
}
|
||
|
||
if (game.selectedTargetIndex === targetIndex) {
|
||
game.selectedTargetIndex = -1;
|
||
} else {
|
||
game.selectedTargetIndex = targetIndex;
|
||
}
|
||
|
||
updateUI();
|
||
}
|
||
|
||
function calculateAttackRange(player) {
|
||
let range = player.attackRange;
|
||
if (player.equipment.horseMinus) {
|
||
range++;
|
||
}
|
||
return Math.max(1, range);
|
||
}
|
||
|
||
function setupEventListeners() {
|
||
document.getElementById('ready-btn').onclick = () => {
|
||
if (game.getCurrentPlayer().isHuman && game.phase === 'ready') {
|
||
game.drawPhase();
|
||
updateUI();
|
||
}
|
||
};
|
||
|
||
document.getElementById('draw-btn').onclick = () => {
|
||
if (game.getCurrentPlayer().isHuman && game.phase === 'draw') {
|
||
game.drawPhase();
|
||
updateUI();
|
||
}
|
||
};
|
||
|
||
document.getElementById('skill-btn').onclick = () => {
|
||
if (game.getCurrentPlayer().isHuman && game.phase === 'play') {
|
||
game.useSkill();
|
||
updateUI();
|
||
}
|
||
};
|
||
|
||
document.getElementById('play-btn').onclick = () => {
|
||
if (game.selectedCardIndex !== -1 && game.getCurrentPlayer().isHuman) {
|
||
if (game.playCard(game.selectedCardIndex)) {
|
||
game.selectedCardIndex = -1;
|
||
game.selectedTargetIndex = -1;
|
||
updateUI();
|
||
}
|
||
}
|
||
};
|
||
|
||
document.getElementById('end-turn-btn').onclick = () => {
|
||
if (game.getCurrentPlayer().isHuman) {
|
||
game.endTurn();
|
||
updateUI();
|
||
}
|
||
};
|
||
|
||
const modal = document.getElementById('card-modal');
|
||
const closeBtn = document.querySelector('.close');
|
||
|
||
closeBtn.onclick = () => {
|
||
modal.style.display = 'none';
|
||
};
|
||
|
||
window.onclick = (event) => {
|
||
if (event.target === modal) {
|
||
modal.style.display = 'none';
|
||
}
|
||
};
|
||
}
|
||
|
||
window.onload = initGame; |