package eu.lestard.snakefx.core; import eu.lestard.grid.Cell; import eu.lestard.grid.GridModel; import eu.lestard.snakefx.viewmodel.CentralViewModel; import javax.inject.Singleton; import java.util.ArrayList; import java.util.List; import static eu.lestard.snakefx.config.Config.*; /** * This class represents the snake. * * @author manuel.mauky */ @Singleton public class Snake { private final int x; private final int y; Direction currentDirection; Direction nextDirection; Cell<State> head; final List<Cell<State>> tail; private final CentralViewModel viewModel; private final GridModel<State> gridModel; /** * @param viewModel the viewModel * @param gridModel the grid on which the snake is created * @param gameLoop the gameloop that is used for the movement of the snake */ public Snake(final CentralViewModel viewModel, final GridModel<State> gridModel, final GameLoop gameLoop) { this.viewModel = viewModel; this.gridModel = gridModel; x = SNAKE_START_X.get(); y = SNAKE_START_Y.get(); tail = new ArrayList<>(); gameLoop.addAction(this::move); viewModel.snakeDirection.addListener((observable,oldDirection,newDirection) -> Snake.this.changeDirection(newDirection)); } /** * Initalizes the fields of the snake. */ public void init() { setHead(gridModel.getCell(x, y)); viewModel.collision.set(false); viewModel.points.set(0); currentDirection = Direction.UP; nextDirection = Direction.UP; } /** * Change the direction of the snake. The direction is only changed when the new direction has <bold>not</bold> the * same orientation as the old one. * <p/> * For example, when the snake currently has the direction UP and the new direction should be DOWN, nothing will * happend because both directions are vertical. * <p/> * This is to prevent the snake from moving directly into its own tail. * * @param newDirection */ private void changeDirection(final Direction newDirection) { if (newDirection.hasSameOrientation(currentDirection)) { viewModel.snakeDirection.setValue(nextDirection); } else { nextDirection = newDirection; } } /** * Move the snake by one field. */ void move() { currentDirection = nextDirection; final Cell<State> newHead = getFromDirection(head, currentDirection); if (newHead.getState().equals(State.TAIL)) { viewModel.collision.set(true); return; } boolean snakeWillGrow = false; if (newHead.getState().equals(State.FOOD)) { snakeWillGrow = true; } Cell<State> lastField = head; for (int i = 0; i < tail.size(); i++) { final Cell<State> f = tail.get(i); lastField.changeState(State.TAIL); tail.set(i, lastField); lastField = f; } if (snakeWillGrow) { grow(lastField); addPoints(); } else { lastField.changeState(State.EMPTY); } setHead(newHead); } /** * returns the cell that is located next to the given cell in the given * direction. * * @param cell * @param direction * @return the cell in the given direction */ Cell<State> getFromDirection(Cell<State> cell, Direction direction) { int column = cell.getColumn(); int row = cell.getRow(); switch(direction){ case UP: row -= 1; break; case DOWN: row += 1; break; case LEFT: column -= 1; break; case RIGHT: column += 1; break; } column += gridModel.getNumberOfColumns(); column = column % gridModel.getNumberOfColumns(); row += gridModel.getNumberOfRows(); row = row % gridModel.getNumberOfRows(); return gridModel.getCell(column, row); } public void newGame() { tail.clear(); init(); } private void setHead(final Cell<State> head) { this.head = head; head.changeState(State.HEAD); } /** * The given field is added to the tail of the snake and gets the state TAIL. * * @param field */ private void grow(final Cell<State> field) { field.changeState(State.TAIL); tail.add(field); } private void addPoints() { final int current = viewModel.points.get(); viewModel.points.set(current + 1); } }