In this series I'll share my progress with my self-imposed programming challenge: build a Battlesnake in as many different programming languages as possible.
Check the first post for a short intro to this series.
You can also follow my progress on GitHub.
Java
Java is the language of Enterprises, the business logic of many large back-office systems is written in it.
I've written a lot of Java code in the past, but Python and JavaScript have taken its place in my contemporary programming activities.
Because Java can be used to write very readable and robust software (Java IDEs tend to have great refactoring support), I still feel it's the right language for some systems.
Compared to its predecessors, Java brought developers a lot of improvements (depending on your taste, of course): automatic memory management, built-in collection types, and an extensive standard library. However, the language is now over 30 years old, and there are some clear signs of the times, such as no JSON support in the standard library (but it does have XML support 😄).
Can Java, as it comes out-of-the-box today, still be used to build a clean Battlesnake implementation? Read along to find out.
Hello world Setup
This is how Snake.java
looks:
public class Snake {
public static void main(String args[]) {
System.out.println("Hello world!");
}
}
This is how the Dockerfile looks:
FROM eclipse-temurin:17-jdk
RUN mkdir /app
WORKDIR /app
COPY Snake.java .
RUN javac Snake.java
CMD ["java", "Snake"]
And here's the development setup in action:
A basic web server
To be honest, I had to Google the availability of a basic web server in the Java standard library. It turns out there's an HTTP server in what is probably one of the oldest parts of the standard library (based on the package name): com.sun.net.httpserver.HttpServer
.
Using HttpServer
is actually quite straightforward, here's my initial code to handle the Battlesnake metadata request:
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
public class Snake {
static class BattleSnakeHandler implements HttpHandler {
public void handle(HttpExchange exchange) throws IOException {
String response = "{\"apiversion\": \"1\", " +
"\"author\": \"'robvanderleek\", \"version\": \"1.0\", " +
"\"color\": \"#b07219\", \"head\": \"safe\", " +
"\"tail\": \"sharp\"}";
exchange.sendResponseHeaders(200, response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
public static void main(String args[]) throws IOException {
int port = Integer.parseInt(
System.getenv().getOrDefault("PORT", "3000"));
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/", new BattleSnakeHandler());
server.setExecutor(null);
server.start();
System.out.println(
String.format("Starting Battlesnake server on port: %d", port));
}
}
Game logic
A significant part of the game logic code is there to parse the incoming JSON data. As mentioned before, the standard Java library does not contain a JSON parser, and a typical parser library contains thousands of lines of code.
With a lot of cutting-corners, I was able to parse the Battlesnake JSON (and only that JSON).
Below is the code of four functions that are used to parse JSON fields, objects, and arrays:
private String getField(String json, String name) {
String needle = '"' + name + '"';
return json.substring(json.indexOf(needle) + needle.length() + 1);
}
private String getBalanced(String json, String name, char open,
char close) {
String start = getField(json, name);
int idx = 0, indent = 0;
do {
if (start.charAt(idx) == open) {
indent++;
} else if (start.charAt(idx) == close) {
indent--;
}
idx++;
} while (indent > 0);
return start.substring(0, idx);
}
private String getObject(String json, String name) {
return getBalanced(json, name, '{', '}');
}
private String getArray(String json, String name) {
return getBalanced(json, name, '[', ']');
}
The remainder of the game logic is quite straightforward, I used a basic Coordinate
class to improve readability, for example in this function:
private Coordinate nearestFood(String board, Coordinate head) {
String foodJson = getArray(board, "food");
Set<Coordinate> food = getCoordinates(foodJson);
double distance = Double.MAX_VALUE;
int x = 255, y = 255;
for (Coordinate f: food) {
double d = Math.sqrt(Math.pow(head.x - f.x, 2) +
Math.pow(head.y - f.y, 2));
if (d < distance) {
distance = d;
x = f.x;
y = f.y;
}
}
return new Coordinate(x, y);
}
I'm sure the game logic can be improved, why not give it a try? 😉
And this is the complete code in action:
The full code for the C Battlesnake can be found here on GitHub.
Feedback appreciated!
I hope you like reading along with my coding adventures.
Let me know in the comments below what you think about the code above, or what programming languages you are looking forward to in this series.
Until the next language!