function getGameInfo($gameID, $lastUpdated = 0, $ignoreUpdateTime = false)
{
    global $sqlhost, $sqlusername, $sqlpassword;
    if ($ignoreUpdateTime) {
        $lastUpdated = 0;
    }
    $conn = new mysqli($sqlhost, $sqlusername, $sqlpassword);
    if ($conn->connect_error) {
        error_log("getGameInfo.php - Connection failed: " . $conn->connect_error);
        return "";
    }
    if ($timeStmt = $conn->prepare("SELECT UNIX_TIMESTAMP(lastUpdated), fullUpdate FROM sweepelite.games WHERE gameID = ?")) {
        $updatedTime = null;
        $timeStmt->bind_param("i", $gameID);
        $timeStmt->execute();
        $timeStmt->bind_result($updated, $fullUpdate);
        while ($timeStmt->fetch()) {
            $updatedTime = $updated;
        }
        $timeStmt->close();
        if ($updatedTime !== null) {
            if ($updatedTime > $lastUpdated) {
                $ret = new SimpleXMLElement("<update/>");
                $ret->addAttribute("id", $gameID);
                if ($fullUpdate || $ignoreUpdateTime) {
                    #Select all information about the game from the game's status columns in the MySQL database and parse it into XML form.
                    if ($query = $conn->prepare("SELECT map, visibility, friendlyTanks, enemyTanks, wrecks, traps, height, width, status FROM sweepelite.games WHERE gameID = ?")) {
                        $query->bind_param("i", $gameID);
                        $query->execute();
                        $query->bind_result($map, $vis, $friendlyTanks, $enemyTanks, $wrecks, $traps, $height, $width, $status);
                        $query->fetch();
                        $query->close();
                        $finalFriendlies = translateTanksToPHP($friendlyTanks);
                        $finalEnemies = translateTanksToPHP($enemyTanks);
                        $finalWrecks = translateTanksToPHP($wrecks);
                        $finalTraps = translateTrapsToPHP($traps);
                        $finalMap = translateMinefieldToMySQL(getMinefieldWithVisibility($gameID, translateMinefieldToPHP($map, $height, $width), translateMinefieldToPHP($vis, $height, $width), $finalWrecks));
                        $ret->addChild('map', $finalMap);
                        $ret->addChild('height', $height);
                        $ret->addChild('width', $width);
                        $ret->addChild('status', $status);
                        if ($finalFriendlies !== null) {
                            $ret->addChild('friendlyTanks');
                            foreach ($finalFriendlies as $k => $v) {
                                if (count($v) === 2) {
                                    $ret->friendlyTanks->addChild('tank', $v[0] . "," . $v[1]);
                                }
                            }
                        }
                        if ($finalEnemies !== null) {
                            $ret->addChild('enemyTanks');
                            foreach ($finalEnemies as $k => $v) {
                                if (count($v) === 2) {
                                    $ret->enemyTanks->addChild('tank', $v[0] . "," . $v[1]);
                                }
                            }
                        }
                        if ($finalTraps !== null) {
                            $ret->addChild('traps');
                            foreach ($finalTraps as $k => $v) {
                                if (count($v) === 3) {
                                    $ret->traps->addChild('trap', $v[0] . "," . $v[1] . "," . $v[2]);
                                }
                            }
                        }
                        if ($hsStmt = $conn->prepare("SELECT MAX(totalScore) FROM sweepelite.players")) {
                            $hsStmt->execute();
                            $hsStmt->bind_result($hs);
                            $hsStmt->fetch();
                            $hsStmt->close();
                            #Add all players in the game and their statuses to the XML.
                            if ($playerQuery = $conn->prepare("SELECT p.username, p.playerID, s.status, s.trapType, s.trapCooldown, s.digNumber, s.flagNumber, p.totalScore FROM sweepelite.players as p INNER JOIN sweepelite.playerstatus as s ON p.playerID=s.playerID WHERE s.gameID=?")) {
                                $playerQuery->bind_param("i", $gameID);
                                $playerQuery->execute();
                                $playerQuery->bind_result($user, $currentID, $status, $trapType, $trapCooldown, $digNumber, $flagNumber, $totalScore);
                                $ret->addChild('players');
                                while ($playerQuery->fetch()) {
                                    $playerInfo = $ret->players->addChild('player', $user);
                                    $playerInfo->addAttribute('status', $status);
                                    $playerInfo->addAttribute('trapType', $trapType);
                                    $playerInfo->addAttribute('trapCooldown', $trapCooldown);
                                    $medals = calculateMedalAttributesForPlayer($digNumber, $flagNumber);
                                    $playerInfo->addAttribute('digMedal', $medals['digMedal']);
                                    $playerInfo->addAttribute('flagMedal', $medals['flagMedal']);
                                    $scoreRank = 5;
                                    //Calculate score rank
                                    if ($totalScore >= $hs) {
                                        $scoreRank = 6;
                                    } else {
                                        if ($totalScore === 0) {
                                            $scoreRank = 0;
                                        } else {
                                            if ($totalScore < $hs * 0.2) {
                                                $scoreRank = 1;
                                            } else {
                                                if ($totalScore < $hs * 0.4) {
                                                    $scoreRank = 2;
                                                } else {
                                                    if ($totalScore < $hs * 0.6) {
                                                        $scoreRank = 3;
                                                    } else {
                                                        if ($totalScore < $hs * 0.8) {
                                                            $scoreRank = 4;
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    $playerInfo->addAttribute('scoreRank', $scoreRank);
                                }
                                $playerQuery->close();
                                if ($gameTimeStmt = $conn->prepare("SELECT v FROM sweepelite.globalvars WHERE k='nextGameTime'")) {
                                    $gameTimeStmt->execute();
                                    $gameTimeStmt->bind_result($time);
                                    while ($gameTimeStmt->fetch()) {
                                        $ret->addChild('nextGameTime', $time);
                                    }
                                } else {
                                    error_log("getGameInfo.php - Unable to prepare next game time statement. " . $conn->errno . ": " . $conn->error);
                                }
                            } else {
                                error_log("getGameInfo.php - Unable to prepare player gathering statement. " . $conn->errno . ": " . $conn->error);
                                $ret->addChild('error', "Internal error occurred, please try again later.");
                            }
                        } else {
                            error_log("getGameInfo.php - Unable to prepare high score gathering statement. " . $conn->errno . ": " . $conn->error);
                            $ret->addChild('error', "Internal error occurred, please try again later.");
                        }
                        if (!$ignoreUpdateTime) {
                            if ($updateQuery = $conn->prepare("UPDATE sweepelite.games SET fullUpdate=0 WHERE gameID=?")) {
                                $updateQuery->bind_param("i", $gameID);
                                $updateQuery->execute();
                                $updateQuery->close();
                            }
                        }
                    }
                }
                #Add other player actions
                $otherPlayers = getPlayerActionsForGame($gameID);
                if (count($otherPlayers) !== 0) {
                    $opNode = $ret->addChild('otherPlayers');
                    foreach ($otherPlayers as $k => $v) {
                        if (count($v) === 2) {
                            $opNode->addChild('otherPlayer', $v[0] . "," . $v[1]);
                        }
                    }
                }
                $finalRet = str_replace('<?xml version="1.0"?>', "", $ret->asXML());
                return $finalRet;
            }
        }
    }
    return "";
}
function resolveAllActions($gameID)
{
    global $sqlhost, $sqlusername, $sqlpassword, $adjacencies;
    #Initialize the connection to the MySQL database.
    $conn = new mysqli($sqlhost, $sqlusername, $sqlpassword);
    if ($conn->connect_error) {
        die("resolveActions.php - Connection failed: " . $conn->connect_error);
    }
    #Get map information, both the minefield and the visibility, for this game.
    if ($stmt = $conn->prepare("SELECT map, visibility, height, width, friendlyTankCountdown, friendlyTanks, enemyTankCountdown, enemyTankCountdownReset, enemyTanks, wrecks, traps FROM sweepelite.games WHERE gameID=?")) {
        $stmt->bind_param("i", $gameID);
        $stmt->execute();
        $stmt->bind_result($m, $v, $h, $w, $tankCount, $t, $enemyTankCount, $enemyTankReset, $e, $wr, $tr);
        if ($stmt->fetch()) {
            $stmt->close();
            #Determine if this is the first ever move in the game. If so, different actions call for different outcomes.
            $firstMove = false;
            if (strpos($v, "1") === false && strpos($v, "2") === false) {
                $firstMove = true;
            }
            $minefield = translateMinefieldToPHP($m, $h, $w);
            $visibility = translateMinefieldToPHP($v, $h, $w);
            $friendlyTanks = translateTanksToPHP($t);
            $enemyTanks = translateTanksToPHP($e);
            $wrecks = translateTanksToPHP($wr);
            $traps = translateTrapsToPHP($tr);
            #Determine if we need to expand the map first due to tank movement.
            $expand = false;
            $baseExploded = false;
            foreach ($friendlyTanks as $tankKey => $tankPos) {
                if ($tankPos[0] == $w - 1) {
                    $expand = true;
                }
            }
            foreach ($enemyTanks as $tankKey => $tankPos) {
                if ($tankPos[0] == 0) {
                    $baseExploded = true;
                }
            }
            if ($expand) {
                $newVals = expandMinefield($minefield, $visibility, 10, 0);
                $minefield = $newVals["minefield"];
                $visibility = $newVals["visibility"];
                if ($enemyTankReset > 3) {
                    $enemyTankReset = $enemyTankReset - 1;
                }
            }
            #Get all player information
            $allPlayers = getPlayersForGame($gameID);
            if (count($allPlayers) > 0) {
                #Retrieve all actions in the action queue for this game and throw them into unique objects in an array for easy access during resolution.
                if ($actionStmt = $conn->prepare("SELECT playerID, actionType, xCoord, yCoord FROM sweepelite.actionqueue WHERE gameID=?")) {
                    $actionqueue = array();
                    $actionStmt->bind_param("i", $gameID);
                    $actionStmt->execute();
                    $actionStmt->bind_result($playerID, $actionType, $xCoord, $yCoord);
                    while ($actionStmt->fetch()) {
                        $temp = array("playerID" => intval($playerID), "actionType" => intval($actionType), "x" => intval($xCoord), "y" => intval($yCoord));
                        array_push($actionqueue, $temp);
                    }
                    $actionStmt->close();
                    $queuedTraps = array();
                    while (count($actionqueue) > 0) {
                        $cur = array_shift($actionqueue);
                        #Check that current action is legal according to tank placement.
                        $legalMove = true;
                        foreach ($friendlyTanks as $tankKey => $tankPos) {
                            if ($cur['x'] == $tankPos[0]) {
                                if ($cur['y'] == $tankPos[1]) {
                                    $legalMove = false;
                                }
                            }
                        }
                        foreach ($enemyTanks as $tankKey => $tankPos) {
                            if ($cur['x'] == $tankPos[0]) {
                                if ($cur['y'] == $tankPos[1]) {
                                    $legalMove = false;
                                }
                            }
                        }
                        foreach ($wrecks as $wkey => $wval) {
                            if ($cur['x'] == $wval[0]) {
                                if ($cur['y'] == $wval[1]) {
                                    $legalMove = false;
                                }
                            }
                        }
                        foreach ($traps as $wkey => $tval) {
                            if ($cur['x'] == $tval[1]) {
                                if ($cur['y'] == $tval[2]) {
                                    $legalMove = false;
                                }
                            }
                        }
                        if ($legalMove) {
                            #Action Type 0 is a shovel action.
                            #When shoveling, the current tile is revealed no matter what.
                            #If the tile is a mine, this kills the current player.
                            #If the tile had a value of 0, new actions are added to the queue for revealing all adjacent tiles to the shoveled tile, barring any that are already not flagged or visible.
                            if ($cur["actionType"] === 0) {
                                #Check if we have previously shoveled this tile or not.
                                $notShoveled = true;
                                foreach (getPlayerValue($allPlayers, $cur["playerID"], "dugTiles") as $key => $shoveled) {
                                    if ($cur['x'] == $shoveled[0]) {
                                        if ($cur['y'] == $shoveled[1]) {
                                            $notShoveled = false;
                                        }
                                    }
                                }
                                if ($notShoveled) {
                                    $visibility[$cur["x"]][$cur["y"]] = 2;
                                    if ($minefield[$cur["x"]][$cur["y"]] === "M") {
                                        if ($firstMove) {
                                            #Temporarily just remove the mine.
                                            $minefield[$cur["x"]][$cur["y"]] = 0;
                                            $visibility[$cur["x"]][$cur["y"]] = 0;
                                            $minefield = updateMinefieldNumbers($minefield);
                                            $newAction = array("playerID" => $cur["playerID"], "actionType" => $cur["actionType"], "x" => $cur["x"], "y" => $cur["y"]);
                                            array_push($actionqueue, $newAction);
                                        } else {
                                            $tempDugTiles = getPlayerValue($allPlayers, $cur["playerID"], "dugTiles");
                                            array_push($tempDugTiles, array($cur["x"], $cur["y"]));
                                            $allPlayers = setPlayerValue($allPlayers, $cur["playerID"], "dugTiles", $tempDugTiles);
                                            $allPlayers = setPlayerValue($allPlayers, $cur["playerID"], 'status', 0);
                                        }
                                    } else {
                                        if ($minefield[$cur["x"]][$cur["y"]] == 0) {
                                            $tempDugTiles = getPlayerValue($allPlayers, $cur["playerID"], "dugTiles");
                                            array_push($tempDugTiles, array($cur["x"], $cur["y"]));
                                            $allPlayers = setPlayerValue($allPlayers, $cur["playerID"], "dugTiles", $tempDugTiles);
                                            foreach ($adjacencies as $adj) {
                                                $targetX = $cur["x"] + $adj[0];
                                                $targetY = $cur["y"] + $adj[1];
                                                $shouldAdd = true;
                                                if ($targetX < 0 or $targetX >= $w) {
                                                    $shouldAdd = false;
                                                }
                                                if ($targetY < 0 or $targetY >= $h) {
                                                    $shouldAdd = false;
                                                }
                                                if ($shouldAdd) {
                                                    $noTanks = true;
                                                    foreach ($friendlyTanks as $tankKey => $tankPos) {
                                                        if ($targetX == $tankPos[0]) {
                                                            if ($targetY == $tankPos[1]) {
                                                                $noTanks = false;
                                                            }
                                                        }
                                                    }
                                                    foreach ($enemyTanks as $tankKey => $tankPos) {
                                                        if ($targetX == $tankPos[0]) {
                                                            if ($targetY == $tankPos[1]) {
                                                                $noTanks = false;
                                                            }
                                                        }
                                                    }
                                                    foreach ($wrecks as $wkey => $wval) {
                                                        if ($targetX == $wval[0]) {
                                                            if ($targetY == $wval[1]) {
                                                                $noTanks = false;
                                                            }
                                                        }
                                                    }
                                                    if ($noTanks) {
                                                        if ($visibility[$targetX][$targetY] == 0) {
                                                            $newAction = array("playerID" => $cur["playerID"], "actionType" => $cur["actionType"], "x" => $targetX, "y" => $targetY);
                                                            array_push($actionqueue, $newAction);
                                                        }
                                                    }
                                                }
                                            }
                                        } else {
                                            $tempDugTiles = getPlayerValue($allPlayers, $cur["playerID"], "dugTiles");
                                            array_push($tempDugTiles, array($cur["x"], $cur["y"]));
                                            $allPlayers = setPlayerValue($allPlayers, $cur["playerID"], "dugTiles", $tempDugTiles);
                                        }
                                    }
                                }
                                #Action Type 1 is a flag action.
                                #If the tile is unrevealed, a flag is placed there instead.
                            } else {
                                if ($cur["actionType"] === 1) {
                                    if ($visibility[$cur["x"]][$cur["y"]] == 0) {
                                        $visibility[$cur["x"]][$cur["y"]] = 1;
                                        if ($minefield[$cur["x"]][$cur["y"]] === "M") {
                                            $allPlayers = alterPlayerValue($allPlayers, $cur["playerID"], "correctFlags", 1);
                                        }
                                        $allPlayers = alterPlayerValue($allPlayers, $cur["playerID"], "flagNumber", 1);
                                    }
                                    #Action Type 2 is a place trap action.
                                    #Place a trap on the space.
                                } else {
                                    if ($cur["actionType"] === 2) {
                                        $cooldown = getPlayerValue($allPlayers, $cur["playerID"], "trapCooldown");
                                        if ($cooldown <= 0) {
                                            $trapVal = getPlayerValue($allPlayers, $cur["playerID"], "trapType");
                                            $tempTrap = array($trapVal, $cur["x"], $cur["y"]);
                                            array_push($queuedTraps, $tempTrap);
                                            $allPlayers = setPlayerValue($allPlayers, $cur["playerID"], "trapCooldown", getCooldownForTrapType($trapVal));
                                        }
                                    }
                                }
                            }
                        } else {
                            error_log("resolveActions.php - Illegal move made by player ID " . $cur["playerID"] . " at coordinates " . $cur["x"] . "," . $cur["y"]);
                        }
                        if (getPlayerValue($allPlayers, $cur['playerID'], 'hasActed') === 0) {
                            if ($cur['actionType'] === 0) {
                                $oldCooldown = getPlayerValue($allPlayers, $cur['playerID'], 'trapCooldown');
                                if ($oldCooldown > 0) {
                                    $allPlayers = setPlayerValue($allPlayers, $cur['playerID'], 'trapCooldown', $oldCooldown - 1);
                                }
                            }
                        }
                        $allPlayers = setPlayerValue($allPlayers, $cur['playerID'], 'hasActed', 1);
                    }
                    #Resolve traps
                    $trapResults = resolveTraps($minefield, $visibility, $friendlyTanks, $enemyTanks, $traps, $wrecks);
                    $traps = $trapResults['traps'];
                    #Add queued traps
                    foreach ($queuedTraps as $trapKey => $trapSpecifics) {
                        $traps = addTrap($traps, $trapSpecifics[0], $trapSpecifics[1], $trapSpecifics[2]);
                    }
                    #All tanks are updated, and any stuff on the map is updated to reflect these changes.
                    $updatedTanks = updateTanks($trapResults['map'], $trapResults['visibility'], $trapResults['friendlyTanks'], $trapResults['enemyTanks'], $trapResults['wrecks']);
                    $friendlyTanks = $updatedTanks['updatedFriendlyTanks'];
                    $enemyTanks = $updatedTanks['updatedEnemyTanks'];
                    $wrecks = $updatedTanks['updatedWrecks'];
                    $visibility = $updatedTanks['updatedVisibility'];
                    #Update the tank count. If it is at 0, add a tank and reset the count to 3.
                    $tankCount = $tankCount - 1;
                    if ($tankCount <= 0) {
                        $addedTank = addFriendlyTank($minefield, $visibility);
                        if ($addedTank['newTankPosition'] !== null) {
                            array_push($friendlyTanks, $addedTank['newTankPosition']);
                        }
                        if ($addedTank['newVisibility'] !== null) {
                            $visibility = $addedTank['newVisibility'];
                        }
                        $tankCount = 3;
                    }
                    #Update the enemy tank count. If it is at 0, add an enemy and reset the count to the current threshold.
                    $enemyTankCount = $enemyTankCount - 1;
                    if ($enemyTankCount <= 0) {
                        $addedTank = addEnemyTank($minefield, $visibility);
                        if ($addedTank['newTankPosition'] !== null) {
                            array_push($enemyTanks, $addedTank['newTankPosition']);
                        }
                        if ($addedTank['newVisibility'] !== null) {
                            $visibility = $addedTank['newVisibility'];
                        }
                        $enemyTankCount = $enemyTankReset;
                    }
                    #All players who did submit actions are updated appropriately.
                    $allPlayers = savePlayersForGame($allPlayers, $gameID);
                    #Empty the action queue of actions relating to this specific game.
                    if ($deleteStmt = $conn->prepare("DELETE FROM sweepelite.actionqueue WHERE gameID=?")) {
                        $deleteStmt->bind_param("i", $gameID);
                        $updated = $deleteStmt->execute();
                        if ($updated === false) {
                            error_log("resolveActions.php - Error occurred during action queue clean up. " . $deleteStmt->errno . ": " . $deleteStmt->error);
                            $deleteStmt->close();
                        } else {
                            $deleteStmt->close();
                            #Try to determine if the game is complete or not.
                            $gameCompleted = false;
                            if ($baseExploded) {
                                $gameCompleted = true;
                                error_log("Game Over - Base blown up!");
                            } else {
                                if (countPlayersWithValue($allPlayers, 'status', 1) > 0) {
                                    $gameCompleted = true;
                                    for ($x = 0; $x < count($visibility) && $gameCompleted; $x++) {
                                        for ($y = 0; $y < count($visibility[$x]) && $gameCompleted; $y++) {
                                            if ($visibility[$x][$y] == 0) {
                                                if ($minefield[$x][$y] === "M") {
                                                    $gameCompleted = false;
                                                }
                                            }
                                        }
                                    }
                                    if ($gameCompleted) {
                                        error_log("Game Over - No unrevealed non-mine tiles left!");
                                    }
                                } else {
                                    $gameCompleted = true;
                                    error_log("Game Over - No living players left!");
                                }
                            }
                            #If game is done, all unrevealed tiles become visible instead.
                            if ($gameCompleted) {
                                for ($x = 0; $x < count($visibility); $x++) {
                                    for ($y = 0; $y < count($visibility[$x]); $y++) {
                                        if ($minefield[$x][$y] === "M") {
                                            $visibility[$x][$y] = 2;
                                        }
                                    }
                                }
                                $allPlayers = setAllPlayersValue($allPlayers, "status", 0);
                                savePlayerScores($allPlayers);
                            }
                            #Update map and visibility values for the game by saving to database.
                            if ($updateStmt = $conn->prepare("UPDATE sweepelite.games SET map=?, visibility=?, friendlyTankCountdown=?, friendlyTanks=?, enemyTankCountdown=?, enemyTanks=?, enemyTankCountdownReset=?, wrecks=?, traps=?, status=?, width=?, height=?, lastUpdated=NOW(), fullUpdate=1 WHERE gameID=?")) {
                                $statusStr = "OPEN";
                                if ($gameCompleted) {
                                    $statusStr = "GAME OVER";
                                }
                                $updateStmt->bind_param("ssisisisssiii", translateMinefieldToMySQL($minefield), translateMinefieldToMySQL($visibility), $tankCount, translateTanksToMySQL($friendlyTanks), $enemyTankCount, translateTanksToMySQL($enemyTanks), $enemyTankReset, translateTanksToMySQL($wrecks), translateTrapsToMySQL($traps), $statusStr, count($minefield), count($minefield[0]), $gameID);
                                $updated = $updateStmt->execute();
                                if ($updated === false) {
                                    error_log("resolveActions.php - Error occurred during map update. " . $updateStmt->errno . ": " . $updateStmt->error);
                                }
                                $updateStmt->close();
                                if ($gameCompleted) {
                                    return false;
                                }
                            } else {
                                error_log("resolveActions.php - Unable to prepare map update after resolving action queue. " . $conn->errno . ": " . $conn->error);
                            }
                        }
                    } else {
                        error_log("resolveActions.php - Unable to prepare delete statement. " . $conn->errno . ": " . $conn->error);
                    }
                } else {
                    error_log("resolveActions.php - Unable to prepare action statement for resolving action queue. " . $conn->errno . ": " . $conn->error);
                }
            } else {
                error_log("resolveActions.php - Did not find any players for current game.");
            }
        } else {
            error_log("resolveActions.php - Unable to resolve map statement for resolving action queue. " . $stmt->errno . ": " . $stmt->error);
        }
    } else {
        error_log("resolveActions.php - Unable to prepare map statement for resolving action queue. " . $conn->errno . ": " . $conn->error);
    }
    return true;
}