优化代码结构和性能:实现基于优先级的AI出牌系统,改进UI更新缓存机制,减少重复代码
This commit is contained in:
parent
71265b4f23
commit
1e17309168
627
game.js
627
game.js
@ -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) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user