优化代码结构和性能:实现基于优先级的AI出牌系统,改进UI更新缓存机制,减少重复代码

This commit is contained in:
张健 2026-01-08 16:53:45 +08:00
parent 71265b4f23
commit 1e17309168

627
game.js
View File

@ -211,6 +211,14 @@ class Card {
}; };
return ranks[this.rank] || this.rank; return ranks[this.rank] || this.rank;
} }
isRed() {
return this.suit === 'heart' || this.suit === 'diamond';
}
isBlack() {
return this.suit === 'spade' || this.suit === 'club';
}
} }
class Player { class Player {
@ -583,13 +591,13 @@ class Game {
this.useSunQuanSkill(); this.useSunQuanSkill();
break; break;
case '关羽': case '关羽':
this.addLog('武圣技能:红色手牌自动当作杀使用'); this.addLog('武圣技能:出牌时红色手牌可以当作杀使用');
break; break;
case '张飞': case '张飞':
this.addLog('咆哮技能:出牌阶段可以使用任意张杀'); this.addLog('咆哮技能:出牌阶段可以使用任意张杀');
break; break;
case '赵云': case '赵云':
this.addLog('龙胆技能:杀闪互换自动触发'); this.addLog('龙胆技能:杀可以当闪使用,闪可以当杀使用');
break; break;
case '诸葛亮': case '诸葛亮':
this.useZhugeLiangSkill(); this.useZhugeLiangSkill();
@ -802,9 +810,107 @@ class Game {
player.showHealEffect(); player.showHealEffect();
this.addLog(`${player.character.name} 使用了【桃】回复1点体力`); this.addLog(`${player.character.name} 使用了【桃】回复1点体力`);
} else if (card.type === 'dodge') { } 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('闪只能在对方使用杀时使用'); this.addLog('闪只能在对方使用杀时使用');
return false; 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); const playedCard = player.removeCard(cardIndex);
this.discardCard(playedCard); this.discardCard(playedCard);
@ -906,6 +1012,16 @@ class Game {
} }
} }
} 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') { } else if (card.type === 'nullify') {
this.addLog('无懈可击需要在锦囊牌使用时使用'); this.addLog('无懈可击需要在锦囊牌使用时使用');
return false; return false;
@ -1074,13 +1190,21 @@ class Game {
let dodgesUsed = 0; let dodgesUsed = 0;
for (let i = 0; i < dodgeCount; i++) { for (let i = 0; i < dodgeCount; i++) {
const dodgeIndex = target.hand.findIndex(c => c.type === 'dodge'); 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 (dodgeIndex !== -1) {
if (target.isHuman) { if (target.isHuman) {
dodgesUsed++; dodgesUsed++;
} else { } else {
target.removeCard(dodgeIndex); const dodgeCard = target.removeCard(dodgeIndex);
this.discardCard(target.hand[dodgeIndex]); this.discardCard(dodgeCard);
dodgesUsed++; dodgesUsed++;
} }
this.showShieldAnimation(target.index); this.showShieldAnimation(target.index);
@ -1109,14 +1233,14 @@ class Game {
while (true) { while (true) {
const attackIndex = currentAttacker.hand.findIndex(c => c.type === 'attack'); const attackIndex = currentAttacker.hand.findIndex(c => c.type === 'attack');
if (attackIndex !== -1) { if (attackIndex !== -1) {
currentAttacker.removeCard(attackIndex); const attackCard = currentAttacker.removeCard(attackIndex);
this.discardCard(currentAttacker.hand[attackIndex]); this.discardCard(attackCard);
this.showAttackAnimation(currentDefender.index); this.showAttackAnimation(currentDefender.index);
const defendIndex = currentDefender.hand.findIndex(c => c.type === 'attack'); const defendIndex = currentDefender.hand.findIndex(c => c.type === 'attack');
if (defendIndex !== -1) { if (defendIndex !== -1) {
currentDefender.removeCard(defendIndex); const defendCard = currentDefender.removeCard(defendIndex);
this.discardCard(currentDefender.hand[defendIndex]); this.discardCard(defendCard);
[currentAttacker, currentDefender] = [currentDefender, currentAttacker]; [currentAttacker, currentDefender] = [currentDefender, currentAttacker];
} else { } else {
currentDefender.takeDamage(1); currentDefender.takeDamage(1);
@ -1131,6 +1255,63 @@ class Game {
} }
} }
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) { startDuel(player1, player2) {
this.handleDuel(player1, player2); this.handleDuel(player1, player2);
} }
@ -1312,85 +1493,273 @@ class Game {
aiPlayCards(player) { aiPlayCards(player) {
const targets = this.players.filter(p => p.isAlive && p.index !== player.index); const targets = this.players.filter(p => p.isAlive && p.index !== player.index);
const canUseMultipleAttacks = this.hasWeaponWithEffect('zhugeliannu') || player.character.name === '张飞'; const canUseMultipleAttacks = this.hasWeaponWithEffect('zhugeliannu') || player.character.name === '张飞';
while (true) { const cardPriority = [
const attackCard = player.hand.findIndex(c => c.type === 'attack'); { type: 'peach', condition: () => player.currentHp < player.maxHp, target: null },
if (attackCard !== -1 && (this.attackCount === 0 || canUseMultipleAttacks)) { { type: 'attack', condition: () => this.attackCount === 0 || canUseMultipleAttacks, target: 'selectTarget', skillCheck: true },
const target = this.aiSelectTarget(player, targets); { type: 'duel', condition: () => true, target: 'selectDuelTarget' },
if (target !== -1) { { type: 'barbarian', condition: () => true, target: null },
this.playCard(attackCard, target); { type: 'arrow', condition: () => true, target: null },
updateUI(); { type: 'fireAttack', condition: () => true, target: 'selectFireAttackTarget' },
continue; { type: 'dismantlement', condition: () => true, target: 'selectDismantleTarget' },
} { type: 'steal', condition: () => true, target: 'selectStealTarget' },
} { type: 'chain', condition: () => true, target: 'selectChainTarget' },
break; { 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 peachCard = player.hand.findIndex(c => c.type === 'peach'); const playNextCard = () => {
if (peachCard !== -1 && player.currentHp < player.maxHp) { let cardsUsed = false;
this.playCard(peachCard);
updateUI();
}
const dismantleCard = player.hand.findIndex(c => c.type === 'dismantlement'); if (player.character.name === '刘备' && player.hand.length >= 2) {
if (dismantleCard !== -1) { const ally = targets.find(t => t.identity === 'loyalist' || t.identity === 'lord');
const target = this.aiSelectTarget(player, targets); if (ally && ally.currentHp < ally.maxHp) {
if (target !== -1) { const cardIndex = player.hand.findIndex(c => c.type === 'peach');
this.playCard(dismantleCard, target); if (cardIndex !== -1) {
updateUI(); this.useLiuBeiSkillAI(player, ally, cardIndex);
cardsUsed = true;
}
} }
} }
const stealCard = player.hand.findIndex(c => c.type === 'steal'); if (!cardsUsed && player.character.name === '孙权' && player.hand.length >= 2) {
if (stealCard !== -1) { const uselessCards = player.hand.filter(c => c.type !== 'attack' && c.type !== 'peach' && c.type !== 'dodge');
const target = this.aiSelectTarget(player, targets); if (uselessCards.length > 0) {
if (target !== -1) { this.useSunQuanSkillAI(player, uselessCards[0]);
this.playCard(stealCard, target); cardsUsed = true;
updateUI();
} }
} }
const drawCard = player.hand.findIndex(c => c.type === 'draw'); if (!cardsUsed && player.character.name === '貂蝉') {
if (drawCard !== -1) { const malePlayers = targets.filter(t => t.character.name !== '貂蝉');
this.playCard(drawCard); if (malePlayers.length >= 2) {
updateUI(); const target1 = malePlayers[0];
const target2 = malePlayers[1];
this.useDiaoChanSkillAI(player, target1, target2);
cardsUsed = true;
}
} }
const equipCard = player.hand.findIndex(c => c.category === 'equip'); if (!cardsUsed) {
if (equipCard !== -1) { for (const priority of cardPriority) {
this.playCard(equipCard); if (cardsUsed) break;
updateUI();
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(() => { setTimeout(() => {
this.endTurn(); this.endTurn();
updateUI(); updateUI();
}, 300); }, 300);
} }
};
playNextCard();
}
aiSelectTarget(player, targets) { aiSelectTarget(player, targets) {
const inRangeTargets = targets.filter(t => this.isInRange(t.index));
if (inRangeTargets.length === 0) {
return -1;
}
if (player.identity === 'lord') { if (player.identity === 'lord') {
const rebel = targets.find(t => t.identity === 'rebel'); const rebel = inRangeTargets.find(t => t.identity === 'rebel');
if (rebel) return rebel.index; if (rebel) return rebel.index;
const spy = targets.find(t => t.identity === 'spy'); const spy = inRangeTargets.find(t => t.identity === 'spy');
if (spy) return spy.index; if (spy) return spy.index;
} else if (player.identity === 'loyalist') { } else if (player.identity === 'loyalist') {
const rebel = targets.find(t => t.identity === 'rebel'); const rebel = inRangeTargets.find(t => t.identity === 'rebel');
if (rebel) return rebel.index; if (rebel) return rebel.index;
} else if (player.identity === 'rebel') { } else if (player.identity === 'rebel') {
const lord = targets.find(t => t.identity === 'lord'); const lord = inRangeTargets.find(t => t.identity === 'lord');
if (lord) return lord.index; if (lord) return lord.index;
const loyalist = targets.find(t => t.identity === 'loyalist'); const loyalist = inRangeTargets.find(t => t.identity === 'loyalist');
if (loyalist) return loyalist.index; if (loyalist) return loyalist.index;
} else if (player.identity === 'spy') { } else if (player.identity === 'spy') {
const weakTarget = targets.reduce((weakest, t) => const weakTarget = inRangeTargets.reduce((weakest, t) =>
t.currentHp < weakest.currentHp ? t : weakest, targets[0]); t.currentHp < weakest.currentHp ? t : weakest, inRangeTargets[0]);
if (weakTarget) return weakTarget.index; if (weakTarget) return weakTarget.index;
} }
return targets.length > 0 ? targets[0].index : -1; 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) { addLog(message, highlight = false) {
@ -1449,7 +1818,7 @@ function updateUI() {
updateUITimer = setTimeout(() => { updateUITimer = setTimeout(() => {
updateUIPending = false; updateUIPending = false;
renderUI(); renderUI();
}, 16); }, 50);
} }
function renderUI() { function renderUI() {
@ -1462,6 +1831,11 @@ function renderUI() {
const attackRange = calculateAttackRange(player); const attackRange = calculateAttackRange(player);
document.getElementById('attack-range').textContent = `攻击距离: ${attackRange}`; 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++) { for (let i = 0; i < 5; i++) {
const p = game.players[i]; const p = game.players[i];
@ -1482,18 +1856,13 @@ function renderUI() {
playerCard.classList.add('active'); playerCard.classList.add('active');
} }
if (game.selectedCardIndex !== -1 && player.isHuman && game.phase === 'play') { if (hasSelectedCard && isAttackOrScroll && i !== game.currentPlayerIndex && p.isAlive) {
const selectedCard = player.hand[game.selectedCardIndex];
if (selectedCard && (selectedCard.type === 'attack' || selectedCard.category === 'scroll')) {
if (i !== game.currentPlayerIndex && p.isAlive) {
if (game.isInRange(i)) { if (game.isInRange(i)) {
playerCard.classList.add('targetable'); playerCard.classList.add('targetable');
} else { } else {
playerCard.classList.add('out-of-range'); playerCard.classList.add('out-of-range');
} }
} }
}
}
if (game.selectedTargetIndex === i) { if (game.selectedTargetIndex === i) {
playerCard.classList.add('targeted'); playerCard.classList.add('targeted');
@ -1508,42 +1877,51 @@ function renderUI() {
const identityEl = document.getElementById(`identity-${i}`); const identityEl = document.getElementById(`identity-${i}`);
const identityInfo = IDENTITIES[p.identity]; const identityInfo = IDENTITIES[p.identity];
if (p.isHuman || p.identity === 'lord' || !p.isAlive) { const showIdentity = p.isHuman || p.identity === 'lord' || !p.isAlive;
if (infoCache.identity !== identityInfo.name) { const identityText = showIdentity ? identityInfo.name : '???';
identityEl.textContent = identityInfo.name; const identityClass = showIdentity ? `identity ${identityInfo.class}` : 'identity hidden';
identityEl.className = `identity ${identityInfo.class}`;
infoCache.identity = identityInfo.name; if (infoCache.identity !== identityText) {
} identityEl.textContent = identityText;
} else { identityEl.className = identityClass;
if (infoCache.identity !== '???') { infoCache.identity = identityText;
identityEl.textContent = '???';
identityEl.className = 'identity hidden';
infoCache.identity = '???';
}
} }
const hpFill = document.getElementById(`hp-bar-${i}`).querySelector('.hp-fill'); 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.style.width = `${(p.currentHp / p.maxHp) * 100}%`;
hpFill.classList.remove('damaged'); hpFill.classList.remove('damaged');
void hpFill.offsetWidth; void hpFill.offsetWidth;
hpFill.classList.add('damaged'); hpFill.classList.add('damaged');
document.getElementById(`hp-value-${i}`).textContent = `${p.currentHp}/${p.maxHp}`; 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; document.getElementById(`cards-${i}`).textContent = p.hand.length;
hpData.handLength = p.hand.length;
}
renderEquipment(i); renderEquipment(i);
const skillEl = document.getElementById(`skill-${i}`); const skillEl = document.getElementById(`skill-${i}`);
if (p.character.skill) { const skillText = p.character.skill || '';
if (infoCache.skill !== p.character.skill) { if (infoCache.skill !== skillText) {
skillEl.textContent = p.character.skill; skillEl.textContent = skillText;
skillEl.title = p.character.skillDesc || ''; skillEl.title = p.character.skillDesc || '';
infoCache.skill = p.character.skill; infoCache.skill = skillText;
}
} else {
if (infoCache.skill !== '') {
skillEl.textContent = '';
infoCache.skill = '';
}
} }
renderHand(i); renderHand(i);
@ -1565,6 +1943,7 @@ function renderUI() {
const handCache = {}; const handCache = {};
const equipmentCache = {}; const equipmentCache = {};
const playerInfoCache = {}; const playerInfoCache = {};
const hpCache = {};
function renderHand(playerIndex) { function renderHand(playerIndex) {
const player = game.players[playerIndex]; const player = game.players[playerIndex];
@ -1573,41 +1952,43 @@ function renderHand(playerIndex) {
if (!handCache[playerIndex]) { if (!handCache[playerIndex]) {
handCache[playerIndex] = { handCache[playerIndex] = {
handLength: 0, handLength: 0,
cards: [] cards: [],
selectedCardIndex: -1
}; };
} }
const cache = handCache[playerIndex]; const cache = handCache[playerIndex];
const currentHandLength = player.hand.length; const currentHandLength = player.hand.length;
const currentSelectedIndex = player.isHuman ? game.selectedCardIndex : -1;
if (currentHandLength === cache.handLength) { if (currentHandLength === cache.handLength && currentSelectedIndex === cache.selectedCardIndex) {
if (player.isHuman) { return;
const existingCards = handContainer.querySelectorAll('.card');
player.hand.forEach((card, index) => {
const cardElement = existingCards[index];
const cardImage = CARD_IMAGES[card.name] || { background: '#fff', icon: '🃏', pattern: 'default' };
const isSelected = game.selectedCardIndex === index;
const wasSelected = cardElement.classList.contains('selected');
if (isSelected !== wasSelected) {
cardElement.classList.toggle('selected', isSelected);
} }
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; return;
} }
handContainer.innerHTML = ''; handContainer.innerHTML = '';
cache.handLength = currentHandLength; cache.handLength = currentHandLength;
cache.cards = []; cache.cards = [];
cache.selectedCardIndex = currentSelectedIndex;
if (player.isHuman) { if (player.isHuman) {
player.hand.forEach((card, index) => { player.hand.forEach((card, index) => {
const cardElement = document.createElement('div'); const cardElement = document.createElement('div');
const cardImage = CARD_IMAGES[card.name] || { background: '#fff', icon: '🃏', pattern: 'default' }; const cardImage = CARD_IMAGES[card.name] || { background: '#fff', icon: '🃏', pattern: 'default' };
cardElement.className = `card ${card.category}` + (game.selectedCardIndex === index ? ' selected' : ''); cardElement.className = `card ${card.category}` + (index === currentSelectedIndex ? ' selected' : '');
cardElement.style.background = cardImage.background; cardElement.style.background = cardImage.background;
cardElement.innerHTML = ` cardElement.innerHTML = `
<div class="card-icon">${cardImage.icon}</div> <div class="card-icon">${cardImage.icon}</div>
@ -1657,46 +2038,26 @@ function renderEquipment(playerIndex) {
cache.horsePlus = player.equipment.horsePlus?.name || null; cache.horsePlus = player.equipment.horsePlus?.name || null;
cache.horseMinus = player.equipment.horseMinus?.name || null; cache.horseMinus = player.equipment.horseMinus?.name || null;
if (player.equipment.weapon) { 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'); const equipCard = document.createElement('div');
equipCard.className = 'equipment-card weapon'; equipCard.className = `equipment-card ${className}`;
equipCard.textContent = player.equipment.weapon.name.substring(0, 2); equipCard.textContent = equip.name.substring(0, 2);
const effect = EQUIPMENT_EFFECTS[player.equipment.weapon.name]; const effect = EQUIPMENT_EFFECTS[equip.name];
if (effect) { if (effect) {
equipCard.title = `${player.equipment.weapon.name}\n效果: ${effect.effect}`; equipCard.title = `${equip.name}\n效果: ${effect.effect}`;
}
equipmentEl.appendChild(equipCard);
}
if (player.equipment.armor) {
const equipCard = document.createElement('div');
equipCard.className = 'equipment-card armor';
equipCard.textContent = player.equipment.armor.name.substring(0, 2);
const effect = EQUIPMENT_EFFECTS[player.equipment.armor.name];
if (effect) {
equipCard.title = `${player.equipment.armor.name}\n效果: ${effect.effect}`;
}
equipmentEl.appendChild(equipCard);
}
if (player.equipment.horsePlus) {
const equipCard = document.createElement('div');
equipCard.className = 'equipment-card horse';
equipCard.textContent = player.equipment.horsePlus.name.substring(0, 2);
const effect = EQUIPMENT_EFFECTS[player.equipment.horsePlus.name];
if (effect) {
equipCard.title = `${player.equipment.horsePlus.name}\n效果: ${effect.effect}`;
}
equipmentEl.appendChild(equipCard);
}
if (player.equipment.horseMinus) {
const equipCard = document.createElement('div');
equipCard.className = 'equipment-card horse';
equipCard.textContent = player.equipment.horseMinus.name.substring(0, 2);
const effect = EQUIPMENT_EFFECTS[player.equipment.horseMinus.name];
if (effect) {
equipCard.title = `${player.equipment.horseMinus.name}\n效果: ${effect.effect}`;
} }
equipmentEl.appendChild(equipCard); equipmentEl.appendChild(equipCard);
} }
});
} }
function selectCard(index) { function selectCard(index) {