package rabbitescape.engine.solution; import static rabbitescape.engine.util.Util.*; import java.io.PrintStream; import rabbitescape.engine.World; import rabbitescape.engine.Token.Type; import rabbitescape.engine.World.CantAddTokenOutsideWorld; import rabbitescape.engine.World.CompletionState; import rabbitescape.engine.World.DontStepAfterFinish; import rabbitescape.engine.World.NoSuchAbilityInThisWorld; import rabbitescape.engine.World.NoneOfThisAbilityLeft; import rabbitescape.engine.solution.SolutionExceptions; import rabbitescape.engine.textworld.TextWorldManip; import rabbitescape.engine.util.Dimension; import rabbitescape.engine.util.Util; public class SolutionRunner { /** * @return true if the supplied solution solved the level * @param output A stream (eg System.out) to print to. May be null if no output * is required. */ public static boolean runSolution( Solution solution, World world, PrintStream output, boolean genTest ) throws SolutionExceptions.ProblemRunningSolution { SandboxGame sandboxGame = new SandboxGame( world ); SolutionInterpreter interpreter = new SolutionInterpreter( solution ); return runSolutionInSandbox( interpreter, sandboxGame, output, genTest ); } public static boolean runSolution( Solution solution, World world) { return runSolution( solution, world, null, false); } public static void runPartialSolution( Solution solution, final SandboxGame sandboxGame ) { SolutionInterpreter interpreter = new SolutionInterpreter( solution, false ); runSolutionInSandbox( interpreter, sandboxGame, null, false ); } private static boolean runSolutionInSandbox( SolutionInterpreter interpreter, SandboxGame sandboxGame, PrintStream output, boolean genTest ) { SolutionTimeStep step = interpreter.next( sandboxGame.getWorld().completionState() ); while ( step != null ) { try { SolutionTimeStep nextStep = interpreter.next( sandboxGame.getWorld().completionState() ); if ( null != output ) { printStep( output, sandboxGame.getWorld(), genTest ); } runTimeStep( sandboxGame, step, nextStep ); step = nextStep; } catch ( SolutionExceptions.ProblemRunningSolution e ) { e.commandIndex = step.commandIndex; e.world = join( "\n", TextWorldManip.renderWorld( sandboxGame.getWorld(), false, false ) ); throw e; } } return sandboxGame.getWorld().completionState().equals( CompletionState.WON ); } private static void printStep(PrintStream s, World w, boolean genTest ) { if ( genTest ) { s.println( TextWorldManip.renderWorldForTest( w ) ); } else { s.println( "Waiting:"+w.num_waiting ); s.println( " Saved:"+w.num_saved ); s.println ( Util.join( "\n", TextWorldManip.renderWorld( w, false, true ) ) ); } } private static void runTimeStep( SandboxGame sandboxGame, SolutionTimeStep step, SolutionTimeStep nextStep ) { for ( TimeStepAction action : step.actions ) { performAction( action, sandboxGame ); } try { if ( shouldStepWorld( nextStep, sandboxGame ) ) { sandboxGame.getWorld().step(); } } catch ( DontStepAfterFinish e ) { throw new SolutionExceptions.RanPastEnd( sandboxGame.getWorld().completionState() ); } } /** * If we have no next step, or the world is finished and the step is just * an assertion step, return false. Otherwise, true. */ private static boolean shouldStepWorld( SolutionTimeStep step, SandboxGame game ) { // TODO: yuck: why do we need an if at all, and why do we have to // tolerate assertions that happen after the world has ended // as well as those that happen as it ends?: it should be one // or the other. if ( step == null ) { return false; } if ( game.getWorld().completionState() != CompletionState.RUNNING && ( step.actions.length == 1 && step.actions[0] instanceof AssertStateAction ) ) { return false; } return true; } private static void performAction( TimeStepAction action, final SandboxGame sandboxGame ) throws SolutionExceptions.UnexpectedState { // TODO: stop using WaitAction to step in the command line interface - // then we can stop catching DontStepAfterFinish. try { doPerformAction( action, sandboxGame ); } catch ( DontStepAfterFinish e ) { throw new SolutionExceptions.RanPastEnd( sandboxGame.getWorld().completionState() ); } catch ( NoneOfThisAbilityLeft e ) { throw new SolutionExceptions.UsedRunOutAbility( e.ability ); } catch ( NoSuchAbilityInThisWorld e ) { throw new SolutionExceptions.UsedMissingAbility( e.ability ); } catch ( CantAddTokenOutsideWorld e ) { Dimension worldSize = sandboxGame.getWorld().size; throw new SolutionExceptions.PlacedTokenOutsideWorld( e.x, e.y, worldSize.width, worldSize.height ); } } private static void doPerformAction( TimeStepAction action, final SandboxGame sandboxGame ) throws SolutionExceptions.UnexpectedState { action.typeSwitch( new TimeStepActionTypeSwitch() { @Override public void caseSelectAction( SelectAction s ) { // TODO: check whether this ability exists, and throw if // not. sandboxGame.setSelectedType( s.type ); } @Override public void caseAssertStateAction( AssertStateAction s ) throws SolutionExceptions.UnexpectedState { if ( sandboxGame.getWorld().completionState() != s.targetState ) { if ( s.targetState == CompletionState.WON ) { throw new SolutionExceptions.DidNotWin( sandboxGame.getWorld().completionState() ); } else { throw new SolutionExceptions.UnexpectedState( s.targetState, sandboxGame.getWorld().completionState() ); } } } @Override public void casePlaceTokenAction( PlaceTokenAction p ) { Type type = sandboxGame.getSelectedType(); World world = sandboxGame.getWorld(); Integer previousNum = world.abilities.get( type ); // (Note: previousNum may be null, so can't be int.) world.changes.addToken( p.x, p.y, type ); if ( world.abilities.get( type ) == previousNum ) { throw new SolutionExceptions.FailedToPlaceToken( p.x, p.y, type ); } } } ); } }