/** * 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.request; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.KoLAdventure; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.RequestLogger; import net.sourceforge.kolmafia.RequestThread; import net.sourceforge.kolmafia.objectpool.IntegerPool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.persistence.ItemDatabase; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.session.InventoryManager; import net.sourceforge.kolmafia.utilities.StringUtilities; public class DwarfFactoryRequest extends GenericRequest { public static final Pattern RUNE_PATTERN = Pattern.compile( "title=\"Dwarf (Digit|Word) Rune (.)\"" ); public static final Pattern ITEMDESC_PATTERN = Pattern.compile( "descitem\\((\\d*)\\)" ); public static final Pattern MEAT_PATTERN = Pattern.compile( "You (gain|lose) (\\d*) Meat" ); public static final Pattern HOPPER_PATTERN = Pattern.compile( "<p>It currently contains (\\d+) ([^.]*)\\.</p>" ); private static final int [] ITEMS = new int[] { ItemPool.SPRING, ItemPool.SPROCKET, ItemPool.COG, ItemPool.MINERS_HELMET, ItemPool.MINERS_PANTS, ItemPool.MATTOCK, ItemPool.LINOLEUM_ORE, ItemPool.ASBESTOS_ORE, ItemPool.CHROME_ORE, ItemPool.DWARF_BREAD, ItemPool.LUMP_OF_COAL, }; private static final int [] ORES = new int[] { ItemPool.LINOLEUM_ORE, ItemPool.ASBESTOS_ORE, ItemPool.CHROME_ORE, ItemPool.LUMP_OF_COAL, }; private static final int [] EQUIPMENT = new int[] { ItemPool.MINERS_HELMET, ItemPool.MINERS_PANTS, ItemPool.MATTOCK, }; private static DwarfNumberTranslator digits = null; private static int[] hopperCount = new int[4]; private static int[] inventoryCount = new int[4]; public static void reset() { DwarfFactoryRequest.digits = null; DwarfFactoryRequest.hopperCount[0] = -1; DwarfFactoryRequest.hopperCount[1] = -1; DwarfFactoryRequest.hopperCount[2] = -1; DwarfFactoryRequest.hopperCount[3] = -1; } public static boolean valid() { return DwarfFactoryRequest.digits != null && DwarfFactoryRequest.digits.valid(); } public DwarfFactoryRequest() { super( "dwarffactory.php" ); } public DwarfFactoryRequest( final String action) { this(); this.addFormField( "action", action ); } @Override public void processResults() { DwarfFactoryRequest.parseResponse( this.getURLString(), this.responseText ); } public static final void parseResponse( final String urlString, final String responseText ) { if ( !urlString.startsWith( "dwarffactory.php" ) ) { return; } Matcher matcher = GenericRequest.ACTION_PATTERN.matcher( urlString ); String action = matcher.find() ? matcher.group(1) : null; // We have nothing special to do for simple visits. if ( action == null ) { return; } if ( action.equals( "ware" ) ) { Matcher runeMatcher = DwarfFactoryRequest.getRuneMatcher( responseText ); String rune1 = DwarfFactoryRequest.getRune( runeMatcher ); String rune2 = DwarfFactoryRequest.getRune( runeMatcher ); String rune3 = DwarfFactoryRequest.getRune( runeMatcher ); int itemId = DwarfFactoryRequest.getItemId( responseText ); DwarfFactoryRequest.setItemRunes( itemId, rune1, rune2, rune3 ); return; } if ( action.equals( "dodice" ) ) { Matcher meatMatcher = MEAT_PATTERN.matcher( responseText ); if ( !meatMatcher.find() ) { return; } boolean won = meatMatcher.group(1).equals( "gain" ); int meat = StringUtilities.parseInt( meatMatcher.group( 2 ) ) / 7; String meat7 = String.valueOf( meat / 7 ) + String.valueOf( meat % 7 ); Matcher runeMatcher = DwarfFactoryRequest.getRuneMatcher( responseText ); String first = DwarfFactoryRequest.getRune( runeMatcher ) + DwarfFactoryRequest.getRune( runeMatcher ); String second = DwarfFactoryRequest.getRune( runeMatcher ) + DwarfFactoryRequest.getRune( runeMatcher ); String message; if ( won ) { message = second + "-" + first + "=" + meat7; } else { message = first + "-" + second + "=" + meat7; } RequestLogger.printLine( message ); RequestLogger.updateSessionLog( message ); DwarfFactoryRequest.addDieRoll( message ); return; } } public static void clearHoppers() { DwarfFactoryRequest.hopperCount[0] = 0; DwarfFactoryRequest.hopperCount[1] = 0; DwarfFactoryRequest.hopperCount[2] = 0; DwarfFactoryRequest.hopperCount[3] = 0; } public static final void decorate( final String urlString, final StringBuffer buffer ) { if ( !urlString.startsWith( "dwarffactory.php" ) ) { return; } Matcher matcher = GenericRequest.ACTION_PATTERN.matcher( urlString ); String action = matcher.find() ? matcher.group(1) : null; if ( action == null ) { return; } if ( action.equals( "dodice" ) ) { if ( !DwarfFactoryRequest.valid() ) { return; } // Find the end of the table following the "you // win/lose xx meat" message. int index = buffer.indexOf( "meat.gif" ); index = buffer.indexOf( "</center>", index ); buffer.insert( index, "The dwarf digit code has been solved!" ); } } private static void setItemRunes( final int itemId, final String rune1, final String rune2, final String rune3 ) { KoLCharacter.ensureUpdatedDwarfFactory(); // If we are looking at runes for ore or equipment and we know // the complete list of ore runes or equipment runes because // we've looked at an office item, we can eliminate runes not // on the appropriate list. String typeRunes = ""; String ores = null; String equipment = null; switch ( itemId ) { case ItemPool.LINOLEUM_ORE: case ItemPool.ASBESTOS_ORE: case ItemPool.CHROME_ORE: case ItemPool.LUMP_OF_COAL: ores = Preferences.getString( "lastDwarfOreRunes" ); if ( ores.length() == 4 ) { typeRunes = ores; } break; case ItemPool.MINERS_HELMET: case ItemPool.MINERS_PANTS: case ItemPool.MATTOCK: equipment = Preferences.getString( "lastDwarfEquipmentRunes" ); if ( equipment.length() == 3 ) { typeRunes = equipment; } break; } String setting = "lastDwarfFactoryItem" + itemId; String oldRunes = Preferences.getString( setting ); String newRunes = ""; if ( typeRunes.equals( "" ) || typeRunes.indexOf( rune1 ) != -1 ) { if ( oldRunes.equals( "" ) || oldRunes.indexOf( rune1 ) != -1 ) { newRunes += rune1; } } if ( typeRunes.equals( "" ) || typeRunes.indexOf( rune2 ) != -1 ) { if ( oldRunes.equals( "" ) || oldRunes.indexOf( rune2 ) != -1 ) { newRunes += rune2; } } if ( typeRunes.equals( "" ) || typeRunes.indexOf( rune3 ) != -1 ) { if ( oldRunes.equals( "" ) || oldRunes.indexOf( rune3) != -1 ) { newRunes += rune3; } } // Eliminate any runes which definitively belong to another item. for ( int i = 0; i < ITEMS.length; ++i ) { int id = ITEMS[i]; if ( id == itemId ) { continue; } String value = Preferences.getString( "lastDwarfFactoryItem" + id ); if ( value.length() == 1 && newRunes.indexOf( value ) != -1 ) { newRunes = StringUtilities.globalStringDelete( newRunes, value ); } } DwarfFactoryRequest.setItemRunes( itemId, newRunes ); } private static void checkForLastRune( String runes, int [] items, String type ) { if ( items == null ) { return; } int candidate = 0; for ( int i = 0; i < items.length; ++i ) { int itemId = items[i]; String setting = "lastDwarfFactoryItem" + itemId; String value = Preferences.getString( setting ); if ( value.length() != 1 ) { // Unidentified item if ( candidate != 0 ) { // Already another return; } candidate = itemId; continue; } // This is an identified rune. Remove from master list. runes = StringUtilities.globalStringDelete( runes, value ); } // If we get here, there is at most one item on the list we've // not identified. if ( candidate != 0 ) { DwarfFactoryRequest.setItemRunes( candidate, runes ); KoLmafia.updateDisplay( "All " + type + " have been identified!" ); } } public static void setItemRunes( final int itemId, final String runes ) { String setting = "lastDwarfFactoryItem" + itemId; Preferences.setString( setting, runes ); if ( runes.length() > 1 ) { return; } // See if we've identified the penultimate item and can thus // deduce the final item. switch ( itemId ) { case ItemPool.LINOLEUM_ORE: case ItemPool.ASBESTOS_ORE: case ItemPool.CHROME_ORE: case ItemPool.LUMP_OF_COAL: String ores = Preferences.getString( "lastDwarfOreRunes" ); if ( ores.length() == 4 ) { DwarfFactoryRequest.checkForLastRune( ores, DwarfFactoryRequest.ORES, "ores" ); } break; case ItemPool.MINERS_HELMET: case ItemPool.MINERS_PANTS: case ItemPool.MATTOCK: String equipment = Preferences.getString( "lastDwarfEquipmentRunes" ); if ( equipment.length() == 3 ) { DwarfFactoryRequest.checkForLastRune( equipment, DwarfFactoryRequest.EQUIPMENT, "pieces of equipment"); } break; } // If the length is 1, that rune has been matched with an // item. Therefore, if it appears in the list of runes for // another item, it can't be the match for that item, too, and // can be removed from that list. DwarfFactoryRequest.pruneItemRunes( itemId, runes ); } private static void pruneItemRunes( final int id, final String rune ) { for ( int i = 0; i < ITEMS.length; ++i ) { int itemId = ITEMS[i]; if ( id == itemId ) { continue; } DwarfFactoryRequest.eliminateItemRune( itemId, rune ); } } private static void eliminateItemRune( final int itemId, final String rune ) { String setting = "lastDwarfFactoryItem" + itemId; String value = Preferences.getString( setting ); if ( value.length() == 1 ) { return; } if ( value.indexOf( rune ) == -1 ) { return; } value = StringUtilities.globalStringDelete( value, rune ); Preferences.setString( setting, value ); if ( value.length() > 1 ) { return; } // We've identified another item. Recurse! DwarfFactoryRequest.pruneItemRunes( itemId, value ); } public static final void setHopperRune( final int hopper, final String responseText ) { KoLCharacter.ensureUpdatedDwarfFactory(); // Parse the rune from the response text String rune = DwarfFactoryRequest.getRune( responseText ); // Associate this rune with this hopper Preferences.setString( "lastDwarfHopper" + hopper, rune ); // Add to list of known ore runes DwarfFactoryRequest.setOreRune( rune ); // See how much ore is currently in the hopper if ( responseText.indexOf( "It is currently empty" ) != -1 ) { hopperCount[ hopper - 1 ] = 0; return; } Matcher matcher = DwarfFactoryRequest.HOPPER_PATTERN.matcher( responseText ); if ( !matcher.find() ) { return; } int count = StringUtilities.parseInt( matcher.group(1) ); hopperCount[ hopper - 1 ] = count; } public static void setOreRune( final String rune ) { String runes = Preferences.getString( "lastDwarfOreRunes" ); if ( runes.indexOf( rune) != -1 ) { return; } // It's a new ore. Add it to the list of ores. runes += rune; Preferences.setString( "lastDwarfOreRunes", runes ); // See if we can auto-identify an ore if ( runes.length() == 4 ) { DwarfFactoryRequest.checkForLastRune( runes, DwarfFactoryRequest.ORES, "ores" ); } // Prune this rune from any non-ores for ( int i = 0; i < ITEMS.length; ++i ) { int itemId = ITEMS[i]; switch ( itemId ) { case ItemPool.LINOLEUM_ORE: case ItemPool.ASBESTOS_ORE: case ItemPool.CHROME_ORE: case ItemPool.LUMP_OF_COAL: continue; } DwarfFactoryRequest.eliminateItemRune( itemId, rune ); } } private static void setEquipmentRune( final String rune ) { String runes = Preferences.getString( "lastDwarfEquipmentRunes" ); if ( runes.indexOf( rune) != -1 ) { return; } // It's a new piece of equipment. Add it to the list of equipment. runes += rune; Preferences.setString( "lastDwarfEquipmentRunes", runes ); // See if we can auto-identify some equipment if ( runes.length() == 3 ) { DwarfFactoryRequest.checkForLastRune( runes, DwarfFactoryRequest.EQUIPMENT, "pieces of equipment"); } // Prune this rune from any non-equipment for ( int i = 0; i < ITEMS.length; ++i ) { int itemId = ITEMS[i]; switch ( itemId ) { case ItemPool.MINERS_HELMET: case ItemPool.MINERS_PANTS: case ItemPool.MATTOCK: continue; } DwarfFactoryRequest.eliminateItemRune( itemId, rune ); } } private static Matcher getRuneMatcher( final String responseText ) { return RUNE_PATTERN.matcher( responseText ); } public static String getRune( final String responseText ) { Matcher matcher = DwarfFactoryRequest.getRuneMatcher( responseText ); return DwarfFactoryRequest.getRune( matcher ); } public static String getRunes( final String responseText ) { Matcher matcher = DwarfFactoryRequest.getRuneMatcher( responseText ); StringBuffer resultBuilder = new StringBuffer(); while ( matcher.find() ) { resultBuilder.append( matcher.group( 2 ) ); } return resultBuilder.toString(); } private static String getRune( final Matcher matcher ) { if ( !matcher.find() ) { return ""; } return matcher.group( 2 ); } private static String getDigits() { return Preferences.getString( "lastDwarfDigitRunes" ); } private static void setDigits() { if ( DwarfFactoryRequest.digits == null ) { return; } String digits = DwarfFactoryRequest.digits.digitString(); Preferences.setString( "lastDwarfDigitRunes", digits ); } public static void setDigits( String digits ) { Preferences.setString( "lastDwarfDigitRunes", digits ); DwarfFactoryRequest.digits = new DwarfNumberTranslator( digits ); } public static int parseNumber( final String runes ) { if ( DwarfFactoryRequest.digits == null ) { DwarfFactoryRequest.digits = new DwarfNumberTranslator(); } return DwarfFactoryRequest.digits.parseNumber( runes ); } public static int parseNumber( final String runes, final String digits ) { DwarfNumberTranslator translator = new DwarfNumberTranslator( digits ); return translator.parseNumber( runes ); } public static void useUnlaminatedItem( final int itemId, final String responseText ) { Matcher matcher = DwarfFactoryRequest.getRuneMatcher( responseText ); StringBuffer runesBuilder = new StringBuffer(); int count = 0; while ( matcher.find() ) { String rune = matcher.group( 2 ); if ( count++ == 0 ) { DwarfFactoryRequest.setEquipmentRune( rune ); runesBuilder.append ( rune ); continue; } String type = matcher.group(1); if ( type.equals( "Word" ) ) { DwarfFactoryRequest.setOreRune( rune ); runesBuilder.append ( ',' ); } runesBuilder.append ( rune ); } Preferences.setString( "lastDwarfOfficeItem" + itemId, runesBuilder.toString() ); } public static void useLaminatedItem( final int itemId, final String responseText ) { Matcher matcher = DwarfFactoryRequest.getRuneMatcher( responseText ); StringBuffer runesBuilder = new StringBuffer(); int count = 0; while ( matcher.find() ) { String rune = matcher.group( 2 ); if ( count++ == 0 ) { DwarfFactoryRequest.setOreRune( rune ); runesBuilder.append( rune ); continue; } if ( count == 2 ) { // Skip rune for "gauges" continue; } String type = matcher.group(1); if ( type.equals( "Word" ) ) { DwarfFactoryRequest.setEquipmentRune( rune ); runesBuilder.append( ',' ); } runesBuilder.append( rune ); } Preferences.setString( "lastDwarfOfficeItem" + itemId, runesBuilder.toString() ); } private static int getItemId( final String responseText ) { Matcher matcher = ITEMDESC_PATTERN.matcher( responseText ); if ( !matcher.find() ) { return -1; } return ItemDatabase.getItemIdFromDescription( matcher.group(1) ); } public static final boolean registerRequest( final String urlString ) { if ( !urlString.startsWith( "dwarffactory.php" ) ) { return false; } Matcher matcher = GenericRequest.ACTION_PATTERN.matcher( urlString ); String action = matcher.find() ? matcher.group(1) : null; // We have nothing special to do for simple visits. if ( action == null ) { return true; } if ( action.equals( "ware" ) ) { String message = "[" + KoLAdventure.getAdventureCount() + "] Dwarven Factory Warehouse"; RequestLogger.printLine( "" ); RequestLogger.printLine( message ); RequestLogger.updateSessionLog(); RequestLogger.updateSessionLog( message ); return true; } if ( action.equals( "dorm" ) ) { String message = "Visiting the Dwarven Factory Dormitory"; RequestLogger.printLine( "" ); RequestLogger.printLine( message ); RequestLogger.updateSessionLog(); RequestLogger.updateSessionLog( message ); return true; } if ( action.equals( "dodice" ) || action.equals( "nodice" ) || action.equals( "nonodice" ) ) { return true; } return false; } // Module to parse special messages from Dwarvish War Uniform items // There is currently an extra space after the last 'really' private static final Pattern DWARF_MATTOCK_PATTERN = Pattern.compile( "<p>Your mattock glows ((really )*) ?bright blue\\.</p>"); public static Matcher hpMessage( final CharSequence responseText ) { Matcher mattockMatcher = DwarfFactoryRequest.DWARF_MATTOCK_PATTERN.matcher( responseText ); return mattockMatcher.find() ? mattockMatcher : null; } public static int deduceHP( final CharSequence responseText ) { Matcher mattockMatcher = DwarfFactoryRequest.hpMessage( responseText ); if ( mattockMatcher == null ) { return 0; } return DwarfFactoryRequest.deduceHP( mattockMatcher ); } public static int deduceHP( final Matcher mattockMatcher ) { return mattockMatcher.end( 1 ) - mattockMatcher.start( 1 ); } private static final Pattern DWARF_HELMET_PATTERN = Pattern.compile( "<p>A small crystal lens flips down.*?</p>" ); public static Matcher attackMessage( final CharSequence responseText ) { Matcher helmetMatcher = DwarfFactoryRequest.DWARF_HELMET_PATTERN.matcher( responseText ); return helmetMatcher.find() ? helmetMatcher : null; } public static int deduceAttack( final CharSequence responseText ) { Matcher helmetMatcher = DwarfFactoryRequest.attackMessage( responseText ); if ( helmetMatcher == null ) { return 0; } return DwarfFactoryRequest.deduceAttack( helmetMatcher ); } public static int deduceAttack( final Matcher helmetMatcher ) { String runes = DwarfFactoryRequest.getRunes( helmetMatcher.group(0) ); return DwarfFactoryRequest.parseNumber( runes ); } // A little light on your sporran lights up <color> // // Two little lights light up on your sporran -- a <color> one and a <color> one. // // Three little lights light up <color>, <color>, and <color> on your sporran. // // Your sporran lights up with a series of four little lights: <color>, <color>, <color>, and <color>. // // A bunch of little lights on your sporran start flashing random colors like there's a rave on your crotch. public static final Pattern DWARF_KILT_PATTERN = Pattern.compile( "<p>.*?our sporran.*?</p>" ); public static Matcher defenseMessage( final CharSequence responseText ) { Matcher kiltMatcher = DwarfFactoryRequest.DWARF_KILT_PATTERN.matcher( responseText ); return kiltMatcher.find() ? kiltMatcher : null; } public static int deduceDefense( final CharSequence responseText ) { Matcher kiltMatcher = DwarfFactoryRequest.defenseMessage( responseText ); if ( kiltMatcher == null ) { return 0; } return DwarfFactoryRequest.deduceDefense( kiltMatcher ); } public static final Pattern COLOR_PATTERN = Pattern.compile( "(red|orange|yellow|green|blue|indigo|violet)" ); public static int deduceDefense( final Matcher kiltMatcher ) { if ( kiltMatcher.group(0).indexOf( "rave on your crotch" ) != -1 ) { return 99999; } Matcher matcher= DwarfFactoryRequest.COLOR_PATTERN.matcher( kiltMatcher.group(0) ); int number = 0; while ( matcher.find() ) { String color = matcher.group(1); int digit = -1; if ( color.equals( "red" ) ) { digit = 0; } else if ( color.equals( "orange" ) ) { digit = 1; } else if ( color.equals( "yellow" ) ) { digit = 2; } else if ( color.equals( "green" ) ) { digit = 3; } else if ( color.equals( "blue" ) ) { digit = 4; } else if ( color.equals( "indigo" ) ) { digit = 5; } else if ( color.equals( "violet" ) ) { digit = 6; } else { return -1; } number = ( number * 7 ) + digit; } return number; } // Module to check whether you've found everything you need public static boolean check( final boolean use ) { StringBuffer output = new StringBuffer(); // Check the office items DwarfFactoryRequest.checkOrUse( ItemPool.SMALL_LAMINATED_CARD, use, output ); DwarfFactoryRequest.checkOrUse( ItemPool.LITTLE_LAMINATED_CARD, use, output ); DwarfFactoryRequest.checkOrUse( ItemPool.NOTBIG_LAMINATED_CARD, use, output ); DwarfFactoryRequest.checkOrUse( ItemPool.UNLARGE_LAMINATED_CARD, use, output ); DwarfFactoryRequest.checkOrUse( ItemPool.DWARVISH_DOCUMENT, use, output ); DwarfFactoryRequest.checkOrUse( ItemPool.DWARVISH_PAPER, use, output ); DwarfFactoryRequest.checkOrUse( ItemPool.DWARVISH_PARCHMENT, use, output ); // Check the hoppers DwarfFactoryRequest.checkHopper( 1, use, output ); DwarfFactoryRequest.checkHopper( 2, use, output ); DwarfFactoryRequest.checkHopper( 3, use, output ); DwarfFactoryRequest.checkHopper( 4, use, output ); // Check the ores and equipment DwarfFactoryRequest.checkItem( ItemPool.MINERS_HELMET, use, output ); DwarfFactoryRequest.checkItem( ItemPool.MINERS_PANTS, use, output ); DwarfFactoryRequest.checkItem( ItemPool.MATTOCK, use, output ); DwarfFactoryRequest.checkItem( ItemPool.LINOLEUM_ORE, use, output ); DwarfFactoryRequest.checkItem( ItemPool.ASBESTOS_ORE, use, output ); DwarfFactoryRequest.checkItem( ItemPool.CHROME_ORE, use, output ); DwarfFactoryRequest.checkItem( ItemPool.LUMP_OF_COAL, use, output ); String text = output.toString(); if ( text.length() > 0 ) { RequestLogger.printLine( text ); return false; } return true; } public static void checkOrUse( final int itemId, final boolean use, final StringBuffer output ) { if ( !InventoryManager.hasItem( itemId ) ) { output.append( "You do not have the " + ItemDatabase.getItemName( itemId ) + KoLConstants.LINE_BREAK ); return; } if ( !use || !Preferences.getString( "lastDwarfOfficeItem" + itemId ).equals( "" ) ) { return; } RequestThread.postRequest( UseItemRequest.getInstance( ItemPool.get( itemId, 1 ) ) ); } public static void checkHopper( final int hopper, final boolean use, final StringBuffer output ) { if ( !use || DwarfFactoryRequest.hopperCount[ hopper - 1 ] != -1 ) { return; } RequestLogger.printLine( "Visiting hopper #" + hopper + " to check ore level" ); RequestThread.postRequest( new DwarfContraptionRequest( "hopper" + ( hopper - 1) ) ); } public static void checkItem( final int itemId, final boolean use, final StringBuffer output ) { if ( Preferences.getString( "lastDwarfFactoryItem" + itemId ).length() != 1 ) { output.append( "You not yet identified the " + ItemDatabase.getItemName( itemId ) + KoLConstants.LINE_BREAK ); return; } } // Module to solve the digit code public static void solve() { // If we don't currently have a digit translator, create one // from the preference if ( DwarfFactoryRequest.digits == null ) { DwarfFactoryRequest.digits = new DwarfNumberTranslator(); } // If it's valid, we're golden if ( DwarfFactoryRequest.digits.valid() ) { return; } // Here's where we solve it. // Step 0: get the unlaminated numbers String[] unlaminated = getUnlaminatedNumbers(); for ( int i = 0; i < unlaminated.length; ++i ) { DwarfFactoryRequest.digits.addNumber( unlaminated[i] ); } // Step 1: try to deduce what we can from the laminated items String[] laminated = getLaminatedNumbers(); for ( int i = 0; i < laminated.length; ++i ) { DwarfFactoryRequest.digits.addNumber( laminated[i] ); } DwarfFactoryRequest.digits.analyzeNumbers(); // Step 2: iterate over saved dice rules, deducing what we can String[] rolls = DwarfFactoryRequest.getDiceRolls(); for ( int i = 0; i < rolls.length; ++i ) { DwarfFactoryRequest.digits.addRoll( rolls[i] ); } DwarfFactoryRequest.digits.analyzeRolls(); // Save the current digit string, complete or not DwarfFactoryRequest.setDigits(); if ( DwarfFactoryRequest.digits.valid() ) { KoLmafia.updateDisplay( "Dwarf digit code solved!" ); } else { RequestLogger.printLine( "Unable to solve digit code. Get more data!" ); } } private static String[] getUnlaminatedNumbers() { ArrayList numbers = new ArrayList(); // lastDwarfOfficeItem3212=H,QEG,BFD,OJI,JED DwarfFactoryRequest.getItemNumbers( numbers, ItemPool.DWARVISH_DOCUMENT, 4 ); DwarfFactoryRequest.getItemNumbers( numbers, ItemPool.DWARVISH_PAPER, 4 ); DwarfFactoryRequest.getItemNumbers( numbers, ItemPool.DWARVISH_PARCHMENT, 4 ); return (String[])numbers.toArray( new String[ numbers.size() ] ); } private static String[] getLaminatedNumbers() { ArrayList numbers = new ArrayList(); // lastDwarfOfficeItem3208=B,HGIG,MGDE,PJD DwarfFactoryRequest.getItemNumbers( numbers, ItemPool.SMALL_LAMINATED_CARD, 3 ); DwarfFactoryRequest.getItemNumbers( numbers, ItemPool.LITTLE_LAMINATED_CARD, 3 ); DwarfFactoryRequest.getItemNumbers( numbers, ItemPool.NOTBIG_LAMINATED_CARD, 3 ); DwarfFactoryRequest.getItemNumbers( numbers, ItemPool.UNLARGE_LAMINATED_CARD, 3 ); return (String[])numbers.toArray( new String[ numbers.size() ] ); } private static void getItemNumbers( final ArrayList list, final int itemId, final int count ) { String setting = DwarfFactoryRequest.getItemSetting( itemId ); String[] splits = setting.split( "," ); if ( splits.length != count + 1 ) { return; } for ( int i = 1; i <= count; ++i ) { list.add( splits[i].substring(1) ); } } private static String getItemSetting( int itemId ) { String settingName = "lastDwarfOfficeItem" + itemId; String setting = Preferences.getString( settingName ); if ( !setting.equals( "" ) ) { return setting; } if ( InventoryManager.hasItem( itemId ) ) { RequestThread.postRequest( UseItemRequest.getInstance( ItemPool.get( itemId, 1 ) ) ); } return Preferences.getString( settingName ); } private static void addDieRoll( final String message ) { // Add the die roll to the list of saved rolls KoLCharacter.ensureUpdatedDwarfFactory(); String dice = Preferences.getString( "lastDwarfDiceRolls" ); Preferences.setString( "lastDwarfDiceRolls", dice + message + ":" ); // Try to crack the digit code DwarfFactoryRequest.solve(); } private static String[] getDiceRolls() { String rolls = Preferences.getString( "lastDwarfDiceRolls" ); return rolls.split( ":" ); } // Module to report on what we've gleaned about the factory quest public static void report() { DwarfFactoryRequest.solve(); if ( DwarfFactoryRequest.digits == null ) { return; } DwarfFactoryRequest.report( DwarfFactoryRequest.digits ); } public static void report( final String digits ) { if ( digits.length() != 7 ) { RequestLogger.printLine( "Digit string must have 7 characters" ); return; } DwarfNumberTranslator translator = new DwarfNumberTranslator( digits ); DwarfFactoryRequest.report( translator ); } private static void report( final DwarfNumberTranslator digits ) { if ( !digits.valid() ) { RequestLogger.printLine( "Invalid or incomplete digit set" ); return; } if ( !DwarfFactoryRequest.check( true ) ) { return; } FactoryData data = new FactoryData( digits ); data.setInventoryCounts(); StringBuffer output = new StringBuffer(); output.append( "<table border=2 cols=6>" ); // Put in a header output.append( "<tr>" ); output.append( "<td rowspan=3 colspan=2></td>" ); output.append( "<td align=center>Hopper #1</td>" ); output.append( "<td align=center>Hopper #2</td>" ); output.append( "<td align=center>Hopper #3</td>" ); output.append( "<td align=center>Hopper #4</td>" ); output.append( "</tr>" ); output.append( "<tr>" ); output.append( "<td align=center>" + data.getHopperOre( 0 ) + "</td>" ); output.append( "<td align=center>" + data.getHopperOre( 1 ) + "</td>" ); output.append( "<td align=center>" + data.getHopperOre( 2 ) + "</td>" ); output.append( "<td align=center>" + data.getHopperOre( 3 ) + "</td>" ); output.append( "</tr>" ); output.append( "<tr>" ); output.append( "<td align=center>" + data.getHopperOreCounts( 0 ) + "</td>" ); output.append( "<td align=center>" + data.getHopperOreCounts( 1 ) + "</td>" ); output.append( "<td align=center>" + data.getHopperOreCounts( 2 ) + "</td>" ); output.append( "<td align=center>" + data.getHopperOreCounts( 3 ) + "</td>" ); output.append( "</tr>" ); // Add HAT output.append( "<tr>" ); output.append( "<td align=center rowspan=2>Hat</td>" ); output.append( "<td align=center>Gauges</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.HAT, 0 ) + "</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.HAT, 1 ) + "</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.HAT, 2 ) + "</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.HAT, 3 ) + "</td>" ); output.append( "</tr>" ); output.append( "<tr>" ); output.append( "<td align=center>Ores</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.HAT, 0 ) + "</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.HAT, 1 ) + "</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.HAT, 2 ) + "</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.HAT, 3 ) + "</td>" ); output.append( "</tr>" ); // Add PANTS output.append( "<tr>" ); output.append( "<td align=center rowspan=2>Pants</td>" ); output.append( "<td align=center>Gauges</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.PANTS, 0 ) + "</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.PANTS, 1 ) + "</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.PANTS, 2 ) + "</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.PANTS, 3 ) + "</td>" ); output.append( "</tr>" ); output.append( "<tr>" ); output.append( "<td align=center>Ores</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.PANTS, 0 ) + "</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.PANTS, 1 ) + "</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.PANTS, 2 ) + "</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.PANTS, 3 ) + "</td>" ); output.append( "</tr>" ); // Add WEAPON output.append( "<tr>" ); output.append( "<td align=center rowspan=2>Weapon</td>" ); output.append( "<td align=center>Gauges</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.WEAPON, 0 ) + "</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.WEAPON, 1 ) + "</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.WEAPON, 2 ) + "</td>" ); output.append( "<td align=center>" + data.getGaugeSetting( FactoryData.WEAPON, 3 ) + "</td>" ); output.append( "</tr>" ); output.append( "<tr>" ); output.append( "<td align=center>Ores</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.WEAPON, 0 ) + "</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.WEAPON, 1 ) + "</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.WEAPON, 2 ) + "</td>" ); output.append( "<td align=center>" + data.getOreQuantityString( FactoryData.WEAPON, 3 ) + "</td>" ); output.append( "</tr>" ); output.append( "</table>" ); RequestLogger.printLine( output.toString() ); RequestLogger.printLine(); } public static class DwarfNumberTranslator { private final HashMap digitMap = new HashMap(); private final HashMap charMap = new HashMap(); public DwarfNumberTranslator() { this( DwarfFactoryRequest.getDigits() ); } public DwarfNumberTranslator( final String digits ) { // Make maps from dwarf digit rune to base 7 digit and // vice versa for ( int i = 0; i < digits.length() && i < 7 ; ++i ) { char digit = digits.charAt( i ); if ( !Character.isLetter( digit ) ) { continue; } this.mapCharacter( digit, i ); } } private void mapCharacter( final char c, final int i ) { Character code = new Character( Character.toUpperCase( c ) ); Integer val = IntegerPool.get( i ); this.mapCharacter( code, val ); } private void mapCharacter( final Character code, final Integer val ) { this.digitMap.put( code, val ); this.charMap.put( val, code ); } public Integer getDigit( final Character code ) { return (Integer) this.digitMap.get( code ); } public Integer getDigit( final char c ) { return this.getDigit( new Character( c ) ); } public String digitString() { StringBuffer valueBuilder = new StringBuffer(); for ( int i = 0; i < 7; ++i ) { Character code = (Character)this.charMap.get( IntegerPool.get( i ) ); valueBuilder.append( code == null ? '-' : code.charValue() ); } return valueBuilder.toString(); } public boolean valid() { return this.digitMap.size() == 7; } public int parseNumber( final String string ) { int number = 0; for ( int i = 0; i < string.length(); ++i ) { Integer val = (Integer) this.digitMap.get( new Character( string.charAt( i ) ) ); if ( val == null ) { return -1; } number = ( number * 7 ) + val.intValue(); } return number; } // Methods for solving the digit code private final ArrayList numbers = new ArrayList(); private final ArrayList digits = new ArrayList(); private void addNewDigit( final char ch ) { Character digit = new Character( ch ); if ( !this.digits.contains( digit ) ) { this.digits.add( digit ); } } public void addNumber( final String number ) { // See if it's a new number for ( int i = 0; i < this.numbers.size(); ++i ) { String old = (String)this.numbers.get(i); if ( old.equals( number) ) { return; } } // Add the new number to the list this.numbers.add( number ); // Add all the digits to the set of digits for ( int i = 0; i < number.length(); ++i ) { this.addNewDigit( number.charAt( i ) ); } } // Step 1: Deduce initial digit of three digit numbers from // laminated items private int numberCount = 0; public void analyzeNumbers() { // If we've already looked at all the numbers in the // array, it's pointless to do it again. if ( this.numbers.size() == numberCount ) { return; } // Save current size of array numberCount = this.numbers.size(); // Gauge numbers encode base-10 numbers from 00 - 99 in // base-7. In base 7, numbers greater than 48 take 3 // digits with the first digit equal to 1. Numbers // greater than 97 take 3 digits with the first digit // equal to 2. In particular, 98 is 200 and 99 is 201. // Therefore, when we have a three digit number, 50 // times out of 52, it will encode "1", and 2 times out // of 52 it will encode "2". If it encodes "1", the // following two digits can be any of the 7 digits. // Make an array to save each initial digit and the set // of all digits that appear after each initial digit char [][] matches = new char[2][8]; int [] counts = new int[2]; for ( int i = 0; i < this.numbers.size(); ++i ) { String val = (String)this.numbers.get(i); // We only deduce digits from 3-digit numbers. if ( val.length() < 3 ) { continue; } char d1 = val.charAt(0); char d2 = val.charAt(1); char d3 = val.charAt(2); int off = 0; for ( int j = 0; j < matches.length; ++ j ) { char match = matches[j][0]; if ( match == 0 || match == d1 ) { off = j; break; } } char [] digits = matches[off]; digits[0] = d1; for ( int k = 1; k < digits.length; ++k ) { char match = digits[k]; if ( match == d2 ) { break; } if ( match == 0 ) { digits[k] = d2; counts[off]++; break; } } for ( int k = 1; k < digits.length; ++k ) { char match = digits[k]; if ( match == d3 ) { break; } if ( match == 0 ) { digits[k] = d3; counts[off]++; break; } } } // We've now saved all the digits. If any initial // character has 3 or more different digits that follow // it, it must be 1. int oneOffset = counts[0] >= 3 ? 0 : counts[1] >= 3 ? 1 : -1; // In the unlikely event that no initial digit has more // than 2 digits that follow it, we can deduce nothing. if ( oneOffset == -1 ) { return; } this.mapCharacter( matches[oneOffset][0], 1 ); // If we have identified 1, if we have another initial // digit, it must be 2. int twoOffset = 1 - oneOffset; if ( counts[ twoOffset ] == 0 ) { return; } this.mapCharacter( matches[twoOffset][0], 2 ); // 2 is always followed by 0 this.mapCharacter( matches[twoOffset][1], 0 ); } // Step 2: Deduce digits from the dice game private final ArrayList rolls = new ArrayList(); public void addRoll( final String roll ) { // AB-CD=xx if ( roll.length() != 8 ) { return; } // See if it's a new roll for ( int i = 0; i < this.rolls.size(); ++i ) { String old = (String)this.rolls.get(i); if ( old.equals( roll) ) { return; } } // We can work even without the laminated items if we // deduce digits from the die rolls. this.addNewDigit( roll.charAt( 0 ) ); this.addNewDigit( roll.charAt( 1 ) ); this.addNewDigit( roll.charAt( 3 ) ); this.addNewDigit( roll.charAt( 4 ) ); // Add the new roll to the list this.rolls.add( roll ); } private int rollCount = 0; public void analyzeRolls() { // If we've already looked at all the rolls in the // array, it's pointless to do it again. if ( this.rolls.size() == this.rollCount ) { return; } // Save current size of array this.rollCount = this.rolls.size(); // Match rolls against set of all possible combinations // and eliminate any which are inconsistent this.matchDigitPermutations(); } private HashSet permutations = new HashSet(); private void matchDigitPermutations() { // Nothing to do if we have identified all the digits if ( this.digitMap.size() == 7 ) { return; } // We can't do this unless we know all 7 runes that are // used for digits. if ( this.digits.size() != 7 ) { return; } // Initialize the set with 5040 permutations of 7 runes. if ( this.permutations.size() == 0 ) { this.generatePermutations( "" ); RequestLogger.printLine( "Initialized " + KoLConstants.COMMA_FORMAT.format( this.permutations.size() ) + " permutations" ); } // Iterate over all the rolls and eliminate any // permutation which is inconsistent int old_perms = this.permutations.size(); for ( int i = 0; i < this.rolls.size() && this.permutations.size() > 1; ++i ) { this.checkPermutations( (String) this.rolls.get( i ) ); int new_perms = this.permutations.size(); if ( old_perms > new_perms ) { int gone = old_perms - new_perms; RequestLogger.printLine( "Eliminated " + KoLConstants.COMMA_FORMAT.format( gone ) + " permutations; " + KoLConstants.COMMA_FORMAT.format( new_perms ) + " left" ); old_perms = new_perms; } } // If only a single permutation remains in the set, we // have cracked the digit code. if ( this.permutations.size() == 1 ) { this.saveSoloPermutation(); } } private void generatePermutations( final String prefix ) { int index = prefix.length(); if ( index == 7 ) { this.permutations.add( prefix ); return; } // If we know the character that goes in this position, // generate only the permutations that have that // character in that position. Character val = (Character) this.charMap.get( IntegerPool.get( index ) ); if ( val != null ) { this.generatePermutations( prefix + val.charValue() ); return; } for ( int i = 0; i < 7; ++i ) { Character rune = (Character) this.digits.get( i ); // If we're already using this character, skip char ch = rune.charValue(); if ( prefix.indexOf( ch ) != -1 ) { continue; } // If we know this rune, only use it in the // correct position. Integer j = (Integer) this.digitMap.get( rune ); if ( j != null && j.intValue() != index ) { continue; } // Otherwise, put this character into position // and recurse. this.generatePermutations( prefix + ch ); } } private void checkPermutations( final String roll ) { // AB-CD=xx if ( roll.length() != 8 ) { return; } char d1 = roll.charAt( 0 ); char d2 = roll.charAt( 1 ); char d3 = roll.charAt( 3 ); char d4 = roll.charAt( 4 ); int high = roll.charAt( 6 ) - '0'; int low = roll.charAt( 7 ) - '0'; int val = ( high * 7) + low; Iterator it = this.permutations.iterator(); while ( it.hasNext() ) { String permutation = (String) it.next(); if ( !this.validPermutation( permutation, d1, d2, d3, d4, val ) ) { it.remove(); } } } private boolean validPermutation( final String permutation, final char d1, final char d2, final char d3, final char d4, final int val ) { int total; int i1 = permutation.indexOf( d1 ); if ( d1 == d2 && i1 == 0 ) { total = 49; } else { int i2 = permutation.indexOf( d2 ); total = i1 * 7 + i2; } int i3 = permutation.indexOf( d3 ); int i4 = permutation.indexOf( d4 ); total -= i3 * 7 + i4; return total == val; } private void saveSoloPermutation() { if ( this.permutations.size() != 1 ) { return; } String [] strings = (String []) this.permutations.toArray( new String [ 1 ] ); String digits = strings[0]; for ( int i = 0; i < 7; ++i ) { this.mapCharacter( digits.charAt( i ), i ); } } /* * Start of obsolete code private void deduceLastDigit() { // Only do this if we know all but one of the digits // and all the characters which are used as digits. if ( this.digitMap.size() != 6 || this.digits.size() != 7 ) { return; } // Find the character we have not used for a digit Character code = null; for ( int i = 0; i < 7; ++i ) { Character c = (Character)this.digits.get( i ); if ( !this.digitMap.containsKey( c ) ) { code = c; break; } } // Find the digit we have not identified for ( int i = 0; i < 7; ++i ) { Integer val = PrimitiveAutoBoxCache.valueOf( i ); if ( this.charMap.get( val ) == null ) { this.mapCharacter( code, val ); return; } } } // The following technique is no longer used; solving via // permutation elimination allows us to all rolls, including // doubles, even if the digit for 0 is not yet known. private LinearSystem makeSystem() { // Iterate until we stop learning variables by simple // substitution. while ( true ) { // The digit for zero is special: a roll of // "00" equals 49 Character z = (Character)this.charMap.get( PrimitiveAutoBoxCache.valueOf( 0 ) ); LinearSystem system = this.makeSystem( z ); if ( system != null ) { return system; } } } private LinearSystem makeSystem( final Character zero) { // Make a list of variables ArrayList variables = new ArrayList(); for ( int i = 0; i < this.digits.size(); ++i ) { Character c = (Character)this.digits.get( i ); if ( !this.digitMap.containsKey( c ) ) { variables.add( c ); } } // Make a linear system containing these variables LinearSystem system = new LinearSystem( variables ); // Give it equations for ( int i = 0; i < this.rolls.size(); ++i ) { String roll = (String) this.rolls.get( i ); // AB-CD=xx if ( roll.length() != 8 ) { continue; } char d1 = roll.charAt( 0 ); char d2 = roll.charAt( 1 ); char d3 = roll.charAt( 3 ); char d4 = roll.charAt( 4 ); int high = roll.charAt( 6 ) - '0'; int low = roll.charAt( 7 ) - '0'; int val = ( high * 7) + low; // Double zero = 49, not 0. Unless we know what // zero is, we can't make that conversion, so // skip all doubles until we learn 0. if ( d1 == d2 ) { if ( !this.digitMap.containsKey( new Character( d1 ) ) && zero == null ) { continue; } // The linear equation solver doesn't // know that 00 is 49. Adjust sum. if ( d1 == zero.charValue() ) { val -= 49; } } int known = this.digitMap.size(); system.addEquation( 7, d1, 1, d2, -7, d3, -1, d4, val ); // If adding an equation found a variable by // substitution, punt, since that could make // already added equations simpler. if ( known != this.digitMap.size() ) { return null; } } return system; } private class LinearSystem { private final int vcount; private final Character[] variables; private final ArrayList equations; public LinearSystem( final ArrayList variables ) { this.vcount = variables.size(); this.variables = (Character[]) variables.toArray( new Character[ this.vcount ] ); this.equations = new ArrayList(); } public void addEquation( final int c1, final char v1, final int c2, final char v2, final int c3, final char v3, final int c4, final char v4, final int val ) { int[] equation = new int[ this.vcount + 1]; // Store the sum first, since we will adjust it. equation[ this.vcount ] = val; // Store or substitute all the variables this.addVariable( equation, c1, v1 ); this.addVariable( equation, c2, v2 ); this.addVariable( equation, c3, v3 ); this.addVariable( equation, c4, v4 ); // If we only have one variable unsubstituted // in this equation, we've discovered a new // digit. int offset = -1; for ( int i = 0; i < this.vcount; ++i ) { // Skip unused variables if ( equation[i] == 0 ) { continue; } // At least two unknown variables. // Register equation. if ( offset != -1 ) { this.equations.add( equation ); return; } // This variable is unknown offset = i; } // If there is only one unsubstituted variable, // register the new digit. if ( offset != -1 ) { char code = this.variables[ offset ].charValue(); int i = equation[ this.vcount ] / equation [ offset ]; DwarfNumberTranslator.this.mapCharacter( code, i ); } // All the variables in this equation are // already known. Ignore it. } public void addVariable( final int[] equation, final int c1, final char v1 ) { Integer digit = DwarfNumberTranslator.this.getDigit( v1 ); // If this is a known variable, substitute if ( digit != null ) { int val = c1 * digit.intValue(); equation[ this.vcount ] -= val; return; } // This is an unknown variable. Find it in the // list. for ( int i = 0; i < this.vcount; ++i ) { if ( v1 == this.variables[ i ].charValue() ) { equation[ i ] += c1; return; } } } private int [][] eqs = null; public void calculate() { int rows = this.equations.size(); int cols = this.vcount; if ( rows == 0 || cols == 0 ) { return; } // Make temporary array out of ArrayList this.eqs = (int[][])this.equations.toArray( new int[rows][cols+1]); // Start examining matrix at upper left corner int row = 0; int col = 0; // Iterate over the columns, going down the rows while ( col < cols ) { // Find row which contains this variable int newRow = this.findRow( row, col ); // If we found one, work with it. if ( newRow != -1 ) { this.swapRows( row, newRow ); // Eliminate this variable in // all rows below this one. this.eliminate( row, col ); // Move to next row row++; } // Move to next column col++; } // Go back up the rows, substituting variables while ( --row >= 0 ) { this.substitute( row ); } // Clear temporary array this.eqs = null; } private int findRow( int row, final int col ) { int nrows = this.eqs.length; while ( row < nrows ) { if ( this.eqs[ row ][ col ] != 0 ) { return row; } row++; } return -1; } private void swapRows( final int row1, final int row2 ) { if ( row1 != row2 ) { int[] temp = this.eqs[ row1 ]; this.eqs[ row1 ] = this.eqs[ row2 ]; this.eqs[ row2 ] = temp; } } private void eliminate( final int row, final int col ) { // We will subtract row from all lower rows. // Its value for the specified column is a // constant we will use frequently. int[] row1 = this.eqs[ row ]; int c1 = row1[ col ]; int nrows = this.eqs.length; int ncols = this.vcount + 1; int current = row + 1; // Iterate over all rows below the specified row while ( current < nrows ) { int[] row2 = this.eqs[ current++ ]; int c2 = row2[ col ]; if ( c2 == 0 ) { continue; } // row2 = ( c1 * row2 ) - (c2 * row1) for ( int i = col; i < ncols; ++i ) { row2[i] = c1 * row2[i] - c2 * row1[i] ; } } } private void substitute( final int row ) { int[] row1 = this.eqs[ row ]; // Iterate over the variables int ncols = this.vcount; int index = -1; boolean valid = true; for ( int col = 0; col < ncols; ++col ) { int c1 = row1[col]; if ( c1 == 0 ) { continue; } Integer digit = DwarfNumberTranslator.this.getDigit( this.variables[col] ); // If this variable is known, substitute if ( digit != null ) { int val = c1 * digit.intValue(); row1[col] = 0; row1[ ncols ] -= val; continue; } // If this variable is unknown, it's a // candidate for discover. if ( index != -1 ) { // Oops. More than one unknown valid = false; } // Save index of unknown variable index = col; } // If we discovered a single unknown variable, // we can now solve for it. if ( valid && index != -1 ) { Character digit = this.variables[index]; Integer val = PrimitiveAutoBoxCache.valueOf( row1[ncols] / row1[index] ); DwarfNumberTranslator.this.mapCharacter( digit, val ); } } } * End of obsolete code */ } public static class FactoryData { public static final int HAT = 0; public static final int PANTS = 1; public static final int WEAPON = 2; private DwarfNumberTranslator digits; private HashMap itemMap = new HashMap(); private HashMap runeMap = new HashMap(); // Indexed by [item] private char [] equipment = new char[3]; // Indexed by [hopper] private int [] ores = new int[4]; // Indexed by [item][hopper] private int [][] oreQuantities = new int[3][4]; private int [][] gaugeSettings = new int[3][4]; public FactoryData( final DwarfNumberTranslator digits ) { // Get a Dwarf Number Translator this.digits = digits; // Make maps from dwarf word rune to itemId and vice versa for ( int i = 0; i < DwarfFactoryRequest.ITEMS.length; ++i ) { int itemId = DwarfFactoryRequest.ITEMS[i]; String setting = "lastDwarfFactoryItem" + itemId; String value = Preferences.getString( setting ); if ( value.length() == 1 ) { Character rune = new Character( value.charAt( 0 ) ); Integer id = IntegerPool.get( itemId ); this.itemMap.put( rune, id ); this.runeMap.put( id, rune ); } } // Get the 3 pieces of equipment this.equipment[ HAT ] = this.findRune( ItemPool.MINERS_HELMET ); this.equipment[ PANTS ] = this.findRune( ItemPool.MINERS_PANTS ); this.equipment[ WEAPON ] = this.findRune( ItemPool.MATTOCK ); // Get the 4 ores in hopper order this.ores[0] = this.findItem( Preferences.getString( "lastDwarfHopper1" ) ); this.ores[1] = this.findItem( Preferences.getString( "lastDwarfHopper2" ) ); this.ores[2] = this.findItem( Preferences.getString( "lastDwarfHopper3" ) ); this.ores[3] = this.findItem( Preferences.getString( "lastDwarfHopper4" ) ); // Translate the unlaminated items into ore quantities this.getOreQuantities(); // Translate the laminated items into gauge settings this.getGaugeSettings(); } public String getHopperOre( final int hopper ) { if ( hopper < 0 || hopper > 3 ) { return ""; } return ItemDatabase.getItemName( this.ores[ hopper ] ); } public void setInventoryCounts() { // Get the current count of ore in inventory for ( int i = 0; i < 4; ++i ) { int itemId = this.ores[ i ]; if ( itemId != -1 ) { DwarfFactoryRequest.inventoryCount[i ] = InventoryManager.getCount( itemId ); } } } public String getHopperOreCounts( final int hopper ) { if ( hopper < 0 || hopper > 3 ) { return ""; } String hcount = String.valueOf( DwarfFactoryRequest.hopperCount[ hopper ] ); String icount = String.valueOf( DwarfFactoryRequest.inventoryCount[ hopper ] ); return hcount + " (H)/" + icount + " (I)"; } public int getOreOnHand( final int hopper ) { int hcount = DwarfFactoryRequest.hopperCount[ hopper ]; int icount = DwarfFactoryRequest.inventoryCount[ hopper ]; return hcount + icount; } public AdventureResult getOre( final int item, final int hopper ) { if ( item < 0 || item > 2 || hopper < 0 || hopper > 3 ) { return null; } int itemId = this.ores[ hopper ]; if ( itemId == -1 ) { return null; } int count = this.oreQuantities[ item ][ hopper ]; return ItemPool.get( itemId, count ); } public int getOreQuantity( final int item, final int hopper ) { if ( item < 0 || item > 2 || hopper < 0 || hopper > 3 ) { return 0; } return this.oreQuantities[ item ][ hopper ]; } public String getOreQuantityString( final int item, final int hopper ) { int needed = this.getOreQuantity( item, hopper ); int have = this.getOreOnHand( hopper ); String color = ( have >= needed ) ? "green" : "red"; return "<font color=" + color + ">" + needed + "</font>"; } private void setOreQuantity( final int item, final int hopper, final int value ) { this.oreQuantities[ item ][ hopper ] = value; } public int getGaugeSetting( final int item, final int hopper ) { if ( item < 0 || item > 2 || hopper < 0 || hopper > 3 ) { return -1; } return this.gaugeSettings[ item ][ hopper ]; } private void setGaugeSetting( final int item, final int hopper, final int value ) { this.gaugeSettings[ item ][ hopper ] = value; } private int findItem( final String rune ) { if ( rune.length() != 1 ) { return -1; } return this.findItem( rune.charAt(0) ); } private int findItem( final char rune ) { Integer val = (Integer) this.itemMap.get( new Character( rune ) ); return val == null ? -1 : val.intValue(); } private char findRune( final int itemId ) { Character val = (Character) this.runeMap.get( IntegerPool.get( itemId ) ); return val == null ? 0 : val.charValue(); } private int findHopper( final char rune ) { int item = this.findItem( rune ); if ( item == -1 ) { return -1; } for ( int i = 0; i < 4; ++i ) { if ( item == this.ores[i] ) { return i; } } return -1; } private int findEquipment( final char rune ) { for ( int i = 0; i < 3; ++i ) { if ( rune == this.equipment[i] ) { return i; } } return -1; } private void getGaugeSettings() { this.getGaugeSetting( Preferences.getString( "lastDwarfOfficeItem3208" ) ); this.getGaugeSetting( Preferences.getString( "lastDwarfOfficeItem3209" ) ); this.getGaugeSetting( Preferences.getString( "lastDwarfOfficeItem3210" ) ); this.getGaugeSetting( Preferences.getString( "lastDwarfOfficeItem3211" ) ); } private void getGaugeSetting( final String setting ) { // lastDwarfOfficeItem3208=B,HGIG,MGDE,PJD // lastDwarfOfficeItem3209=O,HGAA,MGAG,PGEA // lastDwarfOfficeItem3210=J,HFJ,MGED,PGAG // lastDwarfOfficeItem3211=Q,HGE,MGGI,PGG String[] splits = setting.split( "," ); if ( splits.length != 4 ) { return; } int hopper = this.findHopper( splits[0].charAt(0) ); if ( hopper < 0 ) { return; } for ( int i = 1; i <= 3; ++i ) { int item = this.findEquipment( splits[i].charAt(0) ); if ( item < 0 ) { continue; } int number = this.digits.parseNumber( splits[i].substring(1) ); this.setGaugeSetting( item, hopper, number ); } } private void getOreQuantities( ) { this.getOreQuantity( Preferences.getString( "lastDwarfOfficeItem3212" ) ); this.getOreQuantity( Preferences.getString( "lastDwarfOfficeItem3213" ) ); this.getOreQuantity( Preferences.getString( "lastDwarfOfficeItem3214" ) ); } private void getOreQuantity( final String setting ) { // lastDwarfOfficeItem3212=H,QEG,BFD,OJI,JED // lastDwarfOfficeItem3213=M,OGD,BGD,QJI,JGJ // lastDwarfOfficeItem3214=P,JFJ,OJE,BGA,QEG String[] splits = setting.split( "," ); if ( splits.length != 5 ) { return; } int item = this.findEquipment( splits[0].charAt(0) ); if ( item < 0 ) { return; } for ( int i = 1; i <= 4; ++i ) { int hopper = this.findHopper( splits[i].charAt(0) ); if ( hopper < 0 ) { continue; } int number = this.digits.parseNumber( splits[i].substring(1) ); this.setOreQuantity( item, hopper, number ); } } } }