@firestone-hs/simulate-bgs-battle 1.1.244 → 1.1.245
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -27,6 +27,7 @@ class Simulator {
|
|
|
27
27
|
this.sharedState.currentEntityId =
|
|
28
28
|
Math.max(...playerBoard.map((entity) => entity.entityId), ...opponentBoard.map((entity) => entity.entityId)) + 1;
|
|
29
29
|
const suggestedNewCurrentAttacker = start_of_combat_1.handleStartOfCombat(playerEntity, playerBoard, opponentEntity, opponentBoard, this.currentAttacker, this.allCards, this.spawns, this.sharedState, gameState, spectator);
|
|
30
|
+
handleRapidReanimation(playerBoard, playerEntity, opponentBoard, opponentEntity, this.allCards, this.spawns, this.sharedState, spectator);
|
|
30
31
|
this.currentAttacker = suggestedNewCurrentAttacker;
|
|
31
32
|
let counter = 0;
|
|
32
33
|
while (playerBoard.length > 0 && opponentBoard.length > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simulator.js","sourceRoot":"","sources":["../../src/simulation/simulator.ts"],"names":[],"mappings":"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","sourcesContent":["import { AllCardsService, getEffectiveTechLevel } from '@firestone-hs/reference-data';\r\nimport { BgsGameState } from '../bgs-battle-info';\r\nimport { BgsPlayerEntity } from '../bgs-player-entity';\r\nimport { BoardEntity } from '../board-entity';\r\nimport { CardsData } from '../cards/cards-data';\r\nimport { SingleSimulationResult } from '../single-simulation-result';\r\nimport { buildSingleBoardEntity, stringifySimple } from '../utils';\r\nimport { performEntitySpawns, simulateAttack } from './attack';\r\nimport { clearStealthIfNeeded } from './auras';\r\nimport { SharedState } from './shared-state';\r\nimport { Spectator } from './spectator/spectator';\r\nimport { handleStartOfCombat } from './start-of-combat';\r\n\r\n// New simulator should be instantiated for each match\r\nexport class Simulator {\r\n\tprivate currentAttacker: number;\r\n\tprivate currentSpeedAttacker = -1;\r\n\tprivate lastPlayerAttackerEntityIndex: number;\r\n\tprivate lastOpponentAttackerEntityIndex: number;\r\n\tprivate sharedState: SharedState;\r\n\r\n\t// It should come already initialized\r\n\tconstructor(private readonly allCards: AllCardsService, private readonly spawns: CardsData) {\r\n\t\tthis.sharedState = new SharedState();\r\n\t}\r\n\r\n\t// Here we suppose that the BoardEntity only contain at most the enchantments that are linked\r\n\t// to auras (so we probably should hand-filter that, since there are actually few auras)\r\n\tpublic simulateSingleBattle(\r\n\t\tplayerBoard: BoardEntity[],\r\n\t\tplayerEntity: BgsPlayerEntity,\r\n\t\topponentBoard: BoardEntity[],\r\n\t\topponentEntity: BgsPlayerEntity,\r\n\t\tgameState: BgsGameState,\r\n\t\tspectator: Spectator,\r\n\t): SingleSimulationResult {\r\n\t\tspectator.registerStartOfCombat(playerBoard, opponentBoard);\r\n\t\t// Who attacks first is decided by the game before the hero power comes into effect. However, the full board (with the generated minion)\r\n\t\t// is sent tothe simulator\r\n\t\t// But in fact, the first player decision takes into account that additional minion. See\r\n\t\t// https://replays.firestoneapp.com/?reviewId=ddbbbe93-464b-4400-8e8d-4abca8680a2e\r\n\t\t// const effectivePlayerBoardLength =\r\n\t\t// \tplayerEntity.heroPowerId === CardIds.heroPowerUsed\r\n\t\t// \t\t? playerBoard.length - 1\r\n\t\t// \t\t: playerBoard.length;\r\n\t\t// const effectiveOpponentBoardLength =\r\n\t\t// \topponentEntity.heroPowerId === CardIds.heroPowerUsed\r\n\t\t// \t\t? opponentBoard.length - 1\r\n\t\t// \t\t: opponentBoard.length;\r\n\t\tconst effectivePlayerBoardLength = playerBoard.length;\r\n\t\tconst effectiveOpponentBoardLength = opponentBoard.length;\r\n\t\tthis.currentAttacker =\r\n\t\t\teffectivePlayerBoardLength > effectiveOpponentBoardLength\r\n\t\t\t\t? 0\r\n\t\t\t\t: effectiveOpponentBoardLength > effectivePlayerBoardLength\r\n\t\t\t\t? 1\r\n\t\t\t\t: Math.round(Math.random());\r\n\t\tthis.sharedState.currentEntityId =\r\n\t\t\tMath.max(...playerBoard.map((entity) => entity.entityId), ...opponentBoard.map((entity) => entity.entityId)) + 1;\r\n\t\tconst suggestedNewCurrentAttacker = handleStartOfCombat(\r\n\t\t\tplayerEntity,\r\n\t\t\tplayerBoard,\r\n\t\t\topponentEntity,\r\n\t\t\topponentBoard,\r\n\t\t\tthis.currentAttacker,\r\n\t\t\tthis.allCards,\r\n\t\t\tthis.spawns,\r\n\t\t\tthis.sharedState,\r\n\t\t\tgameState,\r\n\t\t\tspectator,\r\n\t\t);\r\n\t\t// console.log('suggestedNewCurrentAttacker', suggestedNewCurrentAttacker);\r\n\t\t// When both players have the same amount of minions, it's possible that Illidan's Start of Combat\r\n\t\t// ability causes the same player to attack twice in a row, which is not the case in real life\r\n\t\t// So when Illidan attacks first, we then look at the expected first attacker. If it was Illidan\r\n\t\t// once more, we switch. Otherwise, we just keep the attacker as planned\r\n\t\t// FIXME: this is probably bogus when both players have Illidan's hero power?\r\n\t\t// if (effectivePlayerBoardLength === effectiveOpponentBoardLength) {\r\n\t\t// Looking at some recent data (2022/05/29), there was one board with 5 entities + Illidan, and another with 3\r\n\t\t// The 5 entities attacked (because of Illidan), and then the other board attacked\r\n\t\t// So this means that Illidan made the attack turn pass over to the other player\r\n\t\tthis.currentAttacker = suggestedNewCurrentAttacker;\r\n\t\t// }\r\n\t\tlet counter = 0;\r\n\t\twhile (playerBoard.length > 0 && opponentBoard.length > 0) {\r\n\t\t\thandleRapidReanimation(\r\n\t\t\t\tplayerBoard,\r\n\t\t\t\tplayerEntity,\r\n\t\t\t\topponentBoard,\r\n\t\t\t\topponentEntity,\r\n\t\t\t\tthis.allCards,\r\n\t\t\t\tthis.spawns,\r\n\t\t\t\tthis.sharedState,\r\n\t\t\t\tspectator,\r\n\t\t\t);\r\n\t\t\tclearStealthIfNeeded(playerBoard, opponentBoard);\r\n\t\t\t// console.log('this.currentSpeedAttacker', this.currentAttacker);\r\n\t\t\t// If there are \"attack immediately\" minions, we keep the same player\r\n\t\t\t// We put it here so that it can kick in after the start of combat effects. However here we don't want\r\n\t\t\t// to change who attacks first, so we repeat that block again after all the attacks have been resolved\r\n\t\t\t// FIXME: This is not strictly correct - if there are multiple attack immediately\r\n\t\t\t// minions that spawn on both player sides it might get a bit more complex\r\n\t\t\t// but overall it works\r\n\t\t\t// Also, this doesn't work when there are several deathrattle competing\r\n\t\t\t// to know who triggers first. See the second test case of the scallywag.test.ts\r\n\t\t\t// that is not handled properly today (the attack should in some cases happen before\r\n\t\t\t// the other deathrattle procs)\r\n\t\t\tif (playerBoard.some((entity) => entity.attackImmediately)) {\r\n\t\t\t\tthis.currentSpeedAttacker = 0;\r\n\t\t\t} else if (opponentBoard.some((entity) => entity.attackImmediately)) {\r\n\t\t\t\tthis.currentSpeedAttacker = 1;\r\n\t\t\t} else {\r\n\t\t\t\tthis.currentSpeedAttacker = -1;\r\n\t\t\t}\r\n\t\t\t// console.log('this.currentSpeedAttacker 2', this.currentAttacker, this.currentSpeedAttacker);\r\n\t\t\tif (this.currentSpeedAttacker === 0 || (this.currentSpeedAttacker === -1 && this.currentAttacker === 0)) {\r\n\t\t\t\tconst opponentEntitiesBeforeAttack = opponentBoard.map((e) => e.entityId).slice(0, this.lastOpponentAttackerEntityIndex);\r\n\t\t\t\tconst outputAttacker = simulateAttack(\r\n\t\t\t\t\tplayerBoard,\r\n\t\t\t\t\tplayerEntity,\r\n\t\t\t\t\topponentBoard,\r\n\t\t\t\t\topponentEntity,\r\n\t\t\t\t\tthis.lastPlayerAttackerEntityIndex,\r\n\t\t\t\t\tthis.allCards,\r\n\t\t\t\t\tthis.spawns,\r\n\t\t\t\t\tthis.sharedState,\r\n\t\t\t\t\tspectator,\r\n\t\t\t\t);\r\n\t\t\t\t// The \"attack immediately\" doesn't change the next attacker\r\n\t\t\t\tthis.lastPlayerAttackerEntityIndex = this.currentSpeedAttacker === -1 ? outputAttacker : this.lastPlayerAttackerEntityIndex;\r\n\t\t\t\tconst opponentEntitiesAfterAttack = opponentBoard.map((e) => e.entityId);\r\n\t\t\t\tconst opponentEntitiesThatDied = opponentEntitiesBeforeAttack.filter((e) => !opponentEntitiesAfterAttack.includes(e));\r\n\t\t\t\tthis.lastOpponentAttackerEntityIndex -= opponentEntitiesThatDied.length;\r\n\t\t\t} else {\r\n\t\t\t\tconst playerEntitiesBeforeAttack = playerBoard.map((e) => e.entityId).slice(0, this.lastPlayerAttackerEntityIndex);\r\n\t\t\t\tconst outputAttacker = simulateAttack(\r\n\t\t\t\t\topponentBoard,\r\n\t\t\t\t\topponentEntity,\r\n\t\t\t\t\tplayerBoard,\r\n\t\t\t\t\tplayerEntity,\r\n\t\t\t\t\tthis.lastOpponentAttackerEntityIndex,\r\n\t\t\t\t\tthis.allCards,\r\n\t\t\t\t\tthis.spawns,\r\n\t\t\t\t\tthis.sharedState,\r\n\t\t\t\t\tspectator,\r\n\t\t\t\t);\r\n\t\t\t\tthis.lastOpponentAttackerEntityIndex =\r\n\t\t\t\t\tthis.currentSpeedAttacker === -1 ? outputAttacker : this.lastOpponentAttackerEntityIndex;\r\n\t\t\t\tconst playerEntitiesAfterAttack = playerBoard.map((e) => e.entityId);\r\n\t\t\t\tconst playerEntitiesThatDied = playerEntitiesBeforeAttack.filter((e) => !playerEntitiesAfterAttack.includes(e));\r\n\t\t\t\tthis.lastPlayerAttackerEntityIndex -= playerEntitiesThatDied.length;\r\n\t\t\t}\r\n\r\n\t\t\t// Update the attacker indices in case there were some deaths\r\n\t\t\tif (playerBoard.some((entity) => entity.attackImmediately)) {\r\n\t\t\t\tthis.currentSpeedAttacker = 0;\r\n\t\t\t} else if (opponentBoard.some((entity) => entity.attackImmediately)) {\r\n\t\t\t\tthis.currentSpeedAttacker = 1;\r\n\t\t\t} else {\r\n\t\t\t\tthis.currentSpeedAttacker = -1;\r\n\t\t\t\tthis.currentAttacker = (this.currentAttacker + 1) % 2;\r\n\t\t\t}\r\n\t\t\tcounter++;\r\n\t\t\tif (counter > 400) {\r\n\t\t\t\tconsole.warn(\r\n\t\t\t\t\t'short-circuiting simulation, too many iterations',\r\n\t\t\t\t\tcounter,\r\n\t\t\t\t\t'\\n',\r\n\t\t\t\t\tstringifySimple(playerBoard, this.allCards),\r\n\t\t\t\t\t'\\n',\r\n\t\t\t\t\tstringifySimple(opponentBoard, this.allCards),\r\n\t\t\t\t);\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (playerBoard.length === 0 && opponentBoard.length === 0) {\r\n\t\t\treturn {\r\n\t\t\t\tresult: 'tied',\r\n\t\t\t} as SingleSimulationResult;\r\n\t\t}\r\n\t\tif (playerBoard.length === 0) {\r\n\t\t\tconst damage = this.buildBoardTotalDamage(opponentBoard) + opponentEntity.tavernTier;\r\n\t\t\tspectator.registerOpponentAttack(playerBoard, opponentBoard, damage);\r\n\t\t\treturn {\r\n\t\t\t\tresult: 'lost',\r\n\t\t\t\tdamageDealt: damage,\r\n\t\t\t};\r\n\t\t}\r\n\t\tconst damage = this.buildBoardTotalDamage(playerBoard) + playerEntity.tavernTier;\r\n\t\tspectator.registerPlayerAttack(playerBoard, opponentBoard, damage);\r\n\t\treturn {\r\n\t\t\tresult: 'won',\r\n\t\t\tdamageDealt: damage,\r\n\t\t};\r\n\t}\r\n\r\n\tprivate buildBoardTotalDamage(playerBoard: readonly BoardEntity[]): number {\r\n\t\treturn playerBoard\r\n\t\t\t.map((entity) => getEffectiveTechLevel(this.allCards.getCard(entity.cardId), this.allCards))\r\n\t\t\t.reduce((a, b) => a + b, 0);\r\n\t}\r\n}\r\n\r\nconst handleRapidReanimation = (\r\n\tplayerBoard: BoardEntity[],\r\n\tplayerEntity: BgsPlayerEntity,\r\n\topponentBoard: BoardEntity[],\r\n\topponentEntity: BgsPlayerEntity,\r\n\tallCards: AllCardsService,\r\n\tspawns: CardsData,\r\n\tsharedState: SharedState,\r\n\tspectator: Spectator,\r\n) => {\r\n\tif (playerEntity.rapidReanimationMinion) {\r\n\t\thandleRapidReanimationForPlayer(playerBoard, playerEntity, opponentBoard, opponentEntity, allCards, spawns, sharedState, spectator);\r\n\t}\r\n\tif (opponentEntity.rapidReanimationMinion) {\r\n\t\thandleRapidReanimationForPlayer(opponentBoard, opponentEntity, playerBoard, playerEntity, allCards, spawns, sharedState, spectator);\r\n\t}\r\n};\r\n\r\nconst handleRapidReanimationForPlayer = (\r\n\tplayerBoard: BoardEntity[],\r\n\tplayerEntity: BgsPlayerEntity,\r\n\topponentBoard: BoardEntity[],\r\n\topponentEntity: BgsPlayerEntity,\r\n\tallCards: AllCardsService,\r\n\tcardsData: CardsData,\r\n\tsharedState: SharedState,\r\n\tspectator: Spectator,\r\n) => {\r\n\tif (playerBoard.length >= 7) {\r\n\t\treturn;\r\n\t}\r\n\tconst newMinion = buildSingleBoardEntity(\r\n\t\tplayerEntity.rapidReanimationMinion.cardId,\r\n\t\tplayerEntity,\r\n\t\tplayerBoard,\r\n\t\tallCards,\r\n\t\tplayerEntity.rapidReanimationMinion.friendly,\r\n\t\tsharedState.currentEntityId++,\r\n\t\tfalse,\r\n\t\tcardsData,\r\n\t\tsharedState,\r\n\t\tplayerEntity.rapidReanimationMinion,\r\n\t\tnull,\r\n\t);\r\n\tconst indexFromRight = Math.min(playerBoard.length, playerEntity.rapidReanimationIndexFromRight ?? 0);\r\n\tperformEntitySpawns(\r\n\t\t[newMinion],\r\n\t\tplayerBoard,\r\n\t\tplayerEntity,\r\n\t\tplayerEntity,\r\n\t\tindexFromRight,\r\n\t\topponentBoard,\r\n\t\topponentEntity,\r\n\t\tallCards,\r\n\t\tcardsData,\r\n\t\tsharedState,\r\n\t\tspectator,\r\n\t);\r\n\tspectator.registerPowerTarget(playerEntity, newMinion, playerBoard);\r\n\tplayerEntity.rapidReanimationMinion = null;\r\n};\r\n"]}
|
|
1
|
+
{"version":3,"file":"simulator.js","sourceRoot":"","sources":["../../src/simulation/simulator.ts"],"names":[],"mappings":"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","sourcesContent":["import { AllCardsService, getEffectiveTechLevel } from '@firestone-hs/reference-data';\r\nimport { BgsGameState } from '../bgs-battle-info';\r\nimport { BgsPlayerEntity } from '../bgs-player-entity';\r\nimport { BoardEntity } from '../board-entity';\r\nimport { CardsData } from '../cards/cards-data';\r\nimport { SingleSimulationResult } from '../single-simulation-result';\r\nimport { buildSingleBoardEntity, stringifySimple } from '../utils';\r\nimport { performEntitySpawns, simulateAttack } from './attack';\r\nimport { clearStealthIfNeeded } from './auras';\r\nimport { SharedState } from './shared-state';\r\nimport { Spectator } from './spectator/spectator';\r\nimport { handleStartOfCombat } from './start-of-combat';\r\n\r\n// New simulator should be instantiated for each match\r\nexport class Simulator {\r\n\tprivate currentAttacker: number;\r\n\tprivate currentSpeedAttacker = -1;\r\n\tprivate lastPlayerAttackerEntityIndex: number;\r\n\tprivate lastOpponentAttackerEntityIndex: number;\r\n\tprivate sharedState: SharedState;\r\n\r\n\t// It should come already initialized\r\n\tconstructor(private readonly allCards: AllCardsService, private readonly spawns: CardsData) {\r\n\t\tthis.sharedState = new SharedState();\r\n\t}\r\n\r\n\t// Here we suppose that the BoardEntity only contain at most the enchantments that are linked\r\n\t// to auras (so we probably should hand-filter that, since there are actually few auras)\r\n\tpublic simulateSingleBattle(\r\n\t\tplayerBoard: BoardEntity[],\r\n\t\tplayerEntity: BgsPlayerEntity,\r\n\t\topponentBoard: BoardEntity[],\r\n\t\topponentEntity: BgsPlayerEntity,\r\n\t\tgameState: BgsGameState,\r\n\t\tspectator: Spectator,\r\n\t): SingleSimulationResult {\r\n\t\tspectator.registerStartOfCombat(playerBoard, opponentBoard);\r\n\t\t// Who attacks first is decided by the game before the hero power comes into effect. However, the full board (with the generated minion)\r\n\t\t// is sent tothe simulator\r\n\t\t// But in fact, the first player decision takes into account that additional minion. See\r\n\t\t// https://replays.firestoneapp.com/?reviewId=ddbbbe93-464b-4400-8e8d-4abca8680a2e\r\n\t\t// const effectivePlayerBoardLength =\r\n\t\t// \tplayerEntity.heroPowerId === CardIds.heroPowerUsed\r\n\t\t// \t\t? playerBoard.length - 1\r\n\t\t// \t\t: playerBoard.length;\r\n\t\t// const effectiveOpponentBoardLength =\r\n\t\t// \topponentEntity.heroPowerId === CardIds.heroPowerUsed\r\n\t\t// \t\t? opponentBoard.length - 1\r\n\t\t// \t\t: opponentBoard.length;\r\n\t\tconst effectivePlayerBoardLength = playerBoard.length;\r\n\t\tconst effectiveOpponentBoardLength = opponentBoard.length;\r\n\t\tthis.currentAttacker =\r\n\t\t\teffectivePlayerBoardLength > effectiveOpponentBoardLength\r\n\t\t\t\t? 0\r\n\t\t\t\t: effectiveOpponentBoardLength > effectivePlayerBoardLength\r\n\t\t\t\t? 1\r\n\t\t\t\t: Math.round(Math.random());\r\n\t\tthis.sharedState.currentEntityId =\r\n\t\t\tMath.max(...playerBoard.map((entity) => entity.entityId), ...opponentBoard.map((entity) => entity.entityId)) + 1;\r\n\t\tconst suggestedNewCurrentAttacker = handleStartOfCombat(\r\n\t\t\tplayerEntity,\r\n\t\t\tplayerBoard,\r\n\t\t\topponentEntity,\r\n\t\t\topponentBoard,\r\n\t\t\tthis.currentAttacker,\r\n\t\t\tthis.allCards,\r\n\t\t\tthis.spawns,\r\n\t\t\tthis.sharedState,\r\n\t\t\tgameState,\r\n\t\t\tspectator,\r\n\t\t);\r\n\t\thandleRapidReanimation(\r\n\t\t\tplayerBoard,\r\n\t\t\tplayerEntity,\r\n\t\t\topponentBoard,\r\n\t\t\topponentEntity,\r\n\t\t\tthis.allCards,\r\n\t\t\tthis.spawns,\r\n\t\t\tthis.sharedState,\r\n\t\t\tspectator,\r\n\t\t);\r\n\t\t// console.log('suggestedNewCurrentAttacker', suggestedNewCurrentAttacker);\r\n\t\t// When both players have the same amount of minions, it's possible that Illidan's Start of Combat\r\n\t\t// ability causes the same player to attack twice in a row, which is not the case in real life\r\n\t\t// So when Illidan attacks first, we then look at the expected first attacker. If it was Illidan\r\n\t\t// once more, we switch. Otherwise, we just keep the attacker as planned\r\n\t\t// FIXME: this is probably bogus when both players have Illidan's hero power?\r\n\t\t// if (effectivePlayerBoardLength === effectiveOpponentBoardLength) {\r\n\t\t// Looking at some recent data (2022/05/29), there was one board with 5 entities + Illidan, and another with 3\r\n\t\t// The 5 entities attacked (because of Illidan), and then the other board attacked\r\n\t\t// So this means that Illidan made the attack turn pass over to the other player\r\n\t\tthis.currentAttacker = suggestedNewCurrentAttacker;\r\n\t\t// }\r\n\t\tlet counter = 0;\r\n\t\twhile (playerBoard.length > 0 && opponentBoard.length > 0) {\r\n\t\t\thandleRapidReanimation(\r\n\t\t\t\tplayerBoard,\r\n\t\t\t\tplayerEntity,\r\n\t\t\t\topponentBoard,\r\n\t\t\t\topponentEntity,\r\n\t\t\t\tthis.allCards,\r\n\t\t\t\tthis.spawns,\r\n\t\t\t\tthis.sharedState,\r\n\t\t\t\tspectator,\r\n\t\t\t);\r\n\t\t\tclearStealthIfNeeded(playerBoard, opponentBoard);\r\n\t\t\t// console.log('this.currentSpeedAttacker', this.currentAttacker);\r\n\t\t\t// If there are \"attack immediately\" minions, we keep the same player\r\n\t\t\t// We put it here so that it can kick in after the start of combat effects. However here we don't want\r\n\t\t\t// to change who attacks first, so we repeat that block again after all the attacks have been resolved\r\n\t\t\t// FIXME: This is not strictly correct - if there are multiple attack immediately\r\n\t\t\t// minions that spawn on both player sides it might get a bit more complex\r\n\t\t\t// but overall it works\r\n\t\t\t// Also, this doesn't work when there are several deathrattle competing\r\n\t\t\t// to know who triggers first. See the second test case of the scallywag.test.ts\r\n\t\t\t// that is not handled properly today (the attack should in some cases happen before\r\n\t\t\t// the other deathrattle procs)\r\n\t\t\tif (playerBoard.some((entity) => entity.attackImmediately)) {\r\n\t\t\t\tthis.currentSpeedAttacker = 0;\r\n\t\t\t} else if (opponentBoard.some((entity) => entity.attackImmediately)) {\r\n\t\t\t\tthis.currentSpeedAttacker = 1;\r\n\t\t\t} else {\r\n\t\t\t\tthis.currentSpeedAttacker = -1;\r\n\t\t\t}\r\n\t\t\t// console.log('this.currentSpeedAttacker 2', this.currentAttacker, this.currentSpeedAttacker);\r\n\t\t\tif (this.currentSpeedAttacker === 0 || (this.currentSpeedAttacker === -1 && this.currentAttacker === 0)) {\r\n\t\t\t\tconst opponentEntitiesBeforeAttack = opponentBoard.map((e) => e.entityId).slice(0, this.lastOpponentAttackerEntityIndex);\r\n\t\t\t\tconst outputAttacker = simulateAttack(\r\n\t\t\t\t\tplayerBoard,\r\n\t\t\t\t\tplayerEntity,\r\n\t\t\t\t\topponentBoard,\r\n\t\t\t\t\topponentEntity,\r\n\t\t\t\t\tthis.lastPlayerAttackerEntityIndex,\r\n\t\t\t\t\tthis.allCards,\r\n\t\t\t\t\tthis.spawns,\r\n\t\t\t\t\tthis.sharedState,\r\n\t\t\t\t\tspectator,\r\n\t\t\t\t);\r\n\t\t\t\t// The \"attack immediately\" doesn't change the next attacker\r\n\t\t\t\tthis.lastPlayerAttackerEntityIndex = this.currentSpeedAttacker === -1 ? outputAttacker : this.lastPlayerAttackerEntityIndex;\r\n\t\t\t\tconst opponentEntitiesAfterAttack = opponentBoard.map((e) => e.entityId);\r\n\t\t\t\tconst opponentEntitiesThatDied = opponentEntitiesBeforeAttack.filter((e) => !opponentEntitiesAfterAttack.includes(e));\r\n\t\t\t\tthis.lastOpponentAttackerEntityIndex -= opponentEntitiesThatDied.length;\r\n\t\t\t} else {\r\n\t\t\t\tconst playerEntitiesBeforeAttack = playerBoard.map((e) => e.entityId).slice(0, this.lastPlayerAttackerEntityIndex);\r\n\t\t\t\tconst outputAttacker = simulateAttack(\r\n\t\t\t\t\topponentBoard,\r\n\t\t\t\t\topponentEntity,\r\n\t\t\t\t\tplayerBoard,\r\n\t\t\t\t\tplayerEntity,\r\n\t\t\t\t\tthis.lastOpponentAttackerEntityIndex,\r\n\t\t\t\t\tthis.allCards,\r\n\t\t\t\t\tthis.spawns,\r\n\t\t\t\t\tthis.sharedState,\r\n\t\t\t\t\tspectator,\r\n\t\t\t\t);\r\n\t\t\t\tthis.lastOpponentAttackerEntityIndex =\r\n\t\t\t\t\tthis.currentSpeedAttacker === -1 ? outputAttacker : this.lastOpponentAttackerEntityIndex;\r\n\t\t\t\tconst playerEntitiesAfterAttack = playerBoard.map((e) => e.entityId);\r\n\t\t\t\tconst playerEntitiesThatDied = playerEntitiesBeforeAttack.filter((e) => !playerEntitiesAfterAttack.includes(e));\r\n\t\t\t\tthis.lastPlayerAttackerEntityIndex -= playerEntitiesThatDied.length;\r\n\t\t\t}\r\n\r\n\t\t\t// Update the attacker indices in case there were some deaths\r\n\t\t\tif (playerBoard.some((entity) => entity.attackImmediately)) {\r\n\t\t\t\tthis.currentSpeedAttacker = 0;\r\n\t\t\t} else if (opponentBoard.some((entity) => entity.attackImmediately)) {\r\n\t\t\t\tthis.currentSpeedAttacker = 1;\r\n\t\t\t} else {\r\n\t\t\t\tthis.currentSpeedAttacker = -1;\r\n\t\t\t\tthis.currentAttacker = (this.currentAttacker + 1) % 2;\r\n\t\t\t}\r\n\t\t\tcounter++;\r\n\t\t\tif (counter > 400) {\r\n\t\t\t\tconsole.warn(\r\n\t\t\t\t\t'short-circuiting simulation, too many iterations',\r\n\t\t\t\t\tcounter,\r\n\t\t\t\t\t'\\n',\r\n\t\t\t\t\tstringifySimple(playerBoard, this.allCards),\r\n\t\t\t\t\t'\\n',\r\n\t\t\t\t\tstringifySimple(opponentBoard, this.allCards),\r\n\t\t\t\t);\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (playerBoard.length === 0 && opponentBoard.length === 0) {\r\n\t\t\treturn {\r\n\t\t\t\tresult: 'tied',\r\n\t\t\t} as SingleSimulationResult;\r\n\t\t}\r\n\t\tif (playerBoard.length === 0) {\r\n\t\t\tconst damage = this.buildBoardTotalDamage(opponentBoard) + opponentEntity.tavernTier;\r\n\t\t\tspectator.registerOpponentAttack(playerBoard, opponentBoard, damage);\r\n\t\t\treturn {\r\n\t\t\t\tresult: 'lost',\r\n\t\t\t\tdamageDealt: damage,\r\n\t\t\t};\r\n\t\t}\r\n\t\tconst damage = this.buildBoardTotalDamage(playerBoard) + playerEntity.tavernTier;\r\n\t\tspectator.registerPlayerAttack(playerBoard, opponentBoard, damage);\r\n\t\treturn {\r\n\t\t\tresult: 'won',\r\n\t\t\tdamageDealt: damage,\r\n\t\t};\r\n\t}\r\n\r\n\tprivate buildBoardTotalDamage(playerBoard: readonly BoardEntity[]): number {\r\n\t\treturn playerBoard\r\n\t\t\t.map((entity) => getEffectiveTechLevel(this.allCards.getCard(entity.cardId), this.allCards))\r\n\t\t\t.reduce((a, b) => a + b, 0);\r\n\t}\r\n}\r\n\r\nconst handleRapidReanimation = (\r\n\tplayerBoard: BoardEntity[],\r\n\tplayerEntity: BgsPlayerEntity,\r\n\topponentBoard: BoardEntity[],\r\n\topponentEntity: BgsPlayerEntity,\r\n\tallCards: AllCardsService,\r\n\tspawns: CardsData,\r\n\tsharedState: SharedState,\r\n\tspectator: Spectator,\r\n) => {\r\n\tif (playerEntity.rapidReanimationMinion) {\r\n\t\thandleRapidReanimationForPlayer(playerBoard, playerEntity, opponentBoard, opponentEntity, allCards, spawns, sharedState, spectator);\r\n\t}\r\n\tif (opponentEntity.rapidReanimationMinion) {\r\n\t\thandleRapidReanimationForPlayer(opponentBoard, opponentEntity, playerBoard, playerEntity, allCards, spawns, sharedState, spectator);\r\n\t}\r\n};\r\n\r\nconst handleRapidReanimationForPlayer = (\r\n\tplayerBoard: BoardEntity[],\r\n\tplayerEntity: BgsPlayerEntity,\r\n\topponentBoard: BoardEntity[],\r\n\topponentEntity: BgsPlayerEntity,\r\n\tallCards: AllCardsService,\r\n\tcardsData: CardsData,\r\n\tsharedState: SharedState,\r\n\tspectator: Spectator,\r\n) => {\r\n\tif (playerBoard.length >= 7) {\r\n\t\treturn;\r\n\t}\r\n\tconst newMinion = buildSingleBoardEntity(\r\n\t\tplayerEntity.rapidReanimationMinion.cardId,\r\n\t\tplayerEntity,\r\n\t\tplayerBoard,\r\n\t\tallCards,\r\n\t\tplayerEntity.rapidReanimationMinion.friendly,\r\n\t\tsharedState.currentEntityId++,\r\n\t\tfalse,\r\n\t\tcardsData,\r\n\t\tsharedState,\r\n\t\tplayerEntity.rapidReanimationMinion,\r\n\t\tnull,\r\n\t);\r\n\tconst indexFromRight = Math.min(playerBoard.length, playerEntity.rapidReanimationIndexFromRight ?? 0);\r\n\tperformEntitySpawns(\r\n\t\t[newMinion],\r\n\t\tplayerBoard,\r\n\t\tplayerEntity,\r\n\t\tplayerEntity,\r\n\t\tindexFromRight,\r\n\t\topponentBoard,\r\n\t\topponentEntity,\r\n\t\tallCards,\r\n\t\tcardsData,\r\n\t\tsharedState,\r\n\t\tspectator,\r\n\t);\r\n\tspectator.registerPowerTarget(playerEntity, newMinion, playerBoard);\r\n\tplayerEntity.rapidReanimationMinion = null;\r\n};\r\n"]}
|