package rabbitescape.engine; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.fail; import static rabbitescape.engine.ChangeDescription.State.*; import static rabbitescape.engine.Tools.*; import static rabbitescape.engine.textworld.TextWorldManip.*; import java.util.HashMap; import java.util.Map; import org.junit.Test; import static rabbitescape.engine.util.Util.*; import rabbitescape.engine.World.CompletionState; import rabbitescape.engine.solution.Solution; import rabbitescape.engine.solution.SolutionExceptions; import rabbitescape.engine.solution.SolutionParser; import rabbitescape.engine.solution.SolutionRunner; import rabbitescape.engine.solution.SolutionExceptions.RanPastEnd; import rabbitescape.engine.textworld.ArrayByKeyElementMissing; import rabbitescape.engine.textworld.DuplicateMetaKey; import rabbitescape.engine.textworld.ItemsLineProcessor; import rabbitescape.engine.textworld.LineProcessor; import rabbitescape.engine.util.Util.IdxObj; public class TestTextWorldManip { @Test public void Round_trip_basic_world() { String[] lines = { "###########", "# Q A c #", "#\\ M i/#", "# O d#", "#r j )( b#", "###########" }; assertThat( renderWorld( createWorld( lines ), false, false ), equalTo( lines ) ); // Also, shouldn't throw if we render this with states renderWorld( createWorld( lines ), true, false ); } @Test public void Basic_world_with_coords() { String[] world = { "############", "# #", "# #", "# #", "# #", "############" }; String[] expected = { "00 ############", "01 # #", "02 # #", "03 # #", "04 # #", "05 ############", " 000000000011", " 012345678901", }; assertThat( renderWorld( createWorld( world ), false, true ), equalTo( expected ) ); } @Test public void Walking_rabbits() { World world = createEmptyWorld( 3, 3 ); ChangeDescription desc = new ChangeDescription(); desc.add( 0, 0, RABBIT_WALKING_RIGHT ); desc.add( 2, 1, RABBIT_WALKING_LEFT ); desc.add( 1, 2, RABBIT_WALKING_RIGHT ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " > ", " < ", " >" ) ); } @Test public void Turning_rabbits() { World world = createEmptyWorld( 3, 2 ); ChangeDescription desc = new ChangeDescription(); desc.add( 0, 0, RABBIT_TURNING_LEFT_TO_RIGHT ); desc.add( 2, 0, RABBIT_TURNING_RIGHT_TO_LEFT ); assertThat( renderChangeDescription( world, desc, false ), equalTo( "| ?", " " ) ); } @Test public void Rising_rabbits_right() { World world = createEmptyWorld( 5, 8 ); ChangeDescription desc = new ChangeDescription(); desc.add( 0, 1, RABBIT_RISING_RIGHT_START ); desc.add( 0, 3, RABBIT_RISING_RIGHT_CONTINUE ); desc.add( 0, 5, RABBIT_RISING_RIGHT_END ); desc.add( 0, 7, RABBIT_TURNING_RIGHT_TO_LEFT_RISING ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " ", " ~ ", " $ ", " ", " ' ", " ", " ", "? " ) ); } @Test public void Rising_rabbits_left() { World world = createEmptyWorld( 5, 8 ); ChangeDescription desc = new ChangeDescription(); desc.add( 4, 1, RABBIT_RISING_LEFT_START ); desc.add( 4, 3, RABBIT_RISING_LEFT_CONTINUE ); desc.add( 4, 5, RABBIT_RISING_LEFT_END ); desc.add( 4, 7, RABBIT_TURNING_LEFT_TO_RIGHT_RISING ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " ", " ` ", " ^ ", " ", " ! ", " ", " ", " |" ) ); } @Test public void Lowering_rabbits_right() { World world = createEmptyWorld( 5, 8 ); ChangeDescription desc = new ChangeDescription(); desc.add( 0, 0, RABBIT_LOWERING_RIGHT_START ); desc.add( 0, 2, RABBIT_LOWERING_RIGHT_CONTINUE ); desc.add( 0, 4, RABBIT_LOWERING_RIGHT_END ); desc.add( 0, 6, RABBIT_TURNING_LEFT_TO_RIGHT_LOWERING ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " ", " - ", " ", " @ ", " _ ", " ", "[ ", " " ) ); } @Test public void Lowering_rabbits_left() { World world = createEmptyWorld( 5, 8 ); ChangeDescription desc = new ChangeDescription(); desc.add( 4, 0, RABBIT_LOWERING_LEFT_START ); desc.add( 4, 2, RABBIT_LOWERING_LEFT_CONTINUE ); desc.add( 4, 4, RABBIT_LOWERING_LEFT_END ); desc.add( 4, 6, RABBIT_TURNING_LEFT_TO_RIGHT_LOWERING ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " ", " = ", " ", " % ", " + ", " ", " [", " " ) ); } @Test public void Falling_rabbits() { World world = createEmptyWorld( 3, 5 ); ChangeDescription desc = new ChangeDescription(); desc.add( 0, 0, RABBIT_FALLING ); desc.add( 2, 1, RABBIT_FALLING ); desc.add( 1, 2, RABBIT_FALLING_1 ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " ", "f ", "f f", " ff", " " ) ); } @Test public void Rabbits_falling_odd_num_squares_to_death() { World world = createEmptyWorld( 3, 2 ); ChangeDescription desc = new ChangeDescription(); desc.add( 0, 0, RABBIT_FALLING_1_TO_DEATH ); desc.add( 2, 0, RABBIT_DYING_OF_FALLING_2 ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " y", "x " ) ); } @Test public void Rabbits_falling_even_num_squares_to_death() { World world = createEmptyWorld( 3, 2 ); ChangeDescription desc = new ChangeDescription(); desc.add( 0, 0, RABBIT_DYING_OF_FALLING ); assertThat( renderChangeDescription( world, desc, false ), equalTo( "X ", " " ) ); } @Test public void Rabbits_walking_down_and_immediately_up() { World world = createEmptyWorld( 5, 2 ); ChangeDescription desc = new ChangeDescription(); desc.add( 0, 0, RABBIT_LOWERING_AND_RISING_RIGHT ); desc.add( 4, 0, RABBIT_LOWERING_AND_RISING_LEFT ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " , . ", " " ) ); } @Test public void Rabbits_walking_up_and_immediately_down() { World world = createEmptyWorld( 5, 2 ); ChangeDescription desc = new ChangeDescription(); desc.add( 0, 0, RABBIT_RISING_AND_LOWERING_RIGHT ); desc.add( 4, 0, RABBIT_RISING_AND_LOWERING_LEFT ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " & m ", " " ) ); } @Test public void Rabbits_falling_onto_slopes() { World world = createEmptyWorld( 8, 3 ); ChangeDescription desc = new ChangeDescription(); desc.add( 0, 0, RABBIT_FALLING_ONTO_LOWER_RIGHT ); desc.add( 1, 0, RABBIT_FALLING_ONTO_RISE_RIGHT ); desc.add( 2, 0, RABBIT_FALLING_ONTO_LOWER_LEFT ); desc.add( 3, 0, RABBIT_FALLING_ONTO_RISE_LEFT ); desc.add( 4, 0, RABBIT_FALLING_1_ONTO_LOWER_RIGHT ); desc.add( 5, 0, RABBIT_FALLING_1_ONTO_RISE_RIGHT ); desc.add( 6, 0, RABBIT_FALLING_1_ONTO_LOWER_LEFT ); desc.add( 7, 0, RABBIT_FALLING_1_ONTO_RISE_LEFT ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " ", "ffffehsa", "ehsa " ) ); } @Test public void Tokens_falling() { World world = createEmptyWorld( 4, 2 ); ChangeDescription desc = new ChangeDescription(); desc.add( 0, 0, TOKEN_BASH_FALLING ); desc.add( 1, 0, TOKEN_DIG_FALLING ); desc.add( 2, 0, TOKEN_BRIDGE_FALLING ); desc.add( 3, 0, TOKEN_BLOCK_FALLING ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " ", "ffff" ) ); } @Test public void Bashing() { World world = createEmptyWorld( 3, 4 ); ChangeDescription desc = new ChangeDescription(); desc.add( 1, 0, RABBIT_BASHING_RIGHT ); desc.add( 1, 1, RABBIT_BASHING_LEFT ); desc.add( 1, 2, RABBIT_BASHING_USELESSLY_RIGHT ); desc.add( 1, 3, RABBIT_BASHING_USELESSLY_LEFT ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " K", "W ", " I", "J " ) ); } @Test public void Climbing() { World world = createEmptyWorld( 17, 3 ); ChangeDescription desc = new ChangeDescription(); desc.add( 1, 1, RABBIT_CLIMBING_LEFT_START ); desc.add( 3, 1, RABBIT_CLIMBING_LEFT_CONTINUE_1 ); desc.add( 5, 1, RABBIT_CLIMBING_LEFT_CONTINUE_2 ); desc.add( 7, 1, RABBIT_CLIMBING_LEFT_END ); desc.add( 9, 1, RABBIT_CLIMBING_RIGHT_START ); desc.add( 11, 1, RABBIT_CLIMBING_RIGHT_CONTINUE_1 ); desc.add( 13, 1, RABBIT_CLIMBING_RIGHT_CONTINUE_2 ); desc.add( 15, 1, RABBIT_CLIMBING_RIGHT_END ); assertThat( renderChangeDescription( world, desc, false ), equalTo( " Y YU F F L", " T G ", " " ) ); } @Test public void Can_supply_default_name() { assertThat( createWorldWithName( "defname", new IgnoreWorldStatsListener(), new String[] {} ).name, equalTo( "defname" ) ); } @Test public void Default_name_is_ignored_if_name_property_found() { String[] lines = { ":name=bar" }; assertThat( createWorldWithName( "defname", new IgnoreWorldStatsListener(), lines ).name, equalTo( "bar" ) ); } @Test public void Can_provide_world_name() { String[] lines = { ":name=My World!" }; assertThat( createWorld( lines ).name, equalTo( "My World!" ) ); } @Test public void Can_provide_world_description() { String[] lines = { ":description=Go here, then there" }; assertThat( createWorld( lines ).description, equalTo( "Go here, then there" ) ); } @Test public void Can_provide_empty_description() { String[] lines = { ":description=" }; assertThat( createWorld( lines ).description, equalTo( "" ) ); } @Test public void Can_obfuscate_hints() { String[] lines = { ":hint.1.code=_Uf?>3ZH_8>U>{3", ":hint.2.code=@XW@W:)e+:+ ", ":hint.3.code=X5g..T[6X4[" }; World world = createWorld( lines ); assertThat( world.hints[0], equalTo( "Select the bash" ) ); assertThat( world.hints[1], equalTo( "Use the bash" ) ); assertThat( world.hints[2], equalTo( "Be the bash" ) ); } @Test public void Can_provide_number_of_rabbits() { String[] lines = { ":num_rabbits=10" }; assertThat( createWorld( lines ).num_rabbits, equalTo( 10 ) ); } @Test public void Empty_lines_are_treated_as_spaces() { World world = createWorld( "####", "", "# #", "" ); assertThat( renderCompleteWorld( world, false ), equalTo( "####", " ", "# #", " " ) ); } @Test public void Full_dump_shows_overlapping_things() { // Make an empty world World world = createWorld( "####", "# #", "# /#", "####" ); // put 2 rabbits and 2 items all in the same place, on top of a block world.rabbits.add( new Rabbit( 2, 2, Direction.RIGHT ) ); world.rabbits.add( new Rabbit( 2, 2, Direction.LEFT ) ); world.things.add( new Token( 2, 2, Token.Type.bash ) ); world.things.add( new Token( 2, 2, Token.Type.bridge ) ); assertThat( renderCompleteWorld( world, false ), equalTo( "####", "# #", "# *#", "####", ":*=/rjbi" ) ); } @Test public void Multiple_overlapping_things_come_in_reading_order() { // Make an empty world World world = createWorld( "####", "# #", "#\\/#", "####" ); // Rabbits in top left world.rabbits.add( new Rabbit( 1, 1, Direction.RIGHT ) ); world.rabbits.add( new Rabbit( 1, 1, Direction.LEFT ) ); // bash and bridge in top right world.things.add( new Token( 2, 1, Token.Type.bash ) ); world.things.add( new Token( 2, 1, Token.Type.bridge ) ); // dig in bottom left and bottom right world.things.add( new Token( 1, 2, Token.Type.dig ) ); world.things.add( new Token( 2, 2, Token.Type.dig ) ); assertThat( renderCompleteWorld( world, false ), equalTo( "####", "#**#", "#**#", "####", ":*=rj", ":*=bi", ":*=\\d", ":*=/d" ) ); } @Test public void Overlap_meta_lines_can_come_straight_after_their_stars() { // This is what we are testing: we can have * meta lines any time // after the * they refer to World world = createWorld( "####", "#**#", ":*=r{index:2}j{index:3}", ":*=bi", "#**#", "####", ":*=\\d", ":*=/d" ); // Result is the same as if they came at the end assertThat( renderCompleteWorld( world, false ), equalTo( "####", "#**#", "#**#", "####", ":*=r{index:2}j{index:3}", ":*=bi", ":*=\\d", ":*=/d" ) ); } @Test public void Blocking_state_is_preserved() { World world = createWorld( "rk ", "###" ); // Pick up block token world.step(); // We are now blocking assertThat( renderWorld( world, true, false ), equalTo( " H ", "###" ) ); // Round trip World world2 = createWorld( renderCompleteWorld( world, false ) ); // We are still blocking assertThat( renderWorld( world2, true, false ), equalTo( " H ", "###" ) ); } @Test public void Digging_state_is_preserved() { World world = createWorld( "rd ", "###", "###", "###" ); // Pick up dig token world.step(); // We are now digging assertThat( renderWorld( world, true, false ), equalTo( " r ", "#D#", "###", "###" ) ); // Round trip World world2 = createWorld( renderCompleteWorld( world, false ) ); // We are still digging assertThat( renderWorld( world2, true, false ), equalTo( " r ", "#D#", "###", "###" ) ); } @Test public void Digging_state_is_preserved_second_step() { World world = createWorld( "rd ", "###", "###", "###" ); // Pick up dig token and begin digging world.step(); world.step(); // We are now digging assertThat( renderWorld( world, true, false ), equalTo( " ", "#D#", "###", "###" ) ); // Round trip @SuppressWarnings( "unused" ) World world2 = createWorld( renderCompleteWorld( world, false ) ); // We are still digging // TODO: bug: we moved on a step in the round trip /*assertThat( renderWorld( world2, true, false ), equalTo( " ", "#D#", "###", "###" ) );*/ } @Test public void Bashing_state_is_preserved() { World world = createWorld( "rb#", "###" ); // Pick up bash token world.step(); // We are now bashing assertThat( renderWorld( world, true, false ), equalTo( " rK", "###" ) ); // Round trip World world2 = createWorld( renderCompleteWorld( world, false ) ); // We are still bashing assertThat( renderWorld( world2, true, false ), equalTo( " rK", "###" ) ); } @Test public void Round_trip_world_with_overlaps() { String[] lines = { "####", "#**#", "#**#", "####", ":*=r{index:1}j{index:40}", ":*=bi", ":*=\\d", ":*=/d" }; assertThat( renderCompleteWorld( createWorld( lines ), false ), equalTo( lines ) ); } @Test public void Deprecated_items_are_still_allowed_in_world_serialisation() { String[] lines = { ":name=My X Trip", ":description=", ":num_rabbits=25", ":num_to_save=4", ":rabbit_delay=2", ":num_saved=5", ":num_killed=4", ":num_waiting=16", ":intro=true", // Deprecated ":paused=false", ":ready_to_explode_all=false", // Deprecated ":bash=1", ":block=4", ":bridge=3", ":dig=2", ":explode=5", "####", "#**#", "#**#", "####", ":*=rj", ":*=bi", ":*=\\d", ":*=/d" }; World world = createWorld( lines ); // Just a basic sanity check assertThat( world.name, equalTo( "My X Trip" ) ); } @Test public void Round_trip_world_with_metadata() { String[] lines = { ":name=My X Trip", ":description=", ":author_name=Alice Jones", ":author_url=http://example.com", ":hint.1=foo\nbar", ":hint.2=baz", ":hint.3=bash", ":num_rabbits=25", ":num_to_save=4", ":rabbit_delay=2", ":num_saved=5", ":num_killed=4", ":num_waiting=16", ":rabbit_index_count=7", ":paused=false", ":bash=1", ":block=4", ":bridge=3", ":dig=2", ":explode=5", "####", "#**#", "#**#", "####", ":*=r{index:5}j{index:7}", ":*=bi", ":*=\\d", ":*=/d" }; assertThat( renderCompleteWorld( createWorld( lines ), true ), equalTo( lines ) ); } @Test public void State_map_from_empty_string_is_empty() { assertThat( ItemsLineProcessor.stateMap( "" ), equalTo( map() ) ); } @Test public void Single_field_state_map() { assertThat( ItemsLineProcessor.stateMap( "adsfg.foo:9" ), equalTo( map( "adsfg.foo", "9" ) ) ); } @Test public void Multiple_field_state_map() { assertThat( ItemsLineProcessor.stateMap( "adsfg.foo:9,x:6,Agf.d.9:10" ), equalTo( map( "adsfg.foo", "9", "x", "6", "Agf.d.9", "10" ) ) ); } private static Map<String, String> map( String... keysAndValues ) { assert ( keysAndValues.length % 2 ) == 0; Map<String, String> ret = new HashMap<String, String>(); for ( int i = 0; i < keysAndValues.length; i += 2 ) { ret.put( keysAndValues[i], keysAndValues[i+1] ); } return ret; } @Test public void Round_trip_world_with_state() { String[] lines = { ":name=My Round Trip", ":description=Go around", ":author_name=bob", ":author_url=", ":hint.1=", ":hint.2=", ":hint.3=", ":num_rabbits=25", ":num_to_save=4", ":rabbit_delay=2", ":num_saved=5", ":num_killed=4", ":num_waiting=16", ":rabbit_index_count=4", ":paused=true", ":bash=1", ":bridge=3", ":dig=2", "###########", "# #", "# #", "# * * * * #", "###########", ":*=r{Bashing.stepsOfBashing:1,index:1}Q{Entrance.timeToNextRabbit:3}", ":*=j{Bridging.bigSteps:1,Bridging.bridgeType:DOWN_UP,Bridging.smallSteps:1,index:2,onSlope:true}", ":*=j{Climbing.hasAbility:true,index:3}", ":*=j{Climbing.abilityActive:true,Climbing.hasAbility:true,index:4}", }; assertThat( renderCompleteWorld( createWorld( lines ), true ), equalTo( lines ) ); } @Test public void Test_variable_rabbit_delay() { String[] lines = { ":rabbit_delay=1,2,3", ":num_rabbits=5", "Q ", " ", "#############################################################################" }; World world = createWorld( lines ); for ( int i = 0 ; i<16 ; i++ ) { world.step(); } String[] resultLines=renderCompleteWorld( world, false ); String[] expectedLines = { "* ", " * * * ** ", "#############################################################################", ":*=Q{Entrance.timeToNextRabbit:2}", ":*=r{index:5}", ":*=r{index:4}", ":*=r{index:3}", ":*=r{index:2}", ":*=r{index:1}" }; assertThat( resultLines, equalTo( expectedLines )); } /** * @brief Test an example world with variable rabbit_delay. * Parse it, reserialise it, and test for changes. */ @Test public void Round_trip_for_variable_delay_world() { String[] lines = { ":name=var delay round trip", ":description=trippy", ":author_name=cyril", ":author_url=", ":hint.1=take", ":hint.2=a", ":hint.3=hint", ":solution.1=", ":solution.2=", ":solution.3=", ":num_rabbits=20", ":num_to_save=18", ":rabbit_delay=10,3,2,10", ":num_saved=0", ":num_killed=0", ":num_waiting=20", ":rabbit_index_count=0", ":paused=false", "#######", "#Q Q#", "# #", "#######" }; assertThat( renderCompleteWorld( createWorld( lines ), true ), equalTo( lines ) ); } @Test public void Comments_for_string_arrays_by_key_associate_correctly() { String[] lines = { ":name=Comments", ":description=verbose", ":author_name=bob", ":author_url=", "% something erudite", ":hint.1=take", "% insight", ":hint.2=a", "% wisdom regarding hint.3", ":hint.3=hint", ":hint.4=hint", ":hint.5=hint", ":hint.6=hint", ":hint.7=hint", ":hint.8=hint", "% some acumen", ":hint.9=hint", "% sagacity personified", ":hint.10=hint", ":hint.11=hint", "% deep understanding", ":hint.12=hint", ":solution.1=", "% a lot of rabbits", ":num_rabbits=20", ":num_to_save=18", ":rabbit_delay=10,3,2,10", ":num_saved=0", ":num_killed=0", ":num_waiting=20", ":rabbit_index_count=0", ":paused=false", "#######", "#Q Q#", "# #", "#######", }; assertThat( renderCompleteWorld( createWorld( lines ), true ), equalTo( lines ) ); } @Test public void Comments_for_abilities_round_trip() { String[] lines = { ":name=Comments", ":description=verbose", ":author_name=bob", ":author_url=", ":num_rabbits=20", ":num_to_save=18", ":rabbit_delay=10,3,2,10", ":num_saved=0", ":num_killed=0", ":num_waiting=20", ":rabbit_index_count=0", ":paused=false", "% a", ":bash=1", "% b", "% c", ":block=4", "% d", ":bridge=3", "% e", ":climb=5", ":dig=2", "%f", ":explode=6", "#######", "#Q Q#", "# #", "#######", }; assertThat( renderCompleteWorld( createWorld( lines ), true ), equalTo( lines ) ); } @Test public void Round_trip_comments() { String[] lines = { ":name=Comments", "% desc 1.", "% desc 2. can have 2 comment line about something ", ":description=verbose", "% I love bob's work", ":author_name=bob", "% his website is great", ":author_url=", "% something erudite about hint.1", ":hint.1=take", ":hint.2=a", "% wisdom regarding hint.3", ":hint.3=hint", ":solution.1=", "% s2 looks like cheeating", ":solution.2=", ":solution.3=", "% a lot of rabbits", ":num_rabbits=20", "% save how many?", ":num_to_save=18", "% why are we waiting?", ":rabbit_delay=10,3,2,10", "% some already out: no", ":num_saved=0", "% dead already", ":num_killed=0", "% the bunnies are queuing in the pre-life", ":num_waiting=20", ":rabbit_index_count=20", "% paused ", ":paused=false", "% pretty ascii art", "#######", "#Q Q#", "# *** #", "#######", "% starpoint comment", ":*=r{index:1}r{index:2}", ":*=j{index:3}j{index:4}", ":*=r{index:5}j{index:20}", "% comments are also OK after", "% all the substantive metadata" }; assertThat( renderCompleteWorld( createWorld( lines ), true ), equalTo( lines ) ); } @Test public void Round_trip_comments_move_with_meta() { String[] lines = { "% something erudite about hint.1", ":hint.1=take", ":hint.2=a", "% wisdom regarding hint.3", ":hint.3=hint", ":solution.1=", "% a lot of rabbits", ":num_rabbits=20", "% save how many?", ":num_to_save=18", "% why are we waiting?", ":rabbit_delay=10,3,2,10", "% some already out: no", ":num_saved=0", "% dead already", ":num_killed=0", "% his website is great", ":author_url=", "% the bunnies are queuing in the pre-life", ":num_waiting=20", ":rabbit_index_count=0", "% paused ", ":paused=false", "% pretty ascii art", "#######", ":name=Comments", "#Q Q#", "% desc 1.", "% desc 2. can have 2 comment line about something ", ":description=verbose", "# #", "% I love bob's work", ":author_name=bob", "#######", "% s2 looks like cheeating", ":solution.2=", ":solution.3=" }; String[] expectedLines = { ":name=Comments", "% desc 1.", "% desc 2. can have 2 comment line about something ", ":description=verbose", "% I love bob's work", ":author_name=bob", "% his website is great", ":author_url=", "% something erudite about hint.1", ":hint.1=take", ":hint.2=a", "% wisdom regarding hint.3", ":hint.3=hint", ":solution.1=", "% s2 looks like cheeating", ":solution.2=", ":solution.3=", "% a lot of rabbits", ":num_rabbits=20", "% save how many?", ":num_to_save=18", "% why are we waiting?", ":rabbit_delay=10,3,2,10", "% some already out: no", ":num_saved=0", "% dead already", ":num_killed=0", "% the bunnies are queuing in the pre-life", ":num_waiting=20", ":rabbit_index_count=0", "% paused ", ":paused=false", "% pretty ascii art", "#######", "#Q Q#", "# #", "#######" }; assertThat( renderCompleteWorld( createWorld( lines ), true ), equalTo( expectedLines ) ); } @Test public void Starpoint_comments_move_to_a_block() { String[] lines = { ":name=Comments", ":description=verbose", ":author_name=bob", ":author_url=", ":hint.1=take", ":hint.2=a", ":hint.3=hint", ":solution.1=", ":solution.2=", ":solution.3=", ":num_rabbits=20", ":num_to_save=18", ":rabbit_delay=10,3,2,10", "#######", "#Q Q#", "# *** #", "#######", "% starpoint comment 1", ":*=rr", "% starpoint comment 2", ":*=jj", "% starpoint comment 3", ":*=rj" }; String[] expectedLines = { ":name=Comments", ":description=verbose", ":author_name=bob", ":author_url=", ":hint.1=take", ":hint.2=a", ":hint.3=hint", ":solution.1=", ":solution.2=", ":solution.3=", ":num_rabbits=20", ":num_to_save=18", ":rabbit_delay=10,3,2,10", "#######", "#Q Q#", "# *** #", "#######", "% starpoint comment 1", "% starpoint comment 2", "% starpoint comment 3", ":*=r{index:1}r{index:2}", ":*=j{index:3}j{index:4}", ":*=r{index:5}j{index:6}" }; assertThat( renderCompleteWorld( createWorld( lines ), true, false ), equalTo( expectedLines ) ); } @Test public void World_comments_move_to_a_block() { String[] lines = { ":name=Comments", ":description=verbose", ":author_name=bob", ":author_url=", ":hint.1=take", ":hint.2=a", ":hint.3=hint", ":solution.1=", ":solution.2=", ":solution.3=", ":num_rabbits=20", ":num_to_save=18", ":rabbit_delay=10,3,2,10", "% interspersed", "#######", "% comments", "#Q Q#", "% move to a", "% block", "# #", "#######" }; String[] expectedLines = { ":name=Comments", ":description=verbose", ":author_name=bob", ":author_url=", ":hint.1=take", ":hint.2=a", ":hint.3=hint", ":solution.1=", ":solution.2=", ":solution.3=", ":num_rabbits=20", ":num_to_save=18", ":rabbit_delay=10,3,2,10", "% interspersed", "% comments", "% move to a", "% block", "#######", "#Q Q#", "# #", "#######" }; assertThat( renderCompleteWorld( createWorld( lines ), true, false ), equalTo( expectedLines ) ); } /** * @brief Key meta should be unique. Test that Duplicate name * entries cause a DuplicateMetaKey to be thrown. */ @Test( expected=DuplicateMetaKey.class ) public void Duplicate_meta_string_is_an_error() { String[] lines = { ":name=1", ":name=2", "#####", "#r #", "#####" }; renderCompleteWorld( createWorld( lines ), true ); } /** * @brief Key meta should be unique. Test that Duplicate num_rabbits * entries cause a DuplicateMetaKey to be thrown. */ @Test( expected=DuplicateMetaKey.class ) public void Duplicate_meta_int_is_an_error() { String[] lines = { ":num_rabbits=1", ":num_rabbits=2", "#####", "#r #", "#####" }; renderCompleteWorld( createWorld( lines ), true ); } /** * @brief Key meta should be unique. Test that Duplicate paused * entries cause a DuplicateMetaKey to be thrown. */ @Test( expected=DuplicateMetaKey.class ) public void Duplicate_meta_boolean_is_an_error() { String[] lines = { ":paused=true", ":paused=true", "#####", "#r #", "#####" }; renderCompleteWorld( createWorld( lines ), true ); } /** * @brief Key meta should be unique. Test that Duplicate rabbit_delay * entries cause a DuplicateMetaKey to be thrown. */ @Test( expected=DuplicateMetaKey.class ) public void Duplicate_meta_intarray_is_an_error() { String[] lines = { ":rabbit_delay=1,2,6", ":rabbit_delay=4", "#####", "#r #", "#####" }; renderCompleteWorld( createWorld( lines ), true ); } /** * @brief Key meta should be unique. Test that Duplicate dig * entries cause a DuplicateMetaKey to be thrown. */ @Test( expected=DuplicateMetaKey.class ) public void Duplicate_ability_is_an_error() { String[] lines = { ":dig=10", ":dig=10", "#####", "#r #", "#####" }; renderCompleteWorld( createWorld( lines ), true ); } /** * @brief Test an example world with some solutions. * Parse it, reserialise it, and test for changes. */ @Test public void Round_trip_for_solutions() { String[] lines = { ":name=solution round trip", ":description=trippy", ":author_name=cyril", ":author_url=", ":hint.1=take", ":hint.2=a", ":hint.3=hint", ":solution.1=10;6", ":solution.2=bash;(3,2)", ":solution.3=6;5", ":num_rabbits=20", ":num_to_save=18", ":rabbit_delay=10,3,2,10", ":num_saved=0", ":num_killed=0", ":num_waiting=20", ":rabbit_index_count=40", ":paused=false", "#######", "#Q Q#", "# #", "#######" }; assertThat( renderCompleteWorld( createWorld( lines ), true ), equalTo( lines ) ); } @Test public void Incorrect_solution_string_throws_exception() { String[] lines = { ":num_rabbits=1", ":solution.1=5", "Q ", " p ", "#####" }; try { runSolutions( lines ); fail( "Exception expected!" ); } catch ( SolutionExceptions.DidNotWin e ) { assertThat( e.solutionId, equalTo( 1 ) ); assertThat( e.commandIndex, equalTo( 2 ) ); assertThat( e.actual, equalTo( CompletionState.LOST ) ); } } @Test public void Incorrect_solution_2_string_throws_exception() { String[] lines = { ":num_rabbits=1", ":solution.1=6", ":solution.2=1;WON", "Q ", " O", "#####" }; try { runSolutions( lines ); fail( "Exception expected!" ); } catch ( SolutionExceptions.DidNotWin e ) { assertThat( e.solutionId, equalTo( 2 ) ); assertThat( e.commandIndex, equalTo( 2 ) ); assertThat( e.actual, equalTo( CompletionState.RUNNING ) ); } } @Test public void Valid_solution_string_throws_no_exception() { String[] lines = { ":num_rabbits=1", ":solution.1=6", "Q ", " O", "#####" }; runSolutions( lines ); } @Test public void Stepping_one_past_end_throws_no_exception() { // TODO: see SolutionRunner.shouldStepWorld: yuck - this should // not pass! String[] lines = { ":num_rabbits=1", ":solution.1=7", "Q ", " O", "#####" }; runSolutions( lines ); } @Test( expected = RanPastEnd.class ) public void Solution_too_many_steps_throws_exception() { String[] lines = { ":num_rabbits=1", ":solution.1=8", "Q ", " O", "#####" }; runSolutions( lines ); } @Test public void Complex_solution_strings() { String[] lines = { ":num_rabbits=1", ":bash=2", ":solution.1=bash&(1,0);4;RUNNING;1;WON", ":solution.2=bash;(1,1);1;(0,1);RUNNING;2;WON", ":solution.3=bash;(1,1)&(1,1);5;RUNNING", ":solution.4=bash;(4,1);(3,1);3;LOST", "Q ", " # O", "#####" }; runSolutions( lines ); } @Test public void Obfuscated_solution() { String[] lines = { ":num_rabbits=1", ":bash=2", ":solution.1.code=WTSMTzF4=n M>DmTBMTr}3$Mz~", "Q ", " # O", "#####" }; runSolutions( lines ); } @Test public void Solutions_are_held_in_world() { String[] lines = { ":num_rabbits=1", ":bash=2", ":solution.1=1", ":solution.2=2", " Q ", "# #", "#####" }; World world = createWorld( lines ); assertThat( world.solutions[0], equalTo( "1" ) ); assertThat( world.solutions[1], equalTo( "2" ) ); } @Test public void Over_10_solutions_are_held_in_world() { String[] lines = { ":num_rabbits=1", ":bash=2", ":solution.1=10", ":solution.2=10", ":solution.3=10", ":solution.4=10", ":solution.5=10", ":solution.6=10", ":solution.7=10", ":solution.8=10", ":solution.9=10", ":solution.10=10", ":solution.11=10", " Q ", "# #", "#####" }; World world = createWorld( lines ); assertThat( world.solutions[0], equalTo( "10" ) ); assertThat( world.solutions[1], equalTo( "10" ) ); assertThat( world.solutions[9], equalTo( "10" ) ); assertThat( world.solutions[10], equalTo( "10" ) ); } @Test ( expected = ArrayByKeyElementMissing.class ) public void Disorderly_solutions_throw_exceptions() { String[] lines = { ":num_rabbits=1", ":bash=2", ":solution.1=10", ":solution.3=11", ":solution.2=10;bash", " Q ", "# #", "#####" }; createWorld( lines ); } @Test ( expected = ArrayByKeyElementMissing.class ) public void Many_solutions_with_gaps() { String[] lines = { ":num_rabbits=1", ":bash=2", ":solution.1=10", ":solution.101=10;bash", " Q ", "# #", "#####" }; World world = createWorld( lines ); assertThat( world.solutions[0], equalTo( "10" ) ); assertThat( world.solutions[1], equalTo( "" ) ); assertThat( world.solutions[100], equalTo( "10;bash" ) ); } @Test public void Identical_KeyListKeys_are_equal() { assertThat( new LineProcessor.KeyListKey( "xyz", 3 ), equalTo( new LineProcessor.KeyListKey( "xyz", 3 ) ) ); assertThat( new LineProcessor.KeyListKey( "xyz", 3 ).hashCode(), equalTo( new LineProcessor.KeyListKey( "xyz", 3 ).hashCode() ) ); } @Test public void Different_KeyListKeys_are_not_equal() { assertThat( new LineProcessor.KeyListKey( "xyz", 3 ), not( equalTo( new LineProcessor.KeyListKey( "xyz", 4 ) ) ) ); assertThat( new LineProcessor.KeyListKey( "xyz", 3 ).hashCode(), not( equalTo( new LineProcessor.KeyListKey( "xyz", 4 ).hashCode() ) ) ); } @Test public void Can_parse_KeyListKey() { assertThat( LineProcessor.parseKeyListKey( "solution.1" ), equalTo( new LineProcessor.KeyListKey( "solution", 1 ) ) ); } @Test public void Parsing_non_KeyListKey_returns_no_match() { assertThat( LineProcessor.parseKeyListKey( "solution1" ), equalTo( new LineProcessor.KeyListKey( "NO KEY LIST MATCH", -1 ) ) ); } @Test public void Comments_only_active_at_start_of_line() { String[] lines = { ":name=Commentary % look at me", ":description=trippy", ":author_name=cyril", ":author_url=", ":hint.1=", ":hint.2=", ":hint.3=", ":num_rabbits=20", ":num_to_save=18", ":rabbit_delay=1", ":num_saved=0", ":num_killed=0", ":num_waiting=20", ":rabbit_index_count=0", ":paused=false", "#######", "#Q Q#", "# #", "#######" }; assertThat( renderCompleteWorld( createWorld( lines ), true ), equalTo( lines ) ); } @Test public void String_hash_is_reproducible() { String[] sa = new String[]{ "Dig for victory", "Tomb raider", "Slot machine", "Space invaders", "UFO" }; int[] h = new int[]{ 1451, 1065, 1175, 1384, 234 }; for ( int i = 0; i < sa.length ; i++ ) { assertThat( LineProcessor.stringHash( sa[i] ), equalTo( h[i] ) ); } } @Test public void Gentest_contains_extra_quotes_and_line_breaks() { String[] lines = { "#####", "# r #", "#####" }; assertThat( renderWorldForTest( createWorld( lines ) ), equalTo( " \"#####\" + \"\\n\" +\n" + " \"# r>#\" + \"\\n\" +\n" + " \"#####\",\n" ) ); } @Test public void Gentest_escapes_backslashes() { String[] lines = { "#####", "#\\ #", "#####" }; assertThat( renderWorldForTest( createWorld( lines ) ), equalTo( " \"#####\" + \"\\n\" +\n" + " \"#\\\\ #\" + \"\\n\" +\n" + " \"#####\",\n" ) ); } @Test public void Gentest_contains_extra_quotes_and_line_breaks_lots_of_types() { String[] lines = { "###########", "# Q A c #", "#\\ i/#", "# O d#", "#r j )( b#", "###########" }; assertThat( renderWorldForTest( createWorld( lines ) ), equalTo( " \"###########\" + \"\\n\" +\n" + " \"# Q A c #\" + \"\\n\" +\n" + " \"#\\\\ g f/#\" + \"\\n\" +\n" + " \"# O fd#\" + \"\\n\" +\n" + " \"#r<j )( f#\" + \"\\n\" +\n" + " \"###########\",\n" ) ); } // --- private void runSolutions( String[] lines ) { World world = createWorld( lines ); for ( IdxObj<String> s : enumerate1( world.solutions ) ) { Solution solution = SolutionParser.parse( s.object ); try { SolutionRunner.runSolution( solution, world ); } catch ( SolutionExceptions.ProblemRunningSolution e ) { e.solutionId = s.index; throw e; } } } }