content: package rabbitescape.render;
  
  import static org.junit.Assert.assertTrue;
  import static org.junit.Assert.assertFalse;
  import static org.junit.Assert.assertEquals;
  import static org.hamcrest.MatcherAssert.assertThat;
  import static org.hamcrest.CoreMatchers.*;
  
  import org.junit.Test;
  
  import rabbitescape.engine.ChangeDescription;
  import rabbitescape.engine.LevelWinListener;
  import rabbitescape.engine.Token;
  import rabbitescape.engine.World;
  import rabbitescape.engine.solution.SolutionIgnorer;
  import rabbitescape.engine.textworld.TextWorldManip;
  import rabbitescape.render.gameloop.GeneralPhysics;
  import rabbitescape.render.gameloop.Physics.StatsChangedListener;
  import rabbitescape.render.gameloop.GeneralPhysics.WorldModifier;
  
  public class TestPhysics
  {
      @Test
      public void Many_threads_can_manipulate_World_simultaneously()
          throws Exception
      {
          final World world = TextWorldManip.createWorld(
              "#      #",
              "# /) r #",
              "########",
              ":climb=1000000"
          );
  
          final int num_threads = 100;
          final int num_iters   = 20;
  
          final WorldModifier modifier = new WorldModifier( world, new SolutionIgnorer() );
  
          class Stepper implements Runnable
          {
              public boolean failed = false;
  
              @Override
              public void run()
              {
                  try
                  {
                      for ( int i = 0; i < num_iters; ++i )
                      {
                          modifier.step();
                      }
                  }
                  catch ( Throwable e )
                  {
                      failed = true;
                      e.printStackTrace();
                  }
              }
          };
  
          class TokenAdder implements Runnable
          {
              public boolean failed = false;
  
              @Override
              public void run()
              {
                  try
                  {
                      for ( int i = 0; i < num_iters; ++i )
                      {
                          modifier.addToken( 5, 0, Token.Type.climb );
                      }
                  }
                  catch ( Throwable e )
                  {
                      failed = true;
                      e.printStackTrace();
                  }
              }
          };
  
          // ---
  
          final Stepper stepper = new Stepper();
          final TokenAdder tokenAdder = new TokenAdder();
  
          Thread[] threads = new Thread[num_threads];
          for ( int i = 0; i < num_threads; i += 2 )
          {
              threads[i    ] = new Thread( stepper );
              threads[i + 1] = new Thread( tokenAdder );
          }
  
          for ( int i = 0; i < num_threads; ++i )
          {
              threads[i].start();
          }
  
          for ( int i = 0; i < num_threads; ++i )
          {
              threads[i].join();
          }
  
          assertFalse( stepper.failed );
          assertFalse( tokenAdder.failed );
      }
  
      @Test
      public void Step_steps_world()
      {
          final World world = TextWorldManip.createWorld(
              "#      #",
              "# /) r #",
              "########",
              ":climb=1000000"
          );
  
          LevelWinListener winListener = null;
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, false );
  
          // This is what we are testing - step once
          for ( int i = 0; i < 10; ++i )
          {
              physics.step( 0, GeneralPhysics.simulation_time_step_ms );
          }
  
          // The rabbit has moved
          assertEquals( 6, world.rabbits.get( 0 ).x );
      }
  
      class TracingWinListener implements LevelWinListener
      {
          public boolean wonCalled  = false;
          public boolean lostCalled = false;
  
          @Override
          public void won()
          {
              assertFalse( wonCalled );
              wonCalled = true;
          }
  
          @Override
          public void lost()
          {
              assertFalse( lostCalled );
              lostCalled = true;
          }
      }
  
      @Test
      public void Step_notifies_when_we_won()
      {
          final World world = TextWorldManip.createWorld(
              "#      #",
              "# /) rO#",
              "########",
              ":num_rabbits=0"
          );
  
          TracingWinListener winListener = new TracingWinListener();
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, false );
  
          // This is what we are testing - 2 time steps - winlistener should hear
          for ( int i = 0; i < 20; ++i )
          {
              physics.step( 0, GeneralPhysics.simulation_time_step_ms );
          }
  
          // The winListener was notified of the win
          assertTrue(  winListener.wonCalled );
          assertFalse( winListener.lostCalled );
      }
  
      @Test
      public void Step_notifies_when_we_lost()
      {
          final World world = TextWorldManip.createWorld(
              "#   ",
              "#  r", // Death in 1 step
              "####",
              ":num_rabbits=0"
          );
  
          TracingWinListener winListener = new TracingWinListener();
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, false );
  
          // This is what we are testing - step twice - winlistener should hear
          for ( int i = 0; i < 20; ++i )
          {
              physics.step( 0, GeneralPhysics.simulation_time_step_ms );
          }
  
          // The winListener was notified of the loss
          assertFalse(  winListener.wonCalled );
          assertTrue(  winListener.lostCalled );
      }
  
      @Test
      public void AddToken_adds_a_token_if_youve_got_some()
      {
          final World world = TextWorldManip.createWorld(
              "#      #",
              "# /) r #",
              "########",
              ":bash=10"
          );
  
          LevelWinListener winListener = null;
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, false );
  
          // Sanity: no things at the moment
          assertEquals( 0, world.things.size() );
  
          // This is what we are testing - add the token
          physics.addToken( 1, 1, Token.Type.bash );
  
          // Allow the change to happen
          world.step();
  
          // It was added - there is now a token
          assertEquals( 1, world.things.size() );
  
          assertEquals(
              ChangeDescription.State.TOKEN_BASH_STILL,
              world.things.get( 0 ).state
          );
      }
  
      @Test
      public void AddToken_does_not_add_a_token_if_youve_not_got_any()
      {
          final World world = TextWorldManip.createWorld(
              "#      #",
              "# /) r #",
              "########",
              ":bash=1"
          );
  
          LevelWinListener winListener = null;
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, false );
  
          // Add 1 - should work
          physics.addToken( 1, 1, Token.Type.bash );
          world.step();
          assertEquals( 1, world.things.size() );
  
          // This is what we are testing - add another, but you don't have it
          physics.addToken( 1, 1, Token.Type.bash );
  
          // It was not added - there still only 1 thing
          assertEquals( 1, world.things.size() );
      }
  
      @Test
      public void AddToken_returns_how_many_are_left()
      {
          final World world = TextWorldManip.createWorld(
              "#      #",
              "# /) r #",
              "########",
              ":bash=2",
              ":climb=12"
          );
  
          LevelWinListener winListener = null;
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, false );
  
          // Add 1 - 1 left
          assertEquals( 1, physics.addToken( 1, 1, Token.Type.bash ) );
  
          // Add another, 0 left
          assertEquals( 0, physics.addToken( 1, 1, Token.Type.bash ) );
  
          // Try to add more, still 0 left
          assertEquals( 0, physics.addToken( 1, 1, Token.Type.bash ) );
  
          // Different type is independent
          assertEquals( 11, physics.addToken( 1, 1, Token.Type.climb ) );
      }
  
      @Test
      public void AddToken_does_not_add_a_token_if_its_outside_world()
      {
          final World world = TextWorldManip.createWorld(
              "#      #",
              "# /) r #",
              "########",
              ":bash=1"
          );
  
          LevelWinListener winListener = null;
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, false );
  
          // Off the left does not add
          physics.addToken( -1, 1, Token.Type.bash );
          world.step();
          assertEquals( 0, world.things.size() );
  
          // Off the right does not add
          physics.addToken( 8, 1, Token.Type.bash );
          world.step();
          assertEquals( 0, world.things.size() );
  
          // Off the top does not add
          physics.addToken( 1, -1, Token.Type.bash );
          world.step();
          assertEquals( 0, world.things.size() );
  
          // Off the bottom does not add
          physics.addToken( 1, 3, Token.Type.bash );
          world.step();
          assertEquals( 0, world.things.size() );
      }
  
      @Test
      public void AddToken_does_not_add_a_token_if_not_running()
      {
          final World world = TextWorldManip.createWorld(
              "#      #",
              "# /) r #",
              "########",
              ":bash=1"
          );
  
          LevelWinListener winListener = null;
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, false );
  
          // Paused does not add
          world.setPaused( true );
          physics.addToken( 1, 1, Token.Type.bash );
  
          // Unpause to step and check
          world.setPaused( false );
          world.step();
          assertEquals( 0, world.things.size() );
  
          // Unpaused does add
          physics.addToken( 1, 1, Token.Type.bash );
          world.step();
          assertEquals( 1, world.things.size() );
      }
  
      @Test
      public void GameRunning_reports_game_status()
      {
          final World world = TextWorldManip.createWorld(
              "#      #",
              "# /) r #",
              "########",
              ":bash=1"
          );
  
          LevelWinListener winListener = null;
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, false );
  
          assertTrue( physics.gameRunning() );
  
          world.setPaused( true );
          assertFalse( physics.gameRunning() );
      }
  
      @Test
      public void Stats_listeners_are_notified_when_stats_change()
      {
          class TrackingStatsListener implements StatsChangedListener
          {
              boolean changedCalled = false;
  
              @Override
              public void changed( int waiting, int out, int saved )
              {
                  assertFalse( changedCalled );
                  changedCalled = true;
              }
          }
  
          final World world = TextWorldManip.createWorld(
              "#      #",
              "# /) r #",
              "########",
              ":block=1"
          );
  
          LevelWinListener winListener = null;
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, false );
  
          // Ask to track stats
          TrackingStatsListener myListener = new TrackingStatsListener();
          physics.addStatsChangedListener( myListener );
  
          // Sanity
          assertFalse( myListener.changedCalled );
  
          // Change something that changes stats
          physics.addToken( 1, 1, Token.Type.block );
  
          // This is what we are testing - step the world
          for ( int i = 0; i < 10; ++i )
          {
              physics.step( 0, GeneralPhysics.simulation_time_step_ms );
          }
  
          // Listeners should have been called
          assertTrue( myListener.changedCalled );
      }
  
      // TODO: Stats_listeners_are_not_notified_when_stats_do_not_change
  
      @Test
      public void Fast_is_set_by_constructor()
      {
          GeneralPhysics physicsSlow = new GeneralPhysics( null, null, false );
          assertThat( physicsSlow.fast, is( false ) );
  
          GeneralPhysics physicsFast = new GeneralPhysics( null, null, true );
          assertThat( physicsFast.fast, is( true ) );
      }
  
      @Test
      public void Step_one_frame_if_fast_is_false()
      {
          final World world = TextWorldManip.createWorld( "#" );
          LevelWinListener winListener = null;
  
          // Make a physics that is not fast
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, false );
  
          // Sanity: we start at frame 0
          assertThat( physics.frame, is( 0 ) );
  
          // This is what we are testing: step forward once
          physics.step( 0, 1 );
  
          // We only moved one frame
          assertThat( physics.frame, is( 1 ) );
      }
  
      @Test
      public void Step_three_frames_if_fast_is_true()
      {
          final World world = TextWorldManip.createWorld( "#" );
          LevelWinListener winListener = null;
  
          // Make a physics that IS fast
          GeneralPhysics physics = new GeneralPhysics(
              world, winListener, true );
  
          // Sanity: we start at frame 0
          assertThat( physics.frame, is( 0 ) );
  
          // This is what we are testing: step forward once
          physics.step( 0, 1 );
  
          // We moved 3 frames
          assertThat( physics.frame, is( 3 ) );
      }
  }
  