Advertisement
Guest User

COTO Mastermind Java Code Rev. 1

a guest
Aug 29th, 2014
500
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 47.87 KB | None | 0 0
  1. /* Fastermind
  2. *  By COTO
  3. *  Entry for the Mastermind Horse Battery Staple Challenge
  4. *  Much code, and way too long spent on this. */
  5.  
  6. import java.io.BufferedReader;
  7. import java.io.FileReader;
  8. import java.io.IOException;
  9. import java.util.*;
  10.  
  11.  
  12. public final class Fastermind {
  13.     // Set to true to have a console printout of individual solutions.
  14.     static final boolean SHOW_ALL_OUTPUTS = false;
  15.  
  16.     // Set to true to have the oracle declare its query strings, pausing for .5 seconds after each.
  17.     static final boolean ORACLE_DECLARES_QUERIES = false;
  18.  
  19.     // The "full" oracle. This is my implementation. Identical to the reference version except for improvements in
  20.     // efficiency and the option to declare queries.
  21.     static final class FullOracle implements Oracle {
  22.         final char[] chKeyChars;
  23.         final char[] chSortedKeyChars;
  24.         int iQueryCount = 0;
  25.  
  26.         FullOracle( String sKey ) {
  27.             chKeyChars = sKey.toCharArray();
  28.             chSortedKeyChars = sKey.toCharArray();
  29.             Arrays.sort( chSortedKeyChars );
  30.         }
  31.  
  32.         public Outcome query( QueryString qsCandidate ) {
  33.             String sCandidate = qsCandidate.contents();
  34.  
  35.             iQueryCount++;
  36.             if( Fastermind.ORACLE_DECLARES_QUERIES ) {
  37.                 System.out.println( "Key: \"" + new String( chKeyChars ) + "\"\nQry: \"" + sCandidate + "\"" );
  38.                 try {
  39.                     Thread.sleep( 250 );
  40.                 } catch( InterruptedException IEx ) { /* ignore */ }
  41.             }
  42.  
  43.             char[] chCandChars = sCandidate.toCharArray();
  44.             char[] chSortedCandChars = sCandidate.toCharArray();
  45.             Arrays.sort( chSortedCandChars );
  46.  
  47.             int nMatch = 0;
  48.             int iDomain = Math.min( chCandChars.length, chKeyChars.length );
  49.             for( int i = 0; i < iDomain; i++ )
  50.                 if( chCandChars[i] == chKeyChars[i] )
  51.                     nMatch++;
  52.  
  53.             int nCoincide = 0;
  54.             int i1 = 0, i2 = 0;
  55.             while( i1 < chSortedCandChars.length && i2 < chSortedKeyChars.length ) {
  56.                 if( chSortedCandChars[i1] == chSortedKeyChars[i2] ) {
  57.                     nCoincide++;
  58.                     i1++;
  59.                     i2++;
  60.                 } else if( chSortedCandChars[i1] < chSortedKeyChars[i2] )
  61.                     i1++;
  62.                 else i2++;
  63.             }
  64.             if( nMatch == chKeyChars.length )
  65.                 return Outcome.VALID;
  66.  
  67.             return new Outcome( nMatch, nCoincide - nMatch );
  68.         }
  69.         int getQueryCount() {
  70.             return iQueryCount;
  71.         }
  72.     }
  73.     // END OF ORACLE ---- <
  74.  
  75.  
  76.     static final int UNKNOWN_COUNT = -1;
  77.  
  78.     static final char ZERO_COUNT_PLACEHOLDER = '-';
  79.     static final char RESOLVED_CHAR_PLACEHOLDER = '+';
  80.  
  81.     static char[] ALPHABET;
  82.     static String ALPHABET_STRING;
  83.     static List<Integer> ASCENDING_ORDER;
  84.  
  85.     public static void main( String[] sArgs ) {
  86.         final SortedSet<String> sWords = readInDictionary( "./dict.txt" );
  87.  
  88.         // Compute the alphabet...
  89.         final Map<Character,Integer> M_CharFrequencies = new HashMap<Character, Integer>( 32 );
  90.         for( String sWord : sWords ) {
  91.             for( char ch : sWord.toCharArray() ) {
  92.                 if( M_CharFrequencies.containsKey( ch ) )
  93.                     M_CharFrequencies.put( ch, M_CharFrequencies.get( ch ) + 1 );
  94.                 else M_CharFrequencies.put( ch, 1 );
  95.             }
  96.         }
  97.         List<Character> chAlphabet = new ArrayList<Character>( M_CharFrequencies.keySet() );
  98.         Collections.sort( chAlphabet, new Comparator<Character>() {
  99.             public int compare( Character ch1, Character ch2 ) {
  100.                 return M_CharFrequencies.get( ch2 ) - M_CharFrequencies.get( ch1 );
  101.             }
  102.         });
  103.  
  104.         ALPHABET = new char[chAlphabet.size()];
  105.         for( int i = 0; i < chAlphabet.size(); i++ )
  106.             ALPHABET[i] = chAlphabet.get( i );
  107.  
  108.         ALPHABET_STRING = new String( ALPHABET );
  109.         DictionaryDB DDb = new DictionaryDB( sWords.size() );
  110.  
  111.         // Compute all word metrics...
  112.         final Map<Integer, SortedSet<String>> M_WordsByLength = new HashMap<Integer, SortedSet<String>>( 32 );
  113.         for( String sWord : sWords ) {
  114.             int nChars = sWord.length();
  115.             SortedSet<String> sEnCharWords = M_WordsByLength.get( nChars );
  116.             if( sEnCharWords == null ) {
  117.                 sEnCharWords = new TreeSet<String>();
  118.                 M_WordsByLength.put( nChars, sEnCharWords );
  119.             }
  120.             sEnCharWords.add( sWord );
  121.             DDb.registerWord( sWord );
  122.         }
  123.  
  124.         ASCENDING_ORDER = new ArrayList<Integer>( DDb.iMaxWordLength );
  125.         for( int i = 0; i < DDb.iMaxWordLength; i++ )
  126.             ASCENDING_ORDER.add( i );
  127.  
  128.         // Read in the codes...
  129.         final List<String> sCodes = readInCodesToCrack( "./codes.txt" );
  130.         int nTotalQueries = 0;
  131.         int nDecoded = 0;
  132.         int nMinQueries = Integer.MAX_VALUE;
  133.         int nMaxQueries = 0;
  134.         long iStartTime = System.currentTimeMillis();
  135.  
  136.         // Main loop. Gets a code, creates full oracle for code, identifies code, moves on.
  137.         for( String sCode : sCodes ) {
  138.             final FullOracle Or = new FullOracle( sCode );
  139.             final int[] iFullCounts = new int[ALPHABET.length],
  140.                 iUsedCounts = new int[ALPHABET.length];
  141.  
  142.             Arrays.fill( iFullCounts, UNKNOWN_COUNT );
  143.  
  144.             final CharCounter CC = new CharCounter( iFullCounts, iUsedCounts, DDb );
  145.  
  146.             // Identify location of spaces...
  147.             final int[] iSpaceLocations = getSpaceLocations( Or, CC, DDb.iMaxWordLength );
  148.  
  149.             CC.setSpaceLocations( iSpaceLocations );
  150.  
  151.             // Sort words in terms of decreasing length. Solving larger words first works best.
  152.             final String[] sCodeWords = new String[3];
  153.             List<Integer> iToResolve = Arrays.asList( 0, 1, 2 );
  154.             Collections.sort( iToResolve, new Comparator<Integer>() {
  155.                 public int compare( Integer I1, Integer I2 ) {
  156.                     return CC.iWordLengths[I2] - CC.iWordLengths[I1];
  157.                 }
  158.             } );
  159.  
  160.             for( int iResolveIndex = 0; iResolveIndex < iToResolve.size(); iResolveIndex++ ) {
  161.                 int iResolveWord = iToResolve.get( iResolveIndex );
  162.                 SortedSet<String> sCodeWordPotentials = M_WordsByLength.get( CC.iWordLengths[iResolveWord] );
  163.                 Oracle OrMasked = WordMaskOracle.forCodeWord( Or, CC.iWordLengths, iResolveWord );
  164.  
  165.                 // We clone the full set of potential code words for each word we're resolving. This is a huge waste
  166.                 // of time and memory, but c'est la vie.
  167.                 sCodeWords[iResolveWord] = resolveCodeWord( new TreeSet<String>( sCodeWordPotentials ), CC, OrMasked,
  168.                     iToResolve.subList( iResolveIndex + 1, 3 ) );
  169.             }
  170.  
  171.             // Check that we've done it correctly...
  172.             if( Or.query( new QueryString( sCodeWords[0] +" "+ sCodeWords[1] +" "+ sCodeWords[2] ) ).isExactMatch ) {
  173.                 nTotalQueries += Or.getQueryCount();
  174.                 nDecoded++;
  175.                 nMinQueries = Math.min( Or.getQueryCount(), nMinQueries );
  176.                 nMaxQueries = Math.max( Or.getQueryCount(), nMaxQueries );
  177.  
  178.                 if( SHOW_ALL_OUTPUTS )
  179.                     System.out.println( sCode + " OK: " + Or.getQueryCount() + " queries (" + nTotalQueries + " in " +
  180.                         nDecoded + " = " + Math.round( nTotalQueries/(double)nDecoded*100. )/100. + ")" );
  181.             } else {
  182.                 System.err.println( "Failed to decode: " + sCode );
  183.                 System.exit( 1 );
  184.             }
  185.         }
  186.  
  187.         // Output the only metrics that matter...
  188.         System.out.println( "Decoded " + nDecoded + " items in " + nTotalQueries + " queries (" +
  189.                 Math.round( nTotalQueries/(double)nDecoded*100. )/100. + " queries per item).\n" +
  190.             "Time to complete: " + (System.currentTimeMillis() - iStartTime) + " msec\n" );
  191.  
  192.         System.out.println( "Min queries: " + nMinQueries );
  193.         System.out.println( "Max queries: " + nMaxQueries );
  194.     }
  195.  
  196.     static SortedSet<String> readInDictionary( String sDictFile ) {
  197.         SortedSet<String> sWords = new TreeSet<String>();
  198.         try {
  199.             BufferedReader BR = new BufferedReader( new FileReader( sDictFile ) );
  200.             String sWord;
  201.             while( (sWord = BR.readLine()) != null )
  202.                 sWords.add( sWord );
  203.  
  204.             BR.close();
  205.         } catch( IOException ioe ) {
  206.             throw new RuntimeException( "could not read contents of dictionary file: " + sDictFile );
  207.         }
  208.         return sWords;
  209.     }
  210.  
  211.     static List<String> readInCodesToCrack( String sCodesFile ) {
  212.         List<String> sCodes = new LinkedList<String>();
  213.         try {
  214.             BufferedReader BR = new BufferedReader( new FileReader( sCodesFile ) );
  215.             String sCode;
  216.             while( (sCode = BR.readLine()) != null )
  217.                 if( !sCode.isEmpty() )
  218.                     sCodes.add( sCode );
  219.  
  220.             BR.close();
  221.         } catch( IOException ioe ) {
  222.             throw new RuntimeException( "could not read contents of codes file: " + sCodesFile );
  223.         }
  224.         return sCodes;
  225.     }
  226.  
  227.     static String resolveCodeWord( SortedSet<String> sCodeWordPotentials, final CharCounter CC, Oracle Or,
  228.         List<Integer> iOutstandingWords )
  229.     {
  230.         CC.filterByExclusion( sCodeWordPotentials );
  231.         if( sCodeWordPotentials.size() == 1 ) {
  232.             String sCodeWord = sCodeWordPotentials.first();
  233.  
  234.             CC.commitUsedChars( sCodeWord );
  235.             return sCodeWord;
  236.         }
  237.         CC.filterByNecessity( sCodeWordPotentials, iOutstandingWords );
  238.         if( sCodeWordPotentials.size() == 1 ) {
  239.             String sCodeWord = sCodeWordPotentials.first();
  240.  
  241.             CC.commitUsedChars( sCodeWord );
  242.             return sCodeWord;
  243.         }
  244.  
  245.         MappingOracle MOr = new MappingOracle( new OracleWithTackedOnCharCounter( Or, sCodeWordPotentials, CC ),
  246.             sCodeWordPotentials.first().length() );
  247.  
  248.         FilterFrame FF = new FilterFrame( sCodeWordPotentials, MOr, Or, CC );
  249.  
  250.         while( true ) {
  251.             FF.queryToResolve();
  252.             if( FF.isResolved() )
  253.                 return FF.codeWord();
  254.  
  255.             int iMatchChar = FF.getOutstandingChar();
  256.             WordSearchType WSTy = WordSearchType.FULL;
  257.             Outcome Oc, Oc2;
  258.  
  259.             FF.i0 = 0;
  260.             while( FF.i0 < FF.n ) {
  261.                 switch( WSTy ) {
  262.                 case FULL:
  263.                     int nCharsToMatch = Math.min( FF.n - FF.i0, 4 );
  264.                     if( !FF.queryIsEfficient( iMatchChar, nCharsToMatch ) ) {
  265.                         FF.i0 += nCharsToMatch;
  266.                         continue;
  267.                     }
  268.                     Oc = FF.query( iMatchChar, nCharsToMatch );
  269.                     if( FF.isResolved() )
  270.                         return FF.codeWord();
  271.  
  272.                     switch( nCharsToMatch ) {
  273.                     case 4:
  274.                         if( Oc.nMatch == 4 )
  275.                             WSTy = WordSearchType.FOUR_MATCHES_IN_NEXT_FOUR;
  276.                         else if( Oc.nMatch == 3 )
  277.                             WSTy = WordSearchType.THREE_MATCHES_IN_NEXT_FOUR;
  278.                         else if( Oc.nMatch == 2 )
  279.                             WSTy = WordSearchType.TWO_MATCHES_IN_NEXT_FOUR;
  280.                         else if( Oc.nMatch == 1 )
  281.                             WSTy = WordSearchType.ONE_MATCH_IN_NEXT_FOUR;
  282.                         else FF.filterBy( iMatchChar, false, false, false, false );
  283.                         break;
  284.  
  285.                     case 3:
  286.                         if( Oc.nMatch == 3 )
  287.                             WSTy = WordSearchType.THREE_MATCHES_IN_NEXT_THREE;
  288.                         else if( Oc.nMatch == 2 )
  289.                             WSTy = WordSearchType.TWO_MATCHES_IN_NEXT_THREE;
  290.                         else if( Oc.nMatch == 1 )
  291.                             WSTy = WordSearchType.ONE_MATCH_IN_NEXT_THREE;
  292.                         else FF.filterBy( iMatchChar, false, false, false );
  293.                         break;
  294.  
  295.                     case 2:
  296.                         if( Oc.nMatch == 2 )
  297.                             WSTy = WordSearchType.TWO_MATCHES_IN_NEXT_TWO;
  298.                         else if( Oc.nMatch == 1 )
  299.                             WSTy = WordSearchType.ONE_MATCH_IN_NEXT_TWO;
  300.                         else FF.filterBy( iMatchChar, false, false );
  301.                         break;
  302.  
  303.                     case 1:
  304.                         if( Oc.nMatch == 1 )
  305.                             WSTy = WordSearchType.ONE_MATCH_IN_NEXT_ONE;
  306.                         else FF.filterBy( iMatchChar, false );
  307.                         break;
  308.                     }
  309.                     break;
  310.  
  311.                 case FOUR_MATCHES_IN_NEXT_FOUR:
  312.                 case THREE_MATCHES_IN_NEXT_THREE:
  313.                     throw new InternalError( "didn't expect this with standard dictionary" );
  314.  
  315.                 case THREE_MATCHES_IN_NEXT_FOUR:
  316.                     WSTy = WordSearchType.FULL;
  317.                     if( FF.pare( new String[] { "+++-", "++-+", "+-++", "-+++" }, iMatchChar, true ) )
  318.                         return FF.codeWord();
  319.                     if( !FF.queryIsEfficient( iMatchChar, 2 ) ) {
  320.                         FF.i0 += 4;
  321.                         break;
  322.                     }
  323.                     Oc = FF.query( iMatchChar, 2 );
  324.                     if( FF.isResolved() )
  325.                         return FF.codeWord();
  326.  
  327.                     if( Oc.nMatch == 1 ) {
  328.                         if( FF.pare( new String[] { "+-", "-+" }, iMatchChar ) )
  329.                             return FF.codeWord();
  330.                         if( !FF.queryIsEfficient( iMatchChar, 1 ) ) {
  331.                             FF.i0 += 4;
  332.                             break;
  333.                         }
  334.                         Oc = FF.query( iMatchChar, 1 );
  335.                         if( FF.isResolved() )
  336.                             return FF.codeWord();
  337.                         if( Oc.nMatch == 1 )
  338.                             FF.filterBy( iMatchChar, true, false, true, true );
  339.                         else FF.filterBy( iMatchChar, false, true, true, true );
  340.                     } else {
  341.                         if( FF.pare( new String[] { "+++-", "++-+" }, iMatchChar ) )
  342.                             return FF.codeWord();
  343.                         if( !FF.queryIsEfficient( iMatchChar, 3 ) ) {
  344.                             FF.i0 += 4;
  345.                             break;
  346.                         }
  347.                         Oc = FF.query( iMatchChar, 3 );
  348.                         if( FF.isResolved() )
  349.                             return FF.codeWord();
  350.                         if( Oc.nMatch == 3 )
  351.                             FF.filterBy( iMatchChar, true, true, true, false );
  352.                         else FF.filterBy( iMatchChar, true, true, false, true );
  353.                     }
  354.                     break;
  355.  
  356.                 case TWO_MATCHES_IN_NEXT_FOUR:
  357.                     WSTy = WordSearchType.FULL;
  358.                     if( FF.pare( new String[] { "++--", "+-+-", "+--+", "-++-", "-+-+", "--++" }, iMatchChar ) )
  359.                         return FF.codeWord();
  360.                     if( !FF.queryIsEfficient( iMatchChar, 2 ) ) {
  361.                         FF.i0 += 4;
  362.                         break;
  363.                     }
  364.  
  365.                     Oc = FF.query( iMatchChar, 2 );
  366.                     if( FF.isResolved() )
  367.                         return FF.codeWord();
  368.  
  369.                     if( Oc.nMatch == 2 )
  370.                         FF.filterBy( iMatchChar, true, true, false, false );
  371.                     else if( Oc.nMatch == 0 )
  372.                         FF.filterBy( iMatchChar, false, false, true, true );
  373.                     else {
  374.                         if( FF.pare( new String[] { "+-+-", "-++-", "+--+", "-+-+" }, iMatchChar ) )
  375.                             return FF.codeWord();
  376.                         if( !FF.queryIsEfficient( iMatchChar, 1 ) ) {
  377.                             FF.i0 += 4;
  378.                             break;
  379.                         }
  380.                         Oc = FF.query( iMatchChar, 1 );
  381.                         if( FF.isResolved() )
  382.                             return FF.codeWord();
  383.  
  384.                         if( FF.pare( Oc.nMatch == 1 ? new String[] { "+" } : new String[] { "-" }, iMatchChar ) )
  385.                             return FF.codeWord();
  386.                         if( !FF.queryIsEfficient( iMatchChar, 3 ) ) {
  387.                             FF.i0 += 4;
  388.                             break;
  389.                         }
  390.                         Oc2 = FF.query( iMatchChar, 3 );
  391.                         if( FF.isResolved() )
  392.                             return FF.codeWord();
  393.  
  394.                         if( Oc.nMatch == 1 && Oc2.nMatch == 2 )
  395.                             FF.filterBy( iMatchChar, true, false, true, false );
  396.                         else if( Oc.nMatch == 1 )
  397.                             FF.filterBy( iMatchChar, true, false, false, true );
  398.                         else if( Oc2.nMatch == 2 )
  399.                             FF.filterBy( iMatchChar, false, true, true, false );
  400.                         else FF.filterBy( iMatchChar, false, true, false, true );
  401.                     }
  402.                     break;
  403.  
  404.                 case ONE_MATCH_IN_NEXT_FOUR:
  405.                     WSTy = WordSearchType.FULL;
  406.                     if( FF.pare( new String[] { "+---", "-+--", "--+-", "---+" }, iMatchChar ) )
  407.                         return FF.codeWord();
  408.                     if( !FF.queryIsEfficient( iMatchChar, 2 ) ) {
  409.                         FF.i0 += 4;
  410.                         break;
  411.                     }
  412.                     Oc = FF.query( iMatchChar, 2 );
  413.                     if( FF.isResolved() )
  414.                         return FF.codeWord();
  415.  
  416.                     if( Oc.nMatch == 1 ) {
  417.                         if( FF.pare( new String[] { "+-", "-+" }, iMatchChar ) )
  418.                             return FF.codeWord();
  419.                         if( !FF.queryIsEfficient( iMatchChar, 1 ) ) {
  420.                             FF.i0 += 4;
  421.                             break;
  422.                         }
  423.                         Oc = FF.query( iMatchChar, 1 );
  424.                         if( FF.isResolved() )
  425.                             return FF.codeWord();
  426.                         if( Oc.nMatch == 1 )
  427.                             FF.filterBy( iMatchChar, true, false, false, false );
  428.                         else FF.filterBy( iMatchChar, false, true, false, false );
  429.                     } else {
  430.                         if( FF.pare( new String[] { "--+-", "---+" }, iMatchChar ) )
  431.                             return FF.codeWord();
  432.                         if( !FF.queryIsEfficient( iMatchChar, 3 ) ) {
  433.                             FF.i0 += 4;
  434.                             break;
  435.                         }
  436.                         Oc = FF.query( iMatchChar, 3 );
  437.                         if( FF.isResolved() )
  438.                             return FF.codeWord();
  439.                         if( Oc.nMatch == 1 )
  440.                             FF.filterBy( iMatchChar, false, false, true, false );
  441.                         else FF.filterBy( iMatchChar, false, false, false, true );
  442.                     }
  443.                     break;
  444.  
  445.                 case TWO_MATCHES_IN_NEXT_THREE:
  446.                     WSTy = WordSearchType.FULL;
  447.                     if( FF.pare( new String[] { "++-", "+-+", "-++" }, iMatchChar ) )
  448.                         return FF.codeWord();
  449.                     if( !FF.queryIsEfficient( iMatchChar, 2 ) ) {
  450.                         FF.i0 += 3;
  451.                         break;
  452.                     }
  453.                     Oc = FF.query( iMatchChar, 2 );
  454.                     if( FF.isResolved() )
  455.                         return FF.codeWord();
  456.  
  457.                     if( Oc.nMatch == 2 )
  458.                         FF.filterBy( iMatchChar, true, true, false );
  459.                     else {
  460.                         if( FF.pare( new String[] { "+-+", "-++" }, iMatchChar ) )
  461.                             return FF.codeWord();
  462.                         if( !FF.queryIsEfficient( iMatchChar, 1 ) ) {
  463.                             FF.i0 += 3;
  464.                             break;
  465.                         }
  466.                         Oc = FF.query( iMatchChar, 1 );
  467.                         if( FF.isResolved() )
  468.                             return FF.codeWord();
  469.  
  470.                         if( Oc.nMatch == 1 )
  471.                             FF.filterBy( iMatchChar, true, false, true );
  472.                         else FF.filterBy( iMatchChar, false, true, true );
  473.                     }
  474.                     break;
  475.  
  476.                 case ONE_MATCH_IN_NEXT_THREE:
  477.                     WSTy = WordSearchType.FULL;
  478.                     if( FF.pare( new String[] { "+--", "-+-", "--+" }, iMatchChar ) )
  479.                         return FF.codeWord();
  480.                     if( !FF.queryIsEfficient( iMatchChar, 1 ) ) {
  481.                         FF.i0 += 3;
  482.                         break;
  483.                     }
  484.                     Oc = FF.query( iMatchChar, 1 );
  485.                     if( FF.isResolved() )
  486.                         return FF.codeWord();
  487.  
  488.                     if( Oc.nMatch == 1 )
  489.                         FF.filterBy( iMatchChar, true, false, false );
  490.                     else {
  491.                         if( FF.pare( new String[] { "-+-", "--+" }, iMatchChar ) )
  492.                             return FF.codeWord();
  493.                         if( !FF.queryIsEfficient( iMatchChar, 2 ) ) {
  494.                             FF.i0 += 3;
  495.                             break;
  496.                         }
  497.                         Oc = FF.query( iMatchChar, 2 );
  498.                         if( FF.isResolved() )
  499.                             return FF.codeWord();
  500.  
  501.                         if( Oc.nMatch == 1 )
  502.                             FF.filterBy( iMatchChar, false, true, false );
  503.                         else FF.filterBy( iMatchChar, false, false, true );
  504.                     }
  505.                     break;
  506.  
  507.                 case TWO_MATCHES_IN_NEXT_TWO:
  508.                     WSTy = WordSearchType.FULL;
  509.                     FF.filterBy( iMatchChar, true, true );
  510.                     break;
  511.  
  512.                 case ONE_MATCH_IN_NEXT_TWO:
  513.                     WSTy = WordSearchType.FULL;
  514.                     if( FF.pare( new String[] { "+-", "-+" }, iMatchChar ) )
  515.                         return FF.codeWord();
  516.                     if( !FF.queryIsEfficient( iMatchChar, 1 ) ) {
  517.                         FF.i0 += 2;
  518.                         break;
  519.                     }
  520.                     Oc = FF.query( iMatchChar, 1 );
  521.                     if( FF.isResolved() )
  522.                         return FF.codeWord();
  523.  
  524.                     if( Oc.nMatch == 1 )
  525.                         FF.filterBy( iMatchChar, true, false );
  526.                     else FF.filterBy( iMatchChar, false, true );
  527.                     break;
  528.  
  529.                 case ONE_MATCH_IN_NEXT_ONE:
  530.                     WSTy = WordSearchType.FULL;
  531.                     FF.filterBy( iMatchChar, true );
  532.                 }
  533.                 FF.queryToResolve();
  534.                 if( FF.isResolved() )
  535.                     return FF.codeWord();
  536.             }
  537.             FF.iResolvedChars.add( iMatchChar );
  538.         }
  539.     }
  540.  
  541.     static String getEnCharMatchString( int iMatchChar, int iCharsToMatch ) {
  542.         char[] chs = new char[iCharsToMatch];
  543.  
  544.         Arrays.fill( chs, ALPHABET[iMatchChar] );
  545.         return new String( chs );
  546.     }
  547.  
  548.     static String getSpaceAt( int iSpace ) {
  549.         char[] chs = getPlaceholders( iSpace+1 );
  550.         chs[iSpace] = ' ';
  551.         return new String( chs );
  552.     }
  553.  
  554.     static char[] getPlaceholders( int iCount ) {
  555.         char[] ch = new char[iCount];
  556.  
  557.         Arrays.fill( ch, ZERO_COUNT_PLACEHOLDER );
  558.         return ch;
  559.     }
  560.  
  561.     static List<Integer> getSpaceOneSearchProfile( final int iCodeLength, final boolean in1HE, final boolean in1HO,
  562.         final boolean in2HE, final boolean in2HO )
  563.     {
  564.         final List<Integer> iSites = new LinkedList<Integer>();
  565.         class Vetter {
  566.             void add( int i ) {
  567.                 if( i > 0 && i < iCodeLength-1 && (
  568.                     (in1HE && (i &1) == 0 && i <= iCodeLength/2) ||
  569.                     (in1HO && (i &1) == 1 && i <= iCodeLength/2) ||
  570.                     (in2HE && (i &1) == 0 && i > iCodeLength/2) ||
  571.                     (in2HO && (i &1) == 1 && i > iCodeLength/2) ) )
  572.                 {
  573.                     iSites.add( i );
  574.                 }
  575.             }
  576.         }
  577.  
  578.         Vetter V = new Vetter();
  579.         int iSiteM1 = (iCodeLength + 1)/3 - 1,
  580.             iSiteP1 = iSiteM1;
  581.  
  582.         while( iSiteM1 > 0 || iSiteP1 < iCodeLength-1 ) {
  583.             V.add( iSiteM1-- );
  584.             V.add( ++iSiteP1 );
  585.         }
  586.         return iSites;
  587.     }
  588.  
  589.     static List<Integer> getSpaceTwoSearchProfile( List<Integer> iSpaceOneProfile, final int iSpace1Location,
  590.         final int iCodeLength, final boolean in1HE, final boolean in1HO, final boolean in2HE, final boolean in2HO )
  591.     {
  592.         final List<Integer> iSites = new LinkedList<Integer>();
  593.         final List<Integer> iSearchedSites = iSpaceOneProfile.subList( 0, iSpaceOneProfile.indexOf( iSpace1Location ) );
  594.         class Vetter {
  595.             void add( int i ) {
  596.                 if( i > 0 && i < iCodeLength-1 && !iSearchedSites.contains( i ) &&
  597.                     Math.abs( i - iSpace1Location ) > 1 && (
  598.                     (in1HE && (i &1) == 0 && i <= iCodeLength/2) ||
  599.                     (in1HO && (i &1) == 1 && i <= iCodeLength/2) ||
  600.                     (in2HE && (i &1) == 0 && i > iCodeLength/2) ||
  601.                     (in2HO && (i &1) == 1 && i > iCodeLength/2) ) )
  602.                 {
  603.                     iSites.add( i );
  604.                 }
  605.             }
  606.         }
  607.  
  608.         Vetter V = new Vetter();
  609.         int iSiteM1 = iSpace1Location > iCodeLength/2 ? (iSpace1Location + 1)/2 - 1 :
  610.                 (iSpace1Location + iCodeLength)/2,
  611.             iSiteP1 = iSiteM1;
  612.  
  613.         while( iSiteM1 > 0 || iSiteP1 < iCodeLength-1 ) {
  614.             V.add( iSiteM1-- );
  615.             V.add( ++iSiteP1 );
  616.         }
  617.         return iSites;
  618.     }
  619.  
  620.     static int[] getSpaceLocations( Oracle Or, final CharCounter CC, final int iMaxWordLength ) {
  621.         final int[] iLocations = new int[3];
  622.  
  623.         StringBuilder SBu = new StringBuilder( ALPHABET.length*iMaxWordLength );
  624.         for( int i = 0; i < iMaxWordLength; i++ )
  625.             SBu.append( new String( ALPHABET ) );
  626.  
  627.         Outcome Oc = Or.query( new QueryString( createEvenSpaceSieve( iMaxWordLength*3 + 2 ) + SBu.toString() ) );
  628.         int iLength = Oc.nMatch + Oc.nCoincide;
  629.  
  630.         iLocations[2] = iLength;
  631.  
  632.         SpaceOracle SOr = new SpaceOracle( Or, CC );
  633.         boolean in1HE = false,
  634.             in1HO = false,
  635.             in2HE = false,
  636.             in2HO = false;
  637.  
  638.         if( Oc.nMatch == 0 ) {
  639.             in1HO = true;
  640.             in2HO = true;
  641.         } else if( Oc.nMatch == 2 ) {
  642.             in1HE = true;
  643.             in2HE = true;
  644.         } else {
  645.             Oc = SOr.query( new QueryString( createFullSpaceSieve( iLength/2 + 1 ) ) );
  646.             if( Oc.nMatch == 2 ) {
  647.                 in1HE = true;
  648.                 in1HO = true;
  649.             } else if( Oc.nMatch == 0 ) {
  650.                 in2HE = true;
  651.                 in2HO = true;
  652.             } else {
  653.                 Oc = SOr.query( new QueryString( createEvenSpaceSieve( iLength/2 + 1 ) ) );
  654.                 if( Oc.nMatch == 0 ) {
  655.                     in1HO = true;
  656.                     in2HE = true;
  657.                 } else {
  658.                     in1HE = true;
  659.                     in2HO = true;
  660.                 }
  661.             }
  662.         }
  663.         List<Integer> iSpaceOneSearchProfile = getSpaceOneSearchProfile( iLength, in1HE, in1HO, in2HE, in2HO );
  664.         for( int iSite : iSpaceOneSearchProfile ) {
  665.             Oc = SOr.query( new QueryString( getSpaceAt( iSite ), 0, 0 ) );
  666.             if( Oc.nMatch == 1 ) {
  667.                 iLocations[0] = iSite;
  668.                 break;
  669.             }
  670.         }
  671.  
  672.         List<Integer> iSpaceTwoSearchProfile = getSpaceTwoSearchProfile( iSpaceOneSearchProfile, iLocations[0],
  673.             iLength, in1HE, in1HO, in2HE, in2HO );
  674.  
  675.         for( int iSite : iSpaceTwoSearchProfile ) {
  676.             Oc = SOr.query( new QueryString( getSpaceAt( iSite ), 0, 0 ) );
  677.             if( Oc.nMatch == 1 ) {
  678.                 iLocations[1] = iSite;
  679.                 break;
  680.             }
  681.         }
  682.         if( iLocations[1] < iLocations[0] ) {
  683.             int i = iLocations[1];
  684.             iLocations[1] = iLocations[0];
  685.             iLocations[0] = i;
  686.         }
  687.         return iLocations;
  688.     }
  689.  
  690.     static String createFullSpaceSieve( int iLength ) {
  691.         char[] chs = new char[iLength];
  692.         Arrays.fill( chs, ' ' );
  693.         return new String( chs );
  694.     }
  695.  
  696.     static String createEvenSpaceSieve( int iLength ) {
  697.         char[] chs = new char[iLength];
  698.         for( int i = 0; i < iLength; i++ )
  699.             chs[i] = (i &1) == 0 ? ' ' : ZERO_COUNT_PLACEHOLDER;
  700.  
  701.         return new String( chs );
  702.     }
  703. }
  704.  
  705.  
  706. // Enumerated type for the main routine. Makes the code easier to follow.
  707. enum WordSearchType {
  708.     FULL,
  709.     ONE_MATCH_IN_NEXT_ONE,
  710.     ONE_MATCH_IN_NEXT_TWO,
  711.     TWO_MATCHES_IN_NEXT_TWO,
  712.     ONE_MATCH_IN_NEXT_THREE,
  713.     TWO_MATCHES_IN_NEXT_THREE,
  714.     THREE_MATCHES_IN_NEXT_THREE,
  715.     ONE_MATCH_IN_NEXT_FOUR,
  716.     TWO_MATCHES_IN_NEXT_FOUR,
  717.     THREE_MATCHES_IN_NEXT_FOUR,
  718.     FOUR_MATCHES_IN_NEXT_FOUR
  719. }
  720.  
  721.  
  722. // Class for computing various relevant word metrics.
  723. class DictionaryDB {
  724.     Map<String,DBEntry> M_Data;
  725.     Map<Integer,int[]> M_MaxCharCountsByWordLength;
  726.     int iMaxWordLength;
  727.     String sDisplacer = null;
  728.  
  729.     DictionaryDB( int nWords ) {
  730.         M_Data = new HashMap<String, DBEntry>( nWords );
  731.         M_MaxCharCountsByWordLength = new TreeMap<Integer, int[]>();
  732.         iMaxWordLength = 0;
  733.     }
  734.  
  735.     void registerWord( String sWord ) {
  736.         int iLength = sWord.length();
  737.         int[] iMaxCharCounts;
  738.         iMaxWordLength = Math.max( iMaxWordLength, iLength );
  739.  
  740.         Map<Integer,Integer> iCharCounts = computeCharCountsForWord( sWord );
  741.  
  742.         M_Data.put( sWord, new DBEntry( iCharCounts ) );
  743.         iMaxCharCounts = M_MaxCharCountsByWordLength.get( iLength );
  744.         if( iMaxCharCounts == null ) {
  745.             iMaxCharCounts = new int[Fastermind.ALPHABET.length];
  746.             M_MaxCharCountsByWordLength.put( iLength, iMaxCharCounts );
  747.         }
  748.         for( Map.Entry<Integer,Integer> E : iCharCounts.entrySet() ) {
  749.             int iCharIndex = E.getKey();
  750.  
  751.             iMaxCharCounts[iCharIndex] = Math.max( iMaxCharCounts[iCharIndex], E.getValue() );
  752.         }
  753.     }
  754.  
  755.     Map<Integer,Integer> charCountsForWord( String sWord ) {
  756.         return M_Data.get( sWord ).M_CharCounts;
  757.     }
  758.  
  759.     int[] maxCharCountsForWordLength( int iWordLength ) {
  760.         return M_MaxCharCountsByWordLength.get( iWordLength );
  761.     }
  762.  
  763.     int maxPossibleInstancesOfChar( int[] iWordLengths, int iCharIndex ) {
  764.         if( iWordLengths == null )
  765.             return iMaxWordLength*3;
  766.  
  767.         int iMaxInstances = 0;
  768.         for( int iWordLength : iWordLengths )
  769.             iMaxInstances += maxCharCountsForWordLength( iWordLength )[iCharIndex];
  770.  
  771.         return iMaxInstances;
  772.     }
  773.  
  774.     String displacer() {
  775.         if( sDisplacer == null )
  776.             sDisplacer = new String( Fastermind.getPlaceholders( iMaxWordLength * 3 ) );
  777.  
  778.         return sDisplacer;
  779.     }
  780.  
  781.     static class DBEntry {
  782.         final Map<Integer,Integer> M_CharCounts;
  783.  
  784.         DBEntry( Map<Integer,Integer> M_CharCounts_ ) {
  785.             M_CharCounts = M_CharCounts_;
  786.         }
  787.     }
  788.  
  789.     static Map<Integer,Integer> computeCharCountsForWord( String sWord ) {
  790.         Map<Integer,Integer> iCounts = new HashMap<Integer, Integer>( sWord.length() );
  791.         for( char ch : sWord.toCharArray() ) {
  792.             int iCharIndex = Fastermind.ALPHABET_STRING.indexOf( ch );
  793.  
  794.             Integer I = iCounts.get( iCharIndex );
  795.             int iCount = 1;
  796.             if( I != null )
  797.                 iCount = I + 1;
  798.  
  799.             iCounts.put( iCharIndex, iCount );
  800.         }
  801.         return iCounts;
  802.     }
  803. }
  804.  
  805.  
  806. // Class for storing known character counts and used character counts, and filtering based on them. Also stores some
  807. // other random data since it's passed around a lot.
  808. class CharCounter {
  809.     int[] iWordLengths;
  810.     final int[] iUsedCounts;
  811.     final int[] iFullCounts;
  812.     final DictionaryDB DDb;
  813.  
  814.     CharCounter( int[] iFullCounts_, int[] iUsedCounts_, DictionaryDB DDb_ ) {
  815.         iFullCounts = iFullCounts_;
  816.         iUsedCounts = iUsedCounts_;
  817.         DDb = DDb_;
  818.     }
  819.  
  820.     void setSpaceLocations( int[] iSpaceLocations ) {
  821.         iWordLengths = new int[iSpaceLocations.length];
  822.         for( int iSub = 0, i = 0; i < iWordLengths.length; i++ ) {
  823.             iWordLengths[i] = iSpaceLocations[i] - iSub;
  824.             iSub = iSpaceLocations[i] + 1;
  825.         }
  826.     }
  827.  
  828.     void commitUsedChars( String sCodeStub ) {
  829.         for( char ch : sCodeStub.toCharArray() )
  830.             iUsedCounts[Fastermind.ALPHABET_STRING.indexOf( ch )]++;
  831.     }
  832.  
  833.     void commitUsedChars( int iMatchChar, int iUseCount ) {
  834.         iUsedCounts[iMatchChar] += iUseCount;
  835.     }
  836.  
  837.     void filterByExclusion( SortedSet<String> sCodeWordPotentials ) {
  838.         Iterator<String> ItCodeWordPotentials = sCodeWordPotentials.iterator();
  839.         while( ItCodeWordPotentials.hasNext() ) {
  840.             for( Map.Entry<Integer,Integer> E : DDb.charCountsForWord( ItCodeWordPotentials.next() ).entrySet() ) {
  841.                 int iCharIndex = E.getKey();
  842.                 if( iFullCounts[iCharIndex] != Fastermind.UNKNOWN_COUNT && E.getValue() > iFullCounts[iCharIndex] -
  843.                     iUsedCounts[iCharIndex] )
  844.                 {
  845.                     ItCodeWordPotentials.remove();
  846.                     break;
  847.                 }
  848.             }
  849.         }
  850.     }
  851.  
  852.     void filterByNecessity( SortedSet<String> sCodeWordPotentials, List<Integer> iOutstandingWords ) {
  853.         int[] iFutureSlacks = new int[Fastermind.ALPHABET.length];
  854.         for( int iOutstandingWord : iOutstandingWords ) {
  855.             int[] iCounts = DDb.maxCharCountsForWordLength( iWordLengths[iOutstandingWord] );
  856.             for( int j = 0; j < iCounts.length; j++ )
  857.                 iFutureSlacks[j] += iCounts[j];
  858.         }
  859.  
  860.         List<Integer> I_Deficits = new LinkedList<Integer>();
  861.         for( int i = 0; i < Fastermind.ALPHABET.length; i++ )
  862.             if( iFullCounts[i] != Fastermind.UNKNOWN_COUNT && iFullCounts[i] - iFutureSlacks[i] - iUsedCounts[i] > 0 )
  863.                 I_Deficits.add( i );
  864.  
  865.         if( I_Deficits.isEmpty() )
  866.             return;
  867.  
  868.         Iterator<String> ItCodeWordPotentials = sCodeWordPotentials.iterator();
  869.         while( ItCodeWordPotentials.hasNext() ) {
  870.             Map<Integer,Integer> M_CharCounts = DDb.charCountsForWord( ItCodeWordPotentials.next() );
  871.             for( Integer I_Deficit : I_Deficits ) {
  872.                 int iCount = 0,
  873.                     iDeficit = I_Deficit;
  874.  
  875.                 Integer I_Count = M_CharCounts.get( I_Deficit );
  876.                 if( I_Count != null )
  877.                     iCount = I_Count;
  878.  
  879.                 if( iCount + iUsedCounts[iDeficit] + iFutureSlacks[iDeficit] < iFullCounts[iDeficit] ) {
  880.                     ItCodeWordPotentials.remove();
  881.                     break;
  882.                 }
  883.             }
  884.         }
  885.     }
  886. }
  887.  
  888.  
  889. // Class handling most of the complex filtering and querying by the main routine.
  890. class FilterFrame {
  891.     static final int MAX_POTENTIALS_FOR_SPECIFIC_DISCRIMINANT = 150;
  892.     static final int GLOBAL_INEFFICIENCY_DISCOUNT = 3;
  893.     static final double MIN_EFFICIENT_QUERY_POWER = 0.45;
  894.  
  895.     int i0;
  896.     int n;
  897.     MappingOracle MOr;
  898.     final Oracle OrResolver;
  899.     SortedSet<String> sCodeWordPotentials;
  900.     final List<Integer> iResolvedChars;
  901.     final CharCounter CC;
  902.     int nInefficient = 0;
  903.  
  904.     FilterFrame( SortedSet<String> sCodeWordPotentials_, MappingOracle MOr_, Oracle OrResolver_, CharCounter CC_ ) {
  905.         i0 = 0;
  906.         sCodeWordPotentials = sCodeWordPotentials_;
  907.         MOr = MOr_;
  908.         OrResolver = OrResolver_;
  909.         CC = CC_;
  910.         n = sCodeWordPotentials.first().length();
  911.         iResolvedChars = new LinkedList<Integer>();
  912.     }
  913.  
  914.     Outcome query( int iMatchChar, int iMatchCount ) {
  915.         String s = Fastermind.getEnCharMatchString( iMatchChar, iMatchCount );
  916.         QueryString qs = new QueryString( i0 > 0 ? (new String(Fastermind.getPlaceholders( i0 )) + s) : s,
  917.             iMatchChar, iMatchCount );
  918.  
  919.         Outcome Oc = MOr.query( qs );
  920.  
  921.         nInefficient = 0;
  922.         checkForExhaustion( iMatchChar );
  923.         commitOutstanding();
  924.  
  925.         return Oc;
  926.     }
  927.  
  928.     boolean queryIsEfficient( int iMatchChar, int nMatchCount ) {
  929.         if( sCodeWordPotentials.size() > MAX_POTENTIALS_FOR_SPECIFIC_DISCRIMINANT )
  930.             return true;
  931.  
  932.         boolean isEfficient = powerOfQuery( iMatchChar, nMatchCount ) >=
  933.             (int)(sCodeWordPotentials.size()*MIN_EFFICIENT_QUERY_POWER) - nInefficient*GLOBAL_INEFFICIENCY_DISCOUNT;
  934.  
  935.         if( !isEfficient )
  936.             nInefficient++;
  937.         else nInefficient = 0;
  938.         return isEfficient;
  939.     }
  940.  
  941.     int powerOfQuery( int iMatchChar, int nMatchCount ) {
  942.         Integer[] iIndicesArray = nextEnIndices( nMatchCount );
  943.         char[] chMatchMask = new char[nMatchCount];
  944.         char chMatch = Fastermind.ALPHABET[iMatchChar];
  945.  
  946.         Map<String,Integer> M_OutcomePowers = new TreeMap<String, Integer>();
  947.         for( String sWord : sCodeWordPotentials ) {
  948.             for( int i = 0; i < nMatchCount; i++ )
  949.                 chMatchMask[i] = sWord.charAt( iIndicesArray[i] ) == chMatch ? '+' : '-';
  950.  
  951.             String sMask = new String( chMatchMask );
  952.             if( M_OutcomePowers.containsKey( sMask ) )
  953.                 M_OutcomePowers.put( sMask, M_OutcomePowers.get( sMask ) + 1 );
  954.             else M_OutcomePowers.put( sMask, 1 );
  955.         }
  956.         return sCodeWordPotentials.size() - Collections.max( M_OutcomePowers.values() );
  957.     }
  958.  
  959.     boolean pare( String[] sMasksArray, int iMatchChar ) {
  960.         return pare( sMasksArray, iMatchChar, false );
  961.     }
  962.  
  963.     boolean pare( String[] sMasksArray, int iMatchChar, boolean isForce ) {
  964.         if( !isForce && sCodeWordPotentials.size() > MAX_POTENTIALS_FOR_SPECIFIC_DISCRIMINANT )
  965.             return false;
  966.  
  967.         int nMatchCount = sMasksArray[0].length();
  968.         Integer[] iIndicesArray = nextEnIndices( nMatchCount );
  969.         char[] chMatchMask = new char[nMatchCount];
  970.         char chMatch = Fastermind.ALPHABET[iMatchChar];
  971.         List<String> sMasks = Arrays.asList( sMasksArray );
  972.         Iterator<String> It = sCodeWordPotentials.iterator();
  973.         while( It.hasNext() ) {
  974.             String sWord = It.next();
  975.             for( int i = 0; i < nMatchCount; i++ )
  976.                 chMatchMask[i] = sWord.charAt( iIndicesArray[i] ) == chMatch ? '+' : '-';
  977.  
  978.             if( !sMasks.contains( new String( chMatchMask ) ) )
  979.                 It.remove();
  980.         }
  981.  
  982.         if( sCodeWordPotentials.size() == 1 )
  983.             commitOutstanding();
  984.         else queryToResolve();
  985.  
  986.         return sCodeWordPotentials.size() == 1;
  987.     }
  988.  
  989.     Integer[] nextEnIndices( int n_ ) {
  990.         List<Integer> iIndices = new LinkedList<Integer>();
  991.         for( int i = 0; i < n_; i++ )
  992.             iIndices.add( i0 + i );
  993.  
  994.         return MOr.map( iIndices ).toArray( new Integer[n_] );
  995.     }
  996.  
  997.     void commitOutstanding() {
  998.         if( sCodeWordPotentials.size() == 1 )
  999.             CC.commitUsedChars( MOr.substring( sCodeWordPotentials.first() ) );
  1000.     }
  1001.  
  1002.     static final int[][] PERMS3_MODULO_12ORDER = {
  1003.         { 0, 1, 2 }, { 0, 2, 1 }, { 1, 2, 0 }
  1004.     };
  1005.  
  1006.     static final int[][] PERMS4_MODULO_12ORDER = {
  1007.         { 0, 1, 2, 3 }, { 0, 1, 3, 2 }, { 0, 2, 1, 3 }, { 0, 2, 3, 1 }, { 0, 3, 1, 2 }, { 0, 2, 2, 1 },
  1008.         { 1, 2, 0, 3 }, { 1, 2, 3, 0 }, { 1, 3, 0, 2 }, { 1, 3, 2, 0 }, { 2, 3, 0, 1 }, { 2, 3, 1, 0 }
  1009.     };
  1010.  
  1011.     void queryToResolve() {
  1012.         char[] ch;
  1013.         String[] sWords;
  1014.  
  1015.         if( sCodeWordPotentials.first().length() == 1 ) {
  1016.             String sCodeWord = sCodeWordPotentials.last();
  1017.  
  1018.             sCodeWordPotentials.remove( sCodeWord );
  1019.             for( String sWord : sCodeWordPotentials ) {
  1020.                 if( OrResolver.query( new QueryString( sWord ) ).nMatch == 1 ) {
  1021.                     sCodeWord = sWord;
  1022.                     break;
  1023.                 }
  1024.             }
  1025.             sCodeWordPotentials.clear();
  1026.             sCodeWordPotentials.add( sCodeWord );
  1027.             commitOutstanding();
  1028.             return;
  1029.         }
  1030.  
  1031.         switch( sCodeWordPotentials.size() ) {
  1032.         case 2:
  1033.             sWords = sCodeWordPotentials.toArray( new String[2] );
  1034.             ch = Fastermind.getPlaceholders( sWords[0].length() );
  1035.             for( int i = 0; i < sWords[0].length(); i++ )
  1036.                 if( sWords[0].charAt( i ) != sWords[1].charAt( i ) ) {
  1037.                     ch[i] = sWords[0].charAt( i );
  1038.                     break;
  1039.                 }
  1040.  
  1041.             sCodeWordPotentials.remove( OrResolver.query(new QueryString( ch )).nMatch == 1 ? sWords[1] : sWords[0] );
  1042.             commitOutstanding();
  1043.             break;
  1044.  
  1045.         case 3:
  1046.         case 4:
  1047.             final int nWords = sCodeWordPotentials.size();
  1048.  
  1049.             sWords = sCodeWordPotentials.toArray( new String[nWords] );
  1050.             for( int[] iWords : nWords == 3 ? PERMS3_MODULO_12ORDER : PERMS4_MODULO_12ORDER ) {
  1051.                 int[] iMeet = { -1, -1, -1 };
  1052.  
  1053.                 for( int i = 0; i < sWords[0].length(); i++ ) {
  1054.                     char ch0 = sWords[iWords[0]].charAt( i ),
  1055.                         ch1 = sWords[iWords[1]].charAt( i ),
  1056.                         ch2 = sWords[iWords[2]].charAt( i );
  1057.  
  1058.                     if( nWords == 3 ) {
  1059.                         if( ch0 != ch2 )
  1060.                             iMeet[ch0 == ch1 ? 1 : 0] = i;
  1061.                     } else {
  1062.                         char ch3 = sWords[iWords[3]].charAt( i );
  1063.                         if( ch0 != ch3 ) {
  1064.                             if( ch0 == ch1 && ch0 == ch2 )
  1065.                                 iMeet[2] = i;
  1066.                             else if( ch0 == ch1 )
  1067.                                 iMeet[1] = i;
  1068.                             else if( ch0 != ch2 )
  1069.                                 iMeet[0] = i;
  1070.                         }
  1071.                     }
  1072.                 }
  1073.                 if( iMeet[0] != -1 && iMeet[1] != -1 && (nWords == 3 || iMeet[2] != -1) ) {
  1074.                     ch = Fastermind.getPlaceholders( sWords[0].length() );
  1075.                     ch[iMeet[0]] = sWords[iWords[0]].charAt( iMeet[0] );
  1076.                     ch[iMeet[1]] = sWords[iWords[1]].charAt( iMeet[1] );
  1077.                     if( nWords == 4 )
  1078.                         ch[iMeet[2]] = sWords[iWords[2]].charAt( iMeet[2] );
  1079.  
  1080.                     int nMatch = nWords - 1 - OrResolver.query( new QueryString( ch ) ).nMatch;
  1081.  
  1082.                     sCodeWordPotentials.clear();
  1083.                     sCodeWordPotentials.add( sWords[iWords[nMatch]] );
  1084.                     commitOutstanding();
  1085.                     return;
  1086.                 }
  1087.             }
  1088.         }
  1089.     }
  1090.  
  1091.     int nResetsDueToExhaustedPool = 0;
  1092.  
  1093.     int getOutstandingChar() {
  1094.         int nChars = Fastermind.ALPHABET.length;
  1095.         if( sCodeWordPotentials.size() <= MAX_POTENTIALS_FOR_SPECIFIC_DISCRIMINANT ) {
  1096.             int[] iOutstandingCharCounts = new int[nChars];
  1097.             for( String sWord : sCodeWordPotentials )
  1098.                 for( Map.Entry<Integer,Integer> E : CC.DDb.charCountsForWord( sWord ).entrySet() )
  1099.                     iOutstandingCharCounts[E.getKey()] += E.getValue();
  1100.  
  1101.             List<IndexedInteger> II_OutstandingCharCounts = new ArrayList<IndexedInteger>( nChars );
  1102.             for( int i = 0; i < nChars; i++ )
  1103.                 if( iOutstandingCharCounts[i] > 0 )
  1104.                     II_OutstandingCharCounts.add( new IndexedInteger( iOutstandingCharCounts[i], i ) );
  1105.  
  1106.             Collections.sort( II_OutstandingCharCounts, new Comparator<IndexedInteger>() {
  1107.                 public int compare( IndexedInteger II1, IndexedInteger II2 ) {
  1108.                     return II2.i - II1.i;
  1109.                 }
  1110.             } );
  1111.  
  1112.             for( IndexedInteger II : II_OutstandingCharCounts ) {
  1113.                 if( !iResolvedChars.contains( II.iIndex ) &&
  1114.                     CC.iUsedCounts[II.iIndex] != CC.iFullCounts[II.iIndex] &&
  1115.                     CC.iFullCounts[II.iIndex] != Fastermind.UNKNOWN_COUNT )
  1116.                 {
  1117.                     return II.iIndex;
  1118.                 }
  1119.             }
  1120.         } else {
  1121.             for( int iIndex = 0; iIndex < nChars; iIndex++ )
  1122.                 if( !iResolvedChars.contains( iIndex ) &&
  1123.                     CC.iUsedCounts[iIndex] != CC.iFullCounts[iIndex] &&
  1124.                     CC.iFullCounts[iIndex] != Fastermind.UNKNOWN_COUNT )
  1125.                 {
  1126.                     return iIndex;
  1127.                 }
  1128.         }
  1129.         if( iResolvedChars.isEmpty() ) {
  1130.             MOr.query( new QueryString( Fastermind.ALPHABET_STRING.substring( 0, 1 ), 0, 1 ) );
  1131.             nResetsDueToExhaustedPool++;
  1132.             if( nResetsDueToExhaustedPool > 10 )
  1133.                 throw new InternalError( "perpetually exhausting pool" );
  1134.         } else {
  1135.             iResolvedChars.remove( 0 );
  1136.         }
  1137.         return getOutstandingChar();
  1138.     }
  1139.  
  1140.     void filterBy( int iMatchChar, boolean... isAt ) {
  1141.         List<Integer> iOns = new LinkedList<Integer>(),
  1142.             iOffs = new LinkedList<Integer>();
  1143.  
  1144.         for( int i = 0; i < isAt.length; i++ ) {
  1145.             if( isAt[i] )
  1146.                 iOns.add( i0 + i );
  1147.             else iOffs.add( i0 + i );
  1148.         }
  1149.         if( !iOffs.isEmpty() )
  1150.             filterByCharExclusion( iMatchChar, iOffs );
  1151.         if( !isResolved() && !iOns.isEmpty() )
  1152.             filterByCharInclusion( iMatchChar, iOns );
  1153.  
  1154.         if( isResolved() ) {
  1155.             commitOutstanding();
  1156.             n = i0 = 0;
  1157.         } else {
  1158.             CC.commitUsedChars( iMatchChar, iOns.size() );
  1159.             i0 += iOffs.size();
  1160.             if( !iOns.isEmpty() ) {
  1161.                 n -= iOns.size();
  1162.                 MOr = MappingOracle.oracleForResolvedCharsAt( MOr, iOns );
  1163.             }
  1164.             checkForExhaustion( iMatchChar );
  1165.             if( isResolved() ) {
  1166.                 commitOutstanding();
  1167.                 n = i0 = 0;
  1168.             }
  1169.         }
  1170.     }
  1171.  
  1172.     void filterByCharExclusion( int iMatchChar, List<Integer> iCharIndices ) {
  1173.         filterByCharWhateverclusion( iMatchChar, iCharIndices, false );
  1174.     }
  1175.  
  1176.     void filterByCharInclusion( int iMatchChar, List<Integer> iCharIndices ) {
  1177.         filterByCharWhateverclusion( iMatchChar, iCharIndices, true );
  1178.     }
  1179.  
  1180.     void filterByCharWhateverclusion( int iMatchChar, List<Integer> iCharIndices, boolean isInclusion ) {
  1181.         char chMatch = Fastermind.ALPHABET[iMatchChar];
  1182.         Iterator<String> ItCodeWordPotentials = sCodeWordPotentials.iterator();
  1183.  
  1184.         iCharIndices = MOr.map( iCharIndices );
  1185.  
  1186.         while( ItCodeWordPotentials.hasNext() ) {
  1187.             String sCodeWord = ItCodeWordPotentials.next();
  1188.  
  1189.             for( int iCharIndex : iCharIndices )
  1190.                 if( (sCodeWord.charAt( iCharIndex ) == chMatch) != isInclusion ) {
  1191.                     ItCodeWordPotentials.remove();
  1192.                     break;
  1193.                 }
  1194.         }
  1195.     }
  1196.  
  1197.     void checkForExhaustion( int iMatchChar ) {
  1198.         if( CC.iUsedCounts[iMatchChar] == CC.iFullCounts[iMatchChar] )
  1199.             filterByCharExclusion( iMatchChar, Fastermind.ASCENDING_ORDER.subList( 0, MOr.getUnresolvedIndexCount() ) );
  1200.     }
  1201.  
  1202.     boolean isResolved() {
  1203.         assert !sCodeWordPotentials.isEmpty();
  1204.         return sCodeWordPotentials.size() == 1;
  1205.     }
  1206.  
  1207.     String codeWord() {
  1208.         return sCodeWordPotentials.first();
  1209.     }
  1210.  
  1211.     static final class IndexedInteger {
  1212.         int i;
  1213.         int iIndex;
  1214.  
  1215.         IndexedInteger( int i_, int iIndex_ ) {
  1216.             i = i_;
  1217.             iIndex = iIndex_;
  1218.         }
  1219.     }
  1220. }
  1221.  
  1222.  
  1223. // Class encapsulates an outcome by the oracle: number of matches, number of "coincidences" (characters present in
  1224. // both strings but not in matching locations).
  1225. class Outcome implements Comparable<Outcome> {
  1226.     final int nMatch;
  1227.     final int nCoincide;
  1228.     final boolean isExactMatch;
  1229.  
  1230.     static final Outcome VALID = new Outcome();
  1231.  
  1232.     private Outcome() {
  1233.         nMatch = -1;
  1234.         nCoincide = -1;
  1235.         isExactMatch = true;
  1236.     }
  1237.  
  1238.     Outcome( int nMatch_, int nCoincide_ ) {
  1239.         nMatch = nMatch_;
  1240.         nCoincide = nCoincide_;
  1241.         isExactMatch = false;
  1242.     }
  1243.  
  1244.     public int compareTo( Outcome Oc ) {
  1245.         return nMatch < Oc.nMatch ? -1 : (nMatch > Oc.nMatch ? 1 : (nCoincide < Oc.nCoincide ? -1 :
  1246.             (nCoincide > Oc.nCoincide ? 1 : 0)));
  1247.     }
  1248.  
  1249.     public boolean equals( Object o ) {
  1250.         if( !(o instanceof Outcome) )
  1251.             return false;
  1252.  
  1253.         Outcome Oc = (Outcome)o;
  1254.         return Oc.nMatch == nMatch && Oc.nCoincide == nCoincide;
  1255.     }
  1256.  
  1257.     public int hashCode() {
  1258.         return nMatch*37 + nCoincide;
  1259.     }
  1260. }
  1261.  
  1262.  
  1263. // Oracle interface. An oracle is the machine that spits out the relevant match data when queried with a specific
  1264. // string.
  1265. interface Oracle {
  1266.     Outcome query( QueryString qsCandidate );
  1267. }
  1268.  
  1269.  
  1270. // A string with extra data attached. Needed to avoid headaches in passing data between the many nested oracles. Not
  1271. // elegant, but gets the job done.
  1272. class QueryString {
  1273.     final String s;
  1274.     final int iIndex;
  1275.     final int nCount;
  1276.  
  1277.     QueryString( String s_ )
  1278.         { this( s_, -1, -1 ); }
  1279.     QueryString( char[] chs )
  1280.         { this( new String( chs ), -1, -1 ); }
  1281.     QueryString( char[] chs, int iIndex_, int nCount_ )
  1282.         { this( new String( chs ), iIndex_, nCount_ ); }
  1283.     QueryString( String s_, int iIndex_, int nCount_ ) {
  1284.         s = s_;
  1285.         iIndex = iIndex_;
  1286.         nCount = nCount_;
  1287.     }
  1288.  
  1289.     String contents() {
  1290.         return s;
  1291.     }
  1292.  
  1293.     QueryString append( String s_ ) {
  1294.         return s_.isEmpty() ? this : new QueryString( s + s_, iIndex, nCount );
  1295.     }
  1296.  
  1297.     QueryString prepend( String s_ ) {
  1298.         return s_.isEmpty() ? this : new QueryString( s_ + s, iIndex, nCount );
  1299.     }
  1300. }
  1301.  
  1302.  
  1303. // Simple hook on the input and output to an oracle.
  1304. abstract class FilteredOracle implements Oracle {
  1305.     final Oracle Or;
  1306.  
  1307.     FilteredOracle( Oracle Or_ ) {
  1308.         Or = Or_;
  1309.     }
  1310.  
  1311.     abstract QueryString localStringToOracleString( QueryString qsLocal );
  1312.  
  1313.     abstract Outcome oracleOutcomeToLocalOutcome( Outcome Oc );
  1314.  
  1315.     public Outcome query( QueryString qsCandidate ) {
  1316.         return oracleOutcomeToLocalOutcome( Or.query( localStringToOracleString( qsCandidate ) ) );
  1317.     }
  1318. }
  1319.  
  1320.  
  1321. // Simple oracle shifts test characters into the position of a specific word.
  1322. class WordMaskOracle extends FilteredOracle {
  1323.     final String sPrefix;
  1324.     final Outcome OcBaseline;
  1325.  
  1326.     WordMaskOracle( Oracle Or_, String sPrefix_, Outcome OcBaseline_ ) {
  1327.         super( Or_ );
  1328.         sPrefix = sPrefix_;
  1329.         OcBaseline = OcBaseline_;
  1330.     }
  1331.  
  1332.     @Override QueryString localStringToOracleString( QueryString qsLocal ) {
  1333.         return qsLocal.prepend( sPrefix );
  1334.     }
  1335.  
  1336.     @Override Outcome oracleOutcomeToLocalOutcome( Outcome Oc ) {
  1337.         return new Outcome( Oc.nMatch - OcBaseline.nMatch, Oc.nCoincide - OcBaseline.nCoincide );
  1338.     }
  1339.  
  1340.     static Oracle forCodeWord( Oracle Or_, int[] iWordLengths, int iCodeWord ) {
  1341.         switch( iCodeWord ) {
  1342.         case 0:
  1343.             return Or_;
  1344.         case 1:
  1345.             return new WordMaskOracle( Or_, new String( Fastermind.getPlaceholders( iWordLengths[0] + 1 ) ),
  1346.                 new Outcome( 0, 0 ) );
  1347.         case 2:
  1348.             return new WordMaskOracle( Or_, new String( Fastermind.getPlaceholders( iWordLengths[0] +
  1349.                 iWordLengths[1] + 2 ) ), new Outcome( 0, 0 ) );
  1350.         }
  1351.         throw new InternalError();
  1352.     }
  1353. }
  1354.  
  1355.  
  1356. // Simpler version of the OracleWithTackedOnCharCounter. This one also tacks on a character counter, but is
  1357. // sufficiently different to warrant its own class.
  1358. final class SpaceOracle extends FilteredOracle {
  1359.     final CharCounter CC;
  1360.     int iFullQueryCharIndex;
  1361.     boolean areAllCharsQueried;
  1362.     QueryString qsStored;
  1363.  
  1364.     SpaceOracle( Oracle Or_, CharCounter CC_ ) {
  1365.         super( Or_ );
  1366.         CC = CC_;
  1367.     }
  1368.  
  1369.     @Override QueryString localStringToOracleString( QueryString qsLocal ) {
  1370.         if( areAllCharsQueried )
  1371.             return qsLocal;
  1372.  
  1373.         qsStored = qsLocal;
  1374.         for( iFullQueryCharIndex = 0; iFullQueryCharIndex < Fastermind.ALPHABET.length; iFullQueryCharIndex++ ) {
  1375.             if( CC.iFullCounts[iFullQueryCharIndex] == Fastermind.UNKNOWN_COUNT )
  1376.                 return qsLocal.append( CC.DDb.displacer() + Fastermind.getEnCharMatchString( iFullQueryCharIndex,
  1377.                     CC.DDb.maxPossibleInstancesOfChar( null, iFullQueryCharIndex ) ) );
  1378.         }
  1379.         areAllCharsQueried = true;
  1380.         return qsLocal;
  1381.     }
  1382.  
  1383.     @Override Outcome oracleOutcomeToLocalOutcome( Outcome Oc ) {
  1384.         if( areAllCharsQueried )
  1385.             return Oc;
  1386.  
  1387.         CC.iFullCounts[iFullQueryCharIndex] = Oc.nMatch + Oc.nCoincide - 1 + qsStored.nCount;
  1388.         return new Outcome( Oc.nMatch, 1 - Oc.nMatch );
  1389.     }
  1390. }
  1391.  
  1392.  
  1393. // Oracle with a tacked on character counter. The name says it all. ;)
  1394. final class OracleWithTackedOnCharCounter extends FilteredOracle {
  1395.     final CharCounter CC;
  1396.     final SortedSet<String> sCodeWordPotentials;
  1397.     int iFullQueryCharIndex;
  1398.     boolean areAllCharsQueried;
  1399.     QueryString qsStoredLocal;
  1400.  
  1401.     OracleWithTackedOnCharCounter( Oracle Or_, SortedSet<String> sCodeWordPotentials_, CharCounter CC_ ) {
  1402.         super( Or_ );
  1403.         sCodeWordPotentials = sCodeWordPotentials_;
  1404.         CC = CC_;
  1405.         iFullQueryCharIndex = 0;
  1406.     }
  1407.  
  1408.     @Override QueryString localStringToOracleString( QueryString qsLocal ) {
  1409.         if( areAllCharsQueried )
  1410.             return qsLocal;
  1411.  
  1412.         qsStoredLocal = qsLocal;
  1413.         for( ; iFullQueryCharIndex < Fastermind.ALPHABET.length; iFullQueryCharIndex++ ) {
  1414.             if( CC.iFullCounts[iFullQueryCharIndex] == Fastermind.UNKNOWN_COUNT ) {
  1415.                 assert iFullQueryCharIndex > 0;
  1416.                 return qsLocal.append( CC.DDb.displacer() + Fastermind.getEnCharMatchString( iFullQueryCharIndex,
  1417.                     CC.DDb.maxPossibleInstancesOfChar( CC.iWordLengths, iFullQueryCharIndex ) ) );
  1418.             }
  1419.         }
  1420.         areAllCharsQueried = true;
  1421.         return qsLocal;
  1422.     }
  1423.  
  1424.     @Override Outcome oracleOutcomeToLocalOutcome( Outcome Oc ) {
  1425.         if( areAllCharsQueried )
  1426.             return Oc;
  1427.  
  1428.         int iImpliedCount = Oc.nMatch + Oc.nCoincide;
  1429.         int iCharIndex = qsStoredLocal.iIndex;
  1430.  
  1431.         assert CC.iFullCounts[iCharIndex] != Fastermind.UNKNOWN_COUNT;
  1432.         iImpliedCount -= Math.min( CC.iFullCounts[iCharIndex], qsStoredLocal.nCount );
  1433.  
  1434.         CC.iFullCounts[iFullQueryCharIndex] = iImpliedCount;
  1435.         if( iImpliedCount == 0 )
  1436.             filterByTotalCharExclusion( iFullQueryCharIndex );
  1437.  
  1438.         return new Outcome( Oc.nMatch, iImpliedCount - CC.iUsedCounts[iFullQueryCharIndex] );
  1439.     }
  1440.  
  1441.     void filterByTotalCharExclusion( int iMatchChar ) {
  1442.         char chMatch = Fastermind.ALPHABET[iMatchChar];
  1443.         Iterator<String> ItCodeWordPotentials = sCodeWordPotentials.iterator();
  1444.  
  1445.         while( ItCodeWordPotentials.hasNext() ) {
  1446.             if( ItCodeWordPotentials.next().indexOf( chMatch ) != -1 )
  1447.                 ItCodeWordPotentials.remove();
  1448.         }
  1449.     }
  1450. }
  1451.  
  1452.  
  1453. // Oracle that handles mapping characters around "gaps" formed by resolved characters from prior guesses.
  1454. final class MappingOracle extends FilteredOracle {
  1455.     static final int[] NO_OFFSETS = new int[0];
  1456.  
  1457.     final int[] iOffsets;
  1458.     final char[] chMapped;
  1459.     final int iMaxMapLength;
  1460.     final int nResolved;
  1461.  
  1462.     MappingOracle( Oracle Or_, int iMaxMapLength_ ) {
  1463.         super( Or_ );
  1464.         iOffsets = NO_OFFSETS;
  1465.         nResolved = 0;
  1466.         chMapped = Fastermind.getPlaceholders( iMaxMapLength_ );
  1467.         iMaxMapLength = iMaxMapLength_;
  1468.     }
  1469.  
  1470.     MappingOracle( Oracle Or_, int[] iOffsets_, char[] chMapped_, int nResolved_ ) {
  1471.         super( Or_ );
  1472.         iOffsets = iOffsets_;
  1473.         chMapped = chMapped_;
  1474.         nResolved = nResolved_;
  1475.         iMaxMapLength = chMapped_.length;
  1476.     }
  1477.  
  1478.     List<Integer> map( List<Integer> iCharIndices ) {
  1479.         if( iOffsets == NO_OFFSETS )
  1480.             return iCharIndices;
  1481.  
  1482.         List<Integer> iMappedIndices = new ArrayList<Integer>( iCharIndices.size() );
  1483.  
  1484.         for( int iIndex : iCharIndices )
  1485.             iMappedIndices.add( map( iIndex ) );
  1486.  
  1487.         return iMappedIndices;
  1488.     }
  1489.  
  1490.     int map( int iIndex ) {
  1491.         if( iOffsets == NO_OFFSETS )
  1492.             return iIndex;
  1493.  
  1494.         return iIndex >= iOffsets.length ? (iIndex + iOffsets[iOffsets.length-1] - (iOffsets.length-1)) :
  1495.             iOffsets[iIndex];
  1496.     }
  1497.  
  1498.     String substring( String sWord ) {
  1499.         if( iOffsets == NO_OFFSETS )
  1500.             return sWord;
  1501.  
  1502.         StringBuilder SBu = new StringBuilder( sWord.length() );
  1503.  
  1504.         int iSub = 0,
  1505.             iMapped = map( iSub );
  1506.  
  1507.         while( iMapped < sWord.length() ) {
  1508.             SBu.append( sWord.charAt( iMapped ) );
  1509.             iMapped = map( ++iSub );
  1510.         }
  1511.         return SBu.toString();
  1512.     }
  1513.  
  1514.     int getUnresolvedIndexCount() {
  1515.         return iMaxMapLength - nResolved;
  1516.     }
  1517.  
  1518.     @Override QueryString localStringToOracleString( QueryString qsLocal ) {
  1519.         if( iOffsets == NO_OFFSETS )
  1520.             return qsLocal;
  1521.  
  1522.         char[] chOracle = new char[iMaxMapLength];
  1523.         String sLocal = qsLocal.contents();
  1524.  
  1525.         System.arraycopy( chMapped, 0, chOracle, 0, iMaxMapLength );
  1526.         for( int i = 0; i < sLocal.length(); i++ )
  1527.             chOracle[map( i )] = sLocal.charAt( i );
  1528.  
  1529.         return new QueryString( chOracle, qsLocal.iIndex, qsLocal.nCount );
  1530.     }
  1531.  
  1532.     @Override Outcome oracleOutcomeToLocalOutcome( Outcome Oc ) {
  1533.         return Oc;
  1534.     }
  1535.  
  1536.     static MappingOracle oracleForResolvedCharsAt( Oracle Or_, List<Integer> iResolvedChars ) {
  1537.         if( Or_ instanceof MappingOracle ) {
  1538.             MappingOracle MOr = (MappingOracle)Or_;
  1539.             iResolvedChars = MOr.map( iResolvedChars );
  1540.  
  1541.             char[] chMappedNew;
  1542.  
  1543.             chMappedNew = new char[MOr.iMaxMapLength];
  1544.             System.arraycopy( MOr.chMapped, 0, chMappedNew, 0, MOr.iMaxMapLength );
  1545.             for( int iResolvedChar : iResolvedChars )
  1546.                 chMappedNew[iResolvedChar] = Fastermind.RESOLVED_CHAR_PLACEHOLDER;
  1547.  
  1548.             List<Integer> iOffsetsNew = new LinkedList<Integer>();
  1549.             int iTo = 0;
  1550.             for( ; iTo < chMappedNew.length; iTo++ )
  1551.                 if( chMappedNew[iTo] == Fastermind.ZERO_COUNT_PLACEHOLDER ) {
  1552.                     iOffsetsNew.add( iTo );
  1553.                 }
  1554.  
  1555.             iOffsetsNew.add( iTo );
  1556.             int[] iOffsetsNewArray = new int[iOffsetsNew.size()];
  1557.             int i = 0;
  1558.             for( int iOffset : iOffsetsNew )
  1559.                 iOffsetsNewArray[i++] = iOffset;
  1560.  
  1561.             return new MappingOracle( MOr.Or, iOffsetsNewArray, chMappedNew, MOr.nResolved + iResolvedChars.size() );
  1562.         }
  1563.         throw new InternalError( "didn't expect this" );
  1564.     }
  1565. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement