JavaFX snake game AI


I am trying to add an AI simulated snake to my snake game that a player can play against. So far I think I do get the concept of how to do certain things but snake should be able to find the nearest apple and collected. it has to also be able to avoid colliding with itself and with objects through out the scene.
I know this is hard to do. I have created some simple algorithms to make the AI find the clossest apple and collect it. the algorithm sometimes fails to make the right turns in order to collect the nearest apple.

the snake also has the possibility to teleport to the opposite sides of the scene. I have difficulty calculating when the snake should decide that it would be fastest to teleport since it would be faster to obtain an apple located at the other side. maybe a condition that checks if the distance between the snake and the clossest apple is greater than the distance between the snake and the border + (apple - opopsite border).

anyways here is a sample video of what the snake could do ATM:

1SRV2gyhLxw

Here is the code that controlls the snake.

 /**
    
     * @author Eudy Contreras
     *
     */
    public class PathFindingAI {
    
    	private AbstractObject objective;
    	private AbstractTile obstacle;
    	private GameManager game;
    	private PlayerTwo snakeAI;
    	private Random rand;
    
    	private boolean makingUTurn = false;
    
    	private double positionX = 0;
    	private double positionY = 0;
    	private double turnOffset = 100;
    
    	private int randomBoost = 200;
    
    	private ObjectivePosition location;
    
    	public PathFindingAI(GameManager game, PlayerTwo snakeAI) {
    		this.game = game;
    		this.snakeAI = snakeAI;
    		initialize();
    	}
    
    	public void initialize() {
    		rand = new Random();
    	}
    
    	public void findObjective() {
    		if (game.getModeID() == GameModeID.LocalMultiplayer)
    			createPath(objective);
    
    	}
    	/*
    	 * Gets called when the game begins in order 
    	 * to initiate the simulation
    	 */
    	public void startSimulation() {
    		if (game.getModeID() == GameModeID.LocalMultiplayer)
    			createPath(findClosest());
    	}
    	/*
    	 * this method gets called from the game loop and it 
    	 * is called at 60fps. The method update and keeps track
    	 * of things
    	 */
    	public void updateSimulation() {
    		if (game.getModeID() == GameModeID.LocalMultiplayer) {
    			if (game.getStateID() == GameStateID.GAMEPLAY && objective != null) {
    				checkCurrentLocation();
    				addRandomBoost(true);
    				checkObjectiveStatus();
    				reRoute();
    			}
    		}
    	}
    	/**
    	 * Method which under certain conditions will activate 
    	 * the speed boost of the snake
    	 * @param random
    	 */
    	public void addRandomBoost(boolean random) {
    		if (random && rand.nextInt(randomBoost) != 0) {
    			return;
    		}
    		if (snakeAI != null) {
    			if (game.getEnergyBarTwo().getEnergyLevel() > 50) {
    				snakeAI.setSpeedThrust(true);
    			} else {
    				snakeAI.setSpeedThrust(false);
    			}
    		}
    
    	}
    	/**
    	 * Method which when called will attempt to find the apple which is closest
    	 * to the current position of the snake!
    	 * @return
    	 */
    	private AbstractObject findClosest() {
    
    		Distance[] distance = new Distance[game.getGameObjectController().getFruitList().size()];
    
    		for (int i = 0; i < game.getGameObjectController().getFruitList().size(); i++) {
    			distance[i] = new Distance(
    					Math.hypot(snakeAI.getX() - game.getGameObjectController().getFruitList().get(i).getX(),
    							snakeAI.getY() - game.getGameObjectController().getFruitList().get(i).getY()),
    					game.getGameObjectController().getFruitList().get(i));
    		}
    
    		double closest = distance[0].getDistance();
    
    		for (int i = 0; i < distance.length; i++) {
    			if (distance[i].getDistance() < closest) {
    				closest = distance[i].getDistance();
    			}
    		}
    		for (int i = 0; i < distance.length; i++) {
    			if (distance[i].getDistance() == closest) {
    				if (distance[i].getObject().isAlive()) {
    					objective = distance[i].getObject();
    					positionX = distance[i].getObject().getX();
    					positionY = distance[i].getObject().getY();
    				}
    			}
    		}
    		if (objective != null)
    			objective.blowUpAlt();
    		return objective;
    	}
    	/**
    	 * Method which gets called in the update method and 
    	 * will create a new path after the snake has perform 
    	 * a uTurn!
    	 */
    	private void reRoute() {
    		if (makingUTurn) {
    			turnOffset--;
    			if (turnOffset <= 0) {
    				makingUTurn = false;
    				createPath(objective);
    			}
    		}
    	}
    
    	@SuppressWarnings("unused")
    	private void log(String str) {
    		System.out.println(str);
    	}
    	/*
    	 * Method which attempts to determine the best course of action in order
    	 * to move towards the objective. The method will first check if the x distance 
    	 * is less or greater than the y distance and based on that it will decide to perform 
    	 * a horizontal or vertical move.  if the method to be perform is a vertical move
    	 * the method will check if the objective is above or below and then perform a move
    	 * based on the objectives coordinates.
    	 */
    	private void createPath(AbstractObject objective) {
    		if (Math.abs(snakeAI.getX() - objective.getX()) < Math.abs(snakeAI.getY() - objective.getY())) {
    			if (objective.getY() > snakeAI.getY()) {
    				location = ObjectivePosition.SOUTH;
    				performMove(PlayerMovement.MOVE_DOWN);
    			}
    			else{
    				location = ObjectivePosition.NORTH;
    				performMove(PlayerMovement.MOVE_UP);
    			}
    		} else {
    			if (objective.getX() > snakeAI.getX()) {
    				location = ObjectivePosition.EAST;
    				performMove(PlayerMovement.MOVE_RIGHT);
    			}
    			else{
    				location = ObjectivePosition.WEST;
    				performMove(PlayerMovement.MOVE_LEFT);
    			}
    		}
    	}
    	/**BUGGGY!!
    	 * 
    	 * This method attempts to determine a course of action once a special bound has intersected any 
    	 * obstacle. the method attempts to determine what action the snake should take base on the position
    	 * of the snake relative to the coordinates and dimensions of the obstacle encountered. The method does 
    	 * not work as intended. NEED HELP =(
    	 * 
    	 * @param obstacle
    	 */
    	public void avoidObstacle(AbstractTile obstacle) {
    		this.obstacle = obstacle;
    		if (game.getModeID() == GameModeID.LocalMultiplayer) {
    			switch (snakeAI.getCurrentDirection()) {
    			case MOVE_DOWN:
    				if ((obstacle.getBounds().getWidth()+obstacle.getBounds().getMinX()) - (snakeAI.getHead().getRadius()+snakeAI.getX())> obstacle.getBounds().getWidth()/2){
    					snakeAI.setDirection(PlayerMovement.MOVE_LEFT);
    				}
    				else{
    					snakeAI.setDirection(PlayerMovement.MOVE_RIGHT);
    				}
    					break;
    			case MOVE_LEFT:
    				if((obstacle.getBounds().getHeight()+obstacle.getBounds().getMinY()) - (snakeAI.getHead().getRadius()+snakeAI.getY())> obstacle.getBounds().getHeight()/2){
    					snakeAI.setDirection(PlayerMovement.MOVE_UP);
    				}
    				else{
    					snakeAI.setDirection(PlayerMovement.MOVE_DOWN);
    				}
    				break;
    			case MOVE_RIGHT:
    				if((obstacle.getBounds().getHeight()+obstacle.getBounds().getMinY()) - (snakeAI.getHead().getRadius()+snakeAI.getY())> obstacle.getBounds().getHeight()/2){
    					snakeAI.setDirection(PlayerMovement.MOVE_UP);
    				}
    				else{
    					snakeAI.setDirection(PlayerMovement.MOVE_DOWN);
    				}
    				break;
    			case MOVE_UP:
    				if ((obstacle.getBounds().getWidth()+obstacle.getBounds().getMinX()) - (snakeAI.getHead().getRadius()+snakeAI.getX())> obstacle.getBounds().getWidth()/2){
    					snakeAI.setDirection(PlayerMovement.MOVE_LEFT);
    				}
    				else{
    					snakeAI.setDirection(PlayerMovement.MOVE_RIGHT);
    				}
    				break;
    			case STANDING_STILL:
    				break;
    			default:
    				break;
    
    			}
    		}
    	}
    	/**
    	 * Method which performs actions based on the current location of the snake and the objective.
    	 * if the snake is within a predetermined threshold the snake will perform the appropriate turn 
    	 * in order to collect the objective. 
    	 */
    	private void checkCurrentLocation() {
    			if (snakeAI.getX() > objective.getX() - objective.getRadius() / 2 && snakeAI.getX() < objective.getX() + objective.getRadius() / 2) {
    				if (objective.getY() < snakeAI.getY()) {
    					snakeAI.setDirection(PlayerMovement.MOVE_UP);
    				} else {
    					snakeAI.setDirection(PlayerMovement.MOVE_DOWN);
    				}
    			}
    			else if (snakeAI.getY() > objective.getY() - objective.getRadius() / 2 && snakeAI.getY() < objective.getY() + objective.getRadius() / 2) {
    				if (objective.getX() < snakeAI.getX()) {
    					snakeAI.setDirection(PlayerMovement.MOVE_LEFT);
    				} else {
    					snakeAI.setDirection(PlayerMovement.MOVE_RIGHT);
    				}
    			}
    	}
    	/**
    	 * Method which checks the status of the current objective and base on the objective's status it will try
    	 * to re-determine a new objective once the current objective has been collected or it has moved! 
    	 */
    	private void checkObjectiveStatus() {
    		if (!objective.isAlive()) {
    			findClosest();
    			createPath(objective);
    		}
    		if (objective.getX() != positionX || objective.getY() != positionY) {
    			findClosest();
    		}
    	}
    	/**
    	 * Method which when called will determine if the snake has to make an u-turn or 
    	 * if the snake can perform the desired turn without complications! NEEDS ANALYSIS!!!!
    	 * @param move: Move which the AI desires to perform
    	 */
    	private void performMove(PlayerMovement move) {
    		if (move == PlayerMovement.MOVE_UP && snakeAI.getCurrentDirection() == PlayerMovement.MOVE_DOWN) {
    			makeUTurn(snakeAI.getCurrentDirection());
    		} else if (move == PlayerMovement.MOVE_DOWN && snakeAI.getCurrentDirection() == PlayerMovement.MOVE_UP) {
    			makeUTurn(snakeAI.getCurrentDirection());
    		} else if (move == PlayerMovement.MOVE_LEFT && snakeAI.getCurrentDirection() == PlayerMovement.MOVE_RIGHT) {
    			makeUTurn(snakeAI.getCurrentDirection());
    		} else if (move == PlayerMovement.MOVE_RIGHT && snakeAI.getCurrentDirection() == PlayerMovement.MOVE_LEFT) {
    			makeUTurn(snakeAI.getCurrentDirection());
    		} else {
    			snakeAI.setDirection(move);
    		}
    	}
    	/**
    	 * Method which when called will perform a turn based on the location of the objective! once the turn 
    	 * is made the path will be recalculated by the reRoute method! The method only gets called when the snake
    	 * attempts to perform an illegal turn!
    	 * @param currentDirection
    	 */
    	
    	private void makeUTurn(PlayerMovement currentDirection) {
    		if (currentDirection == PlayerMovement.MOVE_DOWN || currentDirection == PlayerMovement.MOVE_UP) {
    			if (objective.getX() < snakeAI.getX()) {
    				snakeAI.setDirection(PlayerMovement.MOVE_LEFT);
    				makingUTurn = true;
    				turnOffset = snakeAI.getRadius();
    			} else {
    				snakeAI.setDirection(PlayerMovement.MOVE_RIGHT);
    				makingUTurn = true;
    				turnOffset = snakeAI.getRadius();
    			}
    		} else if (currentDirection == PlayerMovement.MOVE_RIGHT || currentDirection == PlayerMovement.MOVE_LEFT) {
    			if (objective.getY() < snakeAI.getY()) {
    				snakeAI.setDirection(PlayerMovement.MOVE_UP);
    				makingUTurn = true;
    				turnOffset = snakeAI.getRadius();
    			} else {
    				snakeAI.setDirection(PlayerMovement.MOVE_DOWN);
    				makingUTurn = true;
    				turnOffset = snakeAI.getRadius();
    			}
    		}
    	}
    	/**
    	 * Class which holds the distance and the nearest object 
    	 * and the object!
    	 * @author Eudy Contreras
    	 *
    	 */
    	private class Distance {
    
    		private Double distance;
    		private AbstractObject object;
    
    		public Distance(double distance, AbstractObject object) {
    			this.distance = distance;
    			this.object = object;
    		}
    
    		public double getDistance() {
    			return distance;
    		}
    
    		public AbstractObject getObject() {
    			return object;
    		}
    	}
    	public void setPlayer() {
    		this.snakeAI = null;
    		this.snakeAI = game.getGameLoader().getPlayerTwo();
    	}
    
    	public ObjectivePosition getLocation() {
    		return location;
    	}
    
    	public void setLocation(ObjectivePosition location) {
    		this.location = location;
    	}
    
    	public AbstractTile getObstacle() {
    		return obstacle;
    	}
    
    	public void setObstacle(AbstractTile obstacle) {
    		this.obstacle = obstacle;
    	}
    
    	private enum ObjectivePosition {
    		NORTH, SOUTH, WEST, EAST
    	}
    }

I will deeply appreciate any suggestions and help on solving these problems!

It sounds like an A* algorythm would be your answer. That can calculate the closest route is (including teleporting) while avoiding blockages, e.g. your tail.