/** * Copyright (c) 2005-2017, KoLmafia development team * http://kolmafia.sourceforge.net/ * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * [1] Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * [2] Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * [3] Neither the name "KoLmafia" nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.sourceforge.kolmafia; import java.util.List; import net.sourceforge.kolmafia.CakeArenaManager.ArenaOpponent; import net.sourceforge.kolmafia.persistence.FamiliarDatabase; public class FamiliarTool { // Array of current opponents private final Opponent[] opponents; // Index of best opponent to fight private int bestOpponent; // Index of best arena match against that opponent private int bestMatch; // Best weight for own familiar during that match private int bestWeight; // Difference from "perfect" weight for that match private int difference; /** * Initializes Familiar Tool with all Arena Data * * @param opponents Array with Ids of all opponents. The index of each opponent will be re-used as a return value */ public FamiliarTool( final List opponents ) { int opponentCount = opponents.size(); this.opponents = new Opponent[ opponentCount ]; for ( int i = 0; i < opponentCount; ++i ) { ArenaOpponent opponent = (ArenaOpponent) opponents.get( i ); this.opponents[ i ] = new Opponent( opponent ); } } /** * Runs all the calculation to determine the best matchup for a familiar * * @param ownFamiliar Id of the familiar to calculate a matchup for * @param possibleOwnWeights Array with all possibilities for familiar weight * @return The Id number of the best opponent. Further information can be collected through other functions */ public ArenaOpponent bestOpponent( final int ownFamiliar, final int[] possibleOwnWeights ) { int[] ownSkills = FamiliarDatabase.getFamiliarSkills( ownFamiliar ); return this.bestOpponent( ownSkills, possibleOwnWeights ); } /** * Runs all the calculation to determine the best matchup for a familiar * * @param ownSkills our familiar's skills * @param possibleOwnWeights Array with all possibilities for familiar weight * @return The Id number of the best opponent. Further information can be collected through other functions */ public ArenaOpponent bestOpponent( final int[] ownSkills, final int[] possibleOwnWeights ) { int opponentCount = this.opponents.length; int possibleWeights = possibleOwnWeights.length; this.bestMatch = this.bestOpponent = this.bestWeight = -1; this.difference = 500; // initialize to worst possible value for ( int match = 0; match < 4; match++ ) { int ownSkill = ownSkills[ match ]; // Skip hopeless contests if ( ownSkill == 0 ) { continue; } for ( int opponent = 0; opponent < opponentCount; ++opponent ) { Opponent opp = this.opponents[ opponent ]; int opponentWeight = opp.getWeight(); for ( int weightIndex = 0; weightIndex < possibleWeights; ++weightIndex ) { int ownWeight = possibleOwnWeights[ weightIndex ]; int ownPower = ownWeight + ownSkill * 3; int opponentSkill = opp.getSkill( match ); int opponentPower; if ( opponentSkill == 0 ) { opponentPower = 5; } else { opponentPower = opponentWeight + opponentSkill * 3; } // optimal weight for equal skill is +3 if ( this.betterWeightDifference( ownPower - ( opponentPower + 3 ), this.difference ) ) { this.difference = ownPower - ( opponentPower + 3 ); this.bestOpponent = opponent; this.bestMatch = match; this.bestWeight = ownWeight; } } } } if ( this.bestOpponent >= 0 ) { return this.opponents[ this.bestOpponent ].getOpponent(); } return null; } /** * Retrieves match data. Will only supply relevant data for last call to bestOpponent * * @return The Id number of the best match. 1 = 'Ultimate Cage Match', 2 = 'Scavenger Hunt', 3 = 'Obstacle Course', * 4 = 'Hide and Seek' */ public int bestMatch() { return this.bestMatch + 1; } /** * Retrieves weight for matchup. This weight will be a value from the possibleOwnWeights parameter in bestOpponent() * * @return Weight value for chosen matchup */ public int bestWeight() { return this.bestWeight; } /** * Retrieves difference from perfect weight for matchup. Will only supply relevant data for last call to * bestOpponent() * * @return Difference from the perfect weight. 0 = perfect, +X = X pounds too heavy, -X is X pounds too light. */ public int difference() { return this.difference; } private boolean betterWeightDifference( final int newVal, final int oldVal ) { // In order to reduce the probability for accidental loss, // do not consider priority values less than -2, but make // it lower priority than 3. switch ( oldVal ) { case 0: return false; case 1: return newVal == 0; case -1: return newVal == 0 || newVal == 1; case 2: return newVal == 0 || newVal == 1 || newVal == -1; case 3: return newVal == 0 || newVal == 1 || newVal == -1 || newVal == 2; case -2: return newVal == 0 || newVal == 1 || newVal == -1 || newVal == 2 || newVal == 3; default: return newVal == 0 || newVal < oldVal && newVal >= -2; } } private class Opponent { // Cake Arena data structure private final ArenaOpponent opponent; // Familiar type private final int type; // Weight private final int weight; // Arena parameters private int[] arena = new int[ 4 ]; public Opponent( final ArenaOpponent opponent ) { this.opponent = opponent; this.type = FamiliarDatabase.getFamiliarId( opponent.getRace() ); this.weight = opponent.getWeight(); this.arena = FamiliarDatabase.getFamiliarSkills( this.type ); } public ArenaOpponent getOpponent() { return this.opponent; } public int getWeight() { return this.weight; } public int getSkill( final int match ) { return this.arena[ match ]; } } }