Viktory Script

Komputer - an AI for Viktory II

2024-05-02 기준 버전입니다. 최신 버전을 확인하세요.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Viktory Script
// @namespace    http://tampermonkey.net/
// @version      2024-05-03
// @description  Komputer - an AI for Viktory II
// @author       Wilbo Baggins / Stephen 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 the page to load.
    waitForKeyElements("#Foundation_Elemental_7_savePlayerNotes", () => {

        // Alias the game.
        let thisGame = Foundation.$registry[7];

        // Add button to page.
        console.log("Adding AI agent control button.");
        addButton("Run Komputer", runKomputer, thisGame);

        window.moveIntervalId = null;
        window.movingUnitIndex = 0;
        window.moveWave = 0;
        window.battleIsSelected = false;
        window.IS_KOMPUTER_READY = true;
   }); // End wait for page load

})(); // End main script.


function runKomputer(thisGame)
{
    console.log("Checking movePhase.");
    switch(thisGame.movePhase)
    {
        case 0:
            console.log("Game reset.");
            setTimeout(function(){runKomputer(thisGame)}, 600);
            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 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
    let pieceIndex = (thisGame.player.team.color === 0) ? 9 : 51;
    let destinationScreenPoint = thisGame.pieces[pieceIndex].$screenRect.getCenter();
    thisGame.placeCapitalMouseDown(destinationScreenPoint);
    thisGame.overlayCommitOnClick();
    setTimeout(function(){
        thisGame.customizeMapDoAll(true);
        if (thisGame.player.team.color === 0)
        {
            thisGame.endMyTurn();
            window.IS_KOMPUTER_READY = true;
            console.log("Done.");
        }
        // Place first town according to Peter M's strategy. Later reserves are scattered randomly.
        else
        {
            if (thisGame.movePhase === 11)
            {
                thisGame.reserveOnMouseDown(thisGame, function(){return true},0);
                pieceIndex = (Math.random() > 0.5) ? 36 : 53; // Choose left or right side randomly
                destinationScreenPoint = thisGame.pieces[pieceIndex].$screenRect.getCenter();
                thisGame.placeReserveOnMouseUp(destinationScreenPoint)
                thisGame.overlayCommitOnClick();
                setTimeout(function(){
                    thisGame.customizeMapDoAll(true);
                    thisGame.reserveOnMouseDown(thisGame, function() {return true},0);
                    thisGame.placeReserveOnMouseUp( destinationScreenPoint );
                    thisGame.endMyTurn();
                    window.IS_KOMPUTER_READY = true;
                    console.log("Done.");
                }, 1000);
            }
        }}, 800);
}

function waterCount(stringHexData)
{
      let count = 0;
      for (let i = 0; i < stringHexData.length; i++)
      {
          if (stringHexData.charAt(i)==="w")
          {
              count++;
          }
      }
      return count;
}

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);
}

async function moveUnits(thisGame)
{
    const moveIntervalPeriod = 800;
    const moveDelay = 200;
    setTimeout(async function(){
        switch (window.moveWave)
        {
            case 0: {
                console.log("May move land units.");
                const landUnits = getMovableLandUnits(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 = getMovableLandUnits(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.");
                // Reset movement, restart to place reserves or stop for next player.
                window.moveWave = 0;
                let lastPlayerColor = thisGame.perspectiveColor;
                endMovementPhase(thisGame);
                window.moveIntervalId = await setInterval(function(){
                    if (thisGame.movePhase === 11)
                    {
                        clearInterval(window.moveIntervalId);
                        runKomputer(thisGame);
                    }
                    else if(lastPlayerColor !== thisGame.perspectiveColor)
                    {
                        clearInterval(window.moveIntervalId);
                    }
                }, 200);
            }
        }
    }, moveDelay);
}


function getMovableLandUnits(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())
            {
                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;
}


function endMovementPhase(thisGame)
{
    thisGame.endMyMovement();
}

async function moveEachUnitByInterval(thisGame, movableUnits, intervalPeriod)
{
    window.moveIntervalId = await setInterval(function(){
        // Get the unit to move.
        let unit = movableUnits[window.movingUnitIndex];
        let stoppedToExplore = false;
        // On the initial wave, which has only land units, give a 50% chance to move, allowing land units to wait for an incoming frigate.
        const shouldMoveThisWave = (window.moveWave > 0) ? true : ((Math.random() > 0.5) ? true : false);
        if (unit && shouldMoveThisWave)
        {
            let mayBombard = true;
            if (!unit.movementComplete || unit.hasUnloadables())
            {
                const possibleMoves = unit.getMovables();
                if (possibleMoves)
                {
                    // Get destination and track any pending battle.
                    const pieceIndex = getRandomItem(possibleMoves).index;
                    if (thisGame.pieces[pieceIndex].hasRollingOpponent(thisGame.player.team.color))
                    {
                        mayBombard = false;
                    }
                    // Move unit.
                    let originScreenPoint = unit.screenPoint;
                    moveUnitSimulateMouseDown(thisGame, originScreenPoint);
                    let destinationScreenPoint = thisGame.pieces[pieceIndex].$screenRect.getCenter();
                    moveUnitSimulateMouseUp(thisGame, destinationScreenPoint);
                    // Commit to explore after some processing time.
                    let normalPopup = document.getElementById("Foundation_Elemental_7_overlayCommit");
                    if (normalPopup || document.getElementById("Foundation_Elemental_7_customizeMapDoAll"))
                    {
                        if (normalPopup)
                        {
                            thisGame.overlayCommitOnClick();
                        }
                        setTimeout(function(){thisGame.customizeMapDoAll(true)}, 100);
                        stoppedToExplore = true;
                    }
                } // End if possibleMoves
            } // End if not movementComplete or hasUnloadables
            // Bombard on the final wave.
            const isTimeToBombard = (window.moveWave === 2) ? true : false;
            if (isTimeToBombard)
            {
                if (mayBombard && unit.canBombard() && unit.piece !== null)
                {
                    const bombardablePoints = unit.getBombardables();
                    if (bombardablePoints)
                    {
                        bombard(thisGame, unit, bombardablePoints);
                    }
                }
            }
        }// End if unit check
        // Decide how to continue
        if (stoppedToExplore && !unit.movementComplete)
        {
            // Pass: move same unit next interval.
        }
        else if ( (window.movingUnitIndex + 1) < movableUnits.length )
        {
            // Move the next unit next interval.
            window.movingUnitIndex++;
        }
        else
        {
            // Clear wave interval, reset moving unit index, cue next.
            clearInterval(window.moveIntervalId);
            window.movingUnitIndex = 0;
            window.moveWave++;
            runKomputer(thisGame);
        }
    }, intervalPeriod);
}


function bombard(thisGame, unit, bombardablePoints)
{
    thisGame.bombardUnitsMouseDown(unit.screenPoint);
    const randomTargetPoint = getRandomItem(bombardablePoints);
    const targetScreenPoint = thisGame.pieces.findAtPoint(randomTargetPoint).$screenRect.getCenter();
    const firingDelay = 100;
    setTimeout(function(){
        thisGame.bombardUnitsMouseUp(targetScreenPoint);
        const commitDelay = 100;
        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);
                    }
                }
                const reviewDelay = 200;
                setTimeout(function(){
                    thisGame.pieces[unit.piece.index].bombardOkClick(thisGame.player.team.color);
                }, reviewDelay)
            }, applyHitsDelay);
        }, commitDelay);
    }, firingDelay);
}

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)
{
    // Select battle
    if (!window.battleIsSelected) // Todo: think about case where human selects a random battle, then tries to runKomputer.
    {
        thisGame.moveUnitsMouseDown(battlePiece.$screenRect.getCenter());
        window.battleIsSelected = 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.
            const battleReviewDelay = 1600;
            setTimeout(function(){
                thisGame.pieces[battlePiece.index].battleOkClick(thisGame.player.team.color);
                const reRollDelay = 600;
                setTimeout(function(){
                    if (document.getElementById("Foundation_Elemental_7_overlayCommit"))
                    {
                        roll();
                    }
                    else
                    {
                        window.battleIsSelected = false;
                        runKomputer(thisGame);
                    }
                }, reRollDelay);
            }, battleReviewDelay);
        }, applyHitsDelay);
    }, rollDelay);

}

function applyHits(thisGame, pieceIndex, battleData)
{
    const thisPiece = thisGame.pieces[pieceIndex];
    const attackerColor = thisGame.player.team.color;
    const defenderColor = (attackerColor === 0) ? 1 : 0;
    const attackerUnitList = thisPiece.getMilitaryUnitList(attackerColor);
    const defenderUnitList = thisPiece.getDefenderUnitList(defenderColor);
    const attackerHitThreshold = thisGame.getHitThreshold(attackerColor);
    const defenderHitThreshold = thisGame.getHitThreshold(defenderColor);
    thisPiece.defenderBattleInfo = thisPiece.getBattleInfo(defenderUnitList, battleData.attackerRolls, attackerHitThreshold,true);
    thisPiece.attackerBattleInfo = thisPiece.getBattleInfo(attackerUnitList, battleData.defenderRolls, defenderHitThreshold,false);
    // Choose highest value hits on the defender.
    if (thisPiece.defenderBattleInfo.decisionNeeded)
    {
        const hitCount = (thisPiece.defenderBattleInfo.numHit - thisPiece.defenderBattleInfo.numTacticalHit);
        thisPiece.hitHighestMilitaryUnits(defenderUnitList, hitCount, 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(){ thisPiece.battleOkClick(attackerColor) }, 200);
}


async function placeReserves(thisGame)
{
    // Just in case, clear and reset previous phases.
    clearInterval(window.moveIntervalId);
    window.battleIsSelected = false;
    window.movingUnitIndex = 0;
    window.moveWave = 0;
    window.reserveIntervalId = await setInterval(placeReserveUnit, 1000, thisGame);
}

function placeReserveUnit(thisGame){
    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;
            }
        }
        // Place reserve unit on a valid destination
        if (hasPlayableReserveUnit)
        {
            const movingUnitType = thisGame.pieces.getNewPiece().movingUnit.type;
            const destinationBoardPoint = (movingUnitType === "t" || movingUnitType === "y") ? (
                getRandomItem(thisGame.getBuildables(thisGame.player.team.color, thisGame.player.team.rulerColor)) ) : (
                getRandomItem(thisGame.pieces.getReservables(thisGame.player.team.color,thisGame.player.team.rulerColor,movingUnitType, controlsCapital)) );
            const destinationScreenPoint = thisGame.screenRectFromBoardPoint(destinationBoardPoint).getCenter();
            thisGame.placeReserveOnMouseUp(destinationScreenPoint);
            if (document.getElementById("Foundation_Elemental_7_overlayCommit"))
            {
                thisGame.overlayCommitOnClick();
            }
            setTimeout(function(){thisGame.customizeMapDoAll(true);}, 800);
        }
    }
    // End of reserves - ensure ready for next player.
    else
    {
        clearInterval(window.reserveIntervalId);
        window.IS_KOMPUTER_READY = true;
        console.log("Done.");
    }
}


function addButton(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'}
    let button = document.createElement('button'), btnStyle = button.style
    //let parent = document.getElementById("Foundation_Elemental_7_bottomTeamTitles"); // Todo: add something like this for better control of position relative to parent.
    //parent.appendChild(button);  // This hides the button under other elements, regardless of z-index. Maybe, instead, add the element via GBE framework.
    document.body.appendChild(button) // For now, this works well enough.
    button.setAttribute("class", "button_runKomputer");
    button.innerHTML = text
    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 moveUnitSimulateMouseDown(thisGame, screenPoint)
{
    thisGame.maybeResetReservesByMouseUp();
    thisGame.moveBombardUnitsMouseOut(screenPoint=thisGame.constrainPoint(screenPoint));
    thisGame.maybeHideOverlay();
    let unit=thisGame.militaryUnitFromScreenPoint(screenPoint,null,thisGame.player.team.color,thisGame.player.team.rulerColor,true);
    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);
        thisGame.forcingFrigateUnload=(unit.isFrigate() && thisGame.targetPointsAllLand);
        if (thisGame.forcingFrigateUnload)
            unit.centerUnload(screenPoint.subtract(0,5),piece);
        else
        {
            unit.setVisibility(false);
            piece.center(screenPoint.subtract(0,5));
        }
    }
    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();
    }
}

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);
        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);
        }
        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);
            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();
}


/**
     * 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);
    }
};