Snakes AI has got a very simple interface for bot creation. The minimum you need to implement in order to make a functioning bot is a class, that implements the Bot interface.
- JDK 8
- IntelliJ Idea (optional, recommended)
Clone the repo using Git:
// ssh
git clone [email protected]:BeLuckyDaf/snakes-game-tutorial.git
// https (you don't know why the one above doesn't work)
git clone https://github.com/BeLuckyDaf/snakes-game-tutorial.git
or simply download the zip file.
Open snakes.ipr in IntelliJ Idea or any other IDE.
-
Set up JDK
a. Open Project Structure
b. Choose the correct JDK version and project language level, it is level 8.
-
Set up a configuration
a. Open the configuration settings
b. Set correct JRE version for Main
c. Add or change your bot name. (return here after you've finished the tutorial)
The sample bot is already set up in the project, simply compile and launch the game by pressing Shift+F10 or Run > Run 'Main'
in the menu.
First, let's create a new package for you. Name it however you want, for the sake of this tutorial, we'll call it student.
Our bot class must implement the Bot interface, otherwise, we won't be able to tell the game what it does.
package student
public class MyBot implements Bot {
}
That also means, that we must implement the following method, specified in the interface.
public Direction chooseDirection(Snake snake,
Snake opponent,
Coordinate mazeSize,
Coordinate apple)
In fact, this is the only method that we are required to implement in order to make our bot work, so let's make a simple bot, the only function of which would be not dying.
So our code now looks like this:
package student
public class MyBot implements Bot {
@Override
public Direction chooseDirection(Snake snake, Snake opponent, Coordinate mazeSize, Coordinate apple) {
return null;
}
}
The code above is not going to work yet, so at least for it to make sense and for the sake of simplicity, we will now make the snake always go upwards.
@Override
public Direction chooseDirection(Snake snake, Snake opponent, Coordinate mazeSize, Coordinate apple) {
return Direction.UP;
}
You can try it out now, see how to run your bot.
Now to be able to make some randomness we'll also define a list of all directions that we could possible go in as an array.
private static final Direction[] DIRECTIONS = new Direction[] {Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT};
And pick one random direction.
package student
import java.util.Random;
public class MyBot implements Bot {
private static final Direction[] DIRECTIONS = new Direction[] {Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT};
@Override
public Direction chooseDirection(Snake snake, Snake opponent, Coordinate mazeSize, Coordinate apple) {
Random random = new Random();
Direction randomDir = DIRECTIONS[random.nextInt(DIRECTIONS.length)]
return randomDir;
}
}
See how to run your bot.
Now we have a randomly moving bot, but it's not enough, it'll never achieve anything by just randomly moving in different directions, moreover, the snake can't move on all four directions, since there is no way it would go backwards, let's dig into this.
To achieve a more advanced AI, first take look at what information do we possess:
- Our own snake
- The opponent snake
- The size of the maze
- The coordinate of the apple
It would help us if we knew where our own snake's head is located, so let's add that.
Coordinate head = snake.getHead();
We can move in any direction, except going backwards, so we should find the coordinate of "backwards". We will just take the second coordinate from our snake list of body parts.
Coordinate afterHeadNotFinal = null;
if (snake.body.size() >= 2) {
Iterator<Coordinate> it = snake.body.iterator();
it.next();
afterHeadNotFinal = it.next();
}
final Coordinate afterHead = afterHeadNotFinal;
Now remove the backwards direction from the list of our possible moves.
Direction[] validMoves = Arrays.stream(DIRECTIONS)
.filter(d -> !head.moveTo(d).equals(afterHead))
.sorted()
.toArray(Direction[]::new);
Since our bot doesn't want to die, filter out all directions which might cause that.
Direction[] notLosing = Arrays.stream(validMoves)
.filter(d -> head.moveTo(d).inBounds(mazeSize)) // maze bounds
.filter(d -> !opponent.elements.contains(head.moveTo(d))) // opponent body
.filter(d -> !snake.elements.contains(head.moveTo(d))) // and yourself
.sorted()
.toArray(Direction[]::new);
Now choose which move to take. We could add some randomness here, but it is not important now. So if we can move without losing, do it, otherwise, take any valid move that is not backwards, since there is no way to not lose.
if (notLosing.length > 0) return notLosing[0];
else return validMoves[0];
So here is what we came up with, this is included with the repository, you find it as johndoe.SampleBot.
package student
public class MyBot implements Bot {
private static final Direction[] DIRECTIONS = new Direction[] {Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT};
@Override
/* choose the direction (stupidly) */
public Direction chooseDirection(Snake snake, Snake opponent, Coordinate mazeSize, Coordinate apple) {
Coordinate head = snake.getHead();
/* Get the coordinate of the second element of the snake's body
* to prevent going backwards */
Coordinate afterHeadNotFinal = null;
if (snake.body.size() >= 2) {
Iterator<Coordinate> it = snake.body.iterator();
it.next();
afterHeadNotFinal = it.next();
}
final Coordinate afterHead = afterHeadNotFinal;
/* The only illegal move is going backwards. Here we are checking for not doing it */
Direction[] validMoves = Arrays.stream(DIRECTIONS)
.filter(d -> !head.moveTo(d).equals(afterHead)) // Filter out the backwards move
.sorted()
.toArray(Direction[]::new);
/* Just naïve greedy algorithm that tries not to die at each moment in time */
Direction[] notLosing = Arrays.stream(validMoves)
.filter(d -> head.moveTo(d).inBounds(mazeSize)) // Don't leave maze
.filter(d -> !opponent.elements.contains(head.moveTo(d))) // Don't collide with opponent...
.filter(d -> !snake.elements.contains(head.moveTo(d))) // and yourself
.sorted()
.toArray(Direction[]::new);
if (notLosing.length > 0) return notLosing[0];
else return validMoves[0];
/* ^^^ Cannot avoid losing here */
}
}
In order to use your own bot, you must pass your package name and the name of the class as program arguments to the game.
You must pass two bots, in order for the game to work, those could be the same.
Let's use your newly written bot with the one, provided by us. Even though, they actually are the same.
java snakes.SnakesUIMain johndoe.SampleBot student.MyBot
Note: this command is executed in the folder with already compiled .class files, not in the src directory. You do not need to worry about this if you are using an IDE, such as IntelliJ Idea.
If you are using any IDE, you could add those program arguments to be added automatically whenever you want to run or debug the program.
Try to make the bot go towards the apple, it's basically the point of the game, but remember that you are not the only snake on the field.
We're going to let you out on a journey of bot creation now, good luck and have fun!