package onlinefrontlines.game.web; import onlinefrontlines.web.*; import java.util.Random; import java.text.*; import onlinefrontlines.game.*; /** * This action shows the game balance * * @author jorrit * * Copyright (C) 2009-2013 Jorrit Rouwe * * This file is part of Online Frontlines. * * Online Frontlines is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Online Frontlines is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Online Frontlines. If not, see <http://www.gnu.org/licenses/>. */ public class GameBalanceAction extends WebAction { /** * Access to the balance talbe */ public String[][] balanceTable; /** * Execute the action */ protected WebView execute() throws Exception { Random random = new Random(); DecimalFormat f = new DecimalFormat("#.#"); balanceTable = new String[UnitConfig.allUnits.size() + 1][UnitConfig.allUnits.size() + 1]; for (int i = 0; i < UnitConfig.allUnits.size(); ++i) { UnitConfig u1 = UnitConfig.allUnits.get(i); TerrainConfig t1 = u1.unitClass == UnitClass.air? TerrainConfig.airTerrain : TerrainConfig.plainsTerrain; balanceTable[i + 1][0] = u1.name; balanceTable[0][i + 1] = u1.name; for (int j = 0; j < UnitConfig.allUnits.size(); ++j) { UnitConfig u2 = UnitConfig.allUnits.get(j); TerrainConfig t2 = u2.unitClass == UnitClass.air? TerrainConfig.airTerrain : TerrainConfig.plainsTerrain; final int numSamples = 100; final int maxNumAttacks = 15; // Armour loss double avgArmourLoss = 0; int armourLoss[] = new int[numSamples]; // Wins double avgAttacksToWin = 0; int attacksToWin[] = new int[numSamples]; int numWins = 0; // Sample a number of times for (int k = 0; k < numSamples; ++k) { UnitState ui1 = new UnitState(-1, 0, 0, Faction.f1, u1); UnitState ui2 = new UnitState(-1, 1, 0, Faction.f2, u2); // First attack calculates max damage armourLoss[k] = ui2.armour; if (ui1.canAttackUnitClass(ui2.unitConfig.unitClass, ui2.faction)) ui1.attackAndCounter(ui2, t1, t2, random); armourLoss[k] -= ui2.armour; avgArmourLoss += armourLoss[k]; // Finish first attack int numAttacks = 1; for (int a = 1; a < u1.actions; ++a) if (ui1.canAttackUnitClass(ui2.unitConfig.unitClass, ui2.faction)) ui1.attackAndCounter(ui2, t1, t2, random); for (int a = 0; a < u2.actions; ++a) if (ui2.canAttackUnitClass(ui1.unitConfig.unitClass, ui1.faction)) ui2.attackAndCounter(ui1, t2, t1, random); // Subsequent attacks determine winner for (int l = 0; l < maxNumAttacks; ++l) { // Check if one of the units is dead if (ui2.armour <= 0 || ui2.faction == Faction.f1) { // Winner attacksToWin[numWins] = numAttacks; avgAttacksToWin += numAttacks; numWins++; break; } if (ui1.armour <= 0 || ui1.faction == Faction.f2) { // Loser break; } // Perform another attack for (int a = 0; a < u1.actions; ++a) if (ui1.canAttackUnitClass(ui2.unitConfig.unitClass, ui2.faction)) ui1.attackAndCounter(ui2, t1, t2, random); for (int a = 0; a < u2.actions; ++a) if (ui2.canAttackUnitClass(ui1.unitConfig.unitClass, ui1.faction)) ui2.attackAndCounter(ui1, t2, t1, random); numAttacks++; } } // Complete calculating averages avgArmourLoss /= numSamples; if (numWins > 0) avgAttacksToWin /= numWins; // Calculate armour loss deviation double armourLossDeviation = 0; int armourLossMin = Integer.MAX_VALUE; int armourLossMax = 0; for (int k = 0; k < numSamples; ++k) { armourLossDeviation += (avgArmourLoss - armourLoss[k]) * (avgArmourLoss - armourLoss[k]); armourLossMin = Math.min(armourLossMin, armourLoss[k]); armourLossMax = Math.max(armourLossMax, armourLoss[k]); } armourLossDeviation = Math.sqrt(armourLossDeviation / numSamples); // Calculate attacks to win deviation double attacksToWinDeviation = 0; int attacksToWinMin = Integer.MAX_VALUE; int attacksToWinMax = 0; for (int k = 0; k < numWins; ++k) { attacksToWinDeviation += (avgAttacksToWin - attacksToWin[k]) * (avgAttacksToWin - attacksToWin[k]); attacksToWinMin = Math.min(attacksToWinMin, attacksToWin[k]); attacksToWinMax = Math.max(attacksToWinMax, attacksToWin[k]); } if (numWins > 0) attacksToWinDeviation = Math.sqrt(attacksToWinDeviation / numWins); // Calculate win percentage double win_percentage = numWins * 100.0 / numSamples; // Format result String result = "X"; if (avgArmourLoss > 0.01) { result = "DMG: " + f.format(avgArmourLoss) + "\u00B1" + f.format(armourLossDeviation) + " [" + armourLossMin + "," + armourLossMax + "]\n"; result += "WIN: " + f.format(win_percentage) + "%\n"; if (win_percentage > 0.1) result += "#RND: " + f.format(avgAttacksToWin) + "\u00B1" + f.format(attacksToWinDeviation) + " [" + attacksToWinMin + "," + attacksToWinMax + "]\n"; } balanceTable[i + 1][j + 1] = result; } } return getSuccessView(); } }