Skip to content

Commit

Permalink
Merge pull request #22 from briancorbinxyz/feature/packaging
Browse files Browse the repository at this point in the history
Feature/packaging
  • Loading branch information
briancorbinxyz authored Sep 1, 2024
2 parents ebf2397 + 4f3416b commit 373f19e
Show file tree
Hide file tree
Showing 105 changed files with 2,240 additions and 839 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2

- name: Build with Gradle Wrapper
run: ./gradlew build
run: ./gradlew build publishToMavenLocal

# NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html).
# If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version.
Expand Down
54 changes: 34 additions & 20 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "GameClient",
"request": "launch",
"mainClass": "org.xxdc.oss.example.GameClient",
"projectName": "tcp-gameserver"
},
{
"type": "java",
"name": "GameServer",
"request": "launch",
"mainClass": "org.xxdc.oss.example.GameServer",
"projectName": "tcp-gameserver"
},
{
"type": "java",
"name": "Launch Current File",
Expand Down Expand Up @@ -86,10 +100,10 @@
"projectName": "app",
"env": {
"LIB_PATH": "${workspaceFolder}/app/build/cargo/debug",
"PATH": "${workspaceFolder}/app/build/cargo/debug", // For Windows
"LD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug", // For Linux
"DYLD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug" // For macOS
},
"PATH": "${workspaceFolder}/app/build/cargo/debug",
"LD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug",
"DYLD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug"
}
},
{
"type": "java",
Expand All @@ -99,10 +113,10 @@
"projectName": "app",
"env": {
"LIB_PATH": "${workspaceFolder}/app/build/cargo/debug",
"PATH": "${workspaceFolder}/app/build/cargo/debug", // For Windows
"LD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug", // For Linux
"DYLD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug" // For macOS
},
"PATH": "${workspaceFolder}/app/build/cargo/debug",
"LD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug",
"DYLD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug"
}
},
{
"type": "java",
Expand All @@ -113,10 +127,10 @@
"projectName": "app",
"env": {
"LIB_PATH": "${workspaceFolder}/app/build/cargo/debug",
"PATH": "${workspaceFolder}/app/build/cargo/debug", // For Windows
"LD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug", // For Linux
"DYLD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug" // For macOS
},
"PATH": "${workspaceFolder}/app/build/cargo/debug",
"LD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug",
"DYLD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug"
}
},
{
"type": "java",
Expand All @@ -126,10 +140,10 @@
"projectName": "app",
"env": {
"LIB_PATH": "${workspaceFolder}/app/build/cargo/debug",
"PATH": "${workspaceFolder}/app/build/cargo/debug", // For Windows
"LD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug", // For Linux
"DYLD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug" // For macOS
},
"PATH": "${workspaceFolder}/app/build/cargo/debug",
"LD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug",
"DYLD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug"
}
},
{
"type": "java",
Expand All @@ -140,10 +154,10 @@
"projectName": "app",
"env": {
"LIB_PATH": "${workspaceFolder}/app/build/cargo/debug",
"PATH": "${workspaceFolder}/app/build/cargo/debug", // For Windows
"LD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug", // For Linux
"DYLD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug" // For macOS
},
"PATH": "${workspaceFolder}/app/build/cargo/debug",
"LD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug",
"DYLD_LIBRARY_PATH": "${workspaceFolder}/app/build/cargo/debug"
}
}
]
}
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@
"org"
],
"java.configuration.updateBuildConfiguration": "automatic",
"java.dependency.packagePresentation": "hierarchical",
"java.dependency.packagePresentation": "flat",
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ https://openjdk.org/projects/jdk/17/

### Algorithms

The following algorithms are used by the AI BOT in this project (For a detailed discussion see [Road to JDK 25 - An Algorithmic Interlude](https://briancorbinxyz.medium.com/over-engineering-tic-tac-toe-an-algorithmic-interlude-8af3aa13173a):
The following algorithms are used by the AI BOT in this project - for a detailed discussion see [Road to JDK 25 - An Algorithmic Interlude](https://briancorbinxyz.medium.com/over-engineering-tic-tac-toe-an-algorithmic-interlude-8af3aa13173a):

- [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)
Expand Down
103 changes: 103 additions & 0 deletions api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import java.io.File

plugins {
id("buildlogic.java-library-conventions")
}

val libPath = "native/src/main/rust/target/debug"
val osName = System.getProperty("os.name").lowercase()
val osArch = System.getProperty("os.arch").lowercase()

val libSuffix = when {
osName.contains("win") -> "windows-$osArch"
osName.contains("mac") -> "macos-$osArch"
osName.contains("nux") -> "linux-$osArch"
else -> throw GradleException("Unsupported OS")
}

dependencies {
// Native Library (Rust)
implementation(project(":native"))
//testRuntimeOnly("org.xxdc.oss.example:tictactoe-native-$libSuffix:1.1.0")

// JDK9: Platform Logging (Third-Party)
// -> JDK API -> SLF4J -> Logback
testRuntimeOnly("ch.qos.logback:logback-classic:1.5.6")
testRuntimeOnly("org.slf4j:slf4j-api:2.0.13")
testRuntimeOnly("org.slf4j:slf4j-jdk-platform-logging:2.0.13")


// JDK23: JMH (Third-Party) Not required, added for benchmarking
// https://github.com/openjdk/jmh
testImplementation("org.openjdk.jmh:jmh-core:1.37")
testAnnotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.37")
}

// Run JMH benchmark
// ./gradlew jmh
tasks.register<JavaExec>("jmh") {
mainClass.set("org.openjdk.jmh.Main")
classpath = sourceSets["test"].runtimeClasspath
args = listOf("org.xxdc.oss.example.interop.benchmark.PlayerIdsBenchmark")
}

java {
withJavadocJar()
withSourcesJar()
}

// https://docs.gradle.org/current/userguide/publishing_maven.html
publishing {
repositories {
// Publish to GitHub Packages
// https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-java-packages-with-gradle
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/briancorbinxyz/overengineering-tictactoe")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
}
}
}
publications {
create<MavenPublication>("maven") {
groupId = "org.xxdc.oss.example"
artifactId = "tictactoe-api"
from(components["java"])
pom {
name.set("tictactoe")
description.set("An Over-Engineered Tic Tac Toe Game API")
url.set("https://github.com/briancorbinxyz/overengineering-tictactoe")
licenses {
license {
name.set("MIT License")
url.set("https://opensource.org/licenses/MIT")
}
developers {
developer {
id.set("briancorbinxyz")
name.set("Brian Corbin")
email.set("mail@briancorbin.xyz")
}
}
}
scm {
connection.set("scm:git:git://github.com/briancorbinxyz/overengineering-tictactoe.git")
developerConnection.set("scm:git:ssh://github.com/briancorbinxyz/overengineering-tictactoe.git")
url.set("https://github.com/briancorbinxyz/overengineering-tictactoe")
}
}
}
}
}

tasks.withType<Test>().all {
// JDK22: Foreign Function Interface (FFI)
// Resolves Warning:
// WARNING: A restricted method in java.lang.foreign.SymbolLookup has been called
// WARNING: java.lang.foreign.SymbolLookup::libraryLookup has been called by org.xxdc.oss.example.GameBoardNativeImpl in an unnamed module
// WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
// WARNING: Restricted methods will be blocked in a future release unless native access is enabled
jvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "-XX:+UseZGC")
}
Binary file added api/src/main/java/org/.DS_Store
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
/**
* Represents a bot player in the game. The bot player uses a random number generator to make moves
* on the game board.
*
* @param strategyFunction the function that determines the bot's next move
*/
public record BotPlayer(ToIntFunction<GameState> strategyFunction) implements Player, Serializable {

private static final long serialVersionUID = 1L;

/** Constructs a new BotPlayer instance using the default BotStrategy. */
public BotPlayer() {
this(BotStrategy.DEFAULT);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ public class Game implements Serializable, AutoCloseable {

private int moveNumber;

/**
* Constructs a new {@link Game} instance with a 3x3 game board, persistence enabled, and a human
* player as player 'X' and a bot player as player 'O'.
*/
public Game() {
this(
3,
Expand All @@ -40,20 +44,43 @@ public Game() {
new PlayerNode.Local<>("O", new BotPlayer()));
}

/**
* Constructs a new {@link Game} instance with the specified game board size, persistence enabled
* state, and player nodes.
*
* @param size The size of the game board, e.g. 3 for a 3x3 board.
* @param persistenceEnabled Whether game state should be persisted to a file.
* @param players The player nodes for the game, which include the player marker and player
* implementation.
*/
public Game(int size, boolean persistenceEnabled, PlayerNode... players) {
this.playerNodes = PlayerNodes.of(players);
this.gameId = UUID.randomUUID();
this.moveNumber = 0;
this.gameState = new ArrayDeque<>();
this.gameState.add(new GameState(GameBoard.with(size), this.playerNodes.playerMarkerList(), 0));
this.gameState.add(
new GameState(GameBoard.withDimension(size), this.playerNodes.playerMarkerList(), 0));
this.persistenceEnabled = persistenceEnabled;
}

/**
* Loads a {@link Game} instance from the specified file.
*
* @param gameFile The file containing the serialized {@link Game} instance.
* @return The loaded {@link Game} instance.
* @throws IOException If an I/O error occurs while reading the file.
* @throws ClassNotFoundException If the serialized class cannot be found.
*/
public static Game from(File gameFile) throws IOException, ClassNotFoundException {
GamePersistence persistence = new GamePersistence();
return persistence.loadFrom(gameFile);
}

/**
* Plays the game, rendering the board, applying player moves, and persisting the game state if
* enabled. The game continues until a winning player is found or there are no more moves
* available, at which point the winner or tie is logged.
*/
public void play() {
try {
GamePersistence persistence = new GamePersistence();
Expand Down Expand Up @@ -108,20 +135,25 @@ private GameState pushGameState(GameState state) {
return state;
}

/**
* Returns the unique identifier for this game instance.
*
* @return the game ID
*/
public UUID getGameId() {
return gameId;
}

@Override
public void close() throws Exception {
playerNodes.close();
}

private void renderBoard() {
log.log(Level.INFO, "\n" + currentGameState().board());
}

private GameState currentGameState() {
return gameState.peekLast();
}

@Override
public void close() throws Exception {
playerNodes.close();
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package org.xxdc.oss.example;

import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* Represents a game board for a game. The game board has a square dimension and contains a grid of
* game pieces. This interface defines the operations that can be performed on the game board.
*/
public interface GameBoard extends JsonSerializable {

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

static final AtomicBoolean useNative = new AtomicBoolean(true);

/**
* Checks if the given location on the game board is a valid move (i.e. an available location).
*
Expand Down Expand Up @@ -101,8 +108,24 @@ default boolean isEmpty() {
* @param dimension the dimension of the game board, which is the number of rows or columns
* @return a new {@link GameBoard} instance with the specified dimension
*/
static GameBoard with(int dimension) {
static GameBoard withDimension(int dimension) {
// Prefer the native implementation of the game board for performance.
return new GameBoardNativeImpl(dimension);
GameBoard gameBoard;
try {
if (useNative.get()) {
gameBoard = new GameBoardNativeImpl(dimension);
} else {
gameBoard = new GameBoardLocalImpl(dimension);
}
} catch (ExceptionInInitializerError e) {
// Fallback to the Java implementation.
log.log(
Level.WARNING,
"Unable to use native logger, falling back to local logger: {0}",
e.getMessage());
useNative.set(false);
gameBoard = new GameBoardLocalImpl(dimension);
}
return gameBoard;
}
}
Loading

0 comments on commit 373f19e

Please sign in to comment.