# 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 - oppsite border).

anyways here is a sample video of what the snake could do ATM: https://youtu.be/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
*/
if (game.getModeID() == GameModeID.LocalMultiplayer) {
if (game.getStateID() == GameStateID.GAMEPLAY && objective != null) {
checkCurrentLocation();
checkObjectiveStatus();
reRoute();
}
}
}
/**
* Method which under certain conditions will activate
* the speed boost of the snake
* @param 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:
snakeAI.setDirection(PlayerMovement.MOVE_LEFT);
}
else{
snakeAI.setDirection(PlayerMovement.MOVE_RIGHT);
}
break;
case MOVE_LEFT:
snakeAI.setDirection(PlayerMovement.MOVE_UP);
}
else{
snakeAI.setDirection(PlayerMovement.MOVE_DOWN);
}
break;
case MOVE_RIGHT:
snakeAI.setDirection(PlayerMovement.MOVE_UP);
}
else{
snakeAI.setDirection(PlayerMovement.MOVE_DOWN);
}
break;
case MOVE_UP:
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;
} else {
snakeAI.setDirection(PlayerMovement.MOVE_RIGHT);
makingUTurn = true;
}
} else if (currentDirection == PlayerMovement.MOVE_RIGHT || currentDirection == PlayerMovement.MOVE_LEFT) {
if (objective.getY() < snakeAI.getY()) {
snakeAI.setDirection(PlayerMovement.MOVE_UP);
makingUTurn = true;
} else {
snakeAI.setDirection(PlayerMovement.MOVE_DOWN);
makingUTurn = true;
}
}
}
/**
* 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;
}

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!

You need to think about your game board as a potentially endless plane. For this imagine your board is in the center of a larger field, made of 9 copies of yours, in a 3x3 stack. So for each apple with coordinates (x,y), you have 8 more copies with coordinates like this:

(x-w, y-h) (x, y-h) (x+w, y-h)
(x-w, y)   (x, y)   (x+w, y)
(x-w, y+h) (x, y+h) (x+w, y+h)


So, you need to calculate distances to all of those copies in the findClosest() function, 9 distances for each apple.

• Hey thansk for your input. I am not sure if I am missing something! What i am doing now is that I am saving the distance to each visible apple, then I calculate the closest and make the snake make turns base until it gets to the objective and then the process is repeated! I have tried to change the technique in order to take under consideration obstacles that snake may find during it's trajectory to the objective but that fails! – Eudy Contreras Aug 14 '16 at 16:43
• Avoiding obstacles is a different thing and it is less trivial. I do not have an answer for that. The answer is just for the problem of calculating the closest objective considering the teleportation. – Simoroshka Aug 14 '16 at 16:48
• Well thank you very much for your input I will keep trying and I will also try to implement your approach. – Eudy Contreras Aug 14 '16 at 16:50
• By the way, you should check the checkObjectiveStatus() function and make sure it does what you want it to do. Now it looks for a new closest objective every time the objective and the snake are not on the same line, and it looks weird to me. – Simoroshka Aug 14 '16 at 16:51
• Aha! yeah the thing is that the AI snake will position it self within a threshold of 40 pixels. that way it looks more natural and snake wont be directly in front of the objective each time. There is also a mechanic that makes that if the mouth of the snake is not aligned with the apple properly the apple will then bounce from the snake making it more challenging. If any of the snake intercepts an apple two things could happen: it will be eaten or it will bounce. Thank you for noticing – Eudy Contreras Aug 14 '16 at 16:58