/**
* 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.persistence;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.sourceforge.kolmafia.KoLCharacter;
import net.sourceforge.kolmafia.objectpool.EffectPool;
import net.sourceforge.kolmafia.session.InventoryManager;
public class CandyDatabase
{
public static Set<Integer> NO_CANDY = new HashSet<Integer>(); // No candies
public static Set<Integer> tier0Candy = new HashSet<Integer>(); // Unspaded
public static Set<Integer> tier1Candy = new HashSet<Integer>(); // Simple
public static Set<Integer> tier2Candy = new HashSet<Integer>(); // Simple and Complex
public static Set<Integer> tier3Candy = new HashSet<Integer>(); // Complex
public static final String NONE = "none";
public static final String UNSPADED = "unspaded";
public static final String SIMPLE = "simple";
public static final String COMPLEX = "complex";
public static void registerCandy( final Integer itemId, final String type )
{
if ( type.equals( "candy" ) )
{
// Unspaded candy
CandyDatabase.tier0Candy.add( itemId );
return;
}
if ( type.equals( "candy1" ) )
{
// Simple candy
CandyDatabase.tier1Candy.add( itemId );
CandyDatabase.tier2Candy.add( itemId );
}
else if ( type.equals( "candy2" ) )
{
// Complex candy
CandyDatabase.tier3Candy.add( itemId );
CandyDatabase.tier2Candy.add( itemId );
}
else
{
return;
}
}
public static final String getCandyType( final int itemId )
{
// We could look in our various candy sets, but more efficient
// to just look at item attributes
int attributes = ItemDatabase.getAttributes( itemId );
return ( attributes & ItemDatabase.ATTR_CANDY0 ) != 0 ? UNSPADED :
( attributes & ItemDatabase.ATTR_CANDY1 ) != 0 ? SIMPLE :
( attributes & ItemDatabase.ATTR_CANDY2 ) != 0 ? COMPLEX :
NONE;
}
public static final int getEffectTier( final int itemId1, final int itemId2 )
{
String candyType1 = CandyDatabase.getCandyType( itemId1 );
String candyType2 = CandyDatabase.getCandyType( itemId2 );
return ( candyType1 == NONE || candyType2 == NONE ) ? 0 :
( candyType1 == SIMPLE && candyType2 == SIMPLE ) ? 1 :
( candyType1 == COMPLEX && candyType2 == COMPLEX ) ? 3 :
2;
}
public static final int getEffectTier( final int effectId )
{
switch ( effectId )
{
case EffectPool.SYNTHESIS_HOT:
case EffectPool.SYNTHESIS_COLD:
case EffectPool.SYNTHESIS_PUNGENT:
case EffectPool.SYNTHESIS_SCARY:
case EffectPool.SYNTHESIS_GREASY:
return 1;
case EffectPool.SYNTHESIS_STRONG:
case EffectPool.SYNTHESIS_SMART:
case EffectPool.SYNTHESIS_COOL:
case EffectPool.SYNTHESIS_HARDY:
case EffectPool.SYNTHESIS_ENERGY:
return 2;
case EffectPool.SYNTHESIS_GREED:
case EffectPool.SYNTHESIS_COLLECTION:
case EffectPool.SYNTHESIS_MOVEMENT:
case EffectPool.SYNTHESIS_LEARNING:
case EffectPool.SYNTHESIS_STYLE:
return 3;
default:
return 0;
}
}
public static final int getEffectModulus( final int effectId )
{
switch ( effectId )
{
case EffectPool.SYNTHESIS_HOT:
case EffectPool.SYNTHESIS_STRONG:
case EffectPool.SYNTHESIS_GREED:
return 0;
case EffectPool.SYNTHESIS_COLD:
case EffectPool.SYNTHESIS_SMART:
case EffectPool.SYNTHESIS_COLLECTION:
return 1;
case EffectPool.SYNTHESIS_PUNGENT:
case EffectPool.SYNTHESIS_COOL:
case EffectPool.SYNTHESIS_MOVEMENT:
return 2;
case EffectPool.SYNTHESIS_SCARY:
case EffectPool.SYNTHESIS_HARDY:
case EffectPool.SYNTHESIS_LEARNING:
return 3;
case EffectPool.SYNTHESIS_GREASY:
case EffectPool.SYNTHESIS_ENERGY:
case EffectPool.SYNTHESIS_STYLE:
return 4;
default:
return -1;
}
}
public static final int effectTierBase( final int tier )
{
switch ( tier )
{
case 1:
return EffectPool.SYNTHESIS_HOT;
case 2:
return EffectPool.SYNTHESIS_STRONG;
case 3:
return EffectPool.SYNTHESIS_GREED;
}
return -1;
}
private static final int FLAG_AVAILABLE = 0x1;
private static final int FLAG_ALLOWED = 0x2;
public static int makeFlags( final boolean available, final boolean allowed )
{
return ( available ? FLAG_AVAILABLE : 0 ) + ( allowed ? FLAG_ALLOWED : 0 );
}
public static int defaultFlags()
{
boolean loggedIn = KoLCharacter.getUserId() > 0;
boolean available = loggedIn && !KoLCharacter.canInteract();
boolean allowed = loggedIn && KoLCharacter.getRestricted();
return CandyDatabase.makeFlags( available, allowed );
}
public static Set<Integer> candyForTier( final int tier )
{
return CandyDatabase.candyForTier( tier, CandyDatabase.defaultFlags() );
}
public static Set<Integer> candyForTier( final int tier, final int flags )
{
if ( tier < 0 || tier > 3 )
{
return null;
}
Set<Integer> candies =
tier == 0 ? CandyDatabase.tier0Candy :
tier == 1 ? CandyDatabase.tier1Candy :
tier == 2 ? CandyDatabase.tier2Candy :
tier == 3 ? CandyDatabase.tier3Candy :
null;
// If neither flag is set, return full set
if ( ( flags & 0x3) == 0 )
{
return candies;
}
// Otherwise, we must filter
boolean available = ( flags & FLAG_AVAILABLE ) != 0;
boolean allowed = ( flags & FLAG_ALLOWED ) != 0;
Set<Integer> result = new HashSet<Integer>();
for ( Integer itemId : candies )
{
if ( available && InventoryManager.getAccessibleCount( itemId ) == 0 )
{
continue;
}
if ( allowed && !ItemDatabase.isAllowed( itemId ) )
{
continue;
}
result.add( itemId );
}
return result;
}
public static int synthesisResult( final int itemId1, final int itemId2 )
{
if ( !ItemDatabase.isCandyItem( itemId1 ) || !ItemDatabase.isCandyItem( itemId2 ) )
{
return -1;
}
int tier = CandyDatabase.getEffectTier( itemId1, itemId2 );
if ( tier == 0 )
{
return -1;
}
int base = CandyDatabase.effectTierBase( tier );
int modulus = ( itemId1 + itemId2 ) % 5;
return base + modulus;
}
public static Set<Integer> sweetSynthesisPairing( final int effectId, final int itemId1 )
{
return CandyDatabase.sweetSynthesisPairing( effectId, itemId1, CandyDatabase.defaultFlags() );
}
public static Set<Integer> sweetSynthesisPairing( final int effectId, final int itemId1, final int flags )
{
Set<Integer> result = new HashSet<Integer>();
int tier = CandyDatabase.getEffectTier( effectId );
if ( tier < 1 || tier > 3 )
{
return result;
}
String candyType = CandyDatabase.getCandyType( itemId1 );
if ( candyType != SIMPLE && candyType != COMPLEX )
{
return result;
}
Set<Integer> candidates = CandyDatabase.NO_CANDY;
switch ( tier )
{
case 1:
candidates = ( candyType == SIMPLE ) ? CandyDatabase.tier1Candy : CandyDatabase.NO_CANDY;
break;
case 2:
candidates = ( candyType == SIMPLE ) ? CandyDatabase.tier3Candy : CandyDatabase.tier1Candy;
break;
case 3:
candidates = ( candyType == COMPLEX ) ? CandyDatabase.tier3Candy : CandyDatabase.NO_CANDY;
break;
}
int desiredModulus = CandyDatabase.getEffectModulus( effectId );
boolean available = ( flags & FLAG_AVAILABLE ) != 0;
boolean allowed = ( flags & FLAG_ALLOWED ) != 0;
for ( int itemId2 : candidates )
{
if ( ( itemId1 + itemId2 ) % 5 != desiredModulus )
{
continue;
}
if ( available )
{
// You can synthesize two of the same candy.
// If using available candy and you only have
// one, can't reuse it.
int candy2Count = InventoryManager.getAccessibleCount( itemId2 );
if ( ( candy2Count == 0 ) ||
( itemId1 == itemId2 && candy2Count == 1 ) )
{
continue;
}
}
if ( allowed && !ItemDatabase.isAllowed( itemId2 ) )
{
continue;
}
result.add( itemId2 );
}
return result;
}
// *** Phase 5 methods ***
// Here will go fancy code to choose combinations of candies that are
// either cheap (aftercore) or available (in-run) using the provided
// Comparators to sort Candy lists appropriately
// Use ASCENDING_MALL_PRICE_COMPARATOR in aftercore
// Use DESCENDING_COUNT_COMPARATOR in-run
// Pseudo-price for a non-tradeable item
public static final int NON_TRADEABLE_PRICE = 999999999;
public static class Candy
implements Comparable<Candy>
{
private final int itemId;
private final String name;
private int count;
private int mallprice;
private boolean restricted;
public Candy( final int itemId )
{
this.itemId = itemId;
this.name = ItemDatabase.getDataName( itemId );
this.count = InventoryManager.getAccessibleCount( itemId );
this.mallprice = ItemDatabase.isTradeable( itemId ) ? MallPriceDatabase.getPrice( itemId ) : 0;
this.restricted = !ItemDatabase.isAllowedInStandard( itemId );
}
@Override
public boolean equals( final Object o )
{
return ( o instanceof Candy ) && ( this.itemId == ((Candy)o).itemId );
}
public int compareTo( final Candy o )
{
if ( o == null )
{
throw new NullPointerException();
}
return this.itemId - o.itemId;
}
public int getItemId()
{
return this.itemId;
}
public String getName()
{
return this.name;
}
public int getCount()
{
return this.count;
}
public int getCost()
{
return this.mallprice == 0 ? CandyDatabase.NON_TRADEABLE_PRICE : this.mallprice;
}
public int getMallPrice()
{
return this.mallprice;
}
public boolean getRestricted()
{
return this.restricted;
}
public Candy update()
{
this.count = InventoryManager.getAccessibleCount( this.itemId );
this.mallprice = MallPriceDatabase.getPrice( this.itemId );
return this;
}
public String toString()
{
return this.name;
}
}
public static List<Candy> itemIdSetToCandyList( Set<Integer> itemIds )
{
ArrayList<Candy> list = new ArrayList<Candy>();
for ( int itemId : itemIds )
{
list.add( new Candy( itemId ) );
}
return list;
}
// Compare by lowest mall price, then largest quantity, then alphabetically
private static class MallPriceComparator
implements Comparator<Candy>
{
public int compare( Candy o1, Candy o2 )
{
int cost1 = o1.getCost();
int cost2 = o2.getCost();
if ( cost1 != cost2 )
{
return cost1 - cost2;
}
int count1 = o1.getCount();
int count2 = o2.getCount();
if ( count1 != count2 )
{
return count2 - count1;
}
return o1.getName().compareToIgnoreCase( o2.getName() );
}
}
public static final Comparator<Candy> ASCENDING_MALL_PRICE_COMPARATOR = new MallPriceComparator();
// Compare by largest quantity, then by lowest mall price, then alphabetically
private static class InverseCountComparator
implements Comparator<Candy>
{
public int compare( Candy o1, Candy o2 )
{
int count1 = o1.getCount();
int count2 = o2.getCount();
if ( count1 != count2 )
{
return count2 - count1;
}
return o1.getName().compareToIgnoreCase( o2.getName() );
}
}
public static final Comparator<Candy> DESCENDING_COUNT_COMPARATOR = new InverseCountComparator();
public static final Candy[] NO_PAIR = new Candy[0];
public static Candy[] synthesisPair( final int effectId )
{
return CandyDatabase.synthesisPair( effectId, CandyDatabase.defaultFlags() );
}
public static Candy[] synthesisPair( final int effectId, final int flags )
{
boolean available = ( flags & FLAG_AVAILABLE ) != 0;
return available ?
CandyDatabase.synthesisPairByCount( effectId, flags ) :
CandyDatabase.synthesisPairByCost( effectId, flags );
}
private static Candy[] synthesisPairByCount( final int effectId, final int flags )
{
int tier = CandyDatabase.getEffectTier( effectId );
List<Candy> candy1List = CandyDatabase.itemIdSetToCandyList( CandyDatabase.candyForTier( tier, flags ) );
Collections.sort( candy1List, DESCENDING_COUNT_COMPARATOR );
for ( Candy candy : candy1List )
{
if ( candy.getCount() == 0 )
{
// Ran out of available candies
return NO_PAIR;
}
int itemId = candy.getItemId();
List<Candy> candy2List = CandyDatabase.itemIdSetToCandyList( CandyDatabase.sweetSynthesisPairing( effectId, itemId, flags ) );
Collections.sort( candy2List, DESCENDING_COUNT_COMPARATOR );
for ( Candy pairing : candy2List )
{
int count = pairing.getCount();
if ( count == 0 )
{
// Nothing left in this list. Select a new candy1
break;
}
if ( candy.equals( pairing ) && count == 1 )
{
// Pairs with itself but only have one.
continue;
}
Candy[] result = new Candy[2];
result[0] = candy;
result[1] = pairing;
return result;
}
}
return NO_PAIR;
}
private static Candy[] synthesisPairByCost( final int effectId, final int flags )
{
int tier = CandyDatabase.getEffectTier( effectId );
int bestCost = Integer.MAX_VALUE;
Candy candy1 = null;
Candy candy2 = null;
List<Candy> candy1List = CandyDatabase.itemIdSetToCandyList( CandyDatabase.candyForTier( tier, flags ) );
Collections.sort( candy1List, ASCENDING_MALL_PRICE_COMPARATOR );
for ( Candy candy : candy1List )
{
int cost1 = candy.getCost();
if ( cost1 > bestCost )
{
break;
}
int itemId = candy.getItemId();
List<Candy> candy2List = CandyDatabase.itemIdSetToCandyList( CandyDatabase.sweetSynthesisPairing( effectId, itemId, flags ) );
Collections.sort( candy2List, ASCENDING_MALL_PRICE_COMPARATOR );
for ( Candy pairing : candy2List )
{
int cost2 = pairing.getCost();
int currentCost = cost1 + cost2;
if ( currentCost >= bestCost )
{
break;
}
candy1 = candy;
candy2 = pairing;
bestCost = currentCost;
}
}
if ( candy1 == null || candy2 == null )
{
return NO_PAIR;
}
Candy[] result = new Candy[2];
result[0] = candy1;
result[1] = candy2;
return result;
}
}