/**
* 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.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.kolmafia.KoLConstants;
import net.sourceforge.kolmafia.KoLConstants.MafiaState;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.RequestLogger;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.request.GenericRequest;
import net.sourceforge.kolmafia.request.RelayRequest;
import net.sourceforge.kolmafia.request.VolcanoMazeRequest;
import net.sourceforge.kolmafia.utilities.FileUtilities;
import net.sourceforge.kolmafia.utilities.StringUtilities;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public abstract class VolcanoMazeManager
{
private static boolean loaded = false;
// The number of maps in the cycle
public static final int MAPS = 5;
private static VolcanoMap [] maps = new VolcanoMap[ MAPS ];
// Which map we are currently on
private static int currentMap = 0;
// Where you are on that map
private static int currentLocation = -1;
// Constants dictated by size of puzzle
public static final int NROWS = 13;
public static final int NCOLS = 13;
private static final int CELLS = NCOLS * NROWS;
private static final int MIN_SQUARE = 0;
private static final int MAX_SQUARE = CELLS - 1;
// An array, indexed by position, of map # on which this position is
// above the lava.
private static final int[] squares = new int[ CELLS ];
private static final Neighbors[] neighbors = new Neighbors[ CELLS ];
// The number of know platforms. After MAPS maps are known, this had
// better be all of them.
private static int found = 1; // Goal known
// Position of the start: (6,12)
private static final int start = 162;
// Position of the goal: (6,6)
private static final int goal = 84;
private static final String[] IMAGES = new String[]
{
"platformupyou.gif",
"platformgoal.gif",
"platform3.gif",
"lava1.gif",
"lava2.gif",
"lava3.gif",
"lava4.gif",
"lava5.gif",
"lava6.gif",
"lava7.gif",
"lava8.gif",
"lava9.gif",
"lava10.gif",
"lava11.gif",
"lava12.gif",
};
static
{
String base = KoLmafia.imageServerPath() + "itemimages/";
for ( int i = 0; i < VolcanoMazeManager.IMAGES.length; ++i )
{
FileUtilities.downloadImage( base + VolcanoMazeManager.IMAGES[i] );
}
}
public static final void reset()
{
VolcanoMazeManager.loaded = false;
VolcanoMazeManager.currentMap = 0;
VolcanoMazeManager.currentLocation = -1;
for ( int map = 0; map < VolcanoMazeManager.maps.length; ++map )
{
VolcanoMazeManager.maps[ map ] = null;
}
Arrays.fill( VolcanoMazeManager.squares, 0 );
Arrays.fill( VolcanoMazeManager.neighbors, null );
VolcanoMazeManager.found = 1;
}
public static final void clear()
{
VolcanoMazeManager.reset();
for ( int map = 0; map < maps.length; ++map )
{
VolcanoMazeManager.clearCurrentMap( map );
}
}
private static final void loadCurrentMaps()
{
if ( !VolcanoMazeManager.loaded )
{
NemesisManager.ensureUpdatedNemesisStatus();
for ( int map = 0; map < maps.length; ++map )
{
VolcanoMazeManager.loadCurrentMap( map );
}
RequestLogger.printLine( VolcanoMazeManager.found + " total platforms seen." );
VolcanoMazeManager.currentMap = 0;
VolcanoMazeManager.currentLocation = -1;
VolcanoMazeManager.loaded = true;
}
}
private static final void loadCurrentMap( final int map )
{
String setting = "volcanoMaze" + String.valueOf( map + 1 );
String coords = Preferences.getString( setting );
if ( !VolcanoMazeManager.validMap( coords ) )
{
Preferences.setString( setting, "" );
VolcanoMazeManager.maps[ map ] = null;
}
else
{
VolcanoMazeManager.maps[ map ] = new VolcanoMap( coords );
VolcanoMazeManager.addSquares( map );
}
}
private static final void clearCurrentMap( final int map )
{
String setting = "volcanoMaze" + String.valueOf( map + 1 );
Preferences.setString( setting, "" );
}
private static final boolean validMap( final String coordinates )
{
if ( coordinates == null || coordinates.equals( "" ) )
{
return false;
}
String[] platforms = coordinates.split( "\\s*,\\s*" );
for ( int i = 0; i < platforms.length; ++i )
{
String coord = platforms[ i];
if ( !StringUtilities.isNumeric( coord ) )
{
return false;
}
int val = StringUtilities.parseInt( coord );
if ( val < MIN_SQUARE || val > MAX_SQUARE )
{
return false;
}
}
return true;
}
public static final void decorate( final String location, final StringBuffer buffer )
{
if ( !location.startsWith( "volcanomaze.php" ) )
{
return;
}
// Add a "Solve!" button to the Volcanic Cave which invokes the
// "volcano solve" command.
String search = "</form>";
int index = buffer.lastIndexOf( search );
if ( index == -1 )
{
return;
}
index += 7;
// Build a "Solve!" button
StringBuffer button = new StringBuffer();
String url = "/KoLmafia/redirectedCommand?cmd=volcano+solve&pwd=" + GenericRequest.passwordHash;
button.append( "<form name=solveform action='" + url + "' 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 void parseResult( final String responseText )
{
// Load current maps, if necessary
VolcanoMazeManager.loadCurrentMaps();
// Parse what the server gave us
String coords = VolcanoMazeManager.parseCoords( responseText );
// Make sure we got a good map
if ( !VolcanoMazeManager.validMap( coords ) )
{
return;
}
// See if we already have this map
int index = VolcanoMazeManager.currentMap;
do
{
VolcanoMap current = VolcanoMazeManager.maps[ index ];
// Map not found and empty slot found
if ( current == null )
{
VolcanoMazeManager.currentMap = index;
VolcanoMazeManager.maps[ index ] = new VolcanoMap( coords );
break;
}
// There is a map. Is it this map?
if ( coords.equals( current.getCoordinates() ) )
{
VolcanoMazeManager.currentMap = index;
return;
}
// No. Skip to next slot
index = ( index + 1 ) % maps.length;
} while ( index != VolcanoMazeManager.currentMap );
// It's a new map. Save the coordinates in user settings
int sequence = index + 1;
String setting = "volcanoMaze" + String.valueOf( sequence );
Preferences.setString( setting, coords );
// Save the squares
VolcanoMazeManager.addSquares( currentMap );
RequestLogger.printLine( VolcanoMazeManager.found + " total platforms seen." );
}
private static final void addSquares( final int index )
{
VolcanoMap map = VolcanoMazeManager.maps[ index ];
int seq = index + 1;
Integer [] platforms = map.getPlatforms();
int ofound = VolcanoMazeManager.found;
int pcount = platforms.length;
RequestLogger.printLine( "Map #" + seq + " has " + pcount + " platforms" );
for ( int i = 0; i < pcount; ++i )
{
int square = platforms[ i ].intValue();
int old = VolcanoMazeManager.squares[ square];
if ( old == 0 )
{
VolcanoMazeManager.squares[ square ] = seq;
VolcanoMazeManager.found++;
}
else if ( old != seq )
{
// Something is wrong: we already found this
// square elsewhere in the sequence
RequestLogger.printLine( "Platform " + square + " already seen on map #" + old );
}
}
}
private static final String parseCoords( final String responseText )
{
// move=x,y returns simply "false" if can't move there
if ( responseText.equals( "false" ) )
{
return null;
}
else if ( responseText.startsWith( "<html>" ) )
{
return VolcanoMazeManager.parseHTMLCoords( responseText );
}
else
{
return VolcanoMazeManager.parseJSONCoords( responseText );
}
}
private static final Pattern SQUARE_PATTERN = Pattern.compile("<div id=\"sq(\\d+)\" class=\"sq (no|yes)\\s+(you|goal|)\\s*lv(\\d+)\" rel=\"(\\d+),(\\d+)\">");
private static final String parseHTMLCoords( final String responseText )
{
Matcher matcher = VolcanoMazeManager.SQUARE_PATTERN.matcher( responseText );
StringBuffer buffer = new StringBuffer();
boolean first = true;
while ( matcher.find() )
{
String square = matcher.group(1);
String special = matcher.group(3);
if ( special != null )
{
int squint = Integer.parseInt( square );
if ( special.equals( "you" ) )
{
VolcanoMazeManager.currentLocation = squint;
}
// Sanity check
else if ( special.equals( "goal" ) && VolcanoMazeManager.goal != squint )
{
RequestLogger.printLine( "Map says goal is on square " + squint + ", not " + VolcanoMazeManager.goal );
}
}
String type = matcher.group(2);
if ( !type.equals( "yes" ) )
{
continue;
}
// int column = Integer.parseInt(matcher.group(5));
// int row = Integer.parseInt(matcher.group(6));
if ( first )
{
first = false;
}
else
{
buffer.append( "," );
}
buffer.append( square );
}
return buffer.toString();
}
// {"won":"","pos":"6,3","show":["2","6","9","15","20","24","32","34","38","40","47","49","52","56","57","59","64","84","86","92","97","100","105","106","109","111","114","117","127","129","136","144","145","150","153","160","167"]}
private static final Pattern POS_PATTERN = Pattern.compile("(\\d+),(\\d+)");
private static final String parseJSONCoords( final String responseText )
{
StringBuffer buffer = new StringBuffer();
JSONObject JSON;
// Parse the string into a JSON object
try
{
JSON = new JSONObject( responseText );
}
catch ( JSONException e )
{
return "";
}
// "pos" is the player's position
try
{
String pos = JSON.getString( "pos" );
Matcher matcher = VolcanoMazeManager.POS_PATTERN.matcher( pos );
if ( matcher.find() )
{
int col = Integer.parseInt( matcher.group( 1 ) );
int row = Integer.parseInt( matcher.group( 2 ) );
int square = row * NCOLS + col;
VolcanoMazeManager.currentLocation = square;
}
}
catch ( JSONException e )
{
VolcanoMazeManager.currentLocation = -1;
}
// "show" is an array of platforms
JSONArray show;
try
{
show = JSON.getJSONArray( "show" );
}
catch ( JSONException e )
{
return "";
}
// Iterate over the squares
boolean first = true;
int count = show.length();
for ( int index = 0; index < count; ++index )
{
String square = show.optString( index, null );
// Omit the goal square; that is a platform on all maps
if ( square == null || square.equals( "84" ) )
{
continue;
}
if ( first )
{
first = false;
}
else
{
buffer.append( "," );
}
buffer.append( square );
}
return buffer.toString();
}
private static final int row( final int pos )
{
return ( pos / NCOLS );
}
private static final int col( final int pos )
{
return ( pos % NCOLS );
}
private static final int pos( final int row, final int col )
{
return ( row * NCOLS + col );
}
public static final String coordinateString( final int pos )
{
if ( pos == -1 )
{
return "(unknown)";
}
int row = row( pos );
int col = col( pos );
// Yes, KoL really does display ( column, row )
return String.valueOf( col ) + "," + String.valueOf( row );
}
public static final String coordinateString( final int pos, final int map )
{
String cstr = VolcanoMazeManager.coordinateString( pos );
String mstr = ( map >= 0 ) ? ( "map " + String.valueOf( map + 1 ) ) : "(unknown map )";
return cstr + " on " + mstr;
}
public static final String currentCoordinates()
{
return VolcanoMazeManager.coordinateString( currentLocation );
}
public static final void printCurrentCoordinates()
{
if ( VolcanoMazeManager.currentLocation == -1 )
{
RequestLogger.printLine( "I don't know where you are" );
return;
}
RequestLogger.printLine( "Current position: " + VolcanoMazeManager.coordinateString( currentLocation, currentMap ) );
}
private static final boolean discoverMaps()
{
VolcanoMazeManager.loadCurrentMaps();
if ( VolcanoMazeManager.found == CELLS )
{
return true;
}
// Visit the cave to find out where we are
if ( currentLocation < 0 )
{
VolcanoMazeManager.internalVisit();
}
// Give up now if we couldn't do that.
if ( currentLocation < 0 )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You couldn't find the lava cave" );
return false;
}
VolcanoMazeManager.printCurrentCoordinates();
while ( VolcanoMazeManager.found < CELLS )
{
VolcanoMap map = VolcanoMazeManager.maps[ currentMap ];
int me = VolcanoMazeManager.currentLocation;
int next = map.pickNeighbor( me );
if ( next < 0 )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You seem to be stuck" );
return false;
}
RequestLogger.printLine( "Move to: " + VolcanoMazeManager.coordinateString( next, currentMap ) );
int ofound = VolcanoMazeManager.found;
VolcanoMazeRequest req = new VolcanoMazeRequest( next );
req.run();
if ( ofound >= VolcanoMazeManager.found )
{
// This shouldn't happen
KoLmafia.updateDisplay( MafiaState.ERROR, "Moving did not discover new platforms" );
return false;
}
}
return true;
}
// CLI command support
public static final void visit()
{
VolcanoMazeManager.internalVisit();
VolcanoMazeManager.printCurrentCoordinates();
}
private static final void internalVisit()
{
// Must make a new VolcanoMazeRequest every time since that
// class follows redirects.
VolcanoMazeRequest VISITOR = new VolcanoMazeRequest();
VISITOR.run();
}
public static final void jump()
{
// Must make a new VolcanoMazeRequest every time since that
// class follows redirects.
VolcanoMazeRequest JUMP = new VolcanoMazeRequest( true );
JUMP.run();
VolcanoMazeManager.printCurrentCoordinates();
}
public static final void move( final int x, final int y, final boolean print )
{
VolcanoMazeRequest req = new VolcanoMazeRequest( x, y );
req.run();
if ( print )
{
VolcanoMazeManager.displayMap();
}
VolcanoMazeManager.printCurrentCoordinates();
}
public static final void displayMap()
{
VolcanoMazeManager.loadCurrentMaps();
VolcanoMap map = VolcanoMazeManager.maps[ VolcanoMazeManager.currentMap ];
if ( map == null )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "We haven't seen the volcanic cave yet" );
return;
}
map.displayHTMLMap( currentLocation );
}
public static final void displayMap( final int num )
{
if ( num < 1 || num > MAPS )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "Choose map # from 1 - " + MAPS );
return;
}
VolcanoMazeManager.loadCurrentMaps();
VolcanoMap map = VolcanoMazeManager.maps[ num - 1 ];
if ( map == null )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "We haven't seen map #" + num );
return;
}
map.displayHTMLMap( -1 );
}
public static final void platforms()
{
if ( !VolcanoMazeManager.discoverMaps() )
{
return;
}
// Make an HTML table to display platform map
StringBuffer buffer = new StringBuffer();
buffer.append( "<table border cols=14>" );
buffer.append( "<tr><td></td>" );
for ( int col = 0; col < NCOLS; ++col )
{
buffer.append( "<td align=center><b>" );
buffer.append( String.valueOf( col ) );
buffer.append( "</b></td>" );
}
buffer.append( "</tr>" );
for ( int row = 0; row < NROWS; ++row )
{
buffer.append( "<tr>" );
buffer.append( "<td valign=center><b>" );
buffer.append( String.valueOf( row ) );
buffer.append( "</b></td>" );
for ( int col = 0; col < NCOLS; ++col )
{
buffer.append( "<td>" );
int map = squares[ pos( row, col ) ];
buffer.append( String.valueOf( map ) );
buffer.append( "</td>" );
}
buffer.append( "</tr>" );
}
buffer.append( "</table>" );
RequestLogger.printLine( buffer.toString() );
RequestLogger.printLine();
}
private static int pathsMade = 0;
private static int pathsExamined = 0;
public static final void solve()
{
// Save URL to give back to the user's browser
RelayRequest.redirectedCommandURL = "/volcanomaze.php?start=1";
if ( !VolcanoMazeManager.discoverMaps() )
{
return;
}
// Sanity check
if ( VolcanoMazeManager.found < CELLS )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "We couldn't discover all the maps" );
return;
}
Path solution = VolcanoMazeManager.solve( currentLocation, currentMap );
VolcanoMazeManager.printStatistics( solution );
if ( solution == null )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You can't get there from here. Swim to shore and try again." );
return;
}
// Move up next to the goal.
for ( Integer next : solution )
{
int sq = next.intValue();
// Quit when we are about to move to the goal
if ( sq == VolcanoMazeManager.goal )
{
break;
}
VolcanoMazeRequest req = new VolcanoMazeRequest( sq );
req.run();
}
}
public static final void test( final int map, final int x, final int y )
{
VolcanoMazeManager.loadCurrentMaps();
// Sanity check
if ( VolcanoMazeManager.found < CELLS )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You don't know all the maps" );
return;
}
int location = pos( y, x );
Path solution = VolcanoMazeManager.solve( location, map - 1 );
VolcanoMazeManager.printStatistics( solution );
if ( solution == null )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You can't get there from here. Swim to shore and try again." );
return;
}
// Print the solution
for ( Integer next : solution )
{
int pos = next.intValue();
RequestLogger.printLine( "Hop to " + VolcanoMazeManager.coordinateString( pos ) );
}
}
private static final void printStatistics( final Path solution )
{
StringBuffer buffer = new StringBuffer();
buffer.append( "Paths examined/made " );
buffer.append( KoLConstants.COMMA_FORMAT.format( pathsExamined ) );
buffer.append( "/" );
buffer.append( KoLConstants.COMMA_FORMAT.format( pathsMade ) );
buffer.append( " ->" );
if ( solution != null )
{
buffer.append( "solution with " );
buffer.append( String.valueOf( solution.size() ) );
buffer.append( " hops." );
}
else
{
buffer.append( "no solution found." );
}
RequestLogger.printLine( buffer.toString() );
}
// solve( currentLocation, currentMap ): solve the volcano cave puzzle
//
// Inputs:
//
// location - starting square
// map - starting map
//
// Global constants:
//
// CELLS - number of cells in map: rows & columns
// MAPS - number of maps in cycle.
// goal - cell # of goal platform
//
// Global input data:
//
// VolcanoMap maps[ MAPS ]
// Indexed from 0 to MAP
// int squares[ CELLS ]
// Indexed by platform #: map # containing platform
//
// Global input/output data:
//
// Neighbors neighbors[ CELLS ]
// Indexed by platform #: neighbors of platform in same map
//
// Global output data:
//
// pathsMade - paths generated
// pathsExamined - paths examined
private static final Path solve( final int location, final int map )
{
// Generate neighbors for every cell
VolcanoMazeManager.generateNeighbors();
// The work queue of Paths
LinkedList queue = new LinkedList();
// Statistics
VolcanoMazeManager.pathsMade = 0;
VolcanoMazeManager.pathsExamined = 0;
// Find the neighbors for the current location in the current
// map. These are the first hop for all possible paths.
VolcanoMap current = VolcanoMazeManager.maps[ map ];
Neighbors roots = current.neighbors( location );
// We only need to visit any given cell once.
boolean [] visited = new boolean[ CELLS ];
// We have visited the start square
visited[ location ] = true;
// Make a path for each root and add it to the queue.
Integer [] starts = roots.getPlatforms();
for ( int i = 0; i < starts.length; ++i )
{
++VolcanoMazeManager.pathsMade;
Integer square = starts[ i ];
queue.addLast( new Path( square ) );
// We (will) have visited each root
visited[ square.intValue() ] = true;
}
// Perform a breadth-first search of the maze
while ( !queue.isEmpty() )
{
Path path = (Path) queue.removeFirst();
++VolcanoMazeManager.pathsExamined;
// System.out.println( "Examining path: " + path );
Integer last = path.getLast();
Neighbors neighbors = VolcanoMazeManager.neighbors[ last.intValue() ];
Integer [] platforms = neighbors.getPlatforms();
// Examine each neighbor
for ( int i = 0; i < platforms.length; ++i )
{
Integer platform = platforms[ i ];
// If this is a goal, we have the solution
if ( platform.intValue() == VolcanoMazeManager.goal )
{
++VolcanoMazeManager.pathsMade;
return new Path( path, platform );
}
// If neighbor not yet seen, add and search it
int square = platform.intValue();
if ( !visited[ square ] )
{
++VolcanoMazeManager.pathsMade;
queue.addLast( new Path( path, platform ) );
// We (will) have visited this platform
visited[ square ] = true;
}
}
}
// No solution found
return null;
}
private static final void generateNeighbors()
{
for ( int square = 0; square < CELLS; ++square )
{
// Calculate and store neighbors once only
if ( VolcanoMazeManager.neighbors[ square ] != null )
{
continue;
}
// The goal appears in every map
if ( square == goal )
{
VolcanoMazeManager.neighbors[ square ] = new Neighbors( square, null );
continue;
}
// Otherwise, get the neighbors relative to the map
// the square is in.
int index = VolcanoMazeManager.squares[ square ];
VolcanoMap pmap = VolcanoMazeManager.maps[ index % MAPS ];
VolcanoMazeManager.neighbors[ square ] = pmap.neighbors( square );
}
}
private static class VolcanoMap
{
public final String coordinates;
public final Integer [] platforms;
public final boolean[] board = new boolean[ CELLS ];
public VolcanoMap( final String coordinates )
{
this.coordinates = coordinates;
// Make an array of all the platforms
String[] squares = coordinates.split( "\\s*,\\s*" );
ArrayList list = new ArrayList();
for ( int i = 0; i < squares.length; ++i )
{
String coord = squares[ i];
if ( !StringUtilities.isNumeric( coord ) )
{
continue;
}
Integer ival = new Integer( coord );
list.add( ival );
this.board[ ival.intValue() ] = true;
}
this.platforms = (Integer []) list.toArray( new Integer[ list.size() ] );
// Every board has the goal platform
this.board[ VolcanoMazeManager.goal ] = true;
}
public String getCoordinates()
{
return this.coordinates;
}
public Integer [] getPlatforms()
{
return this.platforms;
}
public boolean [] getBoard()
{
return this.board;
}
public boolean inMap( final int row, final int col )
{
return this.inMap( pos( row, col ) );
}
public boolean inMap( final int square )
{
return this.board[ square ];
}
public Neighbors neighbors( final int square )
{
return new Neighbors( square, this );
}
public int pickNeighbor( final int square )
{
Neighbors neighbors = this.neighbors( square );
Integer [] platforms = neighbors.getPlatforms();
// We might be stuck
if ( platforms.length == 0 )
{
return -1;
}
// If there is only one neighbor, that's it
if ( platforms.length == 1 )
{
int next = platforms[ 0 ].intValue();
// Don't pick the goal!
return ( next != VolcanoMazeManager.goal ) ? next : -1;
}
// Otherwise, pick one at random.
int next = VolcanoMazeManager.goal;
while ( next == VolcanoMazeManager.goal )
{
int rnd = KoLConstants.RNG.nextInt( platforms.length );
next = platforms[ rnd ].intValue();
}
return next;
}
public void print( final int player )
{
int prow = row( player );
int pcol = col( player );
StringBuffer buffer = new StringBuffer();
for ( int row = 0; row < NROWS; ++row )
{
if ( row < 9 )
{
buffer.append( " " );
}
buffer.append( String.valueOf( row + 1 ) );
for ( int col = 0; col < NCOLS; ++col )
{
buffer.append( " " );
if ( row == prow && col == pcol )
{
buffer.append( "@" );
}
else if ( row == 6 && col == 6 )
{
buffer.append( "*" );
}
else if ( board[ pos( row, col ) ] )
{
buffer.append( "O" );
}
else
{
buffer.append( "." );
}
}
buffer.append( KoLConstants.LINE_BREAK );
}
System.out.println( buffer.toString() );
}
public void displayHTMLMap( final int player )
{
int prow = row( player );
int pcol = col( player );
StringBuffer buffer = new StringBuffer();
buffer.append( "<table cellpadding=0 cellspacing=0 cols=14>" );
buffer.append( "<tr><td></td>" );
for ( int col = 0; col < NCOLS; ++col )
{
buffer.append( "<td align=center><b>" );
buffer.append( String.valueOf( col ) );
buffer.append( "</b></td>" );
}
buffer.append( "</tr>" );
for ( int row = 0; row < NROWS; ++row )
{
buffer.append( "<tr>" );
buffer.append( "<td valign=center><b>" );
buffer.append( String.valueOf( row ) );
buffer.append( "</b></td>" );
for ( int col = 0; col < NCOLS; ++col )
{
buffer.append( "<td>" );
buffer.append( "<img src=\"" );
buffer.append( KoLmafia.imageServerPath() );
buffer.append( "itemimages/" );
if ( row == prow && col == pcol )
{
buffer.append( "platformupyou" );
}
else if ( row == 6 && col == 6 )
{
buffer.append( "platformgoal" );
}
else if ( board[ pos( row, col ) ] )
{
buffer.append( "platform3" );
}
else
{
int rnd = KoLConstants.RNG.nextInt( 12 );
buffer.append( "lava" );
buffer.append( String.valueOf( rnd + 1 ) );
}
buffer.append( ".gif\" width=30 height=30>" );
buffer.append( "</td>" );
}
buffer.append( "</tr>" );
}
buffer.append( "</table>" );
RequestLogger.printLine( buffer.toString() );
RequestLogger.printLine();
}
}
private static class Neighbors
{
public final Integer [] platforms;
public Neighbors( final int square, final VolcanoMap map )
{
int row = row( square );
int col = col( square );
ArrayList list = new ArrayList();
Neighbors.addSquare( list, map, row - 1, col - 1 );
Neighbors.addSquare( list, map, row - 1, col );
Neighbors.addSquare( list, map, row - 1, col + 1 );
Neighbors.addSquare( list, map, row, col - 1 );
Neighbors.addSquare( list, map, row, col + 1 );
Neighbors.addSquare( list, map, row + 1, col - 1 );
Neighbors.addSquare( list, map, row + 1, col );
Neighbors.addSquare( list, map, row + 1, col + 1 );
this.platforms = new Integer[ list.size() ];
list.toArray( this.platforms );
}
public Integer [] getPlatforms()
{
return this.platforms;
}
private static void addSquare( List list, final VolcanoMap map, int row, int col )
{
if ( row >= 0 && row < NROWS && col >= 0 && col < NCOLS )
{
int square = pos( row, col );
if ( map == null || map.inMap( square ) )
{
list.add( new Integer( square ) );
}
}
}
}
private static class Path
implements Iterable<Integer>
{
private final ArrayList<Integer> list;
public Path( final Integer square )
{
list = new ArrayList<Integer>();
list.add( square );
}
public Iterator<Integer> iterator()
{
return this.list.iterator();
}
public Path( final Path prefix, final Integer square )
{
list = (ArrayList<Integer>) prefix.list.clone();
list.add( square );
}
public boolean contains( final Integer elem )
{
return list.contains( elem );
}
public Integer get( final int index )
{
return list.get( index );
}
public Integer getLast()
{
return list.get( list.size() - 1 );
}
public int size()
{
return list.size();
}
@Override
public String toString()
{
StringBuffer buffer = new StringBuffer();
int count = list.size();
boolean first = true;
buffer.append( "[" );
for ( int i = 0; i < count; ++i )
{
if ( first )
{
first = false;
}
else
{
buffer.append( "," );
}
buffer.append( String.valueOf( list.get( i ) ) );
}
buffer.append( "]" );
return buffer.toString();
}
}
}