/** * 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.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.kolmafia.KoLConstants.MafiaState; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.RequestLogger; import net.sourceforge.kolmafia.webui.RelayLoader; public abstract class WumpusManager { public static final String[] CHAMBERS = { "acrid", "breezy", "creepy", "dripping", "echoing", "fetid", "gloomy", "howling", "immense", null, // j null, // k "long", "moaning", "narrow", "ordinary", "pillared", "quiet", "round", "sparkling", null, // t "underground", "vaulted", "windy", null, // x null, // y null, // z }; public static TreeMap<String,Room> rooms = new TreeMap<String,Room>(); static { // Initialize all rooms to unknown for ( String name : CHAMBERS ) { if ( name != null ) { WumpusManager.rooms.put( name, new Room( name ) ); } } }; // Current room public static Room current; public static Room last; // Locations of hazards public static Room bats1 = null; public static Room bats2 = null; public static Room pit1 = null; public static Room pit2 = null; public static Room wumpus = null; public static final int WARN_SAFE = 0; public static final int WARN_BATS = 1; public static final int WARN_PIT = 2; public static final int WARN_WUMPUS = 4; public static final int WARN_INDEFINITE = 8; public static final int WARN_ALL = 15; public static String[] WARN_STRINGS = new String[] { "safe", "definite bats", "definite pit", "ERROR: BATS AND PIT", "definite Wumpus", "ERROR: BATS AND WUMPUS", "ERROR: PIT AND WUMPUS", "ERROR: BATS, PIT, AND WUMPUS", "safe and unvisited", "possible bats", "possible pit", "possible bats or pit", "possible Wumpus", "possible bats or Wumpus", "possible pit or Wumpus", "possible bats, pit, or Wumpus", }; private static boolean monsterIsWumpus = false; public static String[] ELIMINATE_STRINGS = new String[] { "", "no bats", "no pit", "", "no Wumpus", "", "", "", }; private static final Pattern ROOM_PATTERN = Pattern.compile( "b>The (\\w+) Chamber" ); private static final Pattern LINK_PATTERN = Pattern.compile( "Enter the (\\w+) chamber" ); public static void reset() { // Reset all rooms to initial state for ( Room room : WumpusManager.rooms.values() ) { room.reset(); } // We are not currently in a room WumpusManager.current = null; WumpusManager.last = null; // We don't know where any of the hazards are WumpusManager.bats1 = null; WumpusManager.bats2 = null; WumpusManager.pit1 = null; WumpusManager.pit2 = null; WumpusManager.wumpus = null; // The next monster is not guaranteed to be the wumpus WumpusManager.monsterIsWumpus = false; } // Deductions we made from visiting this room public static final StringBuffer deductions = new StringBuffer(); // Types of deductions public static final int NONE = 0; public static final int VISIT = 1; public static final int LISTEN = 2; public static final int ELIMINATION = 3; public static final int DEDUCTION = 4; public static String[] DEDUCTION_STRINGS = new String[] { "None", "Visit", "Listen", "Elimination", "Deduction" }; public static void visitChoice( String text ) { WumpusManager.last = WumpusManager.current; WumpusManager.current = null; WumpusManager.deductions.setLength( 0 ); if ( text == null ) { return; } Matcher m = WumpusManager.ROOM_PATTERN.matcher( text ); if ( !m.find() ) { return; } String name = m.group( 1 ).toLowerCase(); Room room = WumpusManager.rooms.get( name ); if ( room == null ) { // Internal error: unknown room name KoLmafia.updateDisplay( MafiaState.ERROR, "Unknown room in Wumpus cave: the " + name + " chamber"); return; } // If we have already visited this room, nothing more to do if ( room.visited ) { WumpusManager.current = room; return; } // Wait for the bats to drop you if ( text.contains( "the bats" ) ) { WumpusManager.knownBats( room, VISIT ); return; } if ( text.contains( "Thump" ) ) { WumpusManager.knownPit( room, VISIT ); return; } // Remember the room we are in. If we leave it and // find the Wumpus, we will not be on a choice page // and will need it in order to make deductions. WumpusManager.current = room; // Initialize the exits from the current room m = WumpusManager.LINK_PATTERN.matcher( text ); for ( int i = 0; i < 3; ++i ) { if ( !m.find() ) { // Should not happen; there are always three // exits from a room. KoLmafia.updateDisplay( MafiaState.ERROR, "Internal error: " + i + " exits found in " + WumpusManager.current ); return; } String ename = m.group( 1 ).toLowerCase(); Room exit = WumpusManager.rooms.get( ename ); WumpusManager.current.setExit( i, exit ); exit.addExit( WumpusManager.current ); } WumpusManager.printDeduction( "Exits: " + WumpusManager.current.exitString() ); // Basic logic: assume all rooms have all warnings initially. // Remove any warnings from linked rooms that aren't present // in the current room. int warn = WARN_INDEFINITE; // You hear the fluttering of wings and a high-pitched // squeaking coming from somewhere nearby. if ( text.contains( "a high-pitched squeaking" ) ) { warn |= WARN_BATS; } // You hear a low roaring sound nearby, like the echo of wind // in an empty space. if ( text.contains( "a low roaring sound" ) ) { warn |= WARN_PIT; } // Spatters of blood covering the floor and an overpowering // stench indicate that the Wumpus must be nearby. Tread // carefully, for you'll only get one chance to make good your // attack. if ( text.contains( "the Wumpus must be nearby" ) ) { warn |= WARN_WUMPUS; } WumpusManager.printDeduction( "Sounds: " + WumpusManager.current.listenString() ); WumpusManager.knownSafe( VISIT ); WumpusManager.current.setListen( warn ); for ( int i = 0; i < 3; ++i ) { Room exit = WumpusManager.current.getExit( i ); WumpusManager.possibleHazard( exit, warn ); } // Advanced logic: if only one of the linked rooms has a given // warning, promote that to a definite danger, and remove any // other warnings from that room. WumpusManager.deduce( room ); // Doing this may make further deductions possible. WumpusManager.deduce( room ); // I'm not sure if a 3rd deduction is actually possible, but it // doesn't hurt to try. WumpusManager.deduce( room ); } private static final Pattern ENCOUNTER_PATTERN = Pattern.compile( "Encounter: (.*)" ); private static final Pattern EXITS_PATTERN = Pattern.compile( "Exits: (.*), (.*), (.*)" ); public static String reconstructResponseText( final String encounter, final String exits, final String sounds ) { if ( encounter == null ) { return null; } Matcher m = WumpusManager.ENCOUNTER_PATTERN.matcher( encounter ); if ( !m.find() ) { return null; } StringBuilder buffer = new StringBuilder(); buffer.append( "<b>" ); buffer.append( m.group( 1 ) ); buffer.append ("</b>" ); buffer.append( "\n" ); if ( exits != null ) { m = WumpusManager.EXITS_PATTERN.matcher( exits ); if ( m.find() ) { buffer.append( "Enter " ); buffer.append( m.group( 1 ) ); buffer.append( "\n" ); buffer.append( "Enter " ); buffer.append( m.group( 2 ) ); buffer.append( "\n" ); buffer.append( "Enter " ); buffer.append( m.group( 3 ) ); buffer.append( "\n" ); } } if ( sounds != null ) { if ( sounds.contains( "pit" ) ) { buffer.append( "You hear a low roaring sound nearby, like the echo of wind in an empty space.\n" ); } if ( sounds.contains( "bats" ) ) { buffer.append( "You hear the fluttering of wings and a high-pitched squeaking coming from somewhere nearby.\n" ); } if ( sounds.contains( "Wumpus" ) ) { buffer.append( "Spatters of blood covering the floor and an overpowering stench indicate that the Wumpus must be nearby.\n" ); } } return buffer.toString(); } private static void knownSafe( final int type ) { WumpusManager.knownSafe( WumpusManager.current, type ); } private static void knownSafe( final Room room, final int type ) { // Set Wumpinator flags for this room room.bat = 9; room.pit = 9; room.wumpus = 9; WumpusManager.knownHazard( room, WARN_SAFE, type ); } private static void knownBats( final int type ) { WumpusManager.knownBats( WumpusManager.current, type ); } private static void knownBats( final Room room, final int type ) { // If we already know there are bats here, punt if ( room.bat == 8 ) { return; } // Set Wumpinator flags for this room room.bat = 8; room.pit = 9; room.wumpus = 9; // We know an additional bat room WumpusManager.knownHazard( room, WARN_BATS, type ); // There are exactly two bat rooms per cave if ( WumpusManager.bats1 == null ) { // We've just identified the first bat room WumpusManager.bats1 = room; return; } // We've just identified the second bat room WumpusManager.bats2 = room; // Eliminate bats from rooms that have only "possible" bats WumpusManager.eliminateHazard( WARN_BATS ); } private static void knownPit( final int type ) { WumpusManager.knownPit( WumpusManager.current, type ); } private static void knownPit( final Room room, final int type ) { // If we already know there is a pit here, punt if ( room.pit == 8 ) { return; } // Set Wumpinator flags for this room room.bat = 9; room.pit = 8; room.wumpus = 9; // We know an additional pit WumpusManager.knownHazard( room, WARN_PIT, type ); // There are exactly two pit rooms per cave if ( WumpusManager.pit1 == null ) { // We've just identified the first pit room WumpusManager.pit1 = room; return; } // We've just identified the second pit room WumpusManager.pit2 = room; // Eliminate pits from rooms that have only "possible" pit WumpusManager.eliminateHazard( WARN_PIT ); } private static void knownWumpus( final int type ) { WumpusManager.knownWumpus( WumpusManager.current, type ); } private static void knownWumpus( final Room room, final int type ) { // If we already know the Wumpus is here, punt if ( room.wumpus == 8 ) { return; } // Set Wumpinator flags for this room room.bat = 9; room.pit = 9; room.wumpus = 8; // We know the wumpus room WumpusManager.knownHazard( room, WARN_WUMPUS, type ); // We've just identified the wumpus room WumpusManager.wumpus = room; // Eliminate wumpus from rooms that have only "possible" wumpus WumpusManager.eliminateHazard( WARN_WUMPUS ); } private static void possibleHazard( final Room room, int warn ) { // If we have already been to this room, leave it alone if ( room.visited ) { return; } // If we have already positively identified the hazards in this // room, leave it alone int hazards = room.getHazards(); if ( ( hazards & WARN_INDEFINITE ) == 0 ) { return; } // We hear various sounds from an adjacent room. Mark // this room as a possible source. // If it is possible there are bats in this room... if ( (hazards & WARN_BATS ) != 0 ) { if ( ( warn & WARN_BATS ) != 0 ) { // If we know both bat rooms, no bats in this room. if ( WumpusManager.bats1 != null && WumpusManager.bats2 != null ) { warn &= ~WARN_BATS; } else if ( ++room.bat == 3 ) { WumpusManager.knownBats( room, DEDUCTION ); return; } } else { WumpusManager.eliminateHazard( room, WARN_BATS ); } } // If it is possible there is a pit in this room... if ( ( hazards & WARN_PIT ) != 0 ) { if ( ( warn & WARN_PIT ) != 0 ) { // If we know both pit rooms, no pit in this room. if ( WumpusManager.pit1 != null && WumpusManager.pit2 != null ) { warn &= ~WARN_PIT; } else if ( ++room.pit == 3 ) { WumpusManager.knownPit( room, DEDUCTION ); return; } } else { WumpusManager.eliminateHazard( room, WARN_PIT ); } } // If it is possible the Wumpus is in this room... if ( ( hazards & WARN_WUMPUS ) != 0 ) { if ( ( warn & WARN_WUMPUS ) != 0 ) { // If we know the Wumpus room, no Wumpus in this room. if ( WumpusManager.wumpus != null ) { warn &= ~WARN_WUMPUS; } else if ( ++room.wumpus == 2 ) { WumpusManager.knownWumpus( room, DEDUCTION ); return; } } else { WumpusManager.eliminateHazard( room, WARN_WUMPUS ); } } if ( warn == WARN_INDEFINITE ) { // We know there are no hazards in this room room.pit = 9; room.bat = 9; room.wumpus = 9; } // Register possible hazard int oldStatus = room.setHazards( warn ); int newStatus = room.getHazards(); if ( oldStatus == newStatus ) { return; } // New deduction String warnString = WumpusManager.WARN_STRINGS[ newStatus ]; WumpusManager.addDeduction( "Listen: " + warnString + " in " + room ); } private static void knownHazard( final Room room, int warn, final int type ) { // Remember that the room has been visited. if ( type == VISIT ) { room.visited = true; } int oldStatus = room.setHazards( warn ); int newStatus = room.getHazards(); if ( oldStatus == newStatus ) { return; } // New deduction String idString = WumpusManager.DEDUCTION_STRINGS[ type ]; String warnString = WumpusManager.WARN_STRINGS[ newStatus ]; WumpusManager.addDeduction( idString + ": " + warnString + " in " + room ); // Look at neighbors and see if what we learned in // this one tells us more about other rooms. WumpusManager.deduceNeighbors( room ); } private static void eliminateHazard( final int hazard ) { for ( Room room : WumpusManager.rooms.values() ) { WumpusManager.eliminateHazard( room, hazard ); } } private static void eliminateHazard( final Room room, int hazard ) { // If we've already positively identified this room, // leave it alone int warn = room.getHazards(); if ( ( warn & WARN_INDEFINITE ) == 0 ) { return; } if ( ( hazard & WARN_PIT ) != 0 ) { room.pit = 9; } if ( ( hazard & WARN_BATS ) != 0 ) { room.bat = 9; } if ( ( hazard & WARN_WUMPUS ) != 0 ) { room.wumpus = 9; } int oldStatus = room.setHazards( warn & ~hazard ); int newStatus = room.getHazards(); if ( oldStatus == newStatus ) { return; } // New deduction String warnString = WumpusManager.ELIMINATE_STRINGS[ hazard ]; WumpusManager.addDeduction( "Deduction: " + warnString + " in " + room ); } private static void deduceNeighbors( final Room room ) { // We've learned something new about this room. // Examine all adjacent rooms that we have visited and // heard something from and see if we can deduce // anything more. Room [] exits = room.getExits(); for ( int i = 0; i < exits.length; ++i) { Room neighbor = exits[i]; if ( neighbor != null && neighbor.visited && neighbor.getListen() != WARN_INDEFINITE ) { WumpusManager.deduce( neighbor ); } } } private static void deduce( final Room room ) { if ( room != null ) { WumpusManager.deduce( room, WARN_BATS ); WumpusManager.deduce( room, WARN_PIT ); WumpusManager.deduce( room, WARN_WUMPUS ); } } private static void deduce( final Room room, int mask ) { Room exit = null; for ( int i = 0; i < 3; ++i ) { Room link = room.getExit( i ); if ( link == null ) { // Internal error continue; } if ( ( link.getHazards() & mask ) != 0 ) { if ( exit != null ) { return; // warning not unique } exit = link; } } if ( exit == null ) { return; } switch ( mask ) { case WARN_BATS: WumpusManager.knownBats( exit, ELIMINATION ); break; case WARN_PIT: WumpusManager.knownPit( exit, ELIMINATION ); break; case WARN_WUMPUS: WumpusManager.knownWumpus( exit, ELIMINATION ); break; } } public static void takeChoice( int decision, String text ) { if ( WumpusManager.current == null ) { return; } // There can be 6 decisions - stroll into 3 rooms or charge // into 3 rooms. if ( decision > 3 ) { WumpusManager.monsterIsWumpus = false; decision -= 3; } Room room = WumpusManager.current.getExit( decision - 1 ); if ( room == null ) { // Internal error KoLmafia.updateDisplay( MafiaState.ERROR, "Internal error: unknown exit #" + decision + " from " + WumpusManager.current ); return; } // Unfortunately, the wumpus was nowhere to be seen. if ( text.contains( "wumpus was nowhere to be seen" ) ) { WumpusManager.last = WumpusManager.current; WumpusManager.eliminateHazard( room, WARN_WUMPUS ); return; } // You stroll nonchalantly into the cavern chamber and find, // unexpectedly, a wumpus. // or // Now that you have successfully snuck up and surprised the // wumpus, it doesn't seem to really know how to react. if ( text.contains( "unexpectedly, a wumpus" ) || text.contains( "surprised the wumpus" ) || text.contains( "darkness.gif" ) ) { WumpusManager.last = WumpusManager.current; WumpusManager.knownWumpus( room, VISIT ); return; } } public static void preWumpus( int decision ) { WumpusManager.monsterIsWumpus = decision > 3; } public static boolean isWumpus() { return WumpusManager.monsterIsWumpus; } public static String[] dynamicChoiceOptions( String text ) { if ( WumpusManager.current == null ) { String[] results = new String[ 3 ]; results[ 0 ] = ""; results[ 1 ] = ""; return results; } String[] results = new String[ 6 ]; for ( int i = 0; i < 3; ++i ) { Room room = WumpusManager.current.getExit( i ); if ( room == null ) { // Internal error continue; } String warning = WumpusManager.WARN_STRINGS[ room.getHazards() ]; results[ i ] = warning; results[ i + 3 ] = warning; } return results; } private static void printDeduction( final String text ) { RequestLogger.printLine( text ); RequestLogger.updateSessionLog( text ); } private static void addDeduction( final String text ) { // Print the string to the CLI and session log WumpusManager.printDeduction( text ); if ( WumpusManager.deductions.length() == 0 ) { WumpusManager.deductions.append( "<center>" ); } else { WumpusManager.deductions.append( "<br>" ); } WumpusManager.deductions.append( text ); } public static final void decorate( final StringBuffer buffer ) { if ( WumpusManager.current != null ) { int index = buffer.indexOf( "</table></center></td></tr>" ); if ( index != -1 ) { String link = WumpusManager.getWumpinatorMap(); // String link = WumpusManager.getWumpinatorLink(); buffer.insert( index, "<tr><td><center>" + link + "</center></td></tr>" ); } } if ( WumpusManager.deductions.length() == 0 ) { return; } int index = buffer.indexOf( "name=choiceform1" ); if ( index == -1 ) { return; } index = buffer.lastIndexOf( "<center><form ", index ); if ( index == -1 ) { return; } WumpusManager.deductions.append( "</center><br>" ); buffer.insert( index, WumpusManager.deductions.toString() ); WumpusManager.deductions.setLength( 0 ); } public static final String getWumpinatorURL() { String code = WumpusManager.getWumpinatorCode(); String current = WumpusManager.getCurrentField(); return "http://www.feesher.com/wumpus/wump_map.php?mapstring=" + code + current; } public static final void invokeWumpinator() { RelayLoader.openSystemBrowser( WumpusManager.getWumpinatorURL() ); } private static final Room currentRoom() { if ( WumpusManager.current != null ) { return WumpusManager.current; } if ( WumpusManager.last != null ) { return WumpusManager.last; } return null; } private static final String getCurrentField() { return WumpusManager.getCurrentField( WumpusManager.currentRoom() ); } private static final String getCurrentField( final Room room ) { if ( room == null ) { return ""; } return "¤t=" + room.getCode(); } private static final String getWumpinatorLink() { String code = WumpusManager.getWumpinatorCode(); String current = WumpusManager.getCurrentField(); return "<a href=http://www.feesher.com/wumpus/wump_map.php?mapstring=" + code + current + " target=_blank>View in Wumpinator</a>"; } private static final String getWumpinatorMap() { String layout = WumpusManager.getLayout(); // If we can't generate a map, give a link to Wumpinator if ( layout == null ) { return WumpusManager.getWumpinatorLink(); } String litstring = "litstring=" + layout; String code = "&map=" + WumpusManager.getWumpinatorCode(); String current = WumpusManager.getCurrentField(); return "<tr><td><center><img border=0 src=http://www.feesher.com/wumpus/wump_graphic3.php?" + litstring + code + current + "></center></td></tr>"; } public static final void printStatus() { // Since we use a TreeMap, rooms are in alphabetical order for ( Room room : WumpusManager.rooms.values() ) { String name = room.toString(); String exits = room.shortExitString(); String pit = room.pit == 9 ? "no pit" : room.pit == 8 ? "PIT" : String.valueOf( room.pit ); String bats = room.bat == 9 ? "no bats" : room.bat == 8 ? "BATS" : String.valueOf( room.bat ); String wumpus = room.wumpus == 9 ? "no wumpus" : room.wumpus == 8 ? "WUMPUS" : String.valueOf( room.wumpus ); RequestLogger.printLine( name + ": exits = " + exits + ": " + pit + ", " + bats + ", " + wumpus ); } } // Here's how it's set up: // * It starts with 20 blocks of 6 characters each, each block // corresponding to a particular room. Room "A" is the first block, // room "B" is the second block, etc. // * After the basic block, there's a "::P" delimiter, followed by a set // of characters used to indicate the various "pit groups" that have // been found. Then a "::B" delimiter, followed by a set of characters // used to indicate the various "bat groups". // * Within a room block, the 6 characters are as follows: // - First path destination (0 if unknown, room letter otherwise) // - Second path destination (ditto) // - Third path destination (ditto) // - Pit flag for this room // - Bat flag for this room // - Wumpus flag for this room // // The flags are set as: 0=unknown, 8=hazard confirmed, 9=confirmed // clear, other # = number of potential clues pointing at the hazard so // far. // // Bat groups and pit groups are always separated by colons. Each group // shows the three rooms that were flagged as destinations from a room // with a roaring or squeaking sound. // // So, for example, if you know that room A maps to B, C, and W, and you // can hear a roaring sound from room A, the mapstring looks like this: // // BCW999A00199A0019900 00000000000000000000 00000000000000000000 // 00000000000000000000 00000000000000000000 00000000000000A00199 // ::P:BCW::B // // Room A has been visited, so all its hazard flags are 9 (not // found). Rooms B, C, and W have "1" in the pit hazard flag (meaning // that one adjacent room flags it as a possible pit) and "9" in the bat // and wumpus flags (no chance of either hazard in these rooms). The // only pit group is BCW, and there is no data entered for bat groups. // // If we then add data for room D, mapping to C, E, and F, and with both // bats AND pits detected, the mapstring looks like this: // // BCW999A00199AD0299CE F999D00119D001190000 00000000000000000000 // 00000000000000000000 00000000000000000000 00000000000000A00199 // ::P:BCW:CEF::B:CEF public static final String getWumpinatorCode() { StringBuffer buffer = new StringBuffer(); // Since we use a TreeMap, rooms are in alphabetical order for ( Room room : WumpusManager.rooms.values() ) { // Append code letters for each exit for ( int i = 0; i < 3; ++i ) { Room exit = room.getExit( i ); buffer.append( exit == null ? "0" : exit.getCode() ); } // Append Wumpinator hazard flags buffer.append( String.valueOf( room.pit % 10 ) ); buffer.append( String.valueOf( room.bat % 10 ) ); buffer.append( String.valueOf( room.wumpus % 10 ) ); } // Append pit groups buffer.append( "::P" ); for ( Room room : WumpusManager.rooms.values() ) { if ( ( room.getListen() & WARN_PIT ) == 0 ) { continue; } buffer.append( ":" ); for ( int i = 0; i < 3; ++i ) { Room exit = room.getExit( i ); buffer.append( exit.getCode() ); } } // Append bat groups buffer.append( "::B" ); for ( Room room : WumpusManager.rooms.values() ) { if ( ( room.getListen() & WARN_BATS ) == 0 ) { continue; } buffer.append( ":" ); for ( int i = 0; i < 3; ++i ) { Room exit = room.getExit( i ); buffer.append( exit.getCode() ); } } return buffer.toString(); } private static final int[][][] NODE_PERMUTATIONS = { // 0 = { 1, 2, 5 } { { 1, 2, 5 }, { 1, 5, 2 }, { 2, 1, 5 }, { 2, 5, 1 }, { 5, 1, 2 }, { 5, 2, 1 } }, // 1 = { 0, 3, 6 } { { 0, 3, 6 }, { 0, 6, 3 }, { 3, 0, 6 }, { 3, 6, 0 }, { 6, 0, 3 }, { 6, 3, 0 } }, // 2 = { 0, 4, 7 } { { 0, 4, 7 }, { 0, 7, 4 }, { 4, 0, 7 }, { 4, 7, 0 }, { 7, 0, 4 }, { 7, 4, 0 } }, // 3 = { 1, 4, 8 } { { 1, 4, 8 }, { 1, 8, 4 }, { 4, 1, 8 }, { 4, 8, 1 }, { 8, 1, 4 }, { 8, 4, 1 } }, // 4 = { 2, 3, 9 } { { 2, 3, 9 }, { 2, 9, 3 }, { 3, 2, 9 }, { 3, 9, 2 }, { 9, 2, 3 }, { 9, 3, 2 } }, // 5 = { 0, 10, 11 } { { 0, 10, 11 }, { 0, 11, 10 }, { 10, 0, 11 }, { 10, 11, 0 }, { 11, 0, 10 }, { 11, 10, 0 } }, // 6 = { 1, 10, 12 } { { 1, 10, 12 }, { 1, 12, 10 }, { 10, 1, 12 }, { 10, 12, 1 }, { 12, 1, 10 }, { 12, 10, 1 } }, // 7 = { 2, 11, 13 } { { 2, 11, 13 }, { 2, 13, 11 }, { 11, 2, 13 }, { 11, 13, 2 }, { 13, 2, 11 }, { 13, 11, 2 } }, // 8 = { 3, 12, 14 } { { 3, 12, 14 }, { 3, 14, 12 }, { 12, 3, 14 }, { 12, 14, 3 }, { 14, 3, 12 }, { 14, 12, 3 } }, // 9 = { 4, 13, 14 } { { 4, 13, 14 }, { 4, 14, 13 }, { 13, 4, 14 }, { 13, 14, 4 }, { 14, 4, 13 }, { 14, 13, 4 } }, // 10 = { 5, 6, 15 } { { 5, 6, 15 }, { 5, 15, 6 }, { 6, 5, 15 }, { 6, 15, 5 }, { 15, 5, 6 }, { 15, 6, 5 } }, // 11 = { 5, 7, 16 } { { 5, 7, 16 }, { 5, 16, 7 }, { 7, 5, 16 }, { 7, 16, 5 }, { 16, 5, 7 }, { 16, 7, 5 } }, // 12 = { 6, 8, 17 } { { 6, 8, 17 }, { 6, 17, 8 }, { 8, 6, 17 }, { 8, 17, 6 }, { 17, 6, 8 }, { 17, 8, 6 } }, // 13 = { 7, 9, 18 } { { 7, 9, 18 }, { 7, 18, 9 }, { 9, 7, 18 }, { 9, 18, 7 }, { 18, 7, 9 }, { 18, 9, 7 } }, // 14 = { 8, 9, 19 } { { 8, 9, 19 }, { 8, 19, 9 }, { 9, 8, 19 }, { 9, 19, 8 }, { 19, 8, 9 }, { 19, 9, 8 } }, // 15 = { 10, 16, 17 } { { 10, 16, 17 }, { 10, 17, 16 }, { 16, 10, 17 }, { 16, 17, 10 }, { 17, 10, 16 }, { 17, 16, 10 } }, // 16 = { 11, 15, 18 } { { 11, 15, 18 }, { 11, 18, 15 }, { 15, 11, 18 }, { 15, 18, 11 }, { 18, 11, 15 }, { 18, 15, 11 } }, // 17 = { 12, 15, 19 } { { 12, 15, 19 }, { 12, 19, 15 }, { 15, 12, 19 }, { 15, 19, 12 }, { 19, 12, 15 }, { 19, 15, 12 } }, // 18 = { 13, 16, 19 } { { 13, 16, 19 }, { 13, 19, 16 }, { 16, 13, 19 }, { 16, 19, 13 }, { 19, 13, 16 }, { 19, 16, 13 } }, // 19 = { 14, 17, 18 } { { 14, 17, 18 }, { 14, 18, 17 }, { 17, 14, 18 }, { 17, 18, 14 }, { 18, 14, 17 }, { 18, 17, 14 } }, }; private static Room [] layout = new Room[20]; private final static String emptyLayout = "00000000000000000000"; private static final String getLayout() { Room current = WumpusManager.currentRoom(); String layout = WumpusManager.getLayout( current ); if ( !layout.equals( emptyLayout ) ) { return layout; } // We failed to generate a layout from the specified // room. Try again with any visited room. for ( Room room : WumpusManager.rooms.values() ) { if ( room == current || !room.visited ) { continue; } layout = WumpusManager.getLayout( room ); if ( !layout.equals( emptyLayout ) ) { return layout; } } // Sigh. return null; } private static final String getLayout( final Room room ) { // Initialize layout for ( int i = 0; i < layout.length; ++i ) { layout[ i ] = null; } // Calculate layout with specified room in position 0 WumpusManager.addRoom( 0, room ); // Generate layout string StringBuffer buffer = new StringBuffer(); for ( int i = 0; i < layout.length; ++i ) { Room node = layout[ i ]; buffer.append( node == null ? "0" : node.getCode() ); } String string = buffer.toString(); return string; } private static final boolean addRoom( final int node, final Room room ) { // Attempt to add a room at a particular node if ( layout[ node ] != null ) { // It's OK if this room is already there return layout[ node ] == room; } // If room is already present elsewhere, error for ( int i = 0; i < layout.length; ++i ) { if ( layout[ i ] == room ) { return false; } } // Put this room into the specified position layout[ node ] = room; // Attempt to place the exits in linked nodes Room [] exits = room.getExits(); int [][] permutations = WumpusManager.NODE_PERMUTATIONS[ node ]; for ( int i = 0; i < permutations.length; ++i ) { // Save a copy of the layout so we can easily unwind Room [] copy = (Room [])WumpusManager.layout.clone(); int [] links = permutations[i]; boolean success = true; for ( int j = 0; j < 3; ++j ) { if ( exits[ j ] == null ) { continue; } if ( !addRoom( links[ j ], exits[ j ] ) ) { success = false; break; } } // If we successfully recursively placed the // rooms, return now. if ( success ) { return true; } // Otherwise, restore previous state and try // next permutation WumpusManager.layout = copy; } // We failed to place this room. layout[ node ] = null; return false; } private static class Room { public final String name; public final String code; public boolean visited; public Room[] exits = new Room[3]; // Our flags public int listen; public int hazards; // Wumpinator flags public int pit; public int bat; public int wumpus; public Room( final String name ) { this.name = name; this.code = Character.toString( Character.toUpperCase( name.charAt(0) ) ); this.reset(); } public void reset() { this.visited = false; this.exits[ 0 ] = null; this.exits[ 1 ] = null; this.exits[ 2 ] = null; this.listen = WARN_INDEFINITE; this.hazards = WARN_ALL; this.pit = 0; this.bat = 0; this.wumpus = 0; } public String getName() { return this.name; } public String getCode() { return this.code; } public int getListen() { return this.listen; } public void setListen( final int listen ) { this.listen = listen; } public Room[] getExits() { return this.exits; } public Room getExit( final int index ) { return this.exits[ index ]; } public void setExit( final int index, final Room room ) { this.exits[ index ] = room; } public void addExit( final Room room ) { for ( int index = 0; index < 3; ++index ) { Room exit = this.exits[ index ]; if ( exit == room ) { return; } if ( exit == null ) { this.exits[ index ] = room; return; } } } public boolean hasExit( final Room room ) { for ( int index = 0; index < 3; ++index ) { if ( this.exits[ index ] == room ) { return true; } } return false; } public String exitString() { String exit1 = this.exits[0] == null ? "unknown" : this.exits[0].toString(); String exit2 = this.exits[1] == null ? "unknown" : this.exits[1].toString(); String exit3 = this.exits[2] == null ? "unknown" : this.exits[2].toString(); return exit1 + ", " + exit2 + ", " + exit3; } public String shortExitString() { String exit1 = this.exits[0] == null ? "unknown" : this.exits[0].getName(); String exit2 = this.exits[1] == null ? "unknown" : this.exits[1].getName(); String exit3 = this.exits[2] == null ? "unknown" : this.exits[2].getName(); return exit1 + ", " + exit2 + ", " + exit3; } public String listenString() { StringBuilder buffer = new StringBuilder(); String delim = ""; if ( ( this.listen & WARN_BATS ) != 0 ) { buffer.append( delim ); buffer.append( "bats" ); delim = ", "; } if ( ( this.listen & WARN_PIT ) != 0 ) { buffer.append( delim ); buffer.append( "pit" ); delim = ", "; } if ( ( this.listen & WARN_WUMPUS ) != 0 ) { buffer.append( delim ); buffer.append( "Wumpus" ); delim = ", "; } String sounds = buffer.toString(); return sounds.equals( "" ) ? "none" : sounds; } public int getHazards() { return this.hazards; } public int setHazards( final int hazards ) { // Only set the hazards for rooms we've not // actually visited. int old = this.hazards; if ( ( old & WARN_INDEFINITE ) != 0 ) { this.hazards = old & hazards; } return old; } @Override public String toString() { return "the " + this.name + " chamber"; } } }