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 ) );
}
}