package eu.lestard.snakefx.core;
import eu.lestard.grid.Cell;
import eu.lestard.grid.GridModel;
import eu.lestard.snakefx.viewmodel.CentralViewModel;
import org.junit.Before;
import org.junit.Test;
import org.mockito.internal.util.reflection.Whitebox;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
@SuppressWarnings("unchecked")
public class SnakeTest {
private Snake snake;
private GridModel<State> gridModel;
private GameLoop gameLoopMock;
private static final int X = 4;
private static final int Y = 2;
private CentralViewModel viewModel;
@Before
public void setUp() {
gridModel = new GridModel<>();
gridModel.setNumberOfColumns(10);
gridModel.setNumberOfRows(10);
gridModel.getCells().forEach(cell -> cell.changeState(State.EMPTY));
gameLoopMock = mock(GameLoop.class);
viewModel = new CentralViewModel();
snake = new Snake(viewModel, gridModel, gameLoopMock);
Whitebox.setInternalState(snake, "x", X);
Whitebox.setInternalState(snake, "y", Y);
}
@Test
public void testChangeDirection() {
viewModel.snakeDirection.set(Direction.LEFT);
assertThat(snake.nextDirection).isEqualTo(Direction.LEFT);
}
/**
* When the new direction has the same orientation as the old one ( both are
* horizontal or both are vertical) no change of the direction should be
* made.
*
* Otherwise the head of the snake would move directly into the tail.
*
*
* But if the player pressed LEFT and then DOWN faster then the gap between
* two frames, then the Snake would make a 180 degree turnaround. The LEFT
* keypress wouldn't be filtered out because LEFT has another orientation
* then UP and the DOWN keypress wouldn't be filtered out because LEFT
* (which is the "next direction" now) has another orientation then DOWN.
*
* To prevent this we have two variables for the direction: "nextDirection"
* and "currentDirection". When the player likes to change the direction,
* only nextDirection is changed but he test whether the orientation is the
* same is done with the "currentDirection". When the snake moves, the
* "currentDirection" variable gets the value from "nextDirection".
*/
@Test
public void testChangeDirectionNewHasSameOrientationAsOld() {
// Snake is initialized with currentDirection=UP and nextDirection=UP
snake.init();
viewModel.snakeDirection.set(Direction.DOWN);
// currentDirection and nextDirection is still UP because the
// orientation is the same
assertThat(snake.nextDirection).isEqualTo(Direction.UP);
assertThat(snake.currentDirection).isEqualTo(Direction.UP);
viewModel.snakeDirection.set(Direction.LEFT);
// the nextDirection is now changed...
assertThat(snake.nextDirection).isEqualTo(Direction.LEFT);
// ... the currentDirection is still the old one. It is only changed
// when the snake moves.
assertThat(snake.currentDirection).isEqualTo(Direction.UP);
viewModel.snakeDirection.set(Direction.DOWN);
// nextDirection is not changed as the currentDirection is still UP and
// has the same orientation as DOWN
assertThat(snake.nextDirection).isEqualTo(Direction.LEFT);
assertThat(snake.currentDirection).isEqualTo(Direction.UP);
snake.move();
assertThat(snake.nextDirection).isEqualTo(Direction.LEFT);
// now the currentDirection has changed.
assertThat(snake.currentDirection).isEqualTo(Direction.LEFT);
viewModel.snakeDirection.set(Direction.DOWN);
assertThat(snake.nextDirection).isEqualTo(Direction.DOWN);
// this direction is not valid because the snake haven't moved yet. RIGHT would mean a 180 degree turn-around.
viewModel.snakeDirection.set(Direction.RIGHT);
// the next direction has to be still DOWN as this was the last valid direction change.
assertThat(snake.nextDirection).isEqualTo(Direction.DOWN);
// this is also valid. When the snake haven't moved yet you can change the direction as often as you like as long as the new direction is valid.
viewModel.snakeDirection.set(Direction.UP);
assertThat(snake.nextDirection).isEqualTo(Direction.UP);
}
@Test
public void testMove() {
snake.init();
snake.move();
assertThat(snake.head).isEqualTo(gridModel.getCell(X, Y - 1));
assertThat(gridModel.getCell(X, Y).getState()).isEqualTo(State.EMPTY);
}
/**
* When the snake moves to a field that has the state "FOOD" the snake
* should grow by 1 field.
*/
@Test
public void testGrow() {
// at the start field1 is the head
final Cell<State> field1 = gridModel.getCell(X, Y);
// field2 is above field1
final Cell<State> field2 = gridModel.getCell(X, Y - 1);
field2.changeState(State.FOOD);
// field3 is above field2
final Cell<State> field3 = gridModel.getCell(X, Y - 2);
assertThat(field3.getState()).isEqualTo(State.EMPTY);
// field4 is on the right of field3
final Cell<State> field4 = gridModel.getCell(X+1, Y -2);
assertThat(field4.getState()).isEqualTo(State.EMPTY);
snake.init();
snake.move();
// the head of the snake is now on field2
assertThat(snake.head).isEqualTo(field2);
// field1 is now a part of the tail
assertThat(field1.getState()).isEqualTo(State.TAIL);
assertThat(snake.tail).containsOnly(field1);
// One Point has to be added.
assertThat(viewModel.points.get()).isEqualTo(1);
// in our test the new food is generated on field4
field4.changeState(State.FOOD);
// Now the snake is moving another field forward. This time the new
// field (field3)
// is empty.
snake.move();
// field3 becomes the new head
assertThat(snake.head).isEqualTo(field3);
// field2 becomes part of the tail
assertThat(field2.getState()).isEqualTo(State.TAIL);
assertThat(snake.tail).containsOnly(field2);
// field1 is now empty
assertThat(field1.getState()).isEqualTo(State.EMPTY);
// change the direction to right so that we land on field4
viewModel.snakeDirection.set(Direction.RIGHT);
snake.move();
assertThat(snake.head).isEqualTo(field4);
assertThat(field4.getState()).isEqualTo(State.HEAD);
assertThat(field3.getState()).isEqualTo(State.TAIL);
assertThat(field2.getState()).isEqualTo(State.TAIL);
assertThat(field1.getState()).isEqualTo(State.EMPTY);
assertThat(snake.tail).containsOnly(field3, field2);
}
@Test
public void testCollision() {
snake.init();
final Cell<State> tail = gridModel.getCell(X, Y - 1);
tail.changeState(State.TAIL);
snake.move();
assertThat(viewModel.collision.get()).isTrue();
}
/**
* When the newGame method is called, the head must be set to null and the
* tails arraylist has to be reset.
*
* The init method has to be called too.
*/
@Test
public void testNewGame() {
Cell<State> head = gridModel.getCell(X, Y);
Cell<State> food = gridModel.getCell(X, Y - 1);
food.changeState(State.FOOD);
snake.init();
snake.move();
assertThat(snake.head).isEqualTo(food);
assertThat(snake.tail).hasSize(1);
assertThat(snake.tail).contains(head);
snake.newGame();
// the head is reset and the tail is empty
assertThat(snake.head).isEqualTo(head);
assertThat(snake.tail).isEmpty();
}
/**
* From a given field get the field next to it from a given direction.
*/
@Test
public void testGetFromDirection() {
gridModel.setNumberOfColumns(4);
gridModel.setNumberOfRows(4);
final Cell<State> x2y2 = gridModel.getCell(2, 2);
final Cell<State> x2y3 = snake.getFromDirection(x2y2, Direction.DOWN);
assertThat(x2y3.getColumn()).isEqualTo(2);
assertThat(x2y3.getRow()).isEqualTo(3);
final Cell<State> x3y3 = snake.getFromDirection(x2y3, Direction.RIGHT);
assertThat(x3y3.getColumn()).isEqualTo(3);
assertThat(x3y3.getRow()).isEqualTo(3);
}
/**
* In the game when the snake moves outside of the grid on one side it
* appears again on the other side.
*
* When a field is located directly at the border of the grid and the
* getFromDirection method is called with the direction to the outside of
* the grid, the field on the other side of the grid on the same row/column
* has to be returned.
*/
@Test
public void testGetFromDirectionOtherSideOfTheGrid() {
gridModel.setNumberOfColumns(4);
gridModel.setNumberOfRows(4);
Cell<State> x0y3 = gridModel.getCell(0, 3);
final Cell<State> x3y3 = snake.getFromDirection(x0y3, Direction.LEFT);
assertThat(x3y3.getColumn()).isEqualTo(3);
assertThat(x3y3.getRow()).isEqualTo(3);
x0y3 = snake.getFromDirection(x3y3, Direction.RIGHT);
assertThat(x0y3.getColumn()).isEqualTo(0);
assertThat(x0y3.getRow()).isEqualTo(3);
Cell<State> x2y0 = gridModel.getCell(2, 0);
final Cell<State> x2y3 = snake.getFromDirection(x2y0, Direction.UP);
assertThat(x2y3.getColumn()).isEqualTo(2);
assertThat(x2y3.getRow()).isEqualTo(3);
x2y0 = snake.getFromDirection(x2y3, Direction.DOWN);
assertThat(x2y0.getColumn()).isEqualTo(2);
assertThat(x2y0.getRow()).isEqualTo(0);
}
}