您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Komputer - an AI for Viktory II
// ==UserScript== // @name Viktory Script // @namespace http://tampermonkey.net/ // @version 2024-07-13 // @description Komputer - an AI for Viktory II // @author Stephen Wilbo Montague // @match http://gamesbyemail.com/Games/Viktory2 // @icon https://www.google.com/s2/favicons?sz=64&domain=gamesbyemail.com // ==/UserScript== (function() { // Begin main script. console.log("Hello Viktory."); // Wait for page load. waitForKeyElements("#Foundation_Elemental_7_savePlayerNotes", () => { // Alias the game, add controls. const thisGame = Foundation.$registry[7]; addRunButton("Let Komputer Play", runKomputerClick, thisGame); addStopButton("Cancel", stopKomputerClick); // Add global error handling. window.onerror = function() { console.warn(`Caught error. Will reset controls.`); clearIntervalsAndTimers(); resetGlobals(true); }; // Ready window.IS_KOMPUTER_READY = true; }); // End wait for page load })(); // End main. function runKomputerClick(thisGame) { if (window.IS_KOMPUTER_READY) { resetGlobals(); styleButtonForRun(); runKomputer(thisGame); } } function clearIntervalsAndTimers() { for (var i = setTimeout(function() {}, 0); i > 0; i--) { window.clearInterval(i); window.clearTimeout(i); } } function resetGlobals(resetButton = false) { window.stop = false; window.moveIntervalId = null; window.movingUnitIndex = 0; window.moveWave = 0; window.isExploring = false; window.isBombarding = false; window.isUnloading = false; window.isBattleSelected = false; window.isManeuveringToAttack = false; window.IS_KOMPUTER_READY = false; if (resetButton) { resetKomputerButtonStyle(false); window.IS_KOMPUTER_READY = true; } window.currentPlayerTurn = Foundation.$registry[7].perspectiveColor; } function runKomputer(thisGame) { if (window.stop === true) { stopAndReset(); return; } console.log("Checking movePhase."); // Handle current state switch(thisGame.movePhase) { case 0: console.log("Game won."); setTimeout(function(){ window.IS_KOMPUTER_READY = true; resetKomputerButtonStyle(true); }, 1200); break; case 2: console.log("Placing capital."); placeCapital(thisGame); break; case 5: console.log("Movement Wave: " + window.moveWave); moveUnits(thisGame); break; case 6: console.log("Retreat is not an option. Returning to battle."); thisGame.movePhase = 5; thisGame.update(); break; case 11: console.log("Placing reserves."); placeReserves(thisGame); break; default: console.log("Unhandled movePhase: " + thisGame.movePhase); } } function moveUnits(thisGame) { const moveIntervalPeriod = 1000; const moveDelay = 400; setTimeout(async function(){ switch (window.moveWave) { case 0: { console.log("May move land units."); const landUnits = getAvailableLandUnits(thisGame, thisGame.perspectiveColor); moveEachUnitByInterval(thisGame, landUnits, moveIntervalPeriod); break; } case 1: { console.log("May move frigates."); const frigates = getFrigates(thisGame, thisGame.perspectiveColor); moveEachUnitByInterval(thisGame, frigates, moveIntervalPeriod); break; } case 2: { console.log("May move all available."); const landUnits = getAvailableLandUnits(thisGame, thisGame.perspectiveColor); const frigates = getFrigates(thisGame, thisGame.perspectiveColor); const allMilitaryUnits = landUnits.concat(frigates); moveEachUnitByInterval(thisGame, allMilitaryUnits, moveIntervalPeriod); break; } case 3:{ console.log("Handling all battles."); const battlePiece = findNextBattle(thisGame); if (battlePiece) { fightBattle(thisGame, battlePiece); } else { window.moveWave++; runKomputer(thisGame); } break; } case 4: { console.log("Ending movement."); endMovementPhase(thisGame); } } }, moveDelay); } function getAvailableLandUnits(thisGame, color) { let landUnits = []; for (const piece of thisGame.pieces) { // Skip reserve units if (piece.valueIndex === - 1) { continue; } for (const unit of piece.units) { if (unit.color === color && unit.type !== "f" && ( unit.canMove() || unit.canBombard()) ) { landUnits.push(unit); } } } return landUnits; } function getFrigates(thisGame, color) { let frigates = []; for (const piece of thisGame.pieces) { // Skip reserve units if (piece.valueIndex === - 1) { continue; } for (const unit of piece.units) { if (unit.color === color && unit.type === "f") { frigates.push(unit); } } } return frigates; } async function endMovementPhase(thisGame) { // Reset movement, then restart loop to place reserves, or stop for the next player. window.moveWave = 0; thisGame.endMyMovement(); window.moveIntervalId = await setInterval(function(){ if (thisGame.movePhase === 11) { clearInterval(window.moveIntervalId); runKomputer(thisGame); } else if(window.currentPLayerTurn !== thisGame.perspectiveColor) { clearInterval(window.moveIntervalId); thisGame.removeExcessPieces(thisGame.perspectiveColor, true); window.IS_KOMPUTER_READY = true; resetKomputerButtonStyle(); console.log("Done."); } else { thisGame.endMyMovement(); } }, 200); } async function moveEachUnitByInterval(thisGame, movableUnits, intervalPeriod) { window.moveIntervalId = await setInterval(function(){ if (window.stop === true) { stopAndReset(); return; } // Get the next unit and decide if it may move. const unit = movableUnits[window.movingUnitIndex]; const mayMoveThisWave = decideMayMoveThisWave(thisGame, unit); const firstMoveWave = 0; const finalMoveWave = 2; if (mayMoveThisWave) { const possibleMoves = unit.getMovables(); if (possibleMoves) { // Decide best move, or maybe don't accept any move to stay. const isEarlyMover = ( window.movingUnitIndex < (movableUnits.length * 0.6) && window.moveWave === firstMoveWave ); const pieceIndex = getBestMove(thisGame, possibleMoves, unit, isEarlyMover).index; const shouldAcceptMove = decideMoveAcceptance(thisGame, unit, pieceIndex); if (shouldAcceptMove) { // Move unit. let originScreenPoint = unit.screenPoint; moveUnitSimulateMouseDown(thisGame, originScreenPoint, unit); let destinationScreenPoint = thisGame.pieces[pieceIndex].$screenRect.getCenter(); moveUnitSimulateMouseUp(thisGame, destinationScreenPoint); // Commit to explore after some processing time. const normalPopup = document.getElementById("Foundation_Elemental_7_overlayCommit"); if (normalPopup || document.getElementById("Foundation_Elemental_7_customizeMapDoAll") ) { if (normalPopup) { thisGame.overlayCommitOnClick(); } setTimeout(function(){ const waterPopup = document.getElementById("Foundation_Elemental_7_waterSwap"); if (waterPopup) { thisGame.swapWaterForLand(); console.log("Water swap!"); } setTimeout(function(){ const hexTerrain = thisGame.getMapCustomizationData(); if (hexTerrain.length > 2) { thisGame.playOptions.mapCustomizationData = shuffle(hexTerrain); } thisGame.customizeMapDoAll(true); window.isExploring = false; }, 100); }, 400); window.isExploring = true; } // Make sure frigates can unload all cargo if (unit.isFrigate() && unit.hasUnloadables() && (window.moveWave === finalMoveWave) && thisGame.pieces[pieceIndex].isLand()) { window.isUnloading = true; } else { window.isUnloading = false } } // End if shouldAcceptMove } // End if possibleMoves window.isManeuveringToAttack = (window.isManeuveringToAttack && !unit.movementComplete) ? true : false; }// End if may move decideHowToContinueMove(thisGame, movableUnits, unit, finalMoveWave); }, intervalPeriod); } function decideMayMoveThisWave(thisGame, unit) { if (!unit || !unit.piece || window.isExploring || window.isBombarding) { return false } if (!unit.movementComplete || unit.hasUnloadables()) { // Hold back artillery and cavalry who don't have an adjacent frigate and who aren't maneuvering to attack from moving the first wave - to better support any battles started by infantry. return ( window.moveWave > 0 ? true : (unit.type === "c" || unit.type === "a") && !hasAdjacentFrigate(thisGame, unit.piece) && !window.isManeuveringToAttack ? false : true ); } return false; } function decideHowToContinueMove(thisGame, movableUnits, unit, finalMoveWave) { if (window.isExploring || window.isBombarding || window.isUnloading || window.isManeuveringToAttack) { // Pass: wait for these to finish. } // Bombard on the final wave. else if (window.moveWave === finalMoveWave && unit && unit.canBombard() && unit.piece && unit.getBombardables()) { window.isBombarding = true; bombard(thisGame, unit, unit.getBombardables()); } // Move the next unit next interval. else if ( (window.movingUnitIndex + 1) < movableUnits.length ) { window.movingUnitIndex++; } // Clear interval, reset moving unit index, cue next wave or game action. else { clearInterval(window.moveIntervalId); window.movingUnitIndex = 0; window.moveWave++; runKomputer(thisGame); } } function getBestMove(thisGame, possibleMoves, unit, isEarlyMover) { let bestMoveScore = -1; let bestMoves = []; for (const possibleMove of possibleMoves) { const possibleMoveScore = getMoveScore(thisGame, possibleMove, unit); if (possibleMoveScore > bestMoveScore) { bestMoveScore = possibleMoveScore; bestMoves = []; bestMoves.push(possibleMove); } else if (possibleMoveScore === bestMoveScore) { bestMoves.push(possibleMove); } } return (bestMoves.length > 1 ? getRandomItem(bestMoves) : bestMoves.pop()); } function getMoveScore(thisGame, possibleMove, unit, isEarlyMover) { const piece = thisGame.pieces.findAtXY(possibleMove.x, possibleMove.y); const enemyColor = !thisGame.perspectiveColor * 1; if (unit.isFrigate()) { return getFrigateMoveScore(thisGame, piece, unit, enemyColor); } else { let score = 0; // Convert terrain defenses of [0, 1, 2] to [0.05, 0.1, 0.15]. const terrainDefenseBonus = 0.1 * (( piece.terrainDefenses() * 0.5 ) + 0.5); if (piece.hasRollingOpponent(thisGame.perspectiveColor)) { const defendingUnitCount = piece.countOpponentMilitary(thisGame.perspectiveColor); const defendingRollCount = piece.numDefenderRolls(piece.getOpponentColor(thisGame.perspectiveColor)); const defensivePower = (0.08 * defendingRollCount) + (0.04 * defendingUnitCount); // Check enemy cities & towns. if (piece.hasOpponentCivilization(thisGame.perspectiveColor)) { // Urgently try to retake a lost capital. if (piece.hasCapital(thisGame.perspectiveColor)) { return 1; } // Complete any tactical maneuver across open terrain. if (window.isManeuveringToAttack && piece.hasBattle(thisGame.perspectiveColor, thisGame.player.team.rulerColor)) { return 1; } // Look for undefended enemy towns. if (defendingUnitCount === 0) { score = piece.hasCapital(enemyColor) ? 0.99 : defendingRollCount === 1 ? 0.98 : 0.96; } // Then look at weaker enemy towns. else { score = 1 - defensivePower; } // Randomly increase the priority of attacks so that: // Units will often gather to beseige & surround first, when random is low. // Units are able to attack even a heavy defense, when random is high. // As noted below, once one unit attacks, usually others follow. score += 0.125 * Math.random() + (1 - score) * Math.random() * 0.325; } // Check enemy in the countryside. else { score = 0.9 - defensivePower; // Prioritize enemy beseiging / pinning a friendly town. if (hasAdjacentCivilization(thisGame, piece)) { score += 0.125 * Math.random(); } } // More likely join battles already begun, especially artillery and cavalry, but avoid overkill on weak targets. if (piece.hasBattle(thisGame.perspectiveColor, thisGame.player.team.rulerColor) && isNotOverkill(thisGame, piece)) { score += unit.type === "i" ? 0.1875 : 0.25; } } // Try to beseige / pin enemy cities and towns, on the safest terrain. else if (hasAdjacentEnemyCivilization(thisGame, piece)) { score = 0.7 + terrainDefenseBonus; // Maybe maneuver unit before attack. // If unit has extra moves close to a battle, pass through open terrain to get more attack vectors. const isFirstMoveWave = window.moveWave === 0; const remainingMoveAllowance = unit.movementAllowance - unit.spacesMoved; const canManeuverBeforeAttack = (possibleMove.spacesNeeded < remainingMoveAllowance); const hasOpenTerrain = !piece.isSlowTerrain(thisGame.perspectiveColor, thisGame.player.team.rulerColor); const hasMultipleUnits = thisGame.pieces[unit.piece.index].countMilitaryUnits(thisGame.pieces[unit.piece.index].units); if (isFirstMoveWave && canManeuverBeforeAttack && hasOpenTerrain && hasMultipleUnits && hasAdjacentBattle(thisGame, piece)) { if (Math.random() < 0.5) { // If the unit boards a frigate, don't raise the maneuvering flag, so that the frigate can take control. if (!piece.hasFrigate(thisGame.perspectiveColor)) { window.isManeuveringToAttack = true; } return 1; } } } // Give importance to own civ defense. else if (piece.hasCivilization(thisGame.perspectiveColor)) { const defensivePower = calculateDefensivePower(thisGame, piece); const threat = guessThreat(thisGame, piece); score = defensivePower < threat ? ( piece.hasCapital(thisGame.perspectiveColor) || (defensivePower < 3) ) ? 0.90 + (0.06 * Math.random()) : 0.8 + (0.125 * Math.random()): 0; // Early moving units should concentrate on offense, later units on defense. score -= isEarlyMover ? 0.125 : 0; } // Consider boarding a frigate. else if (piece.hasFrigate(thisGame.perspectiveColor)) { score = 0.82; // More likely board if others on board. if (piece.findFrigate(thisGame.perspectiveColor).cargo.length > 0) { // Results in a range of [0.945, 0.97625], so it may compete with any other attacking or defending moves, except maximum priority ones. // Boarding with enough force to make a difference is often critical to be worthwhile. score += 0.125 + (0.03125 * Math.random()); } } // Move towards a friend / enemy target, ending on the safest terrain. else { const enemyColor = thisGame.perspectiveColor === 0 ? 1 : 0; const enemyArmies = getArmyUnits(thisGame, enemyColor); let enemyTarget = enemyArmies ? getRandomItem(enemyArmies) : getRandomItem(thisGame.pieces.getOpponentCivilizations(thisGame.perspectiveColor)).findCivilization(enemyColor); const distanceToEnemy = thisGame.distanceBewteenPoints(enemyTarget.piece.boardPoint, piece.boardPoint); score = 0.56 + (terrainDefenseBonus / (distanceToEnemy * 4)); score += hasAdjacentHiddenTerrain(thisGame, piece) ? 0.125 : 0 score -= isEarlyMover ? 0.125 : 0; } // Clamp score between [0,1]. score = score < 0 ? 0 : score > 1 ? 1 : score; return score; } } function getFrigateMoveScore(thisGame, piece, unit, enemyColor) { let score = 0; const hasEnemyFrigate = piece.hasFrigate(enemyColor); if (unit.hasUnloadables()) { // Loaded frigates should move toward enemy coastal towns. const enemyCivs = thisGame.pieces.getOpponentCivilizations(thisGame.perspectiveColor); let coastalCivs = []; for (const civPiece of enemyCivs) { if (unit.piece.isPerimeter() && hasAdjacentDeepWater(thisGame, civPiece) || hasAdjacentAccessibleInlandSea(thisGame, civPiece, unit)) { coastalCivs.push(civPiece); } } const targetCivs = coastalCivs.length > 0 ? coastalCivs : enemyCivs; const distance = getDistanceToNearestFrigateTarget(thisGame, targetCivs, piece); if (distance >= 0) { score = ( 1 / (distance + 1) ) * 0.7; } score += distance === 0 ? 1 / (piece.countOpponentMilitary(thisGame.perspectiveColor) + 1) * 0.125 : 0; score += piece.hasBattle(thisGame.perspectiveColor, thisGame.player.team.rulerColor) ? 0.0625 : 0; if (hasEnemyFrigate) { score += 0.03125; } } else { // Unloaded frigates should support friendlies. let friendlyArmyUnits = getArmyUnits(thisGame, thisGame.perspectiveColor); if (friendlyArmyUnits) { const distance = getDistanceToNearestUnit(thisGame, friendlyArmyUnits, piece); score = ( 1 / (distance + 1) ) * 0.7; } score += hasAdjacentCivilization(thisGame, piece) ? 0.125 : 0; score += hasAdjacentEnemyTown(thisGame, piece) ? 0.03125 : 0; if (hasEnemyFrigate && piece.findFrigate(enemyColor).hasUnloadables()) { score += 0.0625; } } // Add small weight for other considerations. score += hasAdjacentBattle(thisGame, piece) ? 0.03125 : 0; score += hasAdjacentEnemyArmy(thisGame, piece) ? 0.03125 : 0; // Clamp to [0,1]. score = score < 0 ? 0 : score > 1 ? 1 : score; return score; } function isNotOverkill(thisGame, piece) { const enemyColor = !thisGame.perspectiveColor * 1; const defenderUnitCount = piece.getMilitaryUnitCount(enemyColor); const defenderRollCount = piece.numDefenderRolls(enemyColor); const defenderPower = (2 * defenderRollCount) + defenderUnitCount; const attackerUnitCount = piece.getMilitaryUnitCount(thisGame.perspectiveColor); const attackerRollCount = piece.numAttackerRolls(thisGame.perspectiveColor); const attackerPower = (2 * attackerRollCount) + attackerUnitCount; const isOverkill = defenderPower < attackerPower * 0.6 ? true : false; return !isOverkill; } function calculateDefensivePower(thisGame, piece) { const civDefenderCount = piece.getMilitaryUnitCount(thisGame.perspectiveColor); const civRollCount = piece.numDefenderRolls(thisGame.perspectiveColor); return (civDefenderCount ? (2 * civRollCount) + civDefenderCount : piece.hasCity(thisGame.perspectiveColor) ? 2 : 1); } function guessThreat(thisGame, piece) { const enemyColor = !thisGame.perspectiveColor * 1; const enemyArmyUnits = getArmyUnits(thisGame, enemyColor); if (!enemyArmyUnits) { return 0; } let threatCount = 0; let hasInfantry = false; let hasCavalry = false; let hasArtillery = false; for (const unit of enemyArmyUnits) { let inRangePoints = unit.getMovables(); if (!inRangePoints) { continue; } for (const point of inRangePoints) { if (point.x === piece.boardPoint.x && point.y === piece.boardPoint.y) { threatCount++; if (!hasInfantry && unit.isInfantry()) { hasInfantry = true break; } if (!hasCavalry && unit.isCavalry()) { hasCavalry = true; break; } if (!hasArtillery && unit.isArtillery()) { hasArtillery = true; break; } break; } } } const enemyFrigates = getFrigates(thisGame, enemyColor); for (const frigate of enemyFrigates) { let amphibEnemyCount = 0; let inRangePoints = frigate.getMovables(); if (!inRangePoints) { continue; } for (const point of inRangePoints) { if (point.x === piece.boardPoint.x && point.y === piece.boardPoint.y) { if (frigate.cargo.length > 0) { amphibEnemyCount += frigate.cargo.length; if (!hasInfantry && frigate.carriesCargo("i")) { hasInfantry = true } if (!hasCavalry && frigate.carriesCargo("c")) { hasCavalry = true; } if (!hasArtillery && frigate.carriesCargo("a")) { hasArtillery = true; } } const frigateCapacity = 3; let loadableUnitCount = 0; let hasFullCapacityPotential = false; if (amphibEnemyCount < frigateCapacity && hasAdjacentEnemyArmy(thisGame, frigate.piece)) { const adjacentPieceIndices = frigate.piece.getAdjacentIndecies(1); for (const adjacentPieceIndex of adjacentPieceIndices) { loadableUnitCount += thisGame.pieces[adjacentPieceIndex].getMilitaryUnitCount(enemyColor); if (loadableUnitCount + amphibEnemyCount >= frigateCapacity) { hasFullCapacityPotential = true; break; } } amphibEnemyCount = hasFullCapacityPotential ? frigateCapacity : amphibEnemyCount + loadableUnitCount; } } // End if point === inRange } // End for each point threatCount += amphibEnemyCount; } // End for each frigate // Estimate likely number of rolls, based on enemy count & type. const attackVectorBonus = threatCount < 2 ? 0 : threatCount < 4 ? 1 : threatCount < 6 ? 2 : threatCount < 10 ? 3 : 4; const threatRollCount = attackVectorBonus + hasInfantry + hasCavalry + hasArtillery; // Weight to favor rolls and combine to estimate threat. return ((2 * threatRollCount) + threatCount); } function getDistanceToNearestUnit(thisGame, units, originPiece) { let minDistance = Number.MAX_VALUE; for (const unit of units) { const distance = thisGame.distanceBewteenPoints(unit.piece.boardPoint, originPiece.boardPoint); if (distance < minDistance) { minDistance = distance; } } return minDistance; } function getDistanceToNearestFrigateTarget(thisGame, enemyCivs, originPiece) { let distance = -1; let minDistance = Number.MAX_VALUE; for (const civPiece of enemyCivs) { distance = thisGame.distanceBewteenPoints(civPiece.boardPoint, originPiece.boardPoint); if (distance < minDistance) { minDistance = distance } } return minDistance; } function decideMoveAcceptance(thisGame, unit, destinationIndex) { // Consider guarding a vulnerable or beseiged town vs attacking a nearby enemy. if (unit.piece.hasCivilization(thisGame.perspectiveColor)) { const defensivePower = calculateDefensivePower(thisGame, unit.piece); const threat = guessThreat(thisGame, unit.piece); const isVulnerable = defensivePower < threat; if ( isVulnerable || unit.piece.hasAdjacentRollingEnemy(thisGame.perspectiveColor, thisGame.player.team.rulerColor)) { // Going to own capital or from own capital to fight is always approved. if (thisGame.pieces[destinationIndex].hasCapital(thisGame.perspectiveColor) || ( unit.piece.hasCapital(thisGame.perspectiveColor) && thisGame.pieces[destinationIndex].hasBattle(thisGame.perspectiveColor)) ) { return true; } // Cavalry may always join battles that don't have friendly cavalry. if (unit.type === "c" && thisGame.pieces[destinationIndex].hasBattle(thisGame.perspectiveColor, thisGame.player.team.rulerColor && !thisGame.pieces[destinationIndex].hasCavalry(thisGame.perspectiveColor))) { return true; } // Check if last defender. if (unit.piece.getMilitaryUnitCount(thisGame.perspectiveColor) === 1) { // If not joining a battle, never leave, and otherwise, still rarely leave. if (!thisGame.pieces[destinationIndex].hasRollingOpponent(thisGame.perspectiveColor) || Math.random() < 0.9) { unit.movementComplete = true; return false; } } // Otherwise maybe stop the move. else { if (Math.random() < 0.2) { unit.movementComplete = true; return false } } } } return true; } function bombard(thisGame, unit, bombardablePoints) { bombardUnitsSimulateMouseDown(thisGame, unit); const targetPoint = getBestTargetPoint(thisGame, bombardablePoints); const targetPiece = thisGame.pieces.findAtPoint(targetPoint); const targetScreenPoint = targetPiece.$screenRect.getCenter(); const fireDelay = 200; setTimeout(function(){ const hasFired = bombardUnitsSimulateMouseUp(thisGame, targetScreenPoint); if (hasFired) { const commitDelay = 200; setTimeout(function(){ thisGame.overlayCommitOnClick(); // Apply hits. const applyHitsDelay = 200; setTimeout(function(){ if (thisGame.battleData) { const data = thisGame.getBattleData(); if (data && data.piece.defenderBattleInfo && data.piece.defenderBattleInfo.decisionNeeded) { applyHits(thisGame, data.piece.index, data, true); } } const reviewDelay = 800; setTimeout(function(){ thisGame.pieces[targetPiece.index].bombardOkClick(thisGame.player.team.color); unit.hasBombarded = unit.noBombard = unit.movementComplete = true; console.log("Bombardment!"); window.isBombarding = false; }, reviewDelay) }, applyHitsDelay); }, commitDelay); } // End if hasFired else { // Validation fix for rare bug where unit forever tries to bombard. unit.hasBombarded = true; unit.noBombard = true; unit.movementComplete = true; window.isBombarding = false; } }, fireDelay); } function getBestTargetPoint(thisGame, bombardablePoints) { for (const bombardablePoint of bombardablePoints) { const piece = thisGame.pieces.findAtPoint(bombardablePoint); if (piece && piece.hasBattle(thisGame.player.team.color, thisGame.player.team.rulerColor) && piece.hasNonRulingOpponentMilitary(thisGame.perspectiveColor, thisGame.player.team.rulerColor)) { return bombardablePoint; } } return getRandomItem(bombardablePoints); } function findNextBattle(thisGame) { for (let piece of thisGame.pieces) { if (piece.hasBattle(thisGame.player.team.color, thisGame.player.team.rulerColor)) { return piece; } } return null; } function fightBattle(thisGame, battlePiece, isReserveBattle = false) { // Clear any exploration popup const explorationPopup = document.getElementById("Foundation_Elemental_7_overlayCommit"); if (explorationPopup || document.getElementById("Foundation_Elemental_7_customizeMapDoAll") ) { if (explorationPopup) { thisGame.overlayCommitOnClick(); } } // Select battle if (!window.isBattleSelected) { thisGame.moveUnitsMouseDown(battlePiece.$screenRect.getCenter()); window.isBattleSelected = true; } // Do prebattle artillery if (document.getElementById("Foundation_Elemental_7_battleOk")) { battlePiece.preBattleOkClick(thisGame.player.team.color); } // Roll loop const rollDelay = 200; setTimeout(function roll(){ thisGame.overlayCommitOnClick(); // Apply hits. const applyHitsDelay = 200; setTimeout(function(){ if (thisGame.battleData) { const data = thisGame.getBattleData(); if (data && data.piece.attackerBattleInfo && data.piece.attackerBattleInfo.decisionNeeded || data.piece.defenderBattleInfo && data.piece.defenderBattleInfo.decisionNeeded) { applyHits(thisGame, battlePiece.index, data); } } // Close battle after review time, if game not won, then reroll or continue game. const battleReviewDelay = 1600; setTimeout(function(){ if (thisGame.movePhase !== 0) { thisGame.pieces[battlePiece.index].battleOkClick(thisGame.player.team.color); const reRollDelay = 1000; setTimeout(function(){ if (document.getElementById("Foundation_Elemental_7_overlayCommit")) { roll(); } else { window.isBattleSelected = false; if (!isReserveBattle) { runKomputer(thisGame); } } }, reRollDelay); } // Game won. Leave battle on screen and end. else { window.IS_KOMPUTER_READY = true; resetKomputerButtonStyle(true); console.log("Viktory."); } }, battleReviewDelay); }, applyHitsDelay); }, rollDelay); } function applyHits(thisGame, pieceIndex, battleData, isBombarding = false) { const thisPiece = thisGame.pieces[pieceIndex]; const attackerColor = thisGame.player.team.color; const defenderColor = (attackerColor === 0) ? 1 : 0; const attackerHitThreshold = thisGame.getHitThreshold(attackerColor); const defenderHitThreshold = thisGame.getHitThreshold(defenderColor); const attackerUnitList = thisPiece.getMilitaryUnitList(attackerColor); const defenderUnitList = thisPiece.getDefenderUnitList(defenderColor); thisPiece.defenderBattleInfo = thisPiece.getBattleInfo(defenderUnitList, battleData.attackerRolls, attackerHitThreshold,true); // Choose highest value hits on the defender. if (thisPiece.defenderBattleInfo.decisionNeeded) { const hitCount = thisPiece.defenderBattleInfo.numTacticalHit; thisPiece.hitHighestMilitaryUnits(defenderUnitList, hitCount, false); } // Check if attackers are present, since attackers may be bombarding. if (attackerUnitList && attackerUnitList.length > 0) { thisPiece.attackerBattleInfo = thisPiece.getBattleInfo(attackerUnitList, battleData.defenderRolls, defenderHitThreshold,false); // Choose lowest value hits on the attacker. if (thisPiece.attackerBattleInfo.decisionNeeded) { const hitCount = (thisPiece.attackerBattleInfo.numHit - thisPiece.attackerBattleInfo.numTacticalHit); thisPiece.hitLowestMilitaryUnits(attackerUnitList, hitCount, false); } } setTimeout(function(){ if (isBombarding) { thisPiece.bombardOkClick(attackerColor); } else { thisPiece.battleOkClick(attackerColor); } }, 200); } async function placeReserves(thisGame) { clearIntervalsAndTimers(); window.reserveIntervalId = await setInterval(placeReserveUnit, 1100, thisGame); } function placeReserveUnit(thisGame){ if (window.stop === true) { stopAndReset(); return; } const reserveUnits = thisGame.player.team.reserveUnits; const controlsCapital = thisGame.doesColorControlTheirCapital(thisGame.player.team.color); let hasPlayableReserveUnit = false; if (thisGame.movePhase === 11 && reserveUnits.length > 0) { for (let i = 0; i < reserveUnits.length; i++) { if (thisGame.couldPlaceReserveUnit(reserveUnits[i], thisGame.player.team.color, controlsCapital)) { thisGame.reserveOnMouseDown(thisGame, function(){return true},i); hasPlayableReserveUnit = true; break; } } } if (hasPlayableReserveUnit) { // Place reserve unit. const movingUnitType = thisGame.pieces.getNewPiece().movingUnit.type; const destinationBoardPoint = (movingUnitType === "t" || movingUnitType === "y") ? ( getBestBuildable(thisGame) ) : ( getBestReservable(thisGame, movingUnitType, controlsCapital) ); const destinationScreenPoint = thisGame.screenRectFromBoardPoint(destinationBoardPoint).getCenter(); thisGame.placeReserveOnMouseUp(destinationScreenPoint); if (document.getElementById("Foundation_Elemental_7_overlayCommit")) { thisGame.overlayCommitOnClick(); setTimeout(function(){ const waterPopup = document.getElementById("Foundation_Elemental_7_waterSwap"); if (waterPopup) { thisGame.swapWaterForLand(); console.log("Water swap!"); } setTimeout(function(){ thisGame.customizeMapDoAll(true); }, 100); }, 700); } } // End placing reserves. Check for battles, then make ready for next player. else { const battlePiece = findNextBattle(thisGame); if (battlePiece) { if (!window.isBattleSelected) { console.log("Handling reserve battle."); fightBattle(thisGame, battlePiece, true); } } else { clearInterval(window.reserveIntervalId); thisGame.removeExcessPieces(thisGame.perspectiveColor, true); if (window.currentPlayerTurn === thisGame.perspectiveColor) { thisGame.endMyTurn(); } window.IS_KOMPUTER_READY = true; resetKomputerButtonStyle(); console.log("Done."); } } } function getBestBuildable(thisGame) { let buildablePoints = thisGame.getBuildables(thisGame.player.team.color, thisGame.player.team.rulerColor); sortByDistanceToEnemy(thisGame, buildablePoints); // Return any closest threatened town point. for (const point of buildablePoints) { const piece = thisGame.pieces.findAtPoint(point); if (piece.hasTown(thisGame.perspectiveColor)) { const hasThreat = guessThreat(thisGame, piece) > 0; if (hasThreat) { return point; } } } // Note which terrain types are occupied. const civilizations = thisGame.pieces.getCivilizations(thisGame.perspectiveColor); let hasMountain = false; let hasGrass = false; let hasForest = false; let hasPlain = false; for (const civ of civilizations) { if (civ.isPlain()) { hasPlain = true; } else if (civ.isGrassland()) { hasGrass = true; } else if (civ.isForest()) { hasForest = true; } else if (civ.isMountain()) { hasMountain = true; } // When occupying one of each terrain type, return buidable point closest to enemy. if (hasPlain && hasGrass && hasForest && hasMountain) { return buildablePoints[0]; } } // Else return a terrain type not yet occupied, closest to the enemy. let terrainPoint = null; if (!hasMountain) { terrainPoint = findTerrain(thisGame, buildablePoints, "m") if (terrainPoint) { return terrainPoint; } } if (!hasGrass) { terrainPoint = findTerrain(thisGame, buildablePoints, "g") if (terrainPoint) { return terrainPoint; } } if (!hasForest) { terrainPoint = findTerrain(thisGame, buildablePoints, "f") if (terrainPoint) { return terrainPoint; } } if (!hasPlain) { terrainPoint = findTerrain(thisGame, buildablePoints, "p") if (terrainPoint) { return terrainPoint; } } return buildablePoints[0]; } function findTerrain(thisGame, buildablePoints, terrainValue) { for (const point of buildablePoints) { const piece = thisGame.pieces.findAtPoint(point); if (piece.value === terrainValue) { return point; } } return null; } function getBestReservable(thisGame, movingUnitType, controlsCapital) { // Get reservable points and remove placeholders. let reservables = thisGame.pieces.getReservables(thisGame.player.team.color,thisGame.player.team.rulerColor, movingUnitType, controlsCapital); for (let i = reservables.length -1; i >= 0; i--) { if (reservables[i].placeHolderOnly) { reservables.splice(i, 1); } } // Consider most vulnerable towns first. sortByDistanceToEnemy(thisGame, reservables); for (const reservable of reservables) { const piece = thisGame.pieces.findAtPoint(reservable); const hasThreat = guessThreat(thisGame, piece) > 0; if (hasThreat && !hasArmy(piece, thisGame.perspectiveColor)) { return reservable; } } return ( reservables.length > 1 ? Math.random() < 0.8 ? reservables[0] : reservables[1] : reservables[0] ); } /// Sorts closest to farthest. function sortByDistanceToEnemy(thisGame, points) { // Get enemy armies or towns. const enemyColor = !thisGame.perspectiveColor * 1; let enemyArmies = getArmyUnits(thisGame, enemyColor); if (!enemyArmies) { enemyArmies = [getRandomItem(thisGame.pieces.getOpponentCivilizations(thisGame.perspectiveColor)).findCivilization(enemyColor)]; if (enemyArmies.length === 0) { return points; } } let minimumPointToEnemyDistances = [] // Find the closest distance of each reservable point to all enemies. for (const point of points) { let minDistanceToArmy = Number.MAX_VALUE; for (const enemyArmy of enemyArmies) { const distanceToArmy = thisGame.distanceBewteenPoints(enemyArmy.piece.boardPoint, point); if (distanceToArmy < minDistanceToArmy) { minDistanceToArmy = distanceToArmy; } } minimumPointToEnemyDistances.push(minDistanceToArmy); } // Sort all reservables based on the closest distance of each to the enemy. points.sort(function(a, b){ return minimumPointToEnemyDistances[points.indexOf(a)] - minimumPointToEnemyDistances[points.indexOf(b)] }); } // Highly specific instructions for the first two turns function placeCapital(thisGame) { // Explore 5 hexes, handle water. let hexOrder = thisGame.getMapCustomizationData(); const hasWater = (hexOrder.indexOf("w") > -1) ? true : false; if (hasWater) { while (waterCount(hexOrder) > 2) { thisGame.swapWaterForLand(); hexOrder = thisGame.getMapCustomizationData(); } thisGame.playOptions.mapCustomizationData = [hexOrder[0], hexOrder[4], hexOrder[1], hexOrder[3], hexOrder[2]].join(""); } thisGame.customizeMapDoAll(true); // Place capital & explore adjacent, using a bit of advice from Peter M's strategy guide. let pieceIndexChoices = (thisGame.player.team.color === 0) ? [7, 9, 24] : [36, 51, 53]; let pieceIndex = (thisGame.player.team.color === 0) ? getRandomItem(pieceIndexChoices) : getSecondCapitalPieceIndex(thisGame, pieceIndexChoices); let destinationScreenPoint = thisGame.pieces[pieceIndex].$screenRect.getCenter(); thisGame.placeCapitalMouseDown(destinationScreenPoint); thisGame.overlayCommitOnClick(); const customizeMapDelay1 = 800; setTimeout(function(){ const waterPopup = document.getElementById("Foundation_Elemental_7_waterSwap"); if (waterPopup) { thisGame.swapWaterForLand(); console.log("Water swap!"); } thisGame.customizeMapDoAll(true); if (thisGame.player.team.color === 0) { thisGame.endMyTurn(); window.IS_KOMPUTER_READY = true; resetKomputerButtonStyle(); console.log("Done."); } // Place first town based on specific location data. Later reserve phases use other guidance. else { if (thisGame.movePhase === 11) { thisGame.reserveOnMouseDown(thisGame, function(){return true},0); pieceIndex = (pieceIndex < 51) ? pieceIndexChoices[0] : (pieceIndex > 51) ? pieceIndexChoices[1]: getRandomItem(pieceIndexChoices); destinationScreenPoint = thisGame.pieces[pieceIndex].$screenRect.getCenter(); thisGame.placeReserveOnMouseUp(destinationScreenPoint) thisGame.overlayCommitOnClick(); const customizeMapDelay2 = 1000; setTimeout(function(){ const waterPopup = document.getElementById("Foundation_Elemental_7_waterSwap"); if (waterPopup) { thisGame.swapWaterForLand(); console.log("Water swap!"); } thisGame.customizeMapDoAll(true); thisGame.reserveOnMouseDown(thisGame, function() {return true},0); thisGame.placeReserveOnMouseUp( destinationScreenPoint ); thisGame.endMyTurn(); window.IS_KOMPUTER_READY = true; resetKomputerButtonStyle(); console.log("Done."); }, customizeMapDelay2); } }}, customizeMapDelay1); } function waterCount(stringHexData) { let count = 0; for (let i = 0; i < stringHexData.length; i++) { if (stringHexData.charAt(i)==="w") { count++; } } return count; } function getSecondCapitalPieceIndex(thisGame, pieceIndexChoices) { // Usually take the opposite side of the opponent, or if center, play random. const randomMoveChance = 0.125; if (Math.random() < randomMoveChance || thisGame.pieces[9].hasUnit(0, "C")) { return pieceIndexChoices.splice(getRandomIndexExclusive(pieceIndexChoices.length), 1); } else { const opponentCapitalPiece = thisGame.pieces.findCapitalPiece(0); if (opponentCapitalPiece.index < 9) { return pieceIndexChoices.pop(); } else { return pieceIndexChoices.shift(); } } } function hasAdjacentCivilization(thisGame, piece) { let adjacentPiece; return ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y-1)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y+1)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y-1)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y+1)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)); } function findAdjacentCivilization(thisGame, piece) { let adjacentPiece; return ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y-1)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y+1)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y-1)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y+1)) != null && adjacentPiece.hasCivilization(thisGame.perspectiveColor)) ? adjacentPiece : null; } function hasAdjacentEnemyCivilization(thisGame, piece) { let adjacentPiece; return ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y)) != null && adjacentPiece.hasOpponentCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y)) != null && adjacentPiece.hasOpponentCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y-1)) != null && adjacentPiece.hasOpponentCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y+1)) != null && adjacentPiece.hasOpponentCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y-1)) != null && adjacentPiece.hasOpponentCivilization(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y+1)) != null && adjacentPiece.hasOpponentCivilization(thisGame.perspectiveColor)); } function hasAdjacentEnemyTown(thisGame, piece) { let adjacentPiece; return ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y)) != null && adjacentPiece.hasOpponentTown(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y)) != null && adjacentPiece.hasOpponentTown(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y-1)) != null && adjacentPiece.hasOpponentTown(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y+1)) != null && adjacentPiece.hasOpponentTown(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y-1)) != null && adjacentPiece.hasOpponentTown(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y+1)) != null && adjacentPiece.hasOpponentTown(thisGame.perspectiveColor)); } function hasAdjacentHiddenTerrain(thisGame, piece) { let adjacentPiece; return ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y)) != null && adjacentPiece.hidden) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y)) != null && adjacentPiece.hidden) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y-1)) != null && adjacentPiece.hidden) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y+1)) != null && adjacentPiece.hidden) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y-1)) != null && adjacentPiece.hidden) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y+1)) != null && adjacentPiece.hidden); } function hasAdjacentDeepWater(thisGame, piece) { let adjacentPiece; return ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y)) != null && adjacentPiece.isPerimeter()) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y)) != null && adjacentPiece.isPerimeter()) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y-1)) != null && adjacentPiece.isPerimeter()) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y+1)) != null && adjacentPiece.isPerimeter()) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y-1)) != null && adjacentPiece.isPerimeter()) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y+1)) != null && adjacentPiece.isPerimeter()); } function hasAdjacentAccessibleInlandSea(thisGame, piece, unit) { let adjacentPiece; return ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y)) != null && adjacentPiece.boardValue === "w" && isAccessibleNow(adjacentPiece, unit)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y)) != null && adjacentPiece.boardValue === "w" && isAccessibleNow(adjacentPiece, unit)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y-1)) != null && adjacentPiece.boardValue === "w" && isAccessibleNow(adjacentPiece, unit)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y+1)) != null && adjacentPiece.boardValue === "w" && isAccessibleNow(adjacentPiece, unit)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y-1)) != null && adjacentPiece.boardValue === "w" && isAccessibleNow(adjacentPiece, unit)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y+1)) != null && adjacentPiece.boardValue === "w" && isAccessibleNow(adjacentPiece, unit)); } function isAccessibleNow(piece, unit) { if (piece && unit) { const unitMovablePoints = unit.getMovables(); if (unitMovablePoints.length) { for (const point of unitMovablePoints) { if (point.x === piece.boardPoint.x && point.y === piece.boardPoint.y) { return true; } } } } return false; } function hasAdjacentFrigate(thisGame, piece) { let adjacentPiece; return ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y)) != null && adjacentPiece.hasFrigate(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y)) != null && adjacentPiece.hasFrigate(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y-1)) != null && adjacentPiece.hasFrigate(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y+1)) != null && adjacentPiece.hasFrigate(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y-1)) != null && adjacentPiece.hasFrigate(thisGame.perspectiveColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y+1)) != null && adjacentPiece.hasFrigate(thisGame.perspectiveColor)); } function hasAdjacentBattle(thisGame, piece) { let adjacentPiece; return ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y)) != null && adjacentPiece.hasBattle(thisGame.perspectiveColor, thisGame.player.team.rulerColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y)) != null && adjacentPiece.hasBattle(thisGame.perspectiveColor, thisGame.player.team.rulerColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y-1)) != null && adjacentPiece.hasBattle(thisGame.perspectiveColor, thisGame.player.team.rulerColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y+1)) != null && adjacentPiece.hasBattle(thisGame.perspectiveColor, thisGame.player.team.rulerColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y-1)) != null && adjacentPiece.hasBattle(thisGame.perspectiveColor, thisGame.player.team.rulerColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y+1)) != null && adjacentPiece.hasBattle(thisGame.perspectiveColor, thisGame.player.team.rulerColor)); } function hasAdjacentEnemyArmy(thisGame, piece) { const enemyColor = !thisGame.perspectiveColor * 1; let adjacentPiece; return ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y)) != null && hasArmy(adjacentPiece, enemyColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y)) != null && hasArmy(adjacentPiece, enemyColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y-1)) != null && hasArmy(adjacentPiece, enemyColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x, piece.boardPoint.y+1)) != null && hasArmy(adjacentPiece, enemyColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x-1, piece.boardPoint.y-1)) != null && hasArmy(adjacentPiece, enemyColor)) || ((adjacentPiece = thisGame.pieces.findAtXY(piece.boardPoint.x+1, piece.boardPoint.y+1)) != null && hasArmy(adjacentPiece, enemyColor)); } function hasArmy(piece, color) { return (piece.hasInfantry(color) || piece.hasArtillery(color) || piece.hasCavalry(color)); } function getArmyUnits(thisGame, color) { let armies = []; for (const piece of thisGame.pieces) { // Skip water and reserve pieces if (piece.isWater() || piece.valueIndex === - 1) { continue; } for (const unit of piece.units) { if (unit.color === color && unit.isMilitary()) { armies.push(unit); } } } return (armies.length > 0 ? armies : null ); } function getRandomItem(items) { const MAX = items.length; const RANDOM_INDEX = getRandomIndexExclusive(MAX); return items[RANDOM_INDEX]; } function getRandomIndexExclusive(max) { return Math.floor(Math.random() * max); } function shuffle(string) { // Schwartzian Transform, from anonymous on Stackoverflow - see Wikipedia for description. return string.split("").map(v => [v, Math.random()]).sort((a, b) => a[1] - b[1]).map(v => v[0]).join(""); } function addRunButton(text, onclick, pointerToGame) { let style = {position: 'absolute', top: '776px', left:'24px', 'z-index': '9999', "-webkit-transition-duration": "0.6s", "transition-duration": "0.6s", overflow: 'hidden', width: '128px', 'font-size': '10px'} let button = document.createElement('button'), btnStyle = button.style document.body.appendChild(button) // For now, this works well enough. button.setAttribute("class", "button_runKomputer"); button.innerHTML = text; button.id = "KomputerButton"; button.onclick = function() {onclick(pointerToGame)}; Object.keys(style).forEach(key => btnStyle[key] = style[key]) // Add Button Press Transition 1 const cssButtonClassString1 = `.button_runKomputer:after{content: ""; background: #90EE90; display: block; position: absolute; padding-top: 300%; padding-left: 350%; margin-left: -20px!important; margin-top: -120%; opacity: 0; transition: all 1.0s}`; const styleTag1 = document.createElement("style"); styleTag1.innerHTML = cssButtonClassString1; document.head.insertAdjacentElement('beforeend', styleTag1); // Add Button Press Transition 2 const cssButtonClassString2 = `.button_runKomputer:active:after{padding: 0; margin: 0; opacity: 1; transition: 0s}`; const styleTag2 = document.createElement("style"); styleTag2.innerHTML = cssButtonClassString2; document.head.insertAdjacentElement('beforeend', styleTag2); } function addStopButton(text, onclick) { let style = {position: 'absolute', top: '796px', left:'56px', 'z-index': '9999', "-webkit-transition-duration": "0.2s", "transition-duration": "0.2s", overflow: 'hidden', width: '64px', 'font-size': '10px'} let button = document.createElement('button'), btnStyle = button.style document.body.appendChild(button) // For now, this works well enough. button.setAttribute("class", "button_stopKomputer"); button.id = "StopKomputerButton"; button.innerHTML = text; button.onclick = function() {onclick()}; Object.keys(style).forEach(key => btnStyle[key] = style[key]) // Add Button Press Transition 1 const cssButtonClassString1 = `.button_stopKomputer:after{content: ""; background: #FF6347; display: block; position: absolute; padding-top: 300%; padding-left: 350%; margin-left: -20px!important; margin-top: -120%; opacity: 0; transition: all 0.4s}`; const styleTag1 = document.createElement("style"); styleTag1.innerHTML = cssButtonClassString1; document.head.insertAdjacentElement('beforeend', styleTag1); // Add Button Press Transition 2 const cssButtonClassString2 = `.button_stopKomputer:active:after{padding: 0; margin: 0; opacity: 1; transition: 0s}`; const styleTag2 = document.createElement("style"); styleTag2.innerHTML = cssButtonClassString2; document.head.insertAdjacentElement('beforeend', styleTag2); } function stopKomputerClick() { window.stop = true; styleButtonForStop(); setTimeout(function(){ if (window.stop === true) { window.stop = false; resetStopKomputerButtonStyle(); throw new Error("Force Stop. Possibly no error. Error thrown in case of undetected infinite loop.") } }, 3000); } function stopAndReset() { clearIntervalsAndTimers(); resetAllButtonStyles(); resetGlobals(true); console.log("Manually Stopped."); } function resetAllButtonStyles() { resetKomputerButtonStyle(); resetStopKomputerButtonStyle(); } function styleButtonForStop() { let button = document.getElementById("StopKomputerButton"); button.style.backgroundColor = 'lightpink'; button.style.color = 'crimson'; button.innerHTML = "Stopping"; } function styleButtonForRun() { let button = document.getElementById("KomputerButton"); button.style.backgroundColor = 'mediumseagreen'; button.style.color = 'crimson'; button.innerHTML = "Running"; } function resetKomputerButtonStyle(isGameWon = false) { let button = document.getElementById("KomputerButton"); button.style.backgroundColor = ''; button.style.color = ''; button.innerHTML = isGameWon ? "Viktory" : "Let Komputer Play"; } function resetStopKomputerButtonStyle() { let button = document.getElementById("StopKomputerButton"); button.style.backgroundColor = ''; button.style.color = ''; button.innerHTML = "Cancel"; } // Clone for an original codebase function with a few mods to support automated play. function moveUnitSimulateMouseDown(thisGame, screenPoint, unit = null) { thisGame.maybeResetReservesByMouseUp(); thisGame.moveBombardUnitsMouseOut(screenPoint=thisGame.constrainPoint(screenPoint)); thisGame.maybeHideOverlay(); if (unit) { thisGame.setTargetPoints(unit.getMovables()); thisGame.onLeftMouseUp="moveUnitsMouseUp"; thisGame.onMouseMove=null; thisGame.onMouseOver=null; thisGame.onMouseOut=null; let piece=thisGame.pieces.getNewPiece(); piece.setMovingUnit(unit); } else { let boardPoint; let piece; if ((boardPoint=thisGame.boardPointFromScreenPoint(screenPoint)) && (piece=thisGame.pieces.findAtPoint(boardPoint)) && piece.hasBattle(thisGame.player.team.color,thisGame.player.team.rulerColor)) { piece.setBorder(true); thisGame.battleIndex=piece.index; thisGame.pushMove("Battle",thisGame.logEntry(6,piece.index,piece.boardValue,piece.getOpponentColor(thisGame.player.team.color)),piece,piece.hasPreBattle(thisGame.player.team.color) ? "processPreBattleMove" : "processStartBattleMove",true,"beginBattle","cancel"); thisGame.update(); } else { thisGame.showOverlay(); } } } // Clone for an original codebase function with a few mods to support automated play. function moveUnitSimulateMouseUp(thisGame, screenPoint) { thisGame.onMouseMove=null; thisGame.onLeftMouseUp=null; if (thisGame.lastLoadableUnit) { thisGame.lastLoadableUnit.setHilite(false); thisGame.lastLoadableUnit=null; } let movingPiece=thisGame.pieces.getNewPiece(); let boardPoint=thisGame.boardPointFromScreenPoint(screenPoint); if (thisGame.isTargetPoint(boardPoint)) { let targetPiece=thisGame.pieces.findAtPoint(boardPoint); // Load frigate if (movingPiece.movingUnit.isLandUnit() && targetPiece.isWater()) { let oldPiece=movingPiece.movingUnit.piece; let loadableUnit=thisGame.militaryUnitFromScreenPoint(screenPoint,null,movingPiece.movingUnit.color,movingPiece.movingUnit.rulerColor,false,false,true); let log=thisGame.logEntry(7,oldPiece.index,oldPiece.boardValue,targetPiece.index,targetPiece.boardValue,movingPiece.movingUnit.type,loadableUnit.type); loadableUnit.loadCargo(movingPiece.movingUnit); movingPiece.setMovingUnit(null); oldPiece.updateUnitDisplay(); loadableUnit.piece.updateUnitDisplay(); thisGame.setTargetPoints(null); let nltd=thisGame.nothingLeftToDo(); thisGame.pushMove("Load",log,targetPiece,"processMoveUnitMove",nltd,nltd ? "commitEndOfTurn" : null); } // Unload frigate else if (movingPiece.movingUnit.isFrigate() && targetPiece.isLand()) { let oldPiece=movingPiece.movingUnit.piece; let log=thisGame.logEntry(8,oldPiece.index,oldPiece.boardValue,targetPiece.index,targetPiece.boardValue,movingPiece.movingUnit.getActiveCargoType(),movingPiece.movingUnit.type); // Select first cargo movingPiece.movingUnit.activeCargoIndex = 0; movingPiece.movingUnit.unloadCargo(targetPiece); movingPiece.movingUnit.piece.updateUnitDisplay(); movingPiece.setMovingUnit(null); targetPiece.updateUnitDisplay(); thisGame.setTargetPoints(null); let nltd=thisGame.nothingLeftToDo(); thisGame.pushMove("Unload",log,targetPiece,"processMoveUnitMove",nltd,nltd ? "commitEndOfTurn" : null); } else { let oldPiece=movingPiece.movingUnit.piece; let tp=thisGame.getTargetPoint(boardPoint); let log=thisGame.logEntry(9,oldPiece.index,oldPiece.boardValue,targetPiece.index,targetPiece.boardValue,movingPiece.movingUnit.type,tp.spacesNeeded); movingPiece.movingUnit.moveTo(targetPiece,tp.spacesNeeded,tp.retreatIndex); movingPiece.setMovingUnit(null); oldPiece.updateUnitDisplay(); targetPiece.updateUnitDisplay(); thisGame.setTargetPoints(null); let nltd=thisGame.nothingLeftToDo(); thisGame.pushMove("Move",log,targetPiece,"processMoveUnitMove",nltd,nltd ? "commitEndOfTurn" : null); } } else { thisGame.setTargetPoints(null); thisGame.onLeftMouseUp=null; thisGame.onMouseMove="moveBombardUnitsMouseMove"; thisGame.onMouseOver="moveBombardUnitsMouseMove"; thisGame.onMouseOut="moveBombardUnitsMouseOut"; if (movingPiece.movingUnit) { movingPiece.movingUnit.setVisibility(true); let piece=movingPiece.movingUnit.piece; movingPiece.setMovingUnit(null); if (piece.boardPoint.equals(boardPoint) && piece.hasBattle(thisGame.player.team.color,thisGame.player.team.rulerColor)) { piece.setBorder(true); thisGame.battleIndex=piece.index; thisGame.pushMove("Battle",thisGame.logEntry(6,piece.index,piece.boardValue,piece.getOpponentColor(thisGame.player.team.color)),piece,piece.hasPreBattle(thisGame.player.team.color) ? "processPreBattleMove" : "processStartBattleMove",true,"beginBattle","cancel"); } } } thisGame.update(); } // Clone for an original codebase function with a few mods to support automated play. function bombardUnitsSimulateMouseDown(thisGame, bombardingUnit = null) { thisGame.maybeResetReservesByMouseUp(); thisGame.maybeHideOverlay(); thisGame.bombardingUnit = bombardingUnit; if (bombardingUnit) { let screenPoint = bombardingUnit.screenPoint; thisGame.moveBombardUnitsMouseOut(screenPoint = thisGame.constrainPoint(screenPoint)); thisGame.setTargetPoints(bombardingUnit.getBombardables()); thisGame.onMouseMove="bombardUnitsMouseMove"; thisGame.onRightMouseUp="bombardUnitsMouseUp"; thisGame.onMouseOver=null; thisGame.onMouseOut=null; let movingPiece = thisGame.pieces.getNewPiece(); movingPiece.setMovingUnit(new GamesByEmail.Viktory2Unit(null,-1,thisGame.player.team.color,"b")); bombardingUnit.setHilite(true); movingPiece.center(screenPoint.subtract(2,5)); } else { thisGame.showOverlay(); } } // Clone for an original codebase function with a few mods to support automated play. function bombardUnitsSimulateMouseUp(thisGame, screenPoint) { thisGame.onMouseMove = null; thisGame.onLeftMouseUp = null; let movingPiece = thisGame.pieces.getNewPiece(); let boardPoint = thisGame.boardPointFromScreenPoint(screenPoint); if (thisGame.isTargetPoint(boardPoint)) { movingPiece.snap(boardPoint); let targetPiece = thisGame.pieces.findAtPoint(boardPoint); thisGame.pushMove("Bombard",thisGame.logEntry(11,thisGame.bombardingUnit.piece.index,thisGame.bombardingUnit.piece.boardValue,targetPiece.index,targetPiece.boardValue,thisGame.bombardingUnit.type,targetPiece.findOpponentMilitary(thisGame.player.team.color).color,targetPiece.countOpponentMilitary(thisGame.player.team.color)),targetPiece,"processBombardUnitMove",true,"beginBombard","cancel"); thisGame.update(); return true; } return false; } /** * Greasemonkey Wrench by CoeJoder, for public use. * Source: https://github.com/CoeJoder/GM_wrench/blob/master/src/GM_wrench.js * Detect and handle AJAXed content. Can force each element to be processed one or more times. * * @example * GM_wrench.waitForKeyElements('div.comments', (element) => { * element.innerHTML = 'This text inserted by waitForKeyElements().'; * }); * * GM_wrench.waitForKeyElements(() => { * const iframe = document.querySelector('iframe'); * if (iframe) { * const iframeDoc = iframe.contentDocument || iframe.contentwindow.document; * return iframeDoc.querySelectorAll('div.comments'); * } * return null; * }, callbackFunc); * * @param {(string|function)} selectorOrFunction The selector string or function. * @param {function} callback The callback function; takes a single DOM element as parameter. If * returns true, element will be processed again on subsequent iterations. * @param {boolean} [waitOnce=true] Whether to stop after the first elements are found. * @param {number} [interval=300] The time (ms) to wait between iterations. * @param {number} [maxIntervals=-1] The max number of intervals to run (negative number for unlimited). */ function waitForKeyElements (selectorOrFunction, callback, waitOnce, interval, maxIntervals) { if (typeof waitOnce === "undefined") { waitOnce = true; } if (typeof interval === "undefined") { interval = 300; } if (typeof maxIntervals === "undefined") { maxIntervals = -1; } var targetNodes = typeof selectorOrFunction === "function" ? selectorOrFunction() : document.querySelectorAll(selectorOrFunction); var targetsFound = targetNodes && targetNodes.length > 0; if (targetsFound) { targetNodes.forEach(function (targetNode) { var attrAlreadyFound = "data-userscript-alreadyFound"; var alreadyFound = targetNode.getAttribute(attrAlreadyFound) || false; if (!alreadyFound) { var cancelFound = callback(targetNode); if (cancelFound) { targetsFound = false; } else { targetNode.setAttribute(attrAlreadyFound, true); } } }); } if (maxIntervals !== 0 && !(targetsFound && waitOnce)) { maxIntervals -= 1; setTimeout(function () { waitForKeyElements(selectorOrFunction, callback, waitOnce, interval, maxIntervals); }, interval); } }