Skip to content

Commit

Permalink
Merge pull request #17 from briancorbinxyz/JDK23++
Browse files Browse the repository at this point in the history
Jdk23++
  • Loading branch information
briancorbinxyz authored Aug 27, 2024
2 parents 7150f54 + 0663fdc commit 6d2fc50
Show file tree
Hide file tree
Showing 75 changed files with 4,856 additions and 2,416 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ Cargo.lock
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/
bin/
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,16 @@
"args": []
},
],
"java.import.exclusions": [
"org.junit.*"
],
"java.completion.importOrder": [
"org.testng",
"java.lang",
"java",
"javax",
"com",
"org"
],
"java.configuration.updateBuildConfiguration": "automatic",
}
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Tic-Tac-Toe in Java deliberately over-engineered to apply features of Java intro

Pairs with the ongoing blog post: [Road to JDK 25 - Over-Engineering Tic-Tac-Toe](https://thelifeof.briancorbin.xyz/Library/03-Resources/Road-to-JDK-25---Over-Engineering-Tic-Tac-Toe!) also serialized to Medium @ [Road to JDK 25 - Over-Engineering Tic-Tac-Toe On Medium](https://briancorbinxyz.medium.com/list/road-to-jdk-25-d0f656f66a8f)

---

### Features

https://openjdk.org/projects/jdk/23/
Expand Down Expand Up @@ -50,3 +52,16 @@ https://openjdk.org/projects/jdk/17/
- **JEP409**: Sealed Classes
- **JEP410**: Remove the Experimental AOT and JIT Compiler
- **JEP415**: Context-Specific Deserialization Filters

---

### Algorithms

The following algorithms are used by the AI BOT in this project:

- [Random](https://en.wikipedia.org/wiki/Randomness) See: [Random.java](app/src/main/java/org/example/bot/Random.java)
- [Minimax](https://en.wikipedia.org/wiki/Minimax) See: [Minimax.java](app/src/main/java/org/example/bot/Minimax.java)
- [Alpha-Beta](https://en.wikipedia.org/wiki/Alpha-beta_pruning) See: [AlphaBeta.java](app/src/main/java/org/example/bot/AlphaBeta.java)
- [MaxN](https://en.wikipedia.org/wiki/Maxn_algorithm) See: [MaxN.java](app/src/main/java/org/example/bot/MaxN.java)
- [Paranoid](https://en.wikipedia.org/wiki/Paranoid_AI) See: [Paranoid.java](app/src/main/java/org/example/bot/Paranoid.java)
- [Monte Carlo](https://en.wikipedia.org/wiki/Monte_Carlo_method) See [MonteCarlo.java](app/src/main/java/org/example/bot/MonteCarloTreeSearch.java)
30 changes: 28 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import java.io.File

plugins {
// Apply the application plugin to add support for building a CLI application in Java.
application
Expand Down Expand Up @@ -126,7 +128,6 @@ spotless {
java {
googleJavaFormat("1.23.0")
.reflowLongStrings()
.aosp()
}
}

Expand Down Expand Up @@ -195,4 +196,29 @@ tasks.named<JavaExec>("run") {
environment("PATH", libPath) // For Windows
environment("LD_LIBRARY_PATH", libPath) // For Linux
environment("DYLD_LIBRARY_PATH", libPath) // For macOS
}
}


/// Install a pre-commit hook to run the Gradle task "spotlessApply" before committing changes.
tasks.register("installGitHook") {
doLast {
val hooksDir = file("${rootDir}/.git/hooks")
val preCommitFile = File(hooksDir, "pre-commit")

if (!preCommitFile.exists()) {
preCommitFile.writeText(
"""
#!/bin/sh
./gradlew spotlessApply
""".trimIndent()
)
preCommitFile.setExecutable(true)
println("Pre-commit hook installed.")
} else {
println("Pre-commit hook already exists.")
}
}
}
tasks.named("build") {
dependsOn("installGitHook")
}
171 changes: 128 additions & 43 deletions app/src/main/java/org/example/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,141 @@
import java.io.File;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import org.example.bot.BotStrategy;
import org.example.bot.BotStrategyConfig;

/** A simple java tic-tac-toe game. */
public class App {

private static final Logger log = System.getLogger(App.class.getName());
private static final Logger log = System.getLogger(App.class.getName());

/**
* Runs the game.
*
* @throws Exception if there is an error whilst playing the game
*/
public void run() throws Exception {
var game = new Game();
game.play();
}
/**
* Runs the game.
*
* @throws Exception if there is an error whilst playing the game
*/
public void run() throws Exception {
var game = newStandardGameMCTS();
game.play();
game.close();
}

/**
* Runs the game from the specified file.
*
* @param gameFile the file containing the saved game state to load
* @throws Exception if there is an error whilst playing the game or loading the game state from
* the file
*/
public void runFrom(File gameFile) throws Exception {
var game = Game.from(gameFile);
game.play();
}
private Game newStandardGame() {
return new Game(
3,
false,
new PlayerNode.Local<>("X", new HumanPlayer()),
new PlayerNode.Local<>("O", new BotPlayer(BotStrategy.ALPHABETA)));
}

/**
* Returns a greeting message for the Tic-Tac-Toe game.
*
* @return the greeting message
*/
public String getGreeting() {
return "Welcome to Tic-Tac-Toe!";
}
private Game newStandardGameMaxN() {
return new Game(
3,
false,
new PlayerNode.Local<>("X", new HumanPlayer()),
new PlayerNode.Local<>("O", new BotPlayer(BotStrategy.MAXN)));
}

private Game newStandardGameParanoid() {
return new Game(
3,
false,
new PlayerNode.Local<>("X", new HumanPlayer()),
new PlayerNode.Local<>("O", new BotPlayer(BotStrategy.PARANOID)));
}

private Game newStandardGameMCTS() {
return new Game(
3,
false,
new PlayerNode.Local<>("X", new HumanPlayer()),
new PlayerNode.Local<>("O", new BotPlayer(BotStrategy.MCTS)));
}

private Game newLargeStandardGame() {
return new Game(
4,
false,
new PlayerNode.Local<>("X", new HumanPlayer()),
new PlayerNode.Local<>(
"O",
new BotPlayer(
BotStrategy.alphabeta(BotStrategyConfig.newBuilder().maxDepth(4).build()))));
}

private Game newMultiplayerGameMCTS() {
return new Game(
10,
false,
new PlayerNode.Local<>("X", new HumanPlayer()),
new PlayerNode.Local<>("O", new BotPlayer(BotStrategy.MCTS)),
new PlayerNode.Local<>("Y", new BotPlayer(BotStrategy.MCTS)));
}

private Game newMultiplayerGameMaxN() {
// slow!
return new Game(
5,
false,
new PlayerNode.Local<>("X", new HumanPlayer()),
new PlayerNode.Local<>(
"O",
new BotPlayer(BotStrategy.maxn(BotStrategyConfig.newBuilder().maxDepth(3).build()))),
new PlayerNode.Local<>(
"Y",
new BotPlayer(BotStrategy.maxn(BotStrategyConfig.newBuilder().maxDepth(3).build()))));
}

private Game newMultiplayerGameParanoid() {
// slow!
return new Game(
10,
false,
new PlayerNode.Local<>("X", new HumanPlayer()),
new PlayerNode.Local<>(
"O",
new BotPlayer(
BotStrategy.paranoid(BotStrategyConfig.newBuilder().maxDepth(2).build()))),
new PlayerNode.Local<>(
"Y",
new BotPlayer(
BotStrategy.paranoid(BotStrategyConfig.newBuilder().maxDepth(2).build()))));
}

/**
* Runs the game from the specified file.
*
* @param gameFile the file containing the saved game state to load
* @throws Exception if there is an error whilst playing the game or loading the game state from
* the file
*/
public void runFrom(File gameFile) throws Exception {
var game = Game.from(gameFile);
game.play();
}

/**
* Returns a greeting message for the Tic-Tac-Toe game.
*
* @return the greeting message
*/
public String getGreeting() {
return "Welcome to Tic-Tac-Toe!";
}

/**
* The main entry point for the Tic-Tac-Toe application.
*
* <p>If command-line arguments are provided, it will load a saved game state from the specified
* file. Otherwise, it will start a new game.
*/
public static void main(String[] args) throws Exception {
App app = new App();
log.log(Level.INFO, () -> app.getGreeting());
if (args.length > 0) {
app.runFrom(new File(args[0]));
} else {
app.run();
}
/**
* The main entry point for the Tic-Tac-Toe application.
*
* <p>If command-line arguments are provided, it will load a saved game state from the specified
* file. Otherwise, it will start a new game.
*/
public static void main(String[] args) throws Exception {
App app = new App();
log.log(Level.INFO, () -> app.getGreeting());
if (args.length > 0) {
app.runFrom(new File(args[0]));
} else {
app.run();
}
}
}
32 changes: 11 additions & 21 deletions app/src/main/java/org/example/BotPlayer.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
package org.example;

import java.io.Serializable;
import java.security.SecureRandom;
import java.util.random.RandomGenerator;
import java.util.function.ToIntFunction;
import org.example.bot.BotStrategy;

/**
* Represents a bot player in the game. The bot player uses a random number generator to make moves
* on the game board.
*/
public record BotPlayer(String playerMarker, RandomGenerator random)
implements Player, Serializable {
public record BotPlayer(ToIntFunction<GameState> strategyFunction) implements Player, Serializable {

private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;

public BotPlayer(String playerMarker) {
// OVER-ENGINEER: Cryptographically secure by default
this(playerMarker, new SecureRandom());
}
public BotPlayer() {
this(BotStrategy.DEFAULT);
}

public String getPlayerMarker() {
return playerMarker;
}

public int nextMove(GameBoard board) {
int dimension = board.getDimension();
int location;
do {
location = random.nextInt(dimension * dimension);
} while (!board.isValidMove(location));
return location;
}
@Override
public int nextMove(GameState state) {
return strategyFunction.applyAsInt(state);
}
}
Loading

0 comments on commit 6d2fc50

Please sign in to comment.