/** * 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.session; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.utilities.StringUtilities; public class DadManager { // You shake your head and look above the tank, at the window into // space. <Clue 1> forms <Clue 2> in the darkness, each more <Clue 3> // than the last. <Clue 4> <Clue 5>, <Clue 6> revealing <Clue 7>- // dimensional monstrosities. // No. Look again. There is nothing. <Is/Are> your <Clue 8> betraying // you? As if on cue, <Clue 9>-sided triangles materialize and then // disappear. So impossible that your <Clue 10> throbs. private static final Pattern CLUE_PATTERN = Pattern.compile( "You shake your head and look above the tank, at the window into space. *([^ ]+) forms ([^ ]+) in the darkness, each more ([^ ]+) than the last. *(?:The )?([^ ]+) ([^,]+), ([^ ]+) revealing (\\d+)-dimensional monstrosities..*?No. *Look again. *There is nothing. *(?:Is|Are) your (.+?) betraying you\\? *As if on cue, (\\d+)-sided triangles materialize and then disappear. *So impossible that your ([^ ]+) throbs.", Pattern.DOTALL ); public enum Element { NONE, HOT, COLD, STENCH, SPOOKY, SLEAZE, PHYSICAL } private static final Object [][] ELEMENTS = { // Starting with the third element, any number of skills can be listed, one per element { Element.NONE, "none", "" }, { Element.HOT, "hot", "Awesome Balls of Fire", "Volcanometeor Showeruption" }, { Element.COLD, "cold", "Snowclone" }, { Element.STENCH, "stench", "Eggsplosion" }, { Element.SPOOKY, "spooky", "Raise Backup Dancer" }, { Element.SLEAZE, "sleaze", "Grease Lightning" }, { Element.PHYSICAL, "physical", "Toynado", "Shrap" }, }; private static Object[] search( Element element ) { for ( int i = 0; i < ELEMENTS.length; ++i ) { Object [] row = ELEMENTS[ i ]; if ( row[ 0 ] == element ) { return row; } } return null; } public static String elementToName( Element element ) { Object [] row = DadManager.search( element ); return row == null ? "Unknown" : (String) row[ 1 ]; } public static String elementToSpell( Element element ) { Object [] row = DadManager.search( element ); if ( row == null ) { return "Unknown"; } for ( int i = 2; i < row.length; i++ ) { String spell = (String) row[ i ]; if ( KoLCharacter.hasSkill( spell ) ) { return spell; } } return "Unknown"; } public static Element intToElement( int index ) { return ( index < 0 || index > ELEMENTS.length ) ? Element.NONE : (Element)ELEMENTS[ index ][0]; } private static final Object [][] WORD1 = { { "chaotic", Element.HOT }, { "rigid", Element.COLD }, { "rotting", Element.STENCH }, { "horrifying", Element.SPOOKY }, { "slimy", Element.SLEAZE }, { "pulpy", Element.PHYSICAL }, }; private static final Object [][] WORD2 = { { "skitter", Element.HOT }, { "shamble", Element.COLD }, { "ooze", Element.STENCH }, { "float", Element.SPOOKY }, { "slither", Element.SLEAZE }, { "swim", Element.PHYSICAL }, }; private static final Object [][] WORD3 = { { "terrible", Element.HOT }, { "awful", Element.COLD }, { "putrescent", Element.STENCH }, { "frightening", Element.SPOOKY }, { "bloated", Element.SLEAZE }, { "curious", Element.PHYSICAL }, }; private static final Object [][] WORD4 = { { "blackness", Element.HOT }, { "space", Element.COLD }, { "void", Element.STENCH }, { "darkness", Element.SPOOKY }, { "emptiness", Element.SLEAZE }, { "portal", Element.PHYSICAL }, }; private static final Object [][] WORD5 = { { "warps", Element.HOT }, { "shifts", Element.COLD }, { "shimmers", Element.STENCH }, { "shakes", Element.SPOOKY }, { "wobbles", Element.SLEAZE }, { "cracks open", Element.PHYSICAL }, }; private static final String [] WORD8 = { "brain", "mind", "reason", "sanity", "grasp on reality", "sixth sense", "eyes", "thoughts", "senses", "memories", "fears", }; private static final String [] WORD10 = { "spleen", "stomach", "skull", "forehead", "brain", "mind", "heart", "throat", "chest", "head", }; private static Object search( String key, Object [][] table ) { for ( int i = 0; i < table.length; ++i ) { Object [] row = table[ i ]; if ( key.equals( row[ 0 ] ) ) { return row[ 1 ]; } } return null; } private static Element wordToElement( String key, Object [][] table ) { Object element = DadManager.search( key.toLowerCase(), table ); return element == null ? Element.NONE : (Element)element; } private static int wordToInteger( String key, String [] table ) { for ( int i = 0; i < table.length; ++i ) { if ( key.equals( table[ i ] ) ) { return i; } } return -1; } // cannonfire40 wrote this forum post: // http://forums.kingdomofloathing.com/vb/showpost.php?p=4439411&postcount=369 // // Rounds 1-3 use simple keyword to element matching. // // Rounds 4-5 use keyword to element matching as well, but their clues // are in reverse order. // // Rounds 6-7 consist of either "Suddenly or slowly", and a number. // That number is of the form 2^x+2^y, where x and y are integers // between 0 and 5 inclusive. X and y are the elements in the order // 0= Hot, 1=Cold, 2=Stench, 3=Spooky, 4=Sleaze, and 5=Physical. // If the word is slowly, the element for the bigger digit is in // round 6, and the smaller number in round 7. Order is reversed // for the two rounds if the word is suddenly. If the digits are // equal, order does not matter because the same element appears in // both rounds. (There is an easier way to explain this using binary, // but I won't get into that here.) // // Round 8 uses a list of 11 terms, with correlate to numeric values // ranging from 2-12. You subtract the value of the element in round 1 // (using the same ordering as before but with 1-6 representing the // elements instead of 0-5) and the remaining value represents the // elemental weakness. // // Round 9 sums the values of the elemental weaknesses in rounds 2 // through 5, adds four, subtracts the number given as a clue, and then // the remaining value represents the elemental weakness for the round. // // Round 10 uses a list of 10 words, each with a value of 1-10. If the // value of the word is less than 10, the elemental weakness is the // same as the round number of the value of the word. If the value is // 10, the weakness corresponds to an element that has yet to appear in // the fight. // // Round 11 does not have a clue, but that doesn't matter, because // using the appropriate hobopolis 120 mp spell wins the fight on round // 10 every time. (Confirmed). public static Element [] ElementalWeakness = new Element[ 11 ]; public static Element weakness( final int round ) { return ( round < 1 || round > 10 ) ? Element.NONE: ElementalWeakness[ round ]; } private static Element unusedElement() { boolean hot = false; boolean cold = false; boolean stench = false; boolean spooky = false; boolean sleaze = false; boolean physical = false; for ( int i = 1; i < 10; ++i ) { switch ( ElementalWeakness[ i ] ) { case HOT: hot = true; break; case COLD: cold = true; break; case STENCH: stench = true; break; case SPOOKY: spooky = true; break; case SLEAZE: sleaze = true; break; case PHYSICAL: physical = true; break; } } return !hot ? Element.HOT : !cold ? Element.COLD : !stench ? Element.STENCH : !spooky ? Element.SPOOKY : !sleaze ? Element.SLEAZE : !physical ? Element.PHYSICAL : Element.NONE; } public static boolean solve( final String responseText ) { // Give us a response text, at least! if ( responseText == null ) { return false; } // Extract the clues from the text Matcher matcher = CLUE_PATTERN.matcher( responseText ); if ( !matcher.find() ) { return false; } // Initialize the array of elemental weaknesses for ( int i = 0; i < ElementalWeakness.length; ++i ) { ElementalWeakness[ i ] = Element.NONE; } // Now parse the clues and fill in the weaknesses ElementalWeakness[ 1 ] = DadManager.wordToElement( matcher.group(1), DadManager.WORD1 ); ElementalWeakness[ 2 ] = DadManager.wordToElement( matcher.group(2), DadManager.WORD2 ); ElementalWeakness[ 3 ] = DadManager.wordToElement( matcher.group(3), DadManager.WORD3 ); ElementalWeakness[ 4 ] = DadManager.wordToElement( matcher.group(5), DadManager.WORD5 ); ElementalWeakness[ 5 ] = DadManager.wordToElement( matcher.group(4), DadManager.WORD4 ); int word7 = StringUtilities.parseInt( matcher.group(7) ); Element [] elements = new Element[6]; int index = 0; if ( ( word7 & 32 ) != 0 ) { elements[index++] = Element.PHYSICAL; } if ( ( word7 & 16 ) != 0 ) { elements[index++] = Element.SLEAZE; } if ( ( word7 & 8 ) != 0 ) { elements[index++] = Element.SPOOKY; } if ( ( word7 & 4 ) != 0 ) { elements[index++] = Element.STENCH; } if ( ( word7 & 2 ) != 0 ) { elements[index++] = Element.COLD; } if ( ( word7 & 1 ) != 0 ) { elements[index++] = Element.HOT; } // If there was only one bit set, the same element was added in twice // If that element is PHYSICAL, none are set since we didn't check for bit 64 if ( index == 0 ) { elements[0] = elements[1] = Element.PHYSICAL; } if ( index == 1 ) { elements[0] = elements[1] = DadManager.intToElement( elements[0].ordinal() - 1 ); } boolean reverse = matcher.group(6).toLowerCase().equals( "suddenly" ); ElementalWeakness[ 6 ] = reverse ? elements[ 1 ] : elements[ 0 ]; ElementalWeakness[ 7 ] = reverse ? elements[ 0 ] : elements[ 1 ]; int word8 = DadManager.wordToInteger( matcher.group(8), DadManager.WORD8 ) + 2; int val8 = word8 - ElementalWeakness[1].ordinal(); ElementalWeakness[ 8 ] = DadManager.intToElement( val8 ); int word9 = StringUtilities.parseInt( matcher.group(9) ); int val9 = ElementalWeakness[2].ordinal() + ElementalWeakness[3].ordinal() + ElementalWeakness[4].ordinal() + ElementalWeakness[5].ordinal() + 4 - word9; ElementalWeakness[ 9 ] = DadManager.intToElement( val9 ); int word10 = DadManager.wordToInteger( matcher.group(10), DadManager.WORD10 ) + 1; ElementalWeakness[ 10 ] = word10 < 1 ? Element.NONE : word10 < 10 ? ElementalWeakness[ word10 ] : DadManager.unusedElement(); return true; } }