package rabbitescape.engine.textworld; import static rabbitescape.engine.util.Util.concat; import static rabbitescape.engine.util.Util.enumerate1; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import rabbitescape.engine.Block; import rabbitescape.engine.ChangeDescription; import rabbitescape.engine.IgnoreWorldStatsListener; import rabbitescape.engine.Rabbit; import rabbitescape.engine.Thing; import rabbitescape.engine.Token; import rabbitescape.engine.VoidMarkerStyle; import rabbitescape.engine.World; import rabbitescape.engine.WorldStatsListener; import rabbitescape.engine.util.Dimension; import rabbitescape.engine.util.Position; import rabbitescape.engine.util.Util; import rabbitescape.engine.util.Util.Function; import rabbitescape.engine.util.Util.IdxObj; import rabbitescape.engine.util.VariantGenerator; public class TextWorldManip { public static final String name = "name"; private static final String description = "description"; private static final String author_name = "author_name"; private static final String author_url = "author_url"; public static final String hint = "hint"; public static final String solution = "solution"; private static final String num_rabbits = "num_rabbits"; private static final String num_to_save = "num_to_save"; private static final String rabbit_delay = "rabbit_delay"; private static final String music = "music"; public static final String void_marker_style = "void_marker_style"; private static final String num_saved = "num_saved"; private static final String num_killed = "num_killed"; private static final String num_waiting = "num_waiting"; private static final String rabbit_index_count = "rabbit_index_count"; private static final String intro = "intro"; private static final String paused = "paused"; private static final String ready_to_explode_all = "ready_to_explode_all"; public static final String water_definition = "n"; public static final List<String> META_INTS = Arrays.asList( num_rabbits, num_to_save, num_saved, num_killed, num_waiting, rabbit_index_count ); public static final List<String> META_INT_ARRAYS = Arrays.asList( rabbit_delay ); public static final List<String> META_STRINGS = Arrays.asList( name, description, author_name, author_url, music, void_marker_style ); public static final List<String> META_STRING_ARRAYS_BY_KEY = Arrays.asList( solution, hint ); public static final List<String> META_BOOLS = Arrays.asList( intro, // Deprecated - leave for backward compatibility paused, ready_to_explode_all // Deprecated - leave for backward compatibility ); public static final List<String> ABILITIES = abilitiesList(); private static List<String> abilitiesList() { List<String> ret = new ArrayList<>(); for ( Token.Type type : Token.Type.values() ) { ret.add( type.name() ); } return ret; } public static World createWorld( String... lines ) throws WrongLineLength, UnknownCharacter { return createWorld( new IgnoreWorldStatsListener(), lines ); } public static World createWorld( WorldStatsListener statsListener, String... lines ) throws WrongLineLength, UnknownCharacter { return createWorldWithName( "", statsListener, lines ); } /** * @param encode if true, create a world with obfuscated hints and solutions */ public static World createWorldWithName( String nameIfNoneSupplied, WorldStatsListener statsListener, String... lines ) { List<Block> blocks = new ArrayList<>(); List<Rabbit> rabbits = new ArrayList<>(); List<Thing> things = new ArrayList<>(); Map<Position, Integer> waterAmounts = new HashMap<>(); Map<Token.Type, Integer> abilities = new HashMap<>(); int variantSeed = 0; // TODO: world property for the seed? LineProcessor processor = new LineProcessor( blocks, rabbits, things, waterAmounts, abilities, lines, new VariantGenerator( variantSeed ) ); int num_rabs = processor.metaInt( num_rabbits, 10 ); World world = createWorldFromLineProcessor( nameIfNoneSupplied, statsListener, blocks, rabbits, things, waterAmounts, abilities, processor, num_rabs ); world.countRabbitsForIndex(); return world; } private static World createWorldFromLineProcessor( String nameIfNoneSupplied, WorldStatsListener statsListener, List<Block> blocks, List<Rabbit> rabbits, List<Thing> things, Map<Position, Integer> waterAmounts, Map<Token.Type, Integer> abilities, LineProcessor processor, int num_rabs ) { return new World( processor.size(), blocks, rabbits, things, waterAmounts, abilities, processor.metaString( name, nameIfNoneSupplied ), processor.metaString( description, "" ), processor.metaString( author_name, "" ), processor.metaString( author_url, "" ), processor.metaStringArrayByKey( hint, new String[] {} ), processor.metaStringArrayByKey( solution, new String[] {} ), num_rabs, processor.metaInt( num_to_save, 1 ), processor.metaIntArray( rabbit_delay, new int[]{4} ), processor.metaString( music, null ), processor.metaInt( num_saved, 0 ), processor.metaInt( num_killed, 0 ), processor.metaInt( num_waiting, num_rabs ), processor.metaInt( rabbit_index_count, 0 ), processor.metaBool( paused, false ), processor.getComments(), statsListener, processor.generateVoidMarkerStyle() ); } public static World createEmptyWorld( int width, int height ) { return new World( new Dimension( width, height ), new ArrayList<Block>(), new ArrayList<Rabbit>(), new ArrayList<Thing>(), new HashMap<Position, Integer>(), new HashMap<Token.Type, Integer>(), "Empty World", //name "", //description "", //author_name "", //author_url new String[] {}, //hints new String[] {}, //solutions 0, //num_rabs 1, //num_to_save new int[]{4}, //rabbit_delay null, //music 0, //num_saved 0, //num_killed 0, //num_waiting 0, //rabbit_index_count false, new Comment[] {}, new IgnoreWorldStatsListener(), VoidMarkerStyle.Style.HIGHLIGHTER ); } public static String[] renderWorld( World world, boolean showChanges, boolean coordinates ) { Chars chars = new Chars( world, false ); BlockRenderer.render( chars, world.blockTable ); RabbitRenderer.render( chars, world.rabbits ); ThingRenderer.render( chars, world.things ); if ( showChanges ) { ChangeRenderer.render( chars, world.describeChanges() ); } return charsToStrings( chars, coordinates ); } public static String[] renderCompleteWorld( World world, boolean meta ) { return renderCompleteWorld( world, meta, true ); } /** * @param world The World to render. * @param meta If true the metadata is included. * @param runtimeMeta If true the runtime metadata is included. * @return The lines as an array. */ public static String[] renderCompleteWorld( World world, boolean meta, boolean runtimeMeta ) { Chars chars = new Chars( world, true ); BlockRenderer.render( chars, world.blockTable ); WaterRenderer.render( chars, world.waterTable ); RabbitRenderer.render( chars, world.rabbits ); ThingRenderer.render( chars, world.things ); String[] things = charsToComplete( chars, world.comments ); if ( meta ) { List<String> worldComments = new ArrayList<String>(); addMeta( worldComments, Comment.WORLD_ASCII_ART, null, world.comments); List<String> endComments = new ArrayList<String>(); // Comments after all substantive meta. addMeta( endComments, null, null, world.comments); return concat( metaLines( world, runtimeMeta ), worldComments.toArray( new String[]{} ), things, endComments.toArray( new String[]{}) ); } else { return things; } } private static Function<String, String> escapeBackslashes = new Function<String, String>() { @Override public String apply( String t ) { return t.replaceAll( "\\\\", "\\\\\\\\" ); } }; public static String renderWorldForTest( World world ) { String[] lines = Util.stringArray( Util.map( escapeBackslashes, renderWorld( world, true, false ) ) ); String glue = "\" + \"\\n\" +\n \""; return " \"" + Util.join( glue, lines ) + "\",\n"; } /** * Adds a line of metadata to the supplied List. If there are any comments * for this key comment lines will be added first. * @param value may be null, in which case only comments will be considered. */ private static void addMeta( List<String> lines, String key, String value, Comment[] comments ) { for( Comment c: comments) { if( c.isFollowedBy( key ) ) { lines.add( c.text ); } } if ( null == value ) { // We were only here to consider adding comments. return; } else { lines.add( ":" + key + "=" + value ); } } private static String[] metaLines( World world, boolean runtimeMeta ) { List<String> ret = new ArrayList<String>(); addMeta( ret, name, world.name, world.comments ); addMeta( ret, description, world.description, world.comments ); addMeta( ret, author_name, world.author_name, world.comments ); addMeta( ret, author_url, world.author_url, world.comments ); addMetaKeyArrayLines( ret, hint, world.hints, world ); addMetaKeyArrayLines( ret, solution, world.solutions, world ); addMeta( ret, num_rabbits, Integer.toString( world.num_rabbits ), world.comments ); addMeta( ret, num_to_save, Integer.toString( world.num_to_save ), world.comments ); addMeta( ret, rabbit_delay, renderIntArray( world.rabbit_delay ), world.comments ); if ( runtimeMeta ) { addMeta( ret, num_saved, Integer.toString( world.num_saved ), world.comments ); addMeta( ret, num_killed, Integer.toString( world.num_killed ), world.comments ); addMeta( ret, num_waiting, Integer.toString( world.num_waiting ), world.comments ); addMeta( ret, rabbit_index_count, Integer.toString( world.getRabbitIndexCount() ), world.comments ); addMeta( ret, paused, Boolean.toString( world.paused ), world.comments ); } abilityMetaLines( world, ret ); return ret.toArray( new String[ret.size()] ); } private static void addMetaKeyArrayLines( List<String> ret, String name, String[] values, World w ) { for ( IdxObj<String> value : enumerate1( values ) ) { String keyWithIndex = name + "." + value.index; addMeta( ret, keyWithIndex, value.object, w.comments ); } } private static void abilityMetaLines( World world, List<String> ret ) { List<Token.Type> abilityNames = new ArrayList<Token.Type>( world.abilities.keySet() ); Comparator<Token.Type> alphabetical = new Comparator<Token.Type>() { @Override public int compare( Token.Type o1, Token.Type o2 ) { return o1.name().compareTo( o2.name() ); } }; Collections.sort( abilityNames, alphabetical ); for ( Token.Type t : abilityNames ) { addMeta( ret, t.name(), Integer.toString( world.abilities.get( t ) ), world.comments ); } } private static String renderIntArray( int[] value ) { String ret = Integer.toString( value[0] ); for ( int i = 1; i< value.length; i++) { ret = ret + "," + Integer.toString( value[i] ) ; } return ret; } public static String[] renderChangeDescription( World world, ChangeDescription desc, boolean coordinates ) { Chars chars = new Chars( world, false ); ChangeRenderer.render( chars, desc ); return charsToStrings( chars, coordinates ); } private static String[] charsToStrings( Chars chars, boolean coordinates ) { int len = chars.numRows(); if ( coordinates ) { len += 2; } String[] ret = new String[len]; for ( int lineNum = 0; lineNum < chars.numRows(); ++lineNum ) { String ans = new String( chars.line( lineNum ) ); if ( coordinates ) { ans = formatRowNum( lineNum ) + " " + ans; } ret[lineNum] = ans; } if ( coordinates ) { addColumnCoords( ret, chars.numCols(), chars.numRows() ); } return ret; } private static String[] charsToComplete( Chars chars, Comment[] comments ) { List<String> ret = new ArrayList<String>(); for ( int lineNum = 0; lineNum < chars.numRows(); ++lineNum ) { ret.add( new String( chars.line( lineNum ) ) ); } addMeta( ret, "*", null, comments ); for ( String starLine : chars.starLines() ) { ret.add( ":*=" + starLine ); } for ( String waterAmountLine : chars.waterAmountLines() ) { ret.add( ":n=" + waterAmountLine ); } return ret.toArray( new String[ ret.size() ] ); } private static void addColumnCoords( String[] ret, int width, int height ) { StringBuilder tensRow = new StringBuilder(); StringBuilder unitsRow = new StringBuilder(); tensRow.append( " " ); unitsRow.append( " " ); for ( int i = 0; i < width; ++i ) { tensRow.append( ( i / 10 ) % 10 ); unitsRow.append( i % 10 ); } ret[height] = tensRow.toString(); ret[height + 1] = unitsRow.toString(); } private static String formatRowNum( int lineNum ) { return String.format( "%02d", lineNum ).substring( 0, 2 ); } }