/** * 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.ArrayList; import java.util.List; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.FamiliarData; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLConstants.MafiaState; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.RequestEditorKit; import net.sourceforge.kolmafia.RequestLogger; import net.sourceforge.kolmafia.RequestThread; import net.sourceforge.kolmafia.objectpool.EffectPool; import net.sourceforge.kolmafia.objectpool.IntegerPool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.persistence.EquipmentDatabase; import net.sourceforge.kolmafia.persistence.ItemFinder; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.EquipmentRequest; import net.sourceforge.kolmafia.request.GenericRequest; import net.sourceforge.kolmafia.request.RabbitHoleRequest; import net.sourceforge.kolmafia.request.RelayRequest; import net.sourceforge.kolmafia.request.UseItemRequest; import net.sourceforge.kolmafia.utilities.FileUtilities; import net.sourceforge.kolmafia.utilities.StringUtilities; public abstract class RabbitHoleManager { public static final Pattern HAT_CLEANER_PATTERN = Pattern.compile( "\\s" ); public static class Hat { private final int length; private final String effect; private final String modifier; public Hat( final int length, final String effect, final String modifier ) { this.length = length; this.effect = effect; this.modifier = modifier; } public int getLength() { return this.length; } public String getEffect() { return this.effect; } public String getModifier() { return this.modifier; } } public final static Hat [] HAT_DATA = { new Hat( 4, "Assaulted with Pepper", "Monster Level +20" ), new Hat( 6, "Three Days Slow", "Familiar Experience +3" ), new Hat( 7, "Cat-Alyzed", "Moxie +10" ), new Hat( 8, "Anytwo Five Elevenis?", "Muscle +10" ), new Hat( 9, "Coated Arms", "Weapon Damage +15" ), new Hat( 10, "Smoky Third Eye", "Mysticality +10" ), new Hat( 11, "Full Bottle in front of Me", "Spell Damage +30%" ), new Hat( 12, "Thick-Skinned", "Maximum HP +50" ), new Hat( 13, "20-20 Second Sight", "Maximum MP +25" ), new Hat( 14, "Slimy Hands", "+10 Sleaze Damage" ), new Hat( 15, "Bottle in front of Me", "Spell Damage +15" ), new Hat( 16, "Fan-Cooled", "+10 Cold Damage" ), new Hat( 17, "Ginger Snapped", "+10 Spooky Damage" ), new Hat( 18, "Egg on your Face", "+10 Stench Damage" ), new Hat( 19, "Pockets of Fire", "+10 Hot Damage" ), new Hat( 20, "Weapon of Mass Destruction", "Weapon Damage +30%" ), new Hat( 21, "Orchid Blood", "Regenerate 5-10 MP per Adventure" ), new Hat( 22, "Dances with Tweedles", "+40% Meat from Monsters" ), new Hat( 23, "Patched In", "Mysticality +20%" ), new Hat( 24, "You Can Really Taste the Dormouse", "+5 to Familiar Weight" ), new Hat( 25, "Turtle Titters", "+3 Stat Gains from Fights" ), new Hat( 26, "Cat Class, Cat Style", "Moxie +20%" ), new Hat( 27, "Surreally Buff", "Muscle +20%" ), new Hat( 28, "Quadrilled", "+20% Items from Monsters" ), new Hat( 29, "Coming Up Roses", "Regenerate 10-20 MP per Adventure" ), new Hat( 30, "Oleaginous Soles", "+40% Combat Initiative" ), new Hat( 31, "Oleaginous Soles", "+40% Combat Initiative" ), }; private static final String[] IMAGES = new String[] { "blanktrans.gif", "chess_pww.gif", "chess_rww.gif", "chess_nww.gif", "chess_bww.gif", "chess_kww.gif", "chess_qww.gif", "chess_pbw.gif", "chess_rbw.gif", "chess_nbw.gif", "chess_bbw.gif", "chess_kbw.gif", "chess_qbw.gif", "chess_pwb.gif", "chess_rwb.gif", "chess_nwb.gif", "chess_bwb.gif", "chess_kwb.gif", "chess_qwb.gif", "chess_pbb.gif", "chess_rbb.gif", "chess_nbb.gif", "chess_bbb.gif", "chess_kbb.gif", "chess_qbb.gif", }; private static final String[] TITLES = new String[] { "blank square", "White Pawn", "White Rook", "White Knight", "White Bishop", "White King", "White Queen", "Black Pawn", "Black Rook", "Black Knight", "Black Bishop", "Black King", "Black Queen", }; private static final String SYMBOLS = "\u2659\u2656\u2658\u2657\u2654\u2655" + "\u265F\u265C\u265E\u265D\u265A\u265B"; static { String base = KoLmafia.imageServerPath() + "otherimages/chess/"; for ( int i = 0; i < RabbitHoleManager.IMAGES.length; ++i ) { FileUtilities.downloadImage( base + RabbitHoleManager.IMAGES[i] ); } } // The chessboard is a table of cells, each labeled with the piece that // is on it. Your piece is labeled "White Knight" (for example) and a // target pieces is "Black Pawn". When you move to a piece, it changes // to "White Pawn" and your valid moves become that of the new piece. // // Valid targets cells are wrapped in HTML <a></a> tags with the URL to // choice.php to move there. // // Rows and columns are numbered from 0 to 7. A choice to capture a // piece adds a field: "xy=6,4" goes to the 7th column in the 5th row. // // Background colors: // // background-color: #fff; white // background-color: #979797; black // // Images: // // chess/blanktrans.gif blank square // // chess/chess_<piece><side><square>.gif // // <piece> = p pawn // <piece> = r rook // <piece> = n knight // <piece> = b bishop // <piece> = k king // <piece> = q queen // // <side> = b black // <side> = w white // // <square> = b black // <square> = w white private static final Pattern SQUARE_PATTERN = Pattern.compile("<td.*?background-color: #(.*?);.*?title=\"(.*?)\".*?otherimages/chess/(blanktrans|(chess_(.)(.)(.)))\\.gif.*?</td>", Pattern.DOTALL ); private static class Square { public final static int UNKNOWN = 0; public final static int WHITE = 1; public final static int BLACK = 2; public final static int EMPTY = 0; public final static int PAWN = 1; public final static int ROOK = 2; public final static int KNIGHT = 3; public final static int BISHOP = 4; public final static int KING = 5; public final static int QUEEN = 6; private final int color; private final int piece; private final int side; private static final Square WHITE_SQUARE = new Square( WHITE ); private static final Square BLACK_SQUARE = new Square( BLACK ); public Square( final Matcher matcher ) { String colorString = matcher.group( 1 ); if ( colorString.equals( "fff" ) ) { this.color = WHITE; } else { this.color = BLACK; } if ( matcher.group( 4 ) == null ) { this.piece = EMPTY; this.side = UNKNOWN; } else { String pieceString = matcher.group( 5 ); if ( pieceString.equals( "p" ) ) { this.piece = PAWN; } else if ( pieceString.equals( "r" ) ) { this.piece = ROOK; } else if ( pieceString.equals( "n" ) ) { this.piece = KNIGHT; } else if ( pieceString.equals( "b" ) ) { this.piece = BISHOP; } else if ( pieceString.equals( "k" ) ) { this.piece = KING; } else if ( pieceString.equals( "q" ) ) { this.piece = QUEEN; } else { this.piece = UNKNOWN; } String sideString = matcher.group( 6 ); if ( sideString.equals( "w" ) ) { this.side = WHITE; } else if ( sideString.equals( "b" ) ) { this.side = BLACK; } else { this.side = UNKNOWN; } } } public Square( final int color ) { this( color, EMPTY, UNKNOWN ); } public Square( final int color, final int piece, final int side ) { this.color = color; this.piece = piece; this.side = side; } public Square( final int color, final String pc, final String sc ) { this.color = color; this.piece = Square.codeToPiece( pc ); this.side = Square.codeToSide( sc ); } public static Square getSquare( final int color ) { return color == WHITE ? WHITE_SQUARE : BLACK_SQUARE; } public String getTitle() { int index = this.piece; if ( index > 0 ) { if (this.side == BLACK ) { index += 6; } } return TITLES[ index ]; } public String getSymbol() { int index = this.piece; if ( index <= 0 ) { return " "; } if (this.side == BLACK ) { index += 6; } return SYMBOLS.substring( index - 1, index ); } public String getColorString() { return this.color == WHITE ? "White Square" : "Black Square"; } public String getImage() { int index = this.piece; if ( index > 0 ) { if (this.color == BLACK ) { index += 12; } if (this.side == BLACK ) { index += 6; } } return IMAGES[ index ]; } public int getColor() { return this.color; } public int getSide() { return this.side; } public int getPiece() { return this.piece; } public boolean isPiece() { return this.piece != Square.EMPTY; } public Square convert() { return new Square( this.color, this.piece, this.side == WHITE ? BLACK : WHITE ); } private static String whiteSquare = "style=\"width: 60px; height: 60px; text-align: center; background-color: #ffffff;\""; private static String blackSquare = "style=\"width: 60px; height: 60px; text-align: center; background-color: #979797;\""; private static String whiteCompact = "style=\"width: 30px; height: 30px; text-align: center; background-color: #ffffff; font-size: 30px\""; private static String blackCompact = "style=\"width: 30px; height: 30px; text-align: center; background-color: #DDDDDD; font-size: 30px\""; public void appendHTML( final StringBuffer buffer ) { if ( Preferences.getBoolean( "compactChessboard" ) ) { buffer.append( "<td " ); buffer.append( this.color == Square.WHITE ? whiteCompact : blackCompact ); buffer.append( ">" ); buffer.append( this.getSymbol() ); buffer.append( "</td>" ); } else { buffer.append( "<td " ); buffer.append( this.color == Square.WHITE ? whiteSquare : blackSquare ); buffer.append( ">" ); buffer.append( "<img src=\"" ); buffer.append( KoLmafia.imageServerPath() ); buffer.append( "otherimages/chess/" ); buffer.append( this.getImage() ); buffer.append( "\" height=50 width=50/>" ); buffer.append( "</td>" ); } } @Override public String toString() { StringBuilder buffer = new StringBuilder(); if ( this.piece == EMPTY ) { buffer.append( "Empty" ); } else { buffer.append( this.getTitle() ); buffer.append( " on a" ); } buffer.append( " " ); buffer.append( this.getColorString() ); return buffer.toString(); } public String pieceCode() { switch ( this.piece ) { case PAWN: return "P"; case ROOK: return "R"; case KNIGHT: return "N"; case BISHOP: return "B"; case KING: return "K"; case QUEEN: return "Q"; } return ""; } public static int codeToPiece( final String code ) { switch ( code.charAt( 0 ) ) { case 'P': case 'p': return PAWN; case 'R': case 'r': return ROOK; case 'N': case 'n': return KNIGHT; case 'B': case 'b': return BISHOP; case 'K': case 'k': return KING; case 'Q': case 'q': return QUEEN; } return UNKNOWN; } public String sideCode() { switch ( this.side ) { case BLACK: return "B"; case WHITE: return "W"; } return ""; } public static int codeToSide( final String code ) { switch ( code.charAt( 0 ) ) { case 'B': case 'b': return BLACK; case 'W': case 'w': return WHITE; } return UNKNOWN; } public static String coords( final int square ) { int row = square / 8; int col = square % 8; return Character.toString( (char)('a' + col) ) + String.valueOf( row + 1 ); } public String notation( final int square ) { return this.sideCode() + this.pieceCode() + Square.coords( square ); } } private static class Board implements Cloneable { private Square[] board; private int current; private int pieces; public Board() { this.board = new Square[ 64 ]; this.current = -1; this.pieces = 0; // Load up with empty white and black squares for ( int i = 0; i < 64; ++i ) { board[ i ] = Square.getSquare( Board.color( i ) ); } } public static int color( final int square ) { int row = square / 8; int col = square % 8; return (row + col ) % 2 == 0 ? Square.WHITE : Square.BLACK; } private static final Pattern CONFIG_PATTERN = Pattern.compile("([bwBW])([prnbkqPRNBKQ])([abcdefghABCDEFGH])([12345678])" ); public Board( final String config ) { // Create an empty board this(); // Split the config string into squares Matcher m = Board.CONFIG_PATTERN.matcher( config ); while ( m.find() ) { // Find the square String cs = m.group( 3 ); String rs = m.group( 4 ); int square = Board.square( rs, cs ); int color = Board.color( square ); // Find the piece String sc = m.group( 1 ); String pc = m.group( 2 ); Square piece = new Square( color, pc, sc ); // Place the piece on the square this.board[ square ] = piece; this.pieces++; if ( piece.getSide() == Square.WHITE ) { this.current = square; } } } private Board( Board board ) { this.board = (Square [])board.board.clone(); this.current = board.current; this.pieces = board.pieces; } public String config() { StringBuilder buffer = new StringBuilder(); boolean first = true; for ( int i = 0; i < 64; ++i ) { Square piece = this.board[ i ]; if ( piece.isPiece() ) { if ( first ) { first = false; } else { buffer.append( "," ); } buffer.append( piece.notation( i ) ); } } return buffer.toString(); } @Override public Object clone() { return new Board( this ); } public int getCurrent() { return this.current; } public int getPieces() { return this.pieces; } public static int square( final int row, final int col ) { return ( row * 8 ) + col; } public static int square( final String rs, final String cs ) { int col = cs.charAt( 0 ) - 'a'; int row = rs.charAt( 0 ) - '1'; return Board.square( row, col ); } public Square get( final int index ) { Square piece = this.board[ index ]; return piece != null ? piece : new Square( Square.BLACK ); } public Square add( final int index, final Square square ) { Square old = this.board[ index ]; this.board[ index ] = square; if ( square.isPiece() ) { this.pieces++; if ( square.getSide() == Square.WHITE ) { this.current = index; } } return old; } public Square remove( final int index ) { Square square = this.board[ index ]; if ( square.isPiece() ) { Square empty = Square.getSquare( square.getColor() ); this.board[ index ] = empty; this.pieces--; } return square; } public void set( final int index, final Square square ) { this.remove( index ); this.add( index, square ); } public Square move( final int index1, final int index2 ) { // Remove the piece from current location Square old = remove( index1 ); // Remove the piece from destination location Square captured = remove( index2 ); // If it was a capture, we take over piece type if ( captured.isPiece() ) { this.add( index2, captured.convert() ); } // Otherwise, we simply move into the square else { this.add( index2, old ); } // Return the former contents of the square return captured; } public int getWinningMove() { if ( this.current < 0 ) { return -1; } Square square = this.board[ this.current ]; if ( !square.isPiece() ) { return -1; } int row = this.current / 8; int col = this.current % 8; switch ( square.getPiece() ) { case Square.PAWN: case Square.KING: if ( row != 1 ) { return -1; } // Fall through case Square.ROOK: case Square.QUEEN: return col; case Square.KNIGHT: if ( row == 1 ) { return col < 2 ? col + 2 : col - 2; } if ( row == 2 ) { return col < 1 ? col + 1 : col - 1; } return -1; case Square.BISHOP: if ( row + col <= 7 ) { return row + col; } if ( col - row >= 0 ) { return col - row; } return -1; } return -1; } public Integer [] getMoves() { if ( this.current < 0 ) { return new Integer[0]; } Square square = this.board[ this.current ]; if ( !square.isPiece() ) { return new Integer[0]; } ArrayList list = new ArrayList(); // Depending on type of piece, generate all moves // available on current board configuration int row = this.current / 8; int col = this.current % 8; switch ( square.getPiece() ) { case Square.PAWN: // Pawns capture diagonally forward one row this.addMove( list, row - 1, col - 1 ); this.addMove( list, row - 1, col + 1 ); break; case Square.KING: // Kings move one in any direction this.addMove( list, row - 1, col - 1 ); this.addMove( list, row - 1, col ); this.addMove( list, row - 1, col + 1 ); this.addMove( list, row, col - 1 ); this.addMove( list, row, col + 1 ); this.addMove( list, row + 1, col - 1 ); this.addMove( list, row + 1, col ); this.addMove( list, row + 1, col + 1 ); break; case Square.KNIGHT: // Knights wiggle this.addMove( list, row - 2, col - 1 ); this.addMove( list, row - 2, col + 1 ); this.addMove( list, row - 1, col + 2 ); this.addMove( list, row + 1, col + 2 ); this.addMove( list, row + 2, col + 1 ); this.addMove( list, row + 2, col - 1 ); this.addMove( list, row + 1, col - 2 ); this.addMove( list, row - 1, col - 2 ); break; case Square.ROOK: this.addRookMoves( list, row, col ); break; case Square.BISHOP: this.addBishopMoves( list, row, col ); break; case Square.QUEEN: this.addRookMoves( list, row, col ); this.addBishopMoves( list, row, col ); break; } // Convert the list into an array Integer [] array = new Integer[ list.size() ]; return (Integer []) list.toArray( array ); } private void addRookMoves( final ArrayList list, final int row, final int col ) { // Go West. Quit when you hit a piece for ( int i = col - 1; i >= 0; --i ) { if ( this.addMove( list, row, i ) ) { break; } } // Go East. Quit when you hit a piece for ( int i = col + 1; i <= 7; ++i ) { if ( this.addMove( list, row, i ) ) { break; } } // Go North. Quit when you hit a piece for ( int i = row - 1; i >= 0; --i ) { if ( this.addMove( list, i, col ) ) { break; } } // Go South. Quit when you hit a piece for ( int i = row + 1; i <= 7; ++i ) { if ( this.addMove( list, i, col ) ) { break; } } } private void addBishopMoves( final ArrayList list, final int row, final int col ) { // Go Northwest. Quit when you hit a piece for ( int irow = row - 1, icol = col - 1; irow >= 0 && icol >= 0; --irow, --icol ) { if ( this.addMove( list, irow, icol ) ) { break; } } // Go Northeast. Quit when you hit a piece for ( int irow = row - 1, icol = col + 1; irow >= 0 && icol <=7; --irow, ++icol ) { if ( this.addMove( list, irow, icol ) ) { break; } } // Go Southwest. Quit when you hit a piece for ( int irow = row + 1, icol = col - 1; irow <= 7 && icol >= 0; ++irow, --icol ) { if ( this.addMove( list, irow, icol ) ) { break; } } // Go Southeast. Quit when you hit a piece for ( int irow = row + 1, icol = col + 1; irow <= 7 && icol <= 7; ++irow, ++icol ) { if ( this.addMove( list, irow, icol ) ) { break; } } } private boolean addMove( final ArrayList list, final int row, final int col ) { // If the proposed move is off the board, fail if ( row < 0 || row > 7 || col < 0 || col > 7 ) { return false; } // If the proposed move is not a capture, fail int square = ( row * 8 ) + col; if ( !this.board[ square ].isPiece() ) { return false; } // Otherwise, tally the move and succeed list.add( IntegerPool.get( square ) ); return true; } public void appendHTML( final StringBuffer buffer ) { buffer.append( "<table cols=9>" ); buffer.append( "<tr>" ); buffer.append( "<td></td><" ); for ( int i = 0; i < 8; i++ ) { buffer.append( "<td><b>" ); buffer.append( (char)( 'a' + i ) ); buffer.append( "</b></td>" ); } buffer.append( "</tr>" ); for ( int i = 0; i < 64; i++ ) { Square square = this.board[ i ]; if ( i % 8 == 0 ) { buffer.append( "<tr>" ); buffer.append( "<td><b>" ); buffer.append( String.valueOf( i / 8 + 1) ); buffer.append( "</b></td>" ); } square.appendHTML( buffer ); if ( i % 8 == 7 ) { buffer.append( "</tr>" ); } } buffer.append( "</table>" ); } } private static String testData = null; private static Board board; private static int moves; public static final void parseChessPuzzle( final String responseText ) { RabbitHoleManager.parseChessPuzzle( responseText, true ); if ( RabbitHoleManager.board != null ) { String message = "Board: " + RabbitHoleManager.board.config(); RequestLogger.updateSessionLog( message ); } } private static final void parseChessPuzzle( final String responseText, final boolean initialVisit ) { if ( responseText == null ) { return; } RabbitHoleManager.board = new Board(); if ( initialVisit ) { RabbitHoleManager.moves = 0; } else { ++RabbitHoleManager.moves; } Matcher matcher = RabbitHoleManager.SQUARE_PATTERN.matcher( responseText ); int index = 0; while ( matcher.find() ) { if ( index < 64 ) { board.add( index, new Square( matcher ) ); } index++; } if ( index != 0 && index != 64 ) { KoLmafia.updateDisplay( "What kind of a chessboard is that? I found " + index + " squares!" ); RabbitHoleManager.board = null; return; } if ( initialVisit ) { Preferences.setString( "lastChessboard", RabbitHoleManager.board.config() ); } } private static final Pattern MOVE_PATTERN = Pattern.compile("xy=((\\d+)(?:%2C|,)(\\d+))"); public static final void parseChessMove( final String urlString, final String responseText ) { // Parse the destination square out of the URL Matcher matcher = MOVE_PATTERN.matcher( urlString ); if ( !matcher.find() ) { return; } int col = StringUtilities.parseInt( matcher.group( 2 ) ); int row = StringUtilities.parseInt( matcher.group( 3 ) ); int moveSquare = Board.square( row, col ); // Save the original piece and the one it captures int square = RabbitHoleManager.board.getCurrent(); Square piece = RabbitHoleManager.board.get( square ); Square newPiece = RabbitHoleManager.board.get( moveSquare ); // Parse the new board RabbitHoleManager.parseChessPuzzle( responseText, false ); // Find the new piece int newSquare = RabbitHoleManager.board.getCurrent(); // Assume we are capturing String action = "x"; // If we completed the puzzle, there will no longer be a board if ( RabbitHoleManager.board.getPieces() == 0 ) { // We simply moved newSquare = moveSquare; action = "-"; // Verify that KoL recognizes that we cleared the board if ( responseText.contains( "queen cookie" ) ) { Preferences.increment( "chessboardsCleared", 1, 50, false ); } } // Did we actually move where we expected? else if ( newSquare != moveSquare ) { // No. Bogus move return; } // Log the move in chess notation String message = RabbitHoleManager.moves + ": " + piece.notation( square ) + action + newPiece.notation( newSquare ); RequestLogger.printLine( message ); RequestLogger.updateSessionLog( message ); } // CLI command support public static final void reset() { RabbitHoleManager.board = null; RabbitHoleManager.moves = 0; } public static final void load() { if ( RabbitHoleManager.board == null ) { RabbitHoleManager.parseChessPuzzle( RabbitHoleManager.testData ); } if ( RabbitHoleManager.board == null ) { String config = Preferences.getString( "lastChessboard" ); RabbitHoleManager.load( config, false ); } if ( RabbitHoleManager.board == null ) { RequestLogger.printLine( "I haven't seen a chessboard recently." ); } } public static final void load( final String config, final boolean save ) { if ( config.trim().equals( "" ) ) { return; } RabbitHoleManager.board = new Board( config ); if ( save && !KoLCharacter.getUserName().equals( "" ) ) { Preferences.setString( "lastChessboard", RabbitHoleManager.board.config() ); } } public static final void board() { RabbitHoleManager.load(); RabbitHoleManager.board( RabbitHoleManager.board ); } private static final void board( final Board board ) { if ( board == null ) { return; } StringBuffer buffer = new StringBuffer(); board.appendHTML( buffer ); RequestLogger.printLine( buffer.toString() ); RequestLogger.printLine(); } public static final void test() { RabbitHoleManager.load(); RabbitHoleManager.test( RabbitHoleManager.board ); } private static final void test( final Board board ) { if ( board == null ) { return; } Path solution = RabbitHoleManager.solve( RabbitHoleManager.board ); if ( solution == null ) { RequestLogger.printLine( "I couldn't solve the current board." ); return; } Integer [] path = solution.toArray(); int square = board.getCurrent(); Square piece = board.get( square ); RequestLogger.printLine( "The " + piece.getTitle() + " on square " + Square.coords( square ) ); for ( int i = 0; i < path.length - 1; ++i ) { int next = path[ i ].intValue(); Square nextPiece = board.get( next ); RequestLogger.printLine( "...takes the " + nextPiece.getTitle() + " on square " + Square.coords( next ) + " (" + piece.notation( square ) + "x" + nextPiece.notation( next ) + ")" ); square = next; piece = nextPiece; } int next = path[ path.length - 1 ].intValue(); RequestLogger.printLine( "...which moves to square " + Square.coords( next ) + " to win. (" + piece.notation( square ) + "-" + Square.coords( next ) + ")" ); } public static final void solve() { RelayRequest.specialCommandResponse = ChoiceManager.lastResponseText; RelayRequest.specialCommandStatus = "Solving..."; if ( RabbitHoleManager.board == null ) { RequestLogger.printLine( "I haven't seen a chessboard recently." ); return; } Path solution = RabbitHoleManager.solve( RabbitHoleManager.board ); if ( solution == null ) { RequestLogger.printLine( "I couldn't solve the current board." ); return; } Integer [] path = solution.toArray(); String response = ""; for ( int i = 0; i < path.length; ++i ) { RelayRequest.specialCommandStatus = "Move " + i + " of " + path.length; int square = path[ i ].intValue(); int row = square / 8; int col = square % 8; String url = "choice.php?pwd&whichchoice=443&option=1&xy=" + String.valueOf( col ) + "," + String.valueOf( row ); GenericRequest req = new GenericRequest( url ); req.run(); response = req.responseText; } RelayRequest.specialCommandResponse = RabbitHoleManager.decorateChessPuzzleResponse( response ); RelayRequest.specialCommandIsAdventure = true; } private static final Path solve( final Board board ) { // Attempt to solve by moving the current piece return RabbitHoleManager.solve( (Board)board.clone(), new Path() ); } private static final Path solve( final Board board, final Path path ) { int current = board.getCurrent(); // If there is no current square, no path if ( current < 0 ) { return null; } // If there is only one piece left standing, we have a // potential solution if ( board.getPieces() == 1 ) { // Find the move which takes the current piece to the // top row int end = board.getWinningMove(); if ( end < 0 ) { // Oops. You can't get there from here. return null; } // Add the final move to the path and return it path.add( end ); return path; } // Save the current piece Square currentPiece = board.get( current ); // Get possible moves for the current piece Integer [] moves = board.getMoves(); for ( int i = 0; i < moves.length; ++i ) { int square = moves[ i ].intValue(); // If there is no piece on the square, skip if ( !board.get( square ).isPiece() ) { continue; } // Add the new square to the current path path.add( square ); // Move the current piece to the new location Square captured = board.move( current, square ); // Recurse Path newPath = solve( board, path ); // If we found a solution, we are golden if ( newPath != null ) { return newPath; } // Otherwise, backtrack path.remove(); board.set( square, captured ); board.set( current, currentPiece ); } // We were unable to find a path to the goal return null; } private static class Path { private final ArrayList list; public Path() { list = new ArrayList(); } public void add( final int square ) { list.add( IntegerPool.get( square ) ); } public void remove() { list.remove( list.size() - 1 ); } public int size() { return list.size(); } public Integer [] toArray() { Integer [] array = (Integer []) list.toArray( new Integer[ list.size() ] ); return array; } } public static final void decorateChessPuzzle( final StringBuffer buffer ) { // Add a "Solve!" button to the Chess Board String search = "</form>"; int index = buffer.lastIndexOf( search ); if ( index == -1 ) { return; } index += 7; StringBuffer button = new StringBuffer(); String url = "/KoLmafia/specialCommand?cmd=chess+solve&pwd=" + GenericRequest.passwordHash; button.append( "<form name=solveform action='" ).append( url ).append( "' method=post>" ); button.append( "<input class=button type=submit value=\"Solve!\">" ); button.append( "</form>" ); // Insert it into the page buffer.insert( index, button ); } public static final String decorateChessPuzzleResponse( final String response ) { StringBuffer buffer = new StringBuffer( response ); RequestEditorKit.getFeatureRichHTML( "choice.php", buffer ); RabbitHoleManager.decorateChessPuzzleResponse( buffer ); return buffer.toString(); } private static final AdventureResult REFLECTION_OF_MAP = ItemPool.get( ItemPool.REFLECTION_OF_MAP, 1); private static final String ADVENTURE_AGAIN = "<b>Adventure Again:</b></td></tr><tr><td style=\"padding: 5px; border: 1px solid blue;\"><center><table><tr><td><center>"; public static final void decorateChessPuzzleResponse( final StringBuffer buffer ) { // Give player a link to use another reflection of a map if ( REFLECTION_OF_MAP.getCount( KoLConstants.inventory ) <= 0 ) { return; } int index = buffer.indexOf( RabbitHoleManager.ADVENTURE_AGAIN ); if ( index == -1 ) { return; } index += RabbitHoleManager.ADVENTURE_AGAIN.length(); String link = "<a href=\"inv_use.php?pwd=" + GenericRequest.passwordHash + "&whichitem=4509\">Use another reflection of a map</a><p>"; buffer.insert( index, link ); } public static final Hat getHatData( int length ) { for ( Hat hat : RabbitHoleManager.HAT_DATA ) { if ( hat.getLength() == length ) { return hat; } } return null; } public static final String getHatDescription( int length ) { Hat hat = RabbitHoleManager.getHatData( length ); if ( hat != null ) { return hat.getEffect() + " (" + hat.getModifier() + ")"; } return "unknown (" + length + " characters)"; } public static final void decorateRabbitHole( final StringBuffer buffer ) { int index = buffer.lastIndexOf( "</table>" ); if ( index == -1 ) { return; } index += 8; List<AdventureResult> hats = EquipmentManager.getEquipmentLists()[ EquipmentManager.HAT ]; AdventureResult curHat = EquipmentManager.getEquipment( EquipmentManager.HAT ); TreeMap<Integer,String> options = new TreeMap<Integer,String>(); for ( AdventureResult hat : hats ) { if ( hat.equals( EquipmentRequest.UNEQUIP ) ) { // no buff without a hat! continue; } int len = RabbitHoleManager.HAT_CLEANER_PATTERN.matcher( hat.getName() ).replaceAll( "" ).length(); StringBuilder buf = new StringBuilder( "<option value=" ); buf.append( hat.getItemId() ); if ( hat.equals( curHat ) ) { buf.append( " selected" ); } buf.append( ">" ); buf.append( hat.getName() ); buf.append( ": " ); buf.append( RabbitHoleManager.getHatDescription( len ) ); buf.append( "</option>" ); options.put( IntegerPool.get( (len << 24) | hat.getItemId() ), buf.toString() ); } String ending = buffer.substring( index ); buffer.delete( index, Integer.MAX_VALUE ); buffer.append( "Hat the player: <select onChange=\"singleUse('inv_equip.php', 'which=2&action=equip&pwd=" ); buffer.append( GenericRequest.passwordHash ); buffer.append( "&ajax=1&whichitem=' + this.options[this.selectedIndex].value);\">" ); for ( String option : options.values() ) { buffer.append( option ); } buffer.append( "</select>" ); buffer.append( ending ); } private static TreeMap getHatMap() { // Make a map of all hats indexed by length List<AdventureResult> hats = EquipmentManager.getEquipmentLists()[ EquipmentManager.HAT ]; FamiliarData current = (FamiliarData) KoLCharacter.getFamiliar(); if ( current.getItem() != null && EquipmentDatabase.isHat( current.getItem() ) ) { hats.add( current.getItem() ); } TreeMap<Integer,StringBuffer> lengths = new TreeMap<Integer,StringBuffer>(); for ( AdventureResult hat : hats ) { if ( hat.equals( EquipmentRequest.UNEQUIP ) ) { // no buff without a hat! continue; } if ( !EquipmentManager.canEquip( hat ) ) { // skip it if we can't equip it continue; } String name = hat.getName(); Integer len = IntegerPool.get( hatLength( name ) ); StringBuffer buffer = lengths.get( len ); if ( buffer == null ) { buffer = new StringBuffer(); lengths.put( len, buffer ); } else { buffer.append( "|" ); } buffer.append( name ); } return lengths; } public static final void hatCommand() { TreeMap<Integer,StringBuffer> lengths = getHatMap(); if ( lengths.size() == 0 ) { RequestLogger.printLine( "You don't have any hats" ); return; } StringBuilder output = new StringBuilder(); output.append( "<table border=2 cols=3>" ); output.append( "<tr>" ); output.append( "<th>Hat</th>" ); output.append( "<th>Benefit</th>" ); output.append( "<th>Effect</th>" ); output.append( "</tr>" ); // For each hat length, generate a table row for ( Integer key : lengths.keySet() ) { Hat hat = RabbitHoleManager.getHatData( key.intValue() ); if ( hat == null ) { continue; } StringBuffer buffer = lengths.get( key ); String[] split = buffer.toString().split( "\\|" ); output.append( "<tr><td>" ); output.append( split[0] ); output.append( "</td><td rowspan=" ); output.append( String.valueOf( split.length ) ); output.append( ">" ); output.append( hat.getModifier() ); output.append( "</td><td rowspan=" ); output.append( String.valueOf( split.length ) ); output.append( ">" ); output.append( hat.getEffect() ); output.append( "</td></tr>" ); for ( int i = 1; i < split.length; ++i ) { output.append( "<tr><td>" ); output.append( split[i] ); output.append( "</td></tr>" ); } } output.append( "</table>" ); RequestLogger.printLine( output.toString() ); RequestLogger.printLine(); } public static boolean teaPartyAvailable() { return !Preferences.getBoolean( "_madTeaParty" ); } public static void getHatBuff( final AdventureResult hat ) { AdventureResult oldHat = EquipmentManager.getEquipment( EquipmentManager.HAT ); if ( !KoLConstants.activeEffects.contains( EffectPool.get( EffectPool.DOWN_THE_RABBIT_HOLE ) ) ) { if ( !InventoryManager.hasItem( ItemPool.DRINK_ME_POTION ) ) { KoLmafia.updateDisplay( MafiaState.ERROR, "You need a DRINK ME! potion to get a hatter buff." ); return; } InventoryManager.retrieveItem( ItemPool.get( ItemPool.DRINK_ME_POTION, 1 ) ); RequestThread.postRequest( UseItemRequest.getInstance( ItemPool.get( ItemPool.DRINK_ME_POTION, 1 ) ) ); } RequestThread.postRequest( new EquipmentRequest( hat, EquipmentManager.HAT ) ); if ( EquipmentManager.getEquipment( EquipmentManager.HAT ).getItemId() != hat.getItemId() ) { KoLmafia.updateDisplay( MafiaState.ERROR, "Failed to equip " + hat.getName() + "." ); return; } Hat data = RabbitHoleManager.getHatData( hatLength( hat.getName() ) ); if ( data == null ) { return; } String effectName = data.getEffect(); String effectModifiers = data.getModifier(); RequestLogger.printLine( "Getting " + effectName + " (" + effectModifiers + ") from the Mad Tea Party..." ); RequestThread.postRequest( new RabbitHoleRequest( "rabbithole_teaparty" ) ); RequestThread.postRequest( new GenericRequest( "choice.php?pwd&whichchoice=441&option=1", true ) ); RequestThread.postRequest( new EquipmentRequest( oldHat, EquipmentManager.HAT ) ); } public static void getHatBuff( final int desiredHatLength ) { if ( RabbitHoleManager.hatLengthAvailable( desiredHatLength ) ) { TreeMap lengths = getHatMap(); String hat = lengths.get( IntegerPool.get( desiredHatLength ) ).toString().split( "\\|" )[ 0 ]; RabbitHoleManager.getHatBuff( ItemFinder.getFirstMatchingItem( hat ) ); } else { KoLmafia.updateDisplay( MafiaState.ERROR, "No matching hat length found." ); } } public static boolean hatLengthAvailable( int desiredHatLength ) { TreeMap lengths = getHatMap(); if ( lengths.size() == 0 ) { return false; } if ( lengths.containsKey( IntegerPool.get( desiredHatLength ) ) ) { return true; } return false; } public static int hatLength( final String name ) { return RabbitHoleManager.HAT_CLEANER_PATTERN.matcher( name ).replaceAll( "" ).length(); } public static final boolean registerChessboardRequest( final String urlString ) { // Don't log anything here. We will log it when we get the // response and see the board. return true; } }