├── gradle ├── version.txt ├── jdk-version.txt ├── plugins │ ├── src │ │ ├── main │ │ │ └── kotlin │ │ │ │ ├── org.example.gradle.feature.test-fixtures.gradle.kts │ │ │ │ ├── org.example.gradle.feature.repositories.settings.gradle.kts │ │ │ │ ├── org.example.gradle.check.format-java.gradle.kts │ │ │ │ ├── org.example.gradle.check.format-gradle.gradle.kts │ │ │ │ ├── org.example.gradle.feature.javadoc.gradle.kts │ │ │ │ ├── org.example.gradle.report.sbom.gradle.kts │ │ │ │ ├── org.example.gradle.check.format-base.gradle.kts │ │ │ │ ├── org.example.gradle.report.develocity.settings.gradle.kts │ │ │ │ ├── org.example.gradle.report.plugin-analysis.gradle.kts │ │ │ │ ├── org.example.gradle.check.format-gradle.root.gradle.kts │ │ │ │ ├── org.example.gradle.component.application.gradle.kts │ │ │ │ ├── org.example.gradle.build.settings.gradle.kts │ │ │ │ ├── org.example.gradle.feature.build-cache.settings.gradle.kts │ │ │ │ ├── org.example.gradle.feature.use-all-catalog-versions.gradle.kts │ │ │ │ ├── org.example.gradle.component.library.gradle.kts │ │ │ │ ├── org.example.gradle.feature.checksum.gradle.kts │ │ │ │ ├── org.example.gradle.check.dependencies.gradle.kts │ │ │ │ ├── org.example.gradle.feature.project-structure.settings.gradle.kts │ │ │ │ ├── org.example.gradle.base.identity.gradle.kts │ │ │ │ ├── org.example.gradle.feature.publish.gradle.kts │ │ │ │ ├── org.example.gradle.base.dependency-rules.gradle.kts │ │ │ │ ├── org.example.gradle.feature.test.gradle.kts │ │ │ │ ├── org.example.gradle.feature.test-end-to-end.gradle.kts │ │ │ │ ├── org.example.gradle.report.test.gradle.kts │ │ │ │ ├── org.example.gradle.base.lifecycle.gradle.kts │ │ │ │ ├── org.example.gradle.report.code-coverage.gradle.kts │ │ │ │ ├── org.example.gradle.feature.compile-java.gradle.kts │ │ │ │ ├── org.example.gradle.check.dependency-versions.gradle.kts │ │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── gradle │ │ │ │ ├── tasks │ │ │ │ ├── JavaVersionConsistencyCheck.kt │ │ │ │ ├── DependencyVersionUpgradesCheck.kt │ │ │ │ ├── PluginApplicationOrderAnalysis.kt │ │ │ │ └── MD5DirectoryChecksum.kt │ │ │ │ └── spotless │ │ │ │ └── SortDependenciesStep.kt │ │ └── test │ │ │ └── kotlin │ │ │ └── org │ │ │ └── example │ │ │ └── gradle │ │ │ └── test │ │ │ ├── fixtures │ │ │ └── GradleProject.kt │ │ │ └── ConventionPluginTest.kt │ └── build.gradle.kts ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── versions │ └── build.gradle.kts ├── aggregation │ └── build.gradle.kts └── libs.versions.toml ├── .gitignore ├── jamcatch ├── jamcatch-stage │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ └── META-INF │ │ │ │ │ └── services │ │ │ │ │ └── org.example.javarca.model.Stage │ │ │ └── java │ │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── jamcatch │ │ │ │ └── stage │ │ │ │ └── JamCatchStage.java │ │ ├── testEndToEnd │ │ │ └── java │ │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── jamcatch │ │ │ │ └── stage │ │ │ │ └── end2end │ │ │ │ └── JamCatchStageEnd2EndTest.java │ │ └── test │ │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── jamcatch │ │ │ └── stage │ │ │ └── test │ │ │ └── JamCatchStageTest.java │ └── build.gradle.kts ├── jamcatch-assets │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ ├── META-INF │ │ │ │ │ └── services │ │ │ │ │ │ └── org.example.javarca.model.AssetSet │ │ │ │ └── org │ │ │ │ │ └── example │ │ │ │ │ └── jamcatch │ │ │ │ │ └── assets │ │ │ │ │ └── res │ │ │ │ │ ├── bg.png │ │ │ │ │ ├── jar_0.png │ │ │ │ │ ├── jar_1.png │ │ │ │ │ ├── jar_2.png │ │ │ │ │ ├── jar_3.png │ │ │ │ │ ├── jar_4.png │ │ │ │ │ ├── jar_5.png │ │ │ │ │ ├── solid.png │ │ │ │ │ ├── wall.png │ │ │ │ │ └── catcher.png │ │ │ └── java │ │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── jamcatch │ │ │ │ └── assets │ │ │ │ └── JamCatchAssets.java │ │ ├── testEndToEnd │ │ │ └── java │ │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── jamcatch │ │ │ │ └── assets │ │ │ │ └── end2end │ │ │ │ └── JamCatchAssetsEnd2EndTest.java │ │ └── test │ │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── jamcatch │ │ │ └── assets │ │ │ └── test │ │ │ └── JamCatchAssetsTest.java │ └── build.gradle.kts └── jamcatch-actors │ ├── src │ ├── main │ │ ├── resources │ │ │ ├── META-INF │ │ │ │ └── services │ │ │ │ │ └── org.example.javarca.model.ActorSet │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── jamcatch │ │ │ │ └── actors │ │ │ │ └── res │ │ │ │ └── jamcatch.csv │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── jamcatch │ │ │ └── actors │ │ │ ├── JamCatchActorSet.java │ │ │ └── collisions │ │ │ └── Collisions.java │ ├── testEndToEnd │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── jamcatch │ │ │ └── actors │ │ │ └── end2end │ │ │ └── JamCatchActorsEnd2EndTest.java │ └── test │ │ └── java │ │ └── org │ │ └── example │ │ └── jamcatch │ │ └── actors │ │ └── test │ │ └── JamCatchActorSetTest.java │ └── build.gradle.kts ├── renderer └── renderer-lwjgl │ ├── src │ ├── main │ │ ├── resources │ │ │ ├── META-INF │ │ │ │ └── services │ │ │ │ │ └── org.example.javarca.engine.Renderer │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── renderer │ │ │ │ └── lwjgl │ │ │ │ └── textures │ │ │ │ └── font.ttf │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── renderer │ │ │ └── lwjgl │ │ │ ├── textures │ │ │ └── TextureManagement.java │ │ │ └── LWJGLRenderer.java │ └── test │ │ └── java │ │ └── org │ │ └── example │ │ └── renderer │ │ └── lwjgl │ │ └── test │ │ └── LWJGLRendererTest.java │ └── build.gradle.kts ├── settings.gradle.kts ├── engine ├── javarca-model │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── javarca │ │ │ └── model │ │ │ ├── Asset.java │ │ │ ├── ActorPropertyModifier.java │ │ │ ├── AssetSet.java │ │ │ ├── ActorSet.java │ │ │ ├── GameConstants.java │ │ │ ├── Stage.java │ │ │ ├── ActorProperty.java │ │ │ ├── ActorCollision.java │ │ │ ├── Actor.java │ │ │ ├── ActorState.java │ │ │ └── ActorStates.java │ │ └── test │ │ └── java │ │ └── org │ │ └── example │ │ └── javarca │ │ └── model │ │ └── test │ │ └── ActorTest.java └── javarca-engine │ ├── src │ ├── testFixtures │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── javarca │ │ │ └── engine │ │ │ └── test │ │ │ └── fixtures │ │ │ ├── Screenshot.java │ │ │ └── RendererFixture.java │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── javarca │ │ │ └── engine │ │ │ ├── Engine.java │ │ │ ├── Renderer.java │ │ │ ├── GameLoop.java │ │ │ ├── impl │ │ │ └── ActorStatesImpl.java │ │ │ ├── GameState.java │ │ │ └── Spot.java │ └── test │ │ └── java │ │ └── org │ │ └── example │ │ └── javarca │ │ └── engine │ │ └── test │ │ └── GameStateTest.java │ └── build.gradle.kts ├── renovate.json ├── apps ├── app-jamcatch-debug │ └── build.gradle.kts └── app-jamcatch │ └── build.gradle.kts ├── gradle.properties ├── .github └── workflows │ └── build.yaml ├── gradlew.bat ├── gradlew ├── LICENSE └── README.md /gradle/version.txt: -------------------------------------------------------------------------------- 1 | 1.2 -------------------------------------------------------------------------------- /gradle/jdk-version.txt: -------------------------------------------------------------------------------- 1 | 21 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | .vscode 4 | build 5 | target 6 | bin 7 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-stage/src/main/resources/META-INF/services/org.example.javarca.model.Stage: -------------------------------------------------------------------------------- 1 | org.example.jamcatch.stage.JamCatchStage -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.feature.test-fixtures.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { id("org.gradle.java-test-fixtures") } 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/resources/META-INF/services/org.example.javarca.model.AssetSet: -------------------------------------------------------------------------------- 1 | org.example.jamcatch.assets.JamCatchAssets -------------------------------------------------------------------------------- /renderer/renderer-lwjgl/src/main/resources/META-INF/services/org.example.javarca.engine.Renderer: -------------------------------------------------------------------------------- 1 | org.example.renderer.lwjgl.LWJGLRenderer -------------------------------------------------------------------------------- /jamcatch/jamcatch-actors/src/main/resources/META-INF/services/org.example.javarca.model.ActorSet: -------------------------------------------------------------------------------- 1 | org.example.jamcatch.actors.JamCatchActorSet -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { includeBuild("gradle/plugins") } 2 | 3 | plugins { id("org.example.gradle.build") } 4 | 5 | rootProject.name = "gradle-project-setup-howto" 6 | -------------------------------------------------------------------------------- /engine/javarca-model/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.gradle.component.library") 3 | id("org.example.gradle.feature.publish") 4 | } 5 | 6 | dependencies { testImplementation(libs.junit.api) } 7 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.feature.repositories.settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | // Get components from Maven Central 3 | repositories.mavenCentral() 4 | } 5 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-actors/src/main/resources/org/example/jamcatch/actors/res/jamcatch.csv: -------------------------------------------------------------------------------- 1 | SYMBOL,PLAYER,SPEEDX,SPEEDY,BLOCKING,DESTRUCTIBLE,POINTS 2 | J,,,1000,1,,10 3 | p,1,1500,,1,, 4 | x,,,,1,, 5 | X,,,,1,, 6 | 0,,,,,, 7 | :,,,,,, -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/bg.png -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_0.png -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_1.png -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_2.png -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_3.png -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_4.png -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/jar_5.png -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/solid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/solid.png -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/wall.png -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/catcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/jamcatch/jamcatch-assets/src/main/resources/org/example/jamcatch/assets/res/catcher.png -------------------------------------------------------------------------------- /renderer/renderer-lwjgl/src/main/resources/org/example/renderer/lwjgl/textures/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjohannes/gradle-project-setup-howto/HEAD/renderer/renderer-lwjgl/src/main/resources/org/example/renderer/lwjgl/textures/font.ttf -------------------------------------------------------------------------------- /engine/javarca-model/src/main/java/org/example/javarca/model/Asset.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model; 2 | 3 | /** 4 | * Provide the graphical asset for a given symbol as a byte stream that encodes a PNG image. 5 | */ 6 | public record Asset(char symbol, byte[] image) {} 7 | -------------------------------------------------------------------------------- /engine/javarca-model/src/main/java/org/example/javarca/model/ActorPropertyModifier.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model; 2 | 3 | /** 4 | * Represents the modification of a {@link ActorProperty} to a certain value. 5 | */ 6 | public record ActorPropertyModifier(ActorProperty p, int value) {} 7 | -------------------------------------------------------------------------------- /engine/javarca-model/src/main/java/org/example/javarca/model/AssetSet.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * Provide a set of {@link Asset}s for a game. 7 | */ 8 | public interface AssetSet { 9 | 10 | Set assets(); 11 | } 12 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.check.format-java.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { id("com.diffplug.spotless") } 2 | 3 | spotless { 4 | java { 5 | importOrder() 6 | removeUnusedImports() 7 | cleanthat() 8 | palantirJavaFormat() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /engine/javarca-engine/src/testFixtures/java/org/example/javarca/engine/test/fixtures/Screenshot.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.engine.test.fixtures; 2 | 3 | import java.io.File; 4 | 5 | public record Screenshot(File file) { 6 | 7 | // More methods to make assertions on the screenshot... 8 | } 9 | -------------------------------------------------------------------------------- /engine/javarca-model/src/main/java/org/example/javarca/model/ActorSet.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * Provide a set of {@link Actor} definitions for a game. 7 | */ 8 | public interface ActorSet { 9 | 10 | Set items(); 11 | } 12 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | ":automergeAll" 6 | ], 7 | "packageRules": [ 8 | { 9 | "matchManagers": "gradle-wrapper", 10 | "ignoreUnstable": false 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /engine/javarca-engine/src/main/java/org/example/javarca/engine/Engine.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.engine; 2 | 3 | /** 4 | * Entry point for any game that runs through this engine. 5 | */ 6 | public class Engine { 7 | 8 | private Engine() {} 9 | 10 | public static void main(String[] args) { 11 | Renderer.launch(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.check.format-gradle.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.example.gradle.spotless.SortDependenciesStep 2 | 3 | plugins { id("com.diffplug.spotless") } 4 | 5 | spotless { 6 | kotlinGradle { 7 | ktfmt().kotlinlangStyle().configure { it.setMaxWidth(500) } 8 | addStep(SortDependenciesStep.create()) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /engine/javarca-model/src/main/java/org/example/javarca/model/GameConstants.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model; 2 | 3 | /** 4 | * Constant values that may be used in {@link Actor} definitions. 5 | */ 6 | public interface GameConstants { 7 | 8 | char SYMBOL_EMPTY_SPOT = '.'; 9 | 10 | int TRUE = 1; 11 | int PRECISION = 10000; 12 | 13 | int STAGE_SIZE = 16; 14 | } 15 | -------------------------------------------------------------------------------- /engine/javarca-model/src/main/java/org/example/javarca/model/Stage.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model; 2 | 3 | /** 4 | * Define the stage in which a Game takes place via symbols representing the {@link Actor}s to be initially placed in 5 | * the game. A stage needs to have 16 lines with each line being 16 characters long. 6 | */ 7 | public interface Stage { 8 | String define(); 9 | } 10 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.feature.javadoc.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.java") 3 | id("org.example.gradle.base.lifecycle") 4 | } 5 | 6 | tasks.withType().configureEach { 7 | options { 8 | this as StandardJavadocDocletOptions 9 | encoding = "UTF-8" 10 | addStringOption("Xwerror", "-Xdoclint:all,-missing") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=2c2402e0741735bf96742d834ccc236d1b6ebef8f3b249df059a0f19cce07efc 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-milestone-3-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /apps/app-jamcatch-debug/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { id("org.example.gradle.component.application") } 2 | 3 | application { mainClass = "org.example.javarca.engine.Engine" } 4 | 5 | dependencies { 6 | runtimeOnly(projects.jamcatchActors) 7 | runtimeOnly(projects.jamcatchStage) 8 | runtimeOnly(projects.javarcaEngine) 9 | runtimeOnly(projects.rendererLwjgl) 10 | runtimeOnly(libs.slf4j.jul) 11 | } 12 | -------------------------------------------------------------------------------- /renderer/renderer-lwjgl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { id("org.example.gradle.component.library") } 2 | 3 | @Suppress("UnstableApiUsage") 4 | dependencies { 5 | api(projects.javarcaEngine) 6 | implementation(libs.lwjgl) 7 | implementation(libs.lwjgl.glfw) 8 | implementation(libs.lwjgl.opengl) 9 | implementation(libs.lwjgl.stb) 10 | implementation(libs.slf4j.api) 11 | 12 | testImplementation(libs.junit.api) 13 | } 14 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-actors/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.gradle.component.library") 3 | id("org.example.gradle.feature.test-end-to-end") 4 | } 5 | 6 | dependencies { 7 | api(projects.javarcaModel) 8 | implementation(libs.commons.csv) 9 | 10 | testImplementation(libs.junit.api) 11 | 12 | testEndToEndImplementation(testFixtures(projects.javarcaEngine)) 13 | testEndToEndImplementation(libs.junit.api) 14 | } 15 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.gradle.component.library") 3 | id("org.example.gradle.feature.test-end-to-end") 4 | } 5 | 6 | dependencies { 7 | api(projects.javarcaModel) 8 | implementation(libs.commons.io) 9 | 10 | testImplementation(libs.junit.api) 11 | 12 | testEndToEndImplementation(testFixtures(projects.javarcaEngine)) 13 | testEndToEndImplementation(libs.junit.api) 14 | } 15 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-stage/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.gradle.component.library") 3 | id("org.example.gradle.feature.test-end-to-end") 4 | } 5 | 6 | dependencies { 7 | api(projects.javarcaModel) 8 | implementation(libs.slf4j.api) 9 | 10 | testImplementation(libs.junit.api) 11 | 12 | testEndToEndImplementation(testFixtures(projects.javarcaEngine)) 13 | testEndToEndImplementation(libs.junit.api) 14 | } 15 | -------------------------------------------------------------------------------- /apps/app-jamcatch/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { id("org.example.gradle.component.application") } 2 | 3 | application { mainClass = "org.example.javarca.engine.Engine" } 4 | 5 | dependencies { 6 | runtimeOnly(projects.jamcatchActors) 7 | runtimeOnly(projects.jamcatchAssets) 8 | runtimeOnly(projects.jamcatchStage) 9 | runtimeOnly(projects.javarcaEngine) 10 | runtimeOnly(projects.rendererLwjgl) 11 | runtimeOnly(libs.slf4j.simple) 12 | } 13 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.report.sbom.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.java") 3 | id("org.cyclonedx.bom") 4 | } 5 | 6 | // Generate a Software Bill of Materials for the software product 7 | tasks.cyclonedxBom { 8 | includeConfigs.add(configurations.runtimeClasspath.name) 9 | jsonOutput = layout.buildDirectory.file("reports/sbom/bom.json") 10 | xmlOutput = layout.buildDirectory.file("reports/sbom/bom.xml") 11 | } 12 | -------------------------------------------------------------------------------- /gradle/versions/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.gradle.base.dependency-rules") 3 | id("org.example.gradle.feature.use-all-catalog-versions") 4 | id("org.example.gradle.check.dependency-versions") 5 | id("org.example.gradle.check.format-gradle") 6 | } 7 | 8 | tasks.checkVersionConsistency { 9 | excludes.add("org.junit.jupiter:junit-jupiter-api") // testing only 10 | excludes.add("org.assertj:assertj-core") // testing only 11 | } 12 | -------------------------------------------------------------------------------- /engine/javarca-engine/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.gradle.component.library") 3 | id("org.example.gradle.feature.test-fixtures") 4 | id("org.example.gradle.feature.publish") 5 | } 6 | 7 | dependencies { 8 | api(projects.javarcaModel) 9 | implementation(libs.slf4j.api) 10 | 11 | testImplementation(libs.junit.api) 12 | 13 | testFixturesImplementation(libs.slf4j.api) 14 | testFixturesRuntimeOnly(projects.rendererLwjgl) 15 | } 16 | -------------------------------------------------------------------------------- /engine/javarca-model/src/main/java/org/example/javarca/model/ActorProperty.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model; 2 | 3 | /** 4 | * Pre-defined properties each {@link Actor} can have. They decide if and how an {@link Actor} moves on 5 | * the {@link Stage} and if {@link Actor}s can collide to trigger {@link ActorCollision} logic. 6 | */ 7 | public enum ActorProperty { 8 | PLAYER, 9 | SPEEDX, 10 | SPEEDY, 11 | BLOCKING, 12 | DESTRUCTIBLE, 13 | POINTS 14 | } 15 | -------------------------------------------------------------------------------- /engine/javarca-model/src/main/java/org/example/javarca/model/ActorCollision.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model; 2 | 3 | /** 4 | * Represents the collision logic between two {@link Actor}s. A collision function has access 5 | * to the two colliding {@link ActorState}s, but may also access and modify the state of any other 6 | * actor on the stage. 7 | */ 8 | public interface ActorCollision { 9 | 10 | void collide(ActorState myState, ActorState otherState, ActorStates allStates); 11 | } 12 | -------------------------------------------------------------------------------- /gradle/aggregation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.java") 3 | id("org.example.gradle.base.lifecycle") 4 | id("org.example.gradle.base.dependency-rules") 5 | id("org.example.gradle.check.format-gradle") 6 | id("org.example.gradle.report.code-coverage") 7 | id("org.example.gradle.report.plugin-analysis") 8 | id("org.example.gradle.report.sbom") 9 | id("org.example.gradle.report.test") 10 | } 11 | 12 | dependencies { implementation(projects.appJamcatch) } 13 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.check.format-base.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.diffplug.spotless.LineEnding 2 | 3 | plugins { 4 | id("org.example.gradle.base.lifecycle") 5 | id("com.diffplug.spotless") 6 | } 7 | 8 | // https://github.com/gradle/gradle/issues/25469#issuecomment-3444231151 9 | spotless { lineEndings = LineEnding.UNIX } 10 | 11 | tasks.named("qualityCheck") { dependsOn(tasks.spotlessCheck) } 12 | 13 | tasks.named("qualityGate") { dependsOn(tasks.spotlessApply) } 14 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.report.develocity.settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { id("com.gradle.develocity") } 2 | 3 | // Configure Build Scans (local builds have to opt-in via --scan) 4 | develocity { 5 | buildScan { 6 | termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use" 7 | termsOfUseAgree = "yes" 8 | if (!providers.environmentVariable("CI").getOrElse("false").toBoolean()) { 9 | publishing.onlyIf { false } // only publish with explicit '--scan' 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.report.plugin-analysis.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.example.gradle.tasks.PluginApplicationOrderAnalysis 2 | 3 | // Parse '*.gradle.kts' files and create a PlantUML diagram based on the application order 4 | tasks.register("analysePluginApplicationOrder") { 5 | group = "help" 6 | 7 | pluginSrcFolders.from(project.layout.projectDirectory.dir("../plugins/src/main/kotlin")) 8 | pluginApplicationDiagram = layout.buildDirectory.file("reports/plugins/plugin-application-order.puml") 9 | } 10 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.check.format-gradle.root.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.diffplug.spotless") 3 | id("org.example.gradle.base.lifecycle") 4 | } 5 | 6 | spotless { 7 | kotlinGradle { 8 | ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } 9 | target("settings.gradle.kts", "gradle/plugins/build.gradle.kts", "gradle/plugins/src/main/**/*.gradle.kts") 10 | } 11 | kotlin { 12 | ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } 13 | target("gradle/plugins/src/main/**/*.kt") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.component.application.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.application") 3 | id("org.example.gradle.base.dependency-rules") 4 | id("org.example.gradle.base.identity") 5 | id("org.example.gradle.base.lifecycle") 6 | id("org.example.gradle.check.dependencies") 7 | id("org.example.gradle.check.format-base") 8 | id("org.example.gradle.check.format-gradle") 9 | id("org.example.gradle.check.format-java") 10 | id("org.example.gradle.feature.compile-java") 11 | id("org.example.gradle.feature.javadoc") 12 | } 13 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Configure the Grade Daemon - memory and same encoding on all machines 2 | org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=384m -XX:+HeapDumpOnOutOfMemoryError 3 | 4 | # activate Gradle configuration cache - instantly start builds that ran before, full parallelism 5 | org.gradle.configuration-cache=true 6 | # 'isolated-projects' can be active only when removing 'com.diffplug.spotless' 7 | # org.gradle.unsafe.isolated-projects=true 8 | 9 | # activate Gradle build cache - switch between branches/commits without rebuilding every time 10 | org.gradle.caching=true 11 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.build.settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.example.gradle.report.develocity") 3 | id("org.example.gradle.feature.repositories") 4 | id("org.example.gradle.feature.build-cache") 5 | id("org.example.gradle.feature.project-structure") 6 | } 7 | 8 | gradle.lifecycle.beforeProject { 9 | if (this.path == ":") { 10 | apply(plugin = "org.example.gradle.base.lifecycle") 11 | apply(plugin = "org.example.gradle.check.format-base") 12 | apply(plugin = "org.example.gradle.check.format-gradle.root") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /renderer/renderer-lwjgl/src/test/java/org/example/renderer/lwjgl/test/LWJGLRendererTest.java: -------------------------------------------------------------------------------- 1 | package org.example.renderer.lwjgl.test; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertNotNull; 4 | 5 | import org.example.javarca.engine.Renderer; 6 | import org.example.renderer.lwjgl.LWJGLRenderer; 7 | import org.junit.jupiter.api.Test; 8 | 9 | public class LWJGLRendererTest { 10 | 11 | @Test 12 | public void testRendererCreation() { 13 | Renderer renderer = new LWJGLRenderer(); 14 | 15 | assertNotNull(renderer, "Renderer should not be null"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /engine/javarca-model/src/main/java/org/example/javarca/model/Actor.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model; 2 | 3 | import java.util.Map; 4 | import java.util.Set; 5 | 6 | /** 7 | * An actor is represented by a 'symbol' and has some fundamental {@link ActorProperty}s. 8 | * It may provide {@link ActorCollision}s which essential make up the behavior of a game. 9 | * If two actors collide on the {@link Stage}, the corresponding collision logic is triggered. 10 | */ 11 | public record Actor( 12 | char symbol, Set modifiers, Map collisionFunctions) {} 13 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.feature.build-cache.settings.gradle.kts: -------------------------------------------------------------------------------- 1 | buildCache { 2 | val ci = providers.environmentVariable("CI").getOrElse("false").toBoolean() 3 | local { isEnabled = !ci } 4 | remote { 5 | url = uri("https://cache.onepiece.software/cache/") 6 | if (ci) { 7 | isPush = true 8 | credentials { 9 | username = providers.environmentVariable("BUILD_CACHE_USER").get() 10 | password = providers.environmentVariable("BUILD_CACHE_PWD").get() 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.feature.use-all-catalog-versions.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.java-platform") 3 | id("org.example.gradle.base.lifecycle") 4 | } 5 | 6 | // Allow upgrading (transitive) versions via catalog by adding constraints 7 | dependencies.constraints { 8 | val libs = versionCatalogs.named("libs") 9 | val catalogEntries = libs.libraryAliases.map { libs.findLibrary(it).get().get() } 10 | catalogEntries.forEach { entry -> 11 | val version = entry.version 12 | if (version != null) { 13 | api(entry) { version { require(version) } } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-actors/src/testEndToEnd/java/org/example/jamcatch/actors/end2end/JamCatchActorsEnd2EndTest.java: -------------------------------------------------------------------------------- 1 | package org.example.jamcatch.actors.end2end; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import org.example.javarca.engine.test.fixtures.RendererFixture; 6 | import org.example.javarca.engine.test.fixtures.Screenshot; 7 | import org.junit.jupiter.api.Test; 8 | 9 | public class JamCatchActorsEnd2EndTest { 10 | 11 | @Test 12 | public void test() { 13 | Screenshot result = RendererFixture.launchAndWaitForFinish(); 14 | 15 | assertTrue(result.file().exists(), "Screenshot should exist"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/testEndToEnd/java/org/example/jamcatch/assets/end2end/JamCatchAssetsEnd2EndTest.java: -------------------------------------------------------------------------------- 1 | package org.example.jamcatch.assets.end2end; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import org.example.javarca.engine.test.fixtures.RendererFixture; 6 | import org.example.javarca.engine.test.fixtures.Screenshot; 7 | import org.junit.jupiter.api.Test; 8 | 9 | public class JamCatchAssetsEnd2EndTest { 10 | 11 | @Test 12 | public void test() { 13 | Screenshot result = RendererFixture.launchAndWaitForFinish(); 14 | 15 | assertTrue(result.file().exists(), "Screenshot should exist"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [libraries] 2 | commons-csv = { module = "org.apache.commons:commons-csv", version = "1.14.1" } 3 | commons-io = { module = "commons-io:commons-io", version = "2.21.0" } 4 | junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version = "6.0.1" } 5 | lwjgl = { module = "org.lwjgl:lwjgl", version = "3.3.6" } 6 | lwjgl-glfw = { module = "org.lwjgl:lwjgl-glfw" } 7 | lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl" } 8 | lwjgl-stb = { module = "org.lwjgl:lwjgl-stb" } 9 | slf4j-api = { module = "org.slf4j:slf4j-api", version = "2.0.17" } 10 | slf4j-jul = { module = "org.slf4j:slf4j-jdk14" } 11 | slf4j-simple = { module = "org.slf4j:slf4j-simple" } 12 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.component.library.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.java-library") 3 | id("org.example.gradle.base.dependency-rules") 4 | id("org.example.gradle.base.identity") 5 | id("org.example.gradle.base.lifecycle") 6 | id("org.example.gradle.check.dependencies") 7 | id("org.example.gradle.check.format-base") 8 | id("org.example.gradle.check.format-gradle") 9 | id("org.example.gradle.check.format-java") 10 | id("org.example.gradle.feature.checksum") 11 | id("org.example.gradle.feature.compile-java") 12 | id("org.example.gradle.feature.javadoc") 13 | id("org.example.gradle.feature.test") 14 | } 15 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-stage/src/testEndToEnd/java/org/example/jamcatch/stage/end2end/JamCatchStageEnd2EndTest.java: -------------------------------------------------------------------------------- 1 | package org.example.jamcatch.stage.end2end; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import org.example.javarca.engine.test.fixtures.RendererFixture; 6 | import org.example.javarca.engine.test.fixtures.Screenshot; 7 | import org.junit.jupiter.api.Tag; 8 | import org.junit.jupiter.api.Test; 9 | 10 | @Tag("slow") 11 | public class JamCatchStageEnd2EndTest { 12 | 13 | @Test 14 | public void test() { 15 | Screenshot result = RendererFixture.launchAndWaitForFinish(); 16 | 17 | assertTrue(result.file().exists(), "Screenshot should exist"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.feature.checksum.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.example.gradle.tasks.MD5DirectoryChecksum 2 | 3 | plugins { id("org.gradle.java") } 4 | 5 | // Generate additional resources required at application runtime 6 | // This is an example for creating and integrating a custom task implementation. 7 | tasks.register("resourcesChecksum") { 8 | val resources = layout.projectDirectory.dir("src/main/resources") 9 | enabled = resources.asFile.exists() // the task is optional, only active when the directory exists 10 | 11 | inputDirectory = resources 12 | checksumFile = layout.buildDirectory.file("generated-resources/md5/resources.MD5") 13 | } 14 | 15 | tasks.processResources { into("META-INF") { from(tasks.named("resourcesChecksum")) } } 16 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.check.dependencies.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.autonomousapps.DependencyAnalysisSubExtension 2 | import com.autonomousapps.tasks.ProjectHealthTask 3 | 4 | plugins { 5 | id("org.gradle.java") 6 | id("com.autonomousapps.dependency-analysis") 7 | id("io.fuchs.gradle.classpath-collision-detector") 8 | id("org.example.gradle.base.lifecycle") 9 | } 10 | 11 | // Configure the dependency analysis plugin to fail if issues are found 12 | configure { issues { onAny { severity("fail") } } } 13 | 14 | tasks.named("qualityCheck") { 15 | dependsOn(tasks.detectCollisions) 16 | dependsOn(tasks.withType()) 17 | } 18 | 19 | tasks.named("qualityGate") { 20 | dependsOn(tasks.detectCollisions) 21 | dependsOn(tasks.withType()) 22 | } 23 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/test/java/org/example/jamcatch/assets/test/JamCatchAssetsTest.java: -------------------------------------------------------------------------------- 1 | package org.example.jamcatch.assets.test; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertFalse; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.util.Set; 7 | import org.example.jamcatch.assets.JamCatchAssets; 8 | import org.example.javarca.model.Asset; 9 | import org.example.javarca.model.AssetSet; 10 | import org.junit.jupiter.api.Test; 11 | 12 | public class JamCatchAssetsTest { 13 | 14 | @Test 15 | public void testAssetSetCreation() { 16 | AssetSet assetSet = new JamCatchAssets(); 17 | 18 | assertNotNull(assetSet); 19 | Set assets = assetSet.assets(); 20 | assertNotNull(assets); 21 | assertFalse(assets.isEmpty(), "Asset set should not be empty"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-actors/src/test/java/org/example/jamcatch/actors/test/JamCatchActorSetTest.java: -------------------------------------------------------------------------------- 1 | package org.example.jamcatch.actors.test; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertFalse; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.util.Set; 7 | import org.example.jamcatch.actors.JamCatchActorSet; 8 | import org.example.javarca.model.Actor; 9 | import org.example.javarca.model.ActorSet; 10 | import org.junit.jupiter.api.Test; 11 | 12 | public class JamCatchActorSetTest { 13 | 14 | @Test 15 | public void testActorSetCreation() { 16 | ActorSet actorSet = new JamCatchActorSet(); 17 | 18 | assertNotNull(actorSet); 19 | Set actors = actorSet.items(); 20 | assertNotNull(actors); 21 | assertFalse(actors.isEmpty(), "Actor set should not be empty"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gradle/plugins/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { `kotlin-dsl` } 2 | 3 | repositories { gradlePluginPortal() } 4 | 5 | dependencies { 6 | implementation("com.autonomousapps:dependency-analysis-gradle-plugin:3.5.1") 7 | implementation("com.diffplug.spotless:spotless-plugin-gradle:8.1.0") 8 | implementation("com.gradle:develocity-gradle-plugin:4.3") 9 | implementation("io.fuchs.gradle.classpath-collision-detector:classpath-collision-detector:1.0.0") 10 | implementation("io.mvnpm.gradle.plugin:native-java-plugin:1.0.0") 11 | implementation("org.cyclonedx:cyclonedx-gradle-plugin:2.4.1") 12 | implementation("org.gradlex:jvm-dependency-conflict-resolution:2.5") 13 | } 14 | 15 | testing.suites.named("test") { 16 | useJUnitJupiter() 17 | dependencies { 18 | implementation("org.junit.jupiter:junit-jupiter-params") 19 | implementation("org.assertj:assertj-core:3.27.6") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.feature.project-structure.settings.gradle.kts: -------------------------------------------------------------------------------- 1 | // Include all subfolders that contain a 'build.gradle.kts' as subprojects 2 | rootDir 3 | .listFiles() 4 | ?.filter { it.isDirectory && !it.name.startsWith(".") && it.name != "gradle" } 5 | ?.flatMap { it.listFiles().asList() } 6 | ?.filter { File(it, "build.gradle.kts").exists() } 7 | ?.forEach { subproject -> 8 | include(":${subproject.name}") 9 | project(":${subproject.name}").projectDir = subproject 10 | } 11 | 12 | // Platform project 13 | include(":versions") 14 | 15 | project(":versions").projectDir = file("gradle/versions") 16 | 17 | // Aggregation and analysis project to create reports about the whole software (coverage, SBOM, ...) 18 | include(":aggregation") 19 | 20 | project(":aggregation").projectDir = file("gradle/aggregation") 21 | 22 | // Allow local projects to be referred to by accessor 23 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 24 | -------------------------------------------------------------------------------- /engine/javarca-engine/src/testFixtures/java/org/example/javarca/engine/test/fixtures/RendererFixture.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.engine.test.fixtures; 2 | 3 | import java.io.File; 4 | import org.example.javarca.engine.Renderer; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | public class RendererFixture { 9 | 10 | public RendererFixture() {} 11 | 12 | public static Screenshot launchAndWaitForFinish() { 13 | Logger logger = LoggerFactory.getLogger(RendererFixture.class); 14 | File file = new File(System.getenv("PRESENTATION_FOLDER") + "/screen.png"); 15 | try { 16 | var renderThread = new Thread(Renderer::launch); 17 | 18 | renderThread.start(); 19 | Thread.sleep(1000); 20 | renderThread.interrupt(); 21 | return new Screenshot(file); 22 | } catch (Throwable e) { 23 | logger.error("Error in test execution", e); 24 | return new Screenshot(file); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-stage/src/test/java/org/example/jamcatch/stage/test/JamCatchStageTest.java: -------------------------------------------------------------------------------- 1 | package org.example.jamcatch.stage.test; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertNotNull; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import org.example.jamcatch.stage.JamCatchStage; 7 | import org.example.javarca.model.Stage; 8 | import org.junit.jupiter.api.Test; 9 | 10 | public class JamCatchStageTest { 11 | 12 | @Test 13 | public void testStageDefinition() { 14 | // Arrange 15 | Stage stage = new JamCatchStage(); 16 | 17 | String definition = stage.define(); 18 | 19 | assertNotNull(definition); 20 | assertTrue(definition.contains("p"), "Stage should contain player character"); 21 | assertTrue(definition.contains("J"), "Stage should contain jar character"); 22 | assertTrue(definition.contains("X"), "Stage should contain solid blocks"); 23 | assertTrue(definition.contains("x"), "Stage should contain wall blocks"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.base.identity.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { id("org.gradle.base") } 2 | 3 | // Set the group required to refer to a Module "from outside". 4 | // I.e., when it is published or used in Included Builds. 5 | group = "org.example" 6 | 7 | // Set the version from 'version.txt' 8 | @Suppress("UnstableApiUsage") 9 | version = providers.fileContents(isolated.rootProject.projectDirectory.file("gradle/version.txt")).asText.getOrElse("") 10 | 11 | // On CI, add timestamp for publishing 12 | if (providers.environmentVariable("CI").getOrElse("false").toBoolean()) { 13 | val gitCommitTimestamp = 14 | providers 15 | .exec { 16 | commandLine("git", "log", "-1", "--format=%ad", "--date=format:%Y%m%d%H%M%S") 17 | isIgnoreExitValue = true 18 | } 19 | .standardOutput 20 | .asText 21 | .get() 22 | .trim() 23 | .let { if (it.isNotEmpty()) "-$it" else it } 24 | version = "$version$gitCommitTimestamp" 25 | } 26 | -------------------------------------------------------------------------------- /engine/javarca-engine/src/main/java/org/example/javarca/engine/Renderer.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.engine; 2 | 3 | import java.util.ServiceLoader; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | /** 8 | * Interface to be implemented on top of a library or framework that renders a running game. 9 | * The implementation should use {@link GameLoop} and {@link GameState}, 10 | */ 11 | public interface Renderer { 12 | static void launch() { 13 | Logger logger = LoggerFactory.getLogger(Renderer.class); 14 | logger.info( 15 | "Welcome to Javarcade! (module={})", Renderer.class.getModule().getName()); 16 | 17 | Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { 18 | logger.error("Unexpected crash!", throwable); 19 | System.exit(1); 20 | }); 21 | 22 | var impl = ServiceLoader.load(Renderer.class).findFirst(); 23 | impl.ifPresentOrElse(Renderer::run, () -> logger.warn("No renderer implementation available")); 24 | } 25 | 26 | void run(); 27 | } 28 | -------------------------------------------------------------------------------- /engine/javarca-model/src/test/java/org/example/javarca/model/test/ActorTest.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model.test; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.HashMap; 6 | import java.util.HashSet; 7 | import java.util.Map; 8 | import java.util.Set; 9 | import org.example.javarca.model.Actor; 10 | import org.example.javarca.model.ActorCollision; 11 | import org.example.javarca.model.ActorPropertyModifier; 12 | import org.junit.jupiter.api.Test; 13 | 14 | public class ActorTest { 15 | 16 | @Test 17 | public void testActorCreation() { 18 | // Arrange 19 | char symbol = 'A'; 20 | Set modifiers = new HashSet<>(); 21 | Map collisionFunctions = new HashMap<>(); 22 | 23 | Actor actor = new Actor(symbol, modifiers, collisionFunctions); 24 | 25 | assertEquals(symbol, actor.symbol()); 26 | assertEquals(modifiers, actor.modifiers()); 27 | assertEquals(collisionFunctions, actor.collisionFunctions()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.feature.publish.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.java") 3 | id("org.gradle.maven-publish") 4 | } 5 | 6 | // Publish with sources and Javadoc 7 | java { 8 | withSourcesJar() 9 | withJavadocJar() 10 | } 11 | 12 | publishing.publications.create("mavenJava") { 13 | from(components["java"]) 14 | 15 | // We use consistent resolution + a platform for controlling versions 16 | // -> Publish the versions that are the result of the consistent resolution 17 | versionMapping { allVariants { fromResolutionResult() } } 18 | } 19 | 20 | // The repository to publish to 21 | publishing.repositories { 22 | maven(providers.environmentVariable("PUBLISHING_REPOSITORY_URL").getOrElse("/tmp/repo")) { 23 | if (url.scheme == "https") { 24 | credentials { 25 | username = providers.environmentVariable("PUBLISHING_REPOSITORY_USER").get() 26 | password = providers.environmentVariable("PUBLISHING_REPOSITORY_PWD").get() 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.base.dependency-rules.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradlex.jvm-dependency-conflict-resolution") 3 | id("io.mvnpm.gradle.plugin.native-java-plugin") 4 | } 5 | 6 | jvmDependencyConflicts { 7 | // Configure build wide consistent resolution. That is, the versions that are used on the 8 | // runtime classpath of the web applications should also be used in all other places 9 | // (e.g. also when compiling a project at the bottom of the dependency graph that does not 10 | // see most of the other dependencies that may influence the version choices). 11 | consistentResolution { 12 | platform(":versions") 13 | providesVersions(":aggregation") 14 | } 15 | 16 | // Configure logging capabilities to default to Slf4JSimple 17 | logging { enforceSlf4JSimple() } 18 | 19 | // Add missing information to metadata of 3rd-party libraries. 20 | patch { 21 | module("com.google.guava:guava") { 22 | // remove annotation libraries we do not need 23 | removeDependency("com.google.j2objc:j2objc-annotations") 24 | removeDependency("org.checkerframework:checker-qual") 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build Project 2 | on: 3 | push: 4 | branches-ignore: [ renovate/** ] 5 | pull_request: 6 | branches: [ main ] 7 | env: 8 | BUILD_CACHE_USER: ${{ secrets.BUILD_CACHE_USER }} 9 | BUILD_CACHE_PWD: ${{ secrets.BUILD_CACHE_PWD }} 10 | jobs: 11 | gradle-build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v6 15 | - uses: actions/setup-java@v5 16 | with: 17 | distribution: temurin 18 | java-version-file: gradle/jdk-version.txt 19 | - uses: gradle/actions/setup-gradle@v5 20 | with: 21 | cache-read-only: false 22 | - uses: testlens-app/setup-testlens@v1.5.1 23 | - run: "./gradlew :plugins:test" # test the Gradle configuration 24 | - run: "./gradlew qualityCheck" 25 | - run: "./gradlew test" 26 | - run: "./gradlew testEndToEnd" 27 | - run: "./gradlew testEndToEndSlow" 28 | - run: "./gradlew build" 29 | - run: "./gradlew cyclonedxBom" 30 | if: ${{ !contains(github.ref_name, '/') }} 31 | - uses: actions/upload-artifact@v6 32 | if: always() 33 | with: 34 | name: reports 35 | path: gradle/aggregation/build/reports 36 | 37 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.feature.test.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.jacoco") // Record test coverage data during test execution 3 | id("org.gradle.java") 4 | } 5 | 6 | testing.suites.named("test") { 7 | targets.all { 8 | useJUnitJupiter() // Use JUnit 5 as test framework 9 | // Configure details for test executions directly on 'Test' task 10 | testTask { 11 | group = "build" 12 | 13 | maxParallelForks = 4 14 | 15 | testLogging.showStandardStreams = true 16 | 17 | maxHeapSize = "1g" 18 | systemProperty("file.encoding", "UTF-8") 19 | } 20 | } 21 | } 22 | 23 | testing.suites.withType { 24 | // remove automatically added compile time dependencies, as we define them explicitly 25 | configurations.getByName(sources.implementationConfigurationName) { 26 | withDependencies { removeIf { it.group == "org.junit.jupiter" && it.name == "junit-jupiter" } } 27 | } 28 | // Configure common test runtime dependencies for *all* projects 29 | dependencies { 30 | runtimeOnly("org.junit.jupiter:junit-jupiter-engine") 31 | runtimeOnly("org.slf4j:slf4j-simple") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.feature.test-end-to-end.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { id("org.gradle.java") } 2 | 3 | // end-to-end tests 4 | @Suppress("UnstableApiUsage") 5 | testing.suites.register("testEndToEnd") { 6 | targets.configureEach { 7 | testTask { 8 | // testing needs to be performed with a renderer implementation that uses this env setting. 9 | environment("PRESENTATION_FOLDER", project.layout.buildDirectory.dir("test-screenshot").get().asFile) 10 | // disable on CI for now, as the tests require a display 11 | if (providers.environmentVariable("CI").getOrElse("false").toBoolean()) { 12 | enabled = false 13 | } 14 | } 15 | } 16 | targets.named("testEndToEnd") { 17 | testTask { 18 | group = "build" 19 | options { 20 | this as JUnitPlatformOptions 21 | excludeTags("slow") 22 | } 23 | } 24 | } 25 | // Add a second task for the endToEndTest suite 26 | targets.register("testEndToEndSlow") { 27 | testTask { 28 | group = "build" 29 | options { 30 | this as JUnitPlatformOptions 31 | includeTags("slow") 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-stage/src/main/java/org/example/jamcatch/stage/JamCatchStage.java: -------------------------------------------------------------------------------- 1 | package org.example.jamcatch.stage; 2 | 3 | import org.example.javarca.model.Stage; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public class JamCatchStage implements Stage { 8 | private static final Logger LOG = LoggerFactory.getLogger(JamCatchStage.class); 9 | 10 | public JamCatchStage() {} 11 | 12 | @Override 13 | public String define() { 14 | LOG.debug("Constructing stage for JamCatch"); 15 | return """ 16 | x . . J . . . . . . 0 0 0 0 0 x 17 | x . . . . . . . . . . . . . . x 18 | x . . : : : : : : : : : . . . x 19 | x . . . . . . . . . . . . . . x 20 | x . . . . . . . . . . . . . . x 21 | x . . . . . . . . . . . . . . x 22 | x . . . . . . . . . . . . . . x 23 | x . . . . . . . . . . . . . . x 24 | x . . . . . . . . . . . . . . x 25 | x . . . . . . . . . . . . . . x 26 | x . . . . . . . . . . . . . . x 27 | x . . . . . . . . . . . . . . x 28 | x . . . . . . . . . . . . . . x 29 | x . . . . . . . . . . . . . . x 30 | x . . . . p . . . . . . . . . x 31 | x X X X X X X X X X X X X X X x 32 | """; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.report.test.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.java") 3 | id("org.gradle.test-report-aggregation") 4 | id("org.example.gradle.base.dependency-rules") 5 | id("org.example.gradle.base.lifecycle") 6 | } 7 | 8 | // Make aggregation "classpath" use the platform for versions (gradle/versions) 9 | configurations.aggregateTestReportResults { extendsFrom(configurations["internal"]) } 10 | 11 | // Integrate testEndToEnd results into the aggregated UNIT_TEST test results 12 | tasks.testAggregateTestReport { 13 | destinationDirectory = layout.buildDirectory.dir("reports/tests") 14 | testResults.from( 15 | configurations.aggregateTestReportResults 16 | .get() 17 | .incoming 18 | .artifactView { 19 | withVariantReselection() 20 | attributes { 21 | attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.VERIFICATION)) 22 | attribute(TestSuiteName.TEST_SUITE_NAME_ATTRIBUTE, objects.named("testEndToEnd")) 23 | attribute( 24 | VerificationType.VERIFICATION_TYPE_ATTRIBUTE, 25 | objects.named(VerificationType.TEST_RESULTS), 26 | ) 27 | } 28 | } 29 | .files 30 | ) 31 | } 32 | 33 | // Generate report when running 'check' 34 | tasks.check { dependsOn(tasks.testAggregateTestReport) } 35 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-assets/src/main/java/org/example/jamcatch/assets/JamCatchAssets.java: -------------------------------------------------------------------------------- 1 | package org.example.jamcatch.assets; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.Set; 8 | import org.apache.commons.io.IOUtils; 9 | import org.example.javarca.model.Asset; 10 | import org.example.javarca.model.AssetSet; 11 | 12 | public class JamCatchAssets implements AssetSet { 13 | 14 | private final Set assets; 15 | 16 | public JamCatchAssets() { 17 | assets = Set.of( 18 | new Asset('.', readImage("bg")), 19 | new Asset('p', readImage("catcher")), 20 | new Asset('x', readImage("wall")), 21 | new Asset('X', readImage("solid")), 22 | new Asset('H', readImage("jar_4")), 23 | new Asset('I', readImage("jar_5")), 24 | new Asset('J', readImage("jar_0")), 25 | new Asset('K', readImage("jar_2")), 26 | new Asset('L', readImage("jar_3"))); 27 | } 28 | 29 | private byte[] readImage(String name) { 30 | try (InputStream is = getClass().getResourceAsStream("res/" + name + ".png")) { 31 | return IOUtils.toByteArray(requireNonNull(is)); 32 | } catch (IOException e) { 33 | throw new RuntimeException(e); 34 | } 35 | } 36 | 37 | @Override 38 | public Set assets() { 39 | return assets; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.base.lifecycle.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { id("org.gradle.base") } 2 | 3 | // Convenience for local development: when running './gradlew' without parameters show the tasks... 4 | defaultTasks("tasks") 5 | 6 | if (gradle.startParameter.taskNames.isEmpty()) { 7 | // ...of group 'build' only 8 | tasks.withType().configureEach { displayGroup = "build" } 9 | } 10 | 11 | tasks.register("qualityCheck") { 12 | group = "verification" 13 | description = "Runs checks (without executing tests)" 14 | dependsOn(tasks.assemble) 15 | } 16 | 17 | tasks.register("qualityGate") { 18 | group = "build" 19 | description = "Runs checks and auto-corrects (without executing tests)" 20 | dependsOn(tasks.assemble) 21 | } 22 | 23 | tasks.check { dependsOn(tasks.named("qualityCheck")) } 24 | 25 | // Cleanup the 'build' group by removing all tasks developers usually do not need to call directly 26 | afterEvaluate { 27 | tasks.configureEach { 28 | if (name in listOf("buildDependents", "buildNeeded", "classes")) { 29 | group = null 30 | } 31 | if (name.endsWith("Classes")) { 32 | group = null 33 | } 34 | if (this is Jar) { 35 | group = null 36 | } 37 | if (this is Test) { 38 | group = "build" 39 | } 40 | // added by Kotlin if used 41 | if (name in listOf("buildKotlinToolingMetadata", "kotlinSourcesJar")) { 42 | group = null 43 | } 44 | // added by Spring Boot plugin if used 45 | if (name in listOf("resolveMainClassName", "resolveTestMainClassName")) { 46 | group = null 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.report.code-coverage.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.jacoco-report-aggregation") 3 | id("org.gradle.java") 4 | id("org.example.gradle.base.dependency-rules") 5 | id("org.example.gradle.base.lifecycle") 6 | } 7 | 8 | // Make aggregation "classpath" use the platform for versions (gradle/versions) 9 | configurations.aggregateCodeCoverageReportResults { extendsFrom(configurations["internal"]) } 10 | 11 | // Integrate testEndToEnd results into the aggregated UNIT_TEST coverage results 12 | tasks.testCodeCoverageReport { 13 | reports.html.outputLocation = layout.buildDirectory.dir("reports/coverage") 14 | reports.xml.outputLocation = layout.buildDirectory.file("reports/coverage.xml") 15 | executionData.from( 16 | configurations.aggregateCodeCoverageReportResults 17 | .get() 18 | .incoming 19 | .artifactView { 20 | withVariantReselection() 21 | attributes { 22 | attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.VERIFICATION)) 23 | attribute(TestSuiteName.TEST_SUITE_NAME_ATTRIBUTE, objects.named("testEndToEnd")) 24 | attribute( 25 | VerificationType.VERIFICATION_TYPE_ATTRIBUTE, 26 | objects.named(VerificationType.JACOCO_RESULTS), 27 | ) 28 | attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.BINARY_DATA_TYPE) 29 | } 30 | } 31 | .files 32 | ) 33 | } 34 | 35 | // Generate report when running 'check' 36 | tasks.check { dependsOn(tasks.testCodeCoverageReport) } 37 | -------------------------------------------------------------------------------- /engine/javarca-engine/src/test/java/org/example/javarca/engine/test/GameStateTest.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.engine.test; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertFalse; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.util.List; 7 | import org.example.javarca.engine.GameState; 8 | import org.example.javarca.engine.Spot; 9 | import org.junit.jupiter.api.Test; 10 | 11 | public class GameStateTest { 12 | 13 | @Test 14 | public void testGameStateInitialization() { 15 | GameState gameState = new GameState(); 16 | 17 | assertNotNull(gameState); 18 | List spots = gameState.getSpots(); 19 | assertNotNull(spots); 20 | assertFalse(spots.isEmpty(), "Spots list should not be empty"); 21 | assertNotNull(gameState.getAll(), "Actor states should not be null"); 22 | assertNotNull(gameState.getImages(), "Images map should not be null"); 23 | } 24 | 25 | @Test 26 | public void testDirectionFlags() { 27 | GameState gameState = new GameState(); 28 | 29 | assertFalse(gameState.isUp(), "Up flag should be false initially"); 30 | assertFalse(gameState.isDown(), "Down flag should be false initially"); 31 | assertFalse(gameState.isLeft(), "Left flag should be false initially"); 32 | assertFalse(gameState.isRight(), "Right flag should be false initially"); 33 | 34 | gameState.setUp(true); 35 | gameState.setDown(true); 36 | gameState.setLeft(true); 37 | gameState.setRight(true); 38 | 39 | assert (gameState.isUp()); 40 | assert (gameState.isDown()); 41 | assert (gameState.isLeft()); 42 | assert (gameState.isRight()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.feature.compile-java.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.java") 3 | id("org.example.gradle.base.lifecycle") 4 | } 5 | 6 | // Configure which JDK and Java version to build with. The version is defined in 7 | // 'gradle/jdk-version.txt' so that GitHub actions can also pick it up from there. 8 | java { 9 | toolchain.languageVersion = 10 | JavaLanguageVersion.of( 11 | providers 12 | .fileContents(isolated.rootProject.projectDirectory.file("gradle/jdk-version.txt")) 13 | .asText 14 | .get() 15 | .trim() 16 | ) 17 | } 18 | 19 | // Configuration to make the build reproducible. This means we override settings that are, by 20 | // default, platform dependent (e.g. different default encoding on Windows and Unix systems). 21 | tasks.withType().configureEach { 22 | options.apply { 23 | isFork = true 24 | encoding = "UTF-8" 25 | compilerArgs.add("-implicit:none") 26 | compilerArgs.add("-Werror") 27 | compilerArgs.add("-Xlint:all,-serial") 28 | } 29 | } 30 | 31 | tasks.withType().configureEach { 32 | isPreserveFileTimestamps = false 33 | isReproducibleFileOrder = true 34 | filePermissions { unix("0664") } 35 | dirPermissions { unix("0775") } 36 | } 37 | 38 | // Tweak 'lifecycle tasks': These are the tasks in the 'build' group that are used in daily 39 | // development. Under normal circumstances, these should be all the tasks developers needs 40 | // in their daily work. 41 | tasks.named("qualityCheck") { dependsOn(tasks.withType()) } 42 | 43 | tasks.named("qualityGate") { dependsOn(tasks.withType()) } 44 | -------------------------------------------------------------------------------- /engine/javarca-model/src/main/java/org/example/javarca/model/ActorState.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model; 2 | 3 | /** 4 | * Represents the state of an {@link Actor} on the {@link Stage} of a running game. 5 | */ 6 | @SuppressWarnings({"UnusedReturnValue", "unused"}) 7 | public interface ActorState { 8 | 9 | /** 10 | * Destroy this actor. 11 | */ 12 | void destroy(); 13 | 14 | /** 15 | * Is the actor still alive? Actors that have been destroyed are no longer alive 16 | * and considered during collision computation. 17 | */ 18 | boolean isAlive(); 19 | 20 | /** 21 | * Horizontal position of the actor on the stage. 22 | */ 23 | int getX(); 24 | 25 | /** 26 | * Set the horizontal position of the actor on the stage. 27 | */ 28 | int setX(int x); 29 | 30 | /** 31 | * Vertical position of the actor on the stage. 32 | */ 33 | int getY(); 34 | 35 | /** 36 | * Set the vertical position of the actor on the stage. 37 | */ 38 | int setY(int y); 39 | 40 | /** 41 | * Current value of the given {@link ActorProperty}. 42 | */ 43 | int getValue(ActorProperty p); 44 | 45 | /** 46 | * Reset the given {@link ActorProperty} to its original value. 47 | */ 48 | int resetValue(ActorProperty p); 49 | 50 | /** 51 | * Set the given {@link ActorProperty} to the given value. 52 | */ 53 | int setValue(ActorProperty p, int value); 54 | 55 | /** 56 | * Increase the given {@link ActorProperty} by the given increment. 57 | * A negative increment may be used to decrease the value. 58 | */ 59 | int addToValue(ActorProperty p, int increment); 60 | 61 | /** 62 | * Multiply the given {@link ActorProperty} by the given multiplier. 63 | */ 64 | int multiplyValue(ActorProperty p, int multiplier); 65 | 66 | /** 67 | * Change the 'skin' character of the actor. While the actor is always referred 68 | * to by its given symbol in game logic (which cannot be changed) the skin gives 69 | * the actor another appearance by the {@link Asset} linked to the skin character. 70 | */ 71 | char setSkin(char skin); 72 | } 73 | -------------------------------------------------------------------------------- /engine/javarca-model/src/main/java/org/example/javarca/model/ActorStates.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.model; 2 | 3 | import java.util.function.Predicate; 4 | 5 | /** 6 | * Represents the state of a set of {@link Actor}s on the {@link Stage} of a running game. 7 | * This allows for matching and modifying a set of {@link ActorState}s in a {@link ActorCollision} function. 8 | */ 9 | @SuppressWarnings({"UnusedReturnValue", "unused"}) 10 | public interface ActorStates { 11 | 12 | /** 13 | * A subset of characters in this set that are represented by the given 'symbol'. 14 | */ 15 | ActorStates filter(char symbol); 16 | 17 | /** 18 | * A subset of characters in this set that have the given property matches the give predicate. 19 | */ 20 | ActorStates filter(ActorProperty p, Predicate value); 21 | 22 | /** 23 | * Set the horizontal position of all actors in this set. 24 | */ 25 | int setX(int x); 26 | 27 | /** 28 | * Set the vertical position of all actors in this set. 29 | */ 30 | int setY(int y); 31 | 32 | /** 33 | * Vertical position of the actor that is the closest to the top. 34 | */ 35 | int getMinY(); 36 | 37 | /** 38 | * Vertical position of the actor that is the closest to the bottom. 39 | */ 40 | int getMaxY(); 41 | 42 | /** 43 | * Horizontal position of the actor that is the farthest left. 44 | */ 45 | int getMinX(); 46 | 47 | /** 48 | * Horizontal position of the actor that is the farthest right. 49 | */ 50 | int getMaxX(); 51 | 52 | /** 53 | * Skin this set of actors with character assets that show a text/number on the stage. 54 | */ 55 | void print(String value); 56 | 57 | /** 58 | * Skin this set of actors with character assets that show a text/number on the stage. 59 | */ 60 | void print(int value); 61 | 62 | /** 63 | * Change the skin of all actors in the set. 64 | */ 65 | void setSkin(char c); 66 | 67 | /** 68 | * Destroy all actors in this set. 69 | */ 70 | void destroy(); 71 | 72 | /** 73 | * Spawn a new actor on the stage. 74 | */ 75 | ActorState spawn(char symbole, int x, int y); 76 | 77 | /** 78 | * Spawn a new actor on the stage. 79 | */ 80 | ActorState spawn(char symbole, int x, int y, char skin); 81 | } 82 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org.example.gradle.check.dependency-versions.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.example.gradle.tasks.DependencyVersionUpgradesCheck 2 | import org.example.gradle.tasks.JavaVersionConsistencyCheck 3 | 4 | plugins { 5 | id("org.example.gradle.base.lifecycle") 6 | id("java-platform") 7 | } 8 | 9 | tasks.register("checkVersionConsistency") { 10 | group = JavaBasePlugin.VERIFICATION_GROUP 11 | definedVersions = provider { 12 | configurations["api"].dependencyConstraints.associate { "${it.group}:${it.name}" to it.version!! } 13 | } 14 | aggregatedClasspath = provider { configurations["mainRuntimeClasspath"].incoming.resolutionResult.allComponents } 15 | reportFile = layout.buildDirectory.file("reports/version-consistency.txt") 16 | } 17 | 18 | tasks.named("qualityCheck") { dependsOn(tasks.named("checkVersionConsistency")) } 19 | 20 | tasks.named("qualityGate") { dependsOn(tasks.named("checkVersionConsistency")) } 21 | 22 | val latestReleases = 23 | configurations.dependencyScope("dependencyVersionUpgrades") { 24 | withDependencies { 25 | add(project.dependencies.platform(project(project.path))) 26 | configurations.api.get().dependencies.forEach { 27 | add(project.dependencies.platform("${it.group}:${it.name}:latest.release") { isTransitive = false }) 28 | } 29 | configurations.api.get().dependencyConstraints.forEach { 30 | add(project.dependencies.create("${it.group}:${it.name}:latest.release") { isTransitive = false }) 31 | } 32 | } 33 | } 34 | val latestReleasesPath = 35 | configurations.resolvable("latestReleasesPath") { 36 | attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) 37 | attributes.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) 38 | extendsFrom(latestReleases.get()) 39 | } 40 | 41 | tasks.register("checkForDependencyVersionUpgrades") { 42 | group = HelpTasksPlugin.HELP_GROUP 43 | projectName.set(project.name) 44 | dependencies.set(configurations.api.get().dependencies.map { "${it.group}:${it.name}:${it.version}" }) 45 | dependencyConstraints.set( 46 | configurations.api.get().dependencyConstraints.map { "${it.group}:${it.name}:${it.version}" } 47 | ) 48 | latestReleasesResolutionResult.set(latestReleasesPath.map { it.incoming.resolutionResult.allComponents }) 49 | } 50 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org/example/gradle/tasks/JavaVersionConsistencyCheck.kt: -------------------------------------------------------------------------------- 1 | package org.example.gradle.tasks 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.artifacts.component.ModuleComponentIdentifier 5 | import org.gradle.api.artifacts.result.ResolvedComponentResult 6 | import org.gradle.api.file.RegularFileProperty 7 | import org.gradle.api.provider.ListProperty 8 | import org.gradle.api.provider.MapProperty 9 | import org.gradle.api.provider.SetProperty 10 | import org.gradle.api.tasks.Input 11 | import org.gradle.api.tasks.OutputFile 12 | import org.gradle.api.tasks.TaskAction 13 | 14 | /** Check that all versions declared in a java-platform build.gradle.kts file are actually used. */ 15 | abstract class JavaVersionConsistencyCheck : DefaultTask() { 16 | 17 | /** The versions declared in the build.gradle.kts file. */ 18 | @get:Input abstract val definedVersions: MapProperty 19 | 20 | /** The aggregated classpath of all modules using the versions to resolve their dependencies. */ 21 | @get:Input abstract val aggregatedClasspath: SetProperty 22 | 23 | /** 24 | * List of versions to ignore. This may be needed if versions for components that are not part of the runtime module 25 | * path of the applications are managed. 26 | */ 27 | @get:Input abstract val excludes: ListProperty 28 | 29 | /** The report TXT file that will contain the issues found. */ 30 | @get:OutputFile abstract val reportFile: RegularFileProperty 31 | 32 | @TaskAction 33 | fun compare() { 34 | var issues = "" 35 | definedVersions.get().forEach { (id, version) -> 36 | val resolved = 37 | aggregatedClasspath.get().find { 38 | val resolvedId = it.id 39 | resolvedId is ModuleComponentIdentifier && resolvedId.moduleIdentifier.toString() == id 40 | } 41 | if (resolved == null) { 42 | if (!excludes.get().contains(id)) { 43 | issues += "Not used: $id:$version\n" 44 | } 45 | } else { 46 | val resolvedVersion = resolved.moduleVersion?.version 47 | if (resolvedVersion != version) { 48 | issues += "Wrong version: $id (declared=$version; used=$resolvedVersion)\n" 49 | } 50 | } 51 | } 52 | 53 | reportFile.get().asFile.writeText(issues) 54 | 55 | if (!issues.isEmpty()) { 56 | throw RuntimeException(issues) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-actors/src/main/java/org/example/jamcatch/actors/JamCatchActorSet.java: -------------------------------------------------------------------------------- 1 | package org.example.jamcatch.actors; 2 | 3 | import static java.nio.charset.StandardCharsets.UTF_8; 4 | import static java.util.Objects.requireNonNull; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Objects; 12 | import java.util.Set; 13 | import java.util.stream.Collectors; 14 | import org.apache.commons.csv.CSVFormat; 15 | import org.apache.commons.csv.CSVParser; 16 | import org.apache.commons.csv.CSVRecord; 17 | import org.example.jamcatch.actors.collisions.Collisions; 18 | import org.example.javarca.model.Actor; 19 | import org.example.javarca.model.ActorProperty; 20 | import org.example.javarca.model.ActorPropertyModifier; 21 | import org.example.javarca.model.ActorSet; 22 | 23 | public class JamCatchActorSet implements ActorSet { 24 | 25 | private final Set actors; 26 | 27 | public JamCatchActorSet() { 28 | var itemsCsv = requireNonNull(JamCatchActorSet.class.getResourceAsStream("res/jamcatch.csv")); 29 | actors = parse(itemsCsv).stream() 30 | .map(record -> new Actor( 31 | record.get("SYMBOL").charAt(0), 32 | Arrays.stream(ActorProperty.values()) 33 | .map(property -> parsePropertyValue(record, property)) 34 | .filter(Objects::nonNull) 35 | .collect(Collectors.toSet()), 36 | Collisions.ALL.getOrDefault(record.get("SYMBOL").charAt(0), Map.of()))) 37 | .collect(Collectors.toSet()); 38 | } 39 | 40 | private static ActorPropertyModifier parsePropertyValue(CSVRecord record, ActorProperty property) { 41 | String value = record.get(property); 42 | if (!value.isBlank()) { 43 | return new ActorPropertyModifier(property, Integer.parseInt(value)); 44 | } 45 | return null; 46 | } 47 | 48 | private static List parse(InputStream itemsCsv) { 49 | try { 50 | var format = CSVFormat.DEFAULT 51 | .builder() 52 | .setHeader() 53 | .setSkipHeaderRecord(true) 54 | .get(); 55 | return CSVParser.parse(itemsCsv, UTF_8, format).getRecords(); 56 | } catch (IOException e) { 57 | throw new RuntimeException(e); 58 | } 59 | } 60 | 61 | @Override 62 | public Set items() { 63 | return actors; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /engine/javarca-engine/src/main/java/org/example/javarca/engine/GameLoop.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.engine; 2 | 3 | import static org.example.javarca.model.ActorProperty.PLAYER; 4 | import static org.example.javarca.model.ActorProperty.SPEEDX; 5 | import static org.example.javarca.model.ActorProperty.SPEEDY; 6 | import static org.example.javarca.model.GameConstants.TRUE; 7 | 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.ScheduledExecutorService; 10 | import java.util.concurrent.TimeUnit; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * The main game loop that calls the update methods on each {@link Spot} in the active game in a regular interval. 16 | */ 17 | public class GameLoop { 18 | private static final Logger LOGGER = LoggerFactory.getLogger(GameLoop.class); 19 | 20 | private ScheduledExecutorService exec; 21 | private GameState gameState; 22 | 23 | public GameLoop() {} 24 | 25 | public void start(GameState gameState) { 26 | this.gameState = gameState; 27 | exec = Executors.newSingleThreadScheduledExecutor(); 28 | exec.scheduleAtFixedRate(this::updateWithErrorLog, 0, 20, TimeUnit.MILLISECONDS); 29 | } 30 | 31 | private void updateWithErrorLog() { 32 | try { 33 | update(); 34 | } catch (Exception e) { 35 | LOGGER.error("Unexpected error in game loop", e); 36 | } 37 | } 38 | 39 | private void update() { 40 | for (Spot spot : gameState.getSpots()) { 41 | // translate keypress into speed 42 | if (spot.isAlive() && spot.getValue(PLAYER) == TRUE) { 43 | if (gameState.isUp()) { 44 | spot.resetValue(SPEEDY); 45 | spot.multiplyValue(SPEEDY, -1); 46 | } else if (gameState.isDown()) { 47 | spot.resetValue(SPEEDY); 48 | } else { 49 | spot.setValue(SPEEDY, 0); 50 | } 51 | 52 | if (gameState.isLeft()) { 53 | spot.resetValue(SPEEDX); 54 | spot.multiplyValue(SPEEDX, -1); 55 | } else if (gameState.isRight()) { 56 | spot.resetValue(SPEEDX); 57 | } else { 58 | spot.setValue(SPEEDX, 0); 59 | } 60 | } 61 | if (spot.isAlive()) { 62 | spot.move(spot.getValue(SPEEDX), spot.getValue(SPEEDY), gameState.getSpots(), gameState.getAll()); 63 | } 64 | } 65 | } 66 | 67 | public void stop() { 68 | exec.shutdown(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org/example/gradle/tasks/DependencyVersionUpgradesCheck.kt: -------------------------------------------------------------------------------- 1 | package org.example.gradle.tasks 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.artifacts.result.ResolvedComponentResult 5 | import org.gradle.api.provider.Property 6 | import org.gradle.api.provider.SetProperty 7 | import org.gradle.api.tasks.Internal 8 | import org.gradle.api.tasks.TaskAction 9 | 10 | /** Checks if new versions are available for what is declared in the platform. */ 11 | abstract class DependencyVersionUpgradesCheck : DefaultTask() { 12 | 13 | @get:Internal abstract val projectName: Property 14 | 15 | @get:Internal abstract val dependencies: SetProperty 16 | 17 | @get:Internal abstract val dependencyConstraints: SetProperty 18 | 19 | @get:Internal abstract val latestReleasesResolutionResult: SetProperty 20 | 21 | @TaskAction 22 | fun check() { 23 | val platformDependencyUpgrades = 24 | dependencies.get().filter { declared -> declared.resolvedVersion() != declared.version() } 25 | val constraintUpgrades = 26 | dependencyConstraints.get().filter { declared -> declared.resolvedVersion() != declared.version() } 27 | if (platformDependencyUpgrades.isNotEmpty() || constraintUpgrades.isNotEmpty()) { 28 | println( 29 | """ 30 | Upgrade the following dependency versions in 'gradle/versions/build.gradle.kts' (dependencies {} block): 31 | 32 | ${platformDependencyUpgrades.joinToString("\n ") { "api(platform(\"${it.ga()}:${it.resolvedVersion()}\"))" }} 33 | 34 | Upgrade the following dependency versions in 'gradle/versions/build.gradle.kts' (dependencies.constraints {} block): 35 | 36 | ${constraintUpgrades.joinToString("\n ") { "api(\"${it.ga()}:${it.resolvedVersion()}\")" }} 37 | 38 | If we cannot perform an upgrade, please add a '{ version { reject("...") } }' statement and a comment 39 | for the versions we cannot support at the moment. 40 | """ 41 | .trimIndent() 42 | ) 43 | } 44 | } 45 | 46 | private fun String.ga() = substring(0, lastIndexOf(":")) 47 | 48 | private fun String.version() = substring(lastIndexOf(":") + 1) 49 | 50 | private fun String.resolvedVersion() = 51 | latestReleasesResolutionResult 52 | .get() 53 | .find { it.moduleVersion!!.module.toString() == ga() } 54 | ?.moduleVersion 55 | ?.version ?: version() // if no fitting version could be determined, return the declared one 56 | } 57 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org/example/gradle/tasks/PluginApplicationOrderAnalysis.kt: -------------------------------------------------------------------------------- 1 | package org.example.gradle.tasks 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.file.ConfigurableFileCollection 5 | import org.gradle.api.file.RegularFileProperty 6 | import org.gradle.api.tasks.InputFiles 7 | import org.gradle.api.tasks.OutputFile 8 | import org.gradle.api.tasks.TaskAction 9 | 10 | abstract class PluginApplicationOrderAnalysis : DefaultTask() { 11 | 12 | @get:InputFiles abstract val pluginSrcFolders: ConfigurableFileCollection 13 | 14 | @get:OutputFile abstract val pluginApplicationDiagram: RegularFileProperty 15 | 16 | @TaskAction 17 | fun analyse() { 18 | val pluginDependencies = 19 | pluginSrcFolders 20 | .associate { srcFolder -> 21 | val pluginProjectName = srcFolder.parentFile.parentFile.parentFile.name 22 | pluginProjectName to 23 | (srcFolder.listFiles() ?: emptyArray()) 24 | .filter { it.name.endsWith(".gradle.kts") } 25 | .associate { pluginFile -> 26 | val pluginId = pluginFile.name.replaceFirst(".gradle.kts", "") 27 | pluginId to 28 | pluginsBlock(pluginFile.readText()) 29 | .lines() 30 | .filter { it.contains("id(") } 31 | .map { it.substring(it.indexOf("id(\"") + 4, it.indexOf("\")")) } 32 | } 33 | } 34 | .filter { it.value.isNotEmpty() } 35 | 36 | val lineBreak = "\n " 37 | pluginApplicationDiagram 38 | .get() 39 | .asFile 40 | .writeText( 41 | """ 42 | @startuml 43 | 44 | ${pluginDependencies.map { (projectName, pluginIds) -> 45 | "package \"$projectName\" {$lineBreak" + 46 | pluginIds.keys.joinToString("") { "agent \"$it\"$lineBreak" } + 47 | "$lineBreak}" 48 | }.joinToString(lineBreak)} 49 | 50 | ${pluginDependencies.values.fold(emptyMap>()) { a, b -> a + b }.filter { 51 | it.value.isNotEmpty() 52 | }.map { 53 | (from, to) -> to.joinToString("") { "\"$from\" --down--> \"$it\"$lineBreak" } 54 | }.joinToString(lineBreak)} 55 | 56 | @enduml 57 | """ 58 | .trimIndent() 59 | ) 60 | 61 | logger.lifecycle("Diagram: ${pluginApplicationDiagram.get().asFile.absolutePath}") 62 | } 63 | 64 | private fun pluginsBlock(script: String) = 65 | script.indexOf("}").let { 66 | when (it) { 67 | -1 -> "" 68 | else -> script.substring(0, it) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org/example/gradle/tasks/MD5DirectoryChecksum.kt: -------------------------------------------------------------------------------- 1 | package org.example.gradle.tasks 2 | 3 | import java.io.File 4 | import java.nio.file.Files 5 | import java.security.DigestInputStream 6 | import java.security.MessageDigest 7 | import org.gradle.api.DefaultTask 8 | import org.gradle.api.file.DirectoryProperty 9 | import org.gradle.api.file.RegularFileProperty 10 | import org.gradle.api.tasks.InputDirectory 11 | import org.gradle.api.tasks.OutputFile 12 | import org.gradle.api.tasks.TaskAction 13 | 14 | /** 15 | * Gradle task based on 'Checksum' Ant Task but stripped down to what we need in this build. 16 | * 17 | * See: https://github.com/apache/ant/blob/master/src/main/org/apache/tools/ant/taskdefs/Checksum.java 18 | */ 19 | abstract class MD5DirectoryChecksum : DefaultTask() { 20 | 21 | @get:InputDirectory abstract val inputDirectory: DirectoryProperty 22 | 23 | @get:OutputFile abstract val checksumFile: RegularFileProperty 24 | 25 | @TaskAction 26 | fun generateChecksum() { 27 | val messageDigest = MessageDigest.getInstance("MD5") 28 | 29 | val allDigests = mutableMapOf() 30 | val bufSize = 8 * 1024 31 | val buf = ByteArray(bufSize) 32 | 33 | val folder = inputDirectory.get().asFile 34 | folder 35 | .walkTopDown() 36 | .filter { it.isFile } 37 | .forEach { 38 | messageDigest.reset() 39 | val fis = Files.newInputStream(it.toPath()) 40 | val dis = DigestInputStream(fis, messageDigest) 41 | while (dis.read(buf, 0, bufSize) != -1) { 42 | // Empty statement 43 | } 44 | dis.close() 45 | fis.close() 46 | val fileDigest = messageDigest.digest() 47 | allDigests[it] = fileDigest 48 | } 49 | // Calculate the total checksum 50 | // Convert the keys (source files) into a sorted array. 51 | val keyArray = allDigests.keys.sortedBy { it.relativeTo(folder).path } 52 | 53 | // Loop over the checksums and generate a total hash. 54 | messageDigest.reset() 55 | keyArray.forEach { 56 | // Add the digest for the file content 57 | val digest = allDigests.getValue(it) 58 | messageDigest.update(digest) 59 | 60 | // Add the file path 61 | val fileName = it.relativeTo(folder).path.replace(File.separatorChar, '/') 62 | messageDigest.update(fileName.toByteArray()) 63 | } 64 | checksumFile.get().asFile.writeText(createDigestString(messageDigest.digest())) 65 | } 66 | 67 | private fun createDigestString(fileDigest: ByteArray): String { 68 | val byteMask = 0xFF 69 | val checksumSb = StringBuilder() 70 | for (digestByte in fileDigest) { 71 | checksumSb.append(String.format("%02x", byteMask and digestByte.toInt())) 72 | } 73 | return checksumSb.toString() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /jamcatch/jamcatch-actors/src/main/java/org/example/jamcatch/actors/collisions/Collisions.java: -------------------------------------------------------------------------------- 1 | package org.example.jamcatch.actors.collisions; 2 | 3 | import static org.example.javarca.model.ActorProperty.POINTS; 4 | import static org.example.javarca.model.ActorProperty.SPEEDY; 5 | import static org.example.javarca.model.GameConstants.PRECISION; 6 | import static org.example.javarca.model.GameConstants.SYMBOL_EMPTY_SPOT; 7 | 8 | import java.util.Map; 9 | import java.util.Random; 10 | import org.example.javarca.model.ActorCollision; 11 | 12 | public interface Collisions { 13 | String DEMO_MODE = System.getenv("DEMO_MODE"); 14 | 15 | Map p = Map.of( 16 | 'J', 17 | (myState, otherState, allStates) -> { 18 | otherState.destroy(); 19 | myState.addToValue(POINTS, otherState.getValue(POINTS)); 20 | allStates.filter('0').print(myState.getValue(POINTS)); 21 | char skin = 'H'; 22 | skin += (char) new Random().nextInt(5); 23 | var newJar = allStates.spawn('J', new Random().nextInt(14) + 1, 0, skin); 24 | newJar.setValue(SPEEDY, otherState.getValue(SPEEDY) + 100); 25 | }, 26 | SYMBOL_EMPTY_SPOT, 27 | (myState, otherState, allStates) -> { 28 | allStates.filter(':').setSkin(SYMBOL_EMPTY_SPOT); // make text row invisible 29 | var target = allStates.filter('J').filter(SPEEDY, v -> v > 0).getMaxX(); 30 | if (DEMO_MODE != null) { 31 | if (myState.getX() > target) { 32 | myState.setX(myState.getX() - 1500); 33 | } else if (myState.getX() < target) { 34 | myState.setX(myState.getX() + 1500); 35 | } 36 | } 37 | }); 38 | Map j = Map.of( 39 | 'J', 40 | (myState, otherState, allStates) -> { 41 | myState.setValue(SPEEDY, 0); 42 | var player = allStates.filter('p'); 43 | player.setY(allStates 44 | .filter('J') 45 | .filter(SPEEDY, v -> v == 0) 46 | .getMinY() 47 | - PRECISION); 48 | if (player.getMinY() <= PRECISION * 2) { 49 | player.destroy(); 50 | allStates.filter(':').print("GAME.OVER"); 51 | } else { 52 | allStates.spawn('J', new Random().nextInt(14) + 1, 0); 53 | } 54 | }, 55 | 'X', 56 | (myState, otherState, allStates) -> { 57 | myState.setValue(SPEEDY, 0); 58 | allStates.spawn('J', new Random().nextInt(14) + 1, 0); 59 | allStates 60 | .filter('p') 61 | .setY(allStates 62 | .filter('J') 63 | .filter(SPEEDY, v -> v == 0) 64 | .getMinY() 65 | - PRECISION); 66 | }); 67 | 68 | Map> ALL = Map.of('p', p, 'J', j); 69 | } 70 | -------------------------------------------------------------------------------- /gradle/plugins/src/test/kotlin/org/example/gradle/test/fixtures/GradleProject.kt: -------------------------------------------------------------------------------- 1 | package org.example.gradle.test.fixtures 2 | 3 | import org.gradle.testkit.runner.BuildResult 4 | import org.gradle.testkit.runner.GradleRunner 5 | import java.io.File 6 | import java.lang.management.ManagementFactory 7 | import java.nio.file.Files 8 | 9 | /** 10 | * Access to a minimal project inside a temporary folder. 11 | * The project contain files that are expected to exist in our setup. 12 | */ 13 | class GradleProject { 14 | 15 | private val projectDir = Files.createTempDirectory("gradle-build").toFile() 16 | private val gradlePropertiesFile = file("gradle.properties") 17 | private val settingsFile = file("settings.gradle.kts") 18 | private val rootBuildFile = file("build.gradle.kts") 19 | private val catalog = file("gradle/libs.versions.toml") 20 | private val versions = file("gradle/versions/build.gradle.kts") 21 | private val aggregation = file("gradle/aggregation/build.gradle.kts") 22 | private val moduleBuildFile = file("product/module/build.gradle.kts") 23 | private val versionFile = file("gradle/version.txt") 24 | private val jdkVersionFile = file("gradle/jdk-version.txt") 25 | 26 | fun withMinimalStructure(): GradleProject { 27 | gradlePropertiesFile.writeText(""" 28 | org.gradle.configuration-cache=true 29 | # org.gradle.unsafe.isolated-projects=true 30 | org.gradle.caching=true 31 | kotlin.stdlib.default.dependency=false 32 | android.useAndroidX=true 33 | """.trimIndent()) 34 | settingsFile.writeText(""" 35 | plugins { 36 | id("org.example.gradle.feature.repositories") 37 | id("org.example.gradle.feature.project-structure") 38 | } 39 | rootProject.name = "test-project" 40 | """.trimIndent()) 41 | rootBuildFile.writeText(""" 42 | plugins { 43 | id("com.autonomousapps.dependency-analysis") 44 | } 45 | """.trimIndent()) 46 | versions.writeText("""plugins { 47 | id("org.example.gradle.base.lifecycle") 48 | id("org.example.gradle.feature.use-all-catalog-versions") 49 | }""".trimIndent()) 50 | aggregation.writeText("") 51 | catalog(""" 52 | [libraries] 53 | foo = { module = "foo:bar" } 54 | """.trimIndent()) 55 | versionFile.writeText("1.0") 56 | jdkVersionFile.writeText("21") 57 | file("app/src/main/resources").mkdirs() 58 | return this 59 | } 60 | 61 | fun settingsFile(content: String) = settingsFile.also { 62 | it.writeText(content) 63 | } 64 | 65 | 66 | fun rootBuildFile(content: String) = rootBuildFile.also { 67 | it.writeText(content) 68 | } 69 | 70 | fun moduleBuildFile(content: String) = moduleBuildFile.also { 71 | it.writeText(content) 72 | } 73 | 74 | fun catalog(content: String) = catalog.also { 75 | it.writeText(content) 76 | } 77 | 78 | fun file(path: String, content: String? = null) = File(projectDir, path).also { 79 | it.parentFile.mkdirs() 80 | if (content != null) { 81 | it.writeText(content) 82 | } 83 | } 84 | 85 | fun help(): BuildResult = runner(listOf("help")).build() 86 | 87 | fun build(): BuildResult = runner(listOf("build")).build() 88 | 89 | fun qualityGate(): BuildResult = runner(listOf("qualityGate")).build() 90 | 91 | fun failQualityGate(): BuildResult = runner(listOf("qualityGate")).buildAndFail() 92 | 93 | private fun runner(args: List) = GradleRunner.create() 94 | .forwardOutput() 95 | .withPluginClasspath() 96 | .withProjectDir(projectDir) 97 | .withArguments(args + listOf("-s", "--warning-mode=all")) 98 | .withDebug(ManagementFactory.getRuntimeMXBean().inputArguments.toString().contains("-agentlib:jdwp")) 99 | 100 | } -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/org/example/gradle/spotless/SortDependenciesStep.kt: -------------------------------------------------------------------------------- 1 | package org.example.gradle.spotless 2 | 3 | import com.diffplug.spotless.FormatterFunc 4 | import com.diffplug.spotless.FormatterStep 5 | import java.util.Locale 6 | 7 | class SortDependenciesStep : java.io.Serializable { 8 | companion object { 9 | fun create(): FormatterStep { 10 | return FormatterStep.create( 11 | "SortDependenciesStep", 12 | SortDependenciesStep(), 13 | SortDependenciesStep::toFormatter, 14 | ) 15 | } 16 | } 17 | 18 | fun toFormatter(): FormatterFunc { 19 | return FormatterFunc { unixStr -> 20 | val lines = unixStr.split('\n') 21 | val blockStartIndex = 22 | lines.indexOfFirst { it.startsWith("dependencies {") || it.startsWith("dependencies.constraints {") } 23 | if (blockStartIndex == -1 || lines[blockStartIndex].contains("}")) { 24 | unixStr // no 'dependencies {} block' or only one line 25 | } else { 26 | val blockEndIndex = blockStartIndex + lines.subList(blockStartIndex, lines.size).indexOf("}") 27 | val declarations = 28 | lines.subList(blockStartIndex + 1, blockEndIndex).filter { it.contains("(") }.map { parse(it) } 29 | val comparator = 30 | Comparator { a, b -> 31 | when { 32 | a.sourceSet.compareTo(b.sourceSet) != 0 -> a.sourceSet.compareTo(b.sourceSet) 33 | a.scope.ordinal != b.scope.ordinal -> a.scope.ordinal.compareTo(b.scope.ordinal) 34 | a.isProject && !b.isProject -> -1 35 | !a.isProject && b.isProject -> 1 36 | else -> a.line.compareTo(b.line) 37 | } 38 | } 39 | 40 | val sourceSetStartLines = 41 | declarations 42 | .filter { it.sourceSet.isNotEmpty() } 43 | .map { DependencyDeclaration(it.sourceSet, Scope.Api, false, "") } 44 | .distinct() 45 | val sorted = (declarations + sourceSetStartLines).sortedWith(comparator).map { it.line } 46 | val blockStart = lines.subList(0, blockStartIndex + 1) 47 | val blockEnd = lines.subList(blockEndIndex, lines.size) 48 | 49 | (blockStart + sorted + blockEnd).joinToString("\n") 50 | } 51 | } 52 | } 53 | 54 | private fun parse(line: String): DependencyDeclaration { 55 | val fullScope = line.trim().substring(0, line.trim().indexOf("(")) 56 | var scope = Scope.Api 57 | var sourceSet = "" 58 | val isProject = line.contains("(projects.") 59 | val isThirdParty = line.contains("(libs.") 60 | 61 | if (!isProject && !isThirdParty) { 62 | println("WARN: Discouraged dependency notation: ${line.trim()}") 63 | } 64 | 65 | Scope.values().forEach { scopeCandidate -> 66 | if (fullScope == scopeCandidate.name.replaceFirstChar { it.lowercase(Locale.getDefault()) }) { 67 | scope = scopeCandidate 68 | sourceSet = "" 69 | } 70 | if (fullScope.endsWith(scopeCandidate.name)) { 71 | scope = scopeCandidate 72 | sourceSet = fullScope.substring(0, fullScope.length - scopeCandidate.name.length) 73 | } 74 | } 75 | return DependencyDeclaration(sourceSet, scope, isProject, line) 76 | } 77 | 78 | private enum class Scope { 79 | Api, 80 | Implementation, 81 | CompileOnlyApi, 82 | CompileOnly, 83 | RuntimeOnly, 84 | ProvidedCompile, 85 | ProvidedRuntime, 86 | AnnotationProcessor, 87 | } 88 | 89 | private data class DependencyDeclaration( 90 | val sourceSet: String, 91 | val scope: Scope, 92 | val isProject: Boolean, 93 | val line: String, 94 | ) 95 | } 96 | -------------------------------------------------------------------------------- /engine/javarca-engine/src/main/java/org/example/javarca/engine/impl/ActorStatesImpl.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.engine.impl; 2 | 3 | import static org.example.javarca.model.GameConstants.PRECISION; 4 | import static org.example.javarca.model.GameConstants.STAGE_SIZE; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.function.Predicate; 9 | import java.util.stream.Collectors; 10 | import org.example.javarca.engine.Spot; 11 | import org.example.javarca.model.ActorProperty; 12 | import org.example.javarca.model.ActorState; 13 | import org.example.javarca.model.ActorStates; 14 | 15 | public class ActorStatesImpl implements ActorStates { 16 | 17 | private final List root; 18 | private final List spots; 19 | private final Map prototypes; 20 | 21 | public ActorStatesImpl(List spots, List root, Map prototypes) { 22 | this.spots = spots; 23 | this.root = root; 24 | this.prototypes = prototypes; 25 | } 26 | 27 | @Override 28 | public ActorStates filter(char symbol) { 29 | return new ActorStatesImpl( 30 | spots.stream() 31 | .filter(s -> s.isAlive() && s.getSymbol() == symbol) 32 | .collect(Collectors.toList()), 33 | root, 34 | prototypes); 35 | } 36 | 37 | @Override 38 | public ActorStates filter(ActorProperty p, Predicate predicate) { 39 | return new ActorStatesImpl( 40 | spots.stream() 41 | .filter(s -> s.isAlive() && predicate.test(s.getValue(p))) 42 | .collect(Collectors.toList()), 43 | root, 44 | prototypes); 45 | } 46 | 47 | @Override 48 | public void print(String value) { 49 | for (int i = 0; i < spots.size() && i < value.length(); i++) { 50 | spots.get(i).setSkin(value.charAt(i)); 51 | } 52 | } 53 | 54 | @Override 55 | public void print(int value) { 56 | String s = String.format("%1$" + spots.size() + "s", value).replace(' ', '0'); 57 | for (int i = 0; i < spots.size() && i < s.length(); i++) { 58 | spots.get(i).setSkin(s.charAt(i)); 59 | } 60 | } 61 | 62 | @Override 63 | public void setSkin(char c) { 64 | spots.forEach(s -> s.setSkin(c)); 65 | } 66 | 67 | @Override 68 | public void destroy() { 69 | spots.forEach(ActorState::destroy); 70 | } 71 | 72 | @Override 73 | public int setX(int x) { 74 | spots.forEach(s -> s.setX(x)); 75 | return x; 76 | } 77 | 78 | @Override 79 | public int setY(int y) { 80 | spots.forEach(s -> s.setY(y)); 81 | return y; 82 | } 83 | 84 | @Override 85 | public int getMinY() { 86 | return spots.stream().map(Spot::getY).min(Integer::compareTo).orElse(0); 87 | } 88 | 89 | @Override 90 | public int getMaxY() { 91 | return spots.stream().map(Spot::getY).max(Integer::compareTo).orElse((STAGE_SIZE - 1) * PRECISION); 92 | } 93 | 94 | @Override 95 | public int getMinX() { 96 | return spots.stream().map(Spot::getX).min(Integer::compareTo).orElse(0); 97 | } 98 | 99 | @Override 100 | public int getMaxX() { 101 | return spots.stream().map(Spot::getX).max(Integer::compareTo).orElse((STAGE_SIZE - 1) * PRECISION); 102 | } 103 | 104 | @Override 105 | public ActorState spawn(char symbol, int x, int y) { 106 | return spawn(symbol, x, y, symbol); 107 | } 108 | 109 | @Override 110 | public ActorState spawn(char symbol, int x, int y, char skin) { 111 | var prototype = prototypes.get(symbol); 112 | Spot newSpot; 113 | if (prototype == null) { 114 | newSpot = new Spot(symbol, x, y); 115 | } else { 116 | newSpot = prototype.clone(symbol, x, y); 117 | } 118 | newSpot.setSkin(skin); 119 | root.add(newSpot); 120 | return newSpot; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /engine/javarca-engine/src/main/java/org/example/javarca/engine/GameState.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.engine; 2 | 3 | import static java.util.function.Function.identity; 4 | import static org.example.javarca.model.GameConstants.STAGE_SIZE; 5 | import static org.example.javarca.model.GameConstants.SYMBOL_EMPTY_SPOT; 6 | 7 | import java.util.HashMap; 8 | import java.util.LinkedHashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.ServiceLoader; 12 | import java.util.concurrent.CopyOnWriteArrayList; 13 | import java.util.stream.Collectors; 14 | import org.example.javarca.engine.impl.ActorStatesImpl; 15 | import org.example.javarca.model.Actor; 16 | import org.example.javarca.model.ActorPropertyModifier; 17 | import org.example.javarca.model.ActorSet; 18 | import org.example.javarca.model.ActorStates; 19 | import org.example.javarca.model.Asset; 20 | import org.example.javarca.model.AssetSet; 21 | import org.example.javarca.model.Stage; 22 | 23 | /** 24 | * Represents the state of the currently running game. The state is primarily composed of a number of {@link Spot}s, 25 | * initialized through a {@link Stage}, that may change their state over time. 26 | */ 27 | public class GameState { 28 | Stage EMPTY_DEFAULT = () -> ("x".repeat(STAGE_SIZE)) 29 | + ("x" + ".".repeat(STAGE_SIZE - 2) + "x\n").repeat(STAGE_SIZE - 2) 30 | + ("x".repeat(STAGE_SIZE)); 31 | 32 | private boolean up; 33 | private boolean down; 34 | private boolean left; 35 | private boolean right; 36 | 37 | private final Map actors = new LinkedHashMap<>(); 38 | 39 | private final List spots = new CopyOnWriteArrayList<>(); 40 | private final Map images = new HashMap<>(); 41 | private ActorStates all; 42 | 43 | public GameState() { 44 | init(); 45 | } 46 | 47 | private void init() { 48 | ServiceLoader.load(ActorSet.class) 49 | .forEach(set -> 50 | actors.putAll(set.items().stream().collect(Collectors.toMap(Actor::symbol, identity())))); 51 | ServiceLoader.load(AssetSet.class) 52 | .forEach(set -> 53 | images.putAll(set.assets().stream().collect(Collectors.toMap(Asset::symbol, Asset::image)))); 54 | var stage = ServiceLoader.load(Stage.class).findFirst().orElse(EMPTY_DEFAULT); 55 | List renderedStage = Spot.render(stage).toList(); 56 | Map prototypes = new LinkedHashMap<>(); 57 | renderedStage.forEach(spot -> { 58 | if (actors.containsKey(spot.getSymbol())) { 59 | spots.addFirst(spot.clone(SYMBOL_EMPTY_SPOT)); 60 | var item = actors.get(spot.getSymbol()); 61 | spot.init( 62 | item.modifiers().stream() 63 | .collect(Collectors.toMap(ActorPropertyModifier::p, ActorPropertyModifier::value)), 64 | item.collisionFunctions()); 65 | spots.add(spot); 66 | prototypes.put(spot.getSymbol(), spot); 67 | } else { 68 | spots.addFirst(spot); 69 | } 70 | }); 71 | all = new ActorStatesImpl(spots, spots, prototypes); 72 | } 73 | 74 | public List getSpots() { 75 | return spots; 76 | } 77 | 78 | public ActorStates getAll() { 79 | return all; 80 | } 81 | 82 | public Map getImages() { 83 | return images; 84 | } 85 | 86 | public boolean isUp() { 87 | return up; 88 | } 89 | 90 | public void setUp(boolean up) { 91 | this.up = up; 92 | } 93 | 94 | public boolean isDown() { 95 | return down; 96 | } 97 | 98 | public void setDown(boolean down) { 99 | this.down = down; 100 | } 101 | 102 | public boolean isLeft() { 103 | return left; 104 | } 105 | 106 | public void setLeft(boolean left) { 107 | this.left = left; 108 | } 109 | 110 | public boolean isRight() { 111 | return right; 112 | } 113 | 114 | public void setRight(boolean right) { 115 | this.right = right; 116 | } 117 | 118 | public void action() { 119 | System.out.println("Boom!"); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /gradle/plugins/src/test/kotlin/org/example/gradle/test/ConventionPluginTest.kt: -------------------------------------------------------------------------------- 1 | package org.example.gradle.test 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.example.gradle.test.fixtures.GradleProject 5 | import org.gradle.testkit.runner.TaskOutcome.SUCCESS 6 | import org.junit.jupiter.api.Test 7 | import org.junit.jupiter.params.ParameterizedTest 8 | import org.junit.jupiter.params.provider.MethodSource 9 | import java.io.File 10 | 11 | class ConventionPluginTest { 12 | 13 | @ParameterizedTest 14 | @MethodSource("pluginIds") 15 | fun `each plugin can be applied individually without error`(pluginId: String) { 16 | val p = GradleProject() 17 | when { 18 | pluginId.endsWith(".settings") -> p.settingsFile("""plugins { id("${pluginId.substringBeforeLast(".settings")}") }""") 19 | pluginId.endsWith(".root") -> p.rootBuildFile("""plugins { id("${pluginId.substringBeforeLast(".settings")}") }""") 20 | else -> p.withMinimalStructure().moduleBuildFile("""plugins { id("$pluginId") }""") 21 | } 22 | 23 | val result = p.help() 24 | 25 | assertThat(result.task(":help")!!.outcome).isEqualTo(SUCCESS) 26 | 27 | } 28 | 29 | @Test 30 | fun `qualityGate sorts dependencies of a library`() { 31 | val p = GradleProject().withMinimalStructure() 32 | p.catalog(""" 33 | [libraries] 34 | resteasy-core = { module = "org.jboss.resteasy:resteasy-core", version = "4.7.6.Final" } 35 | resteasy-jackson2-provider = { module = "org.jboss.resteasy:resteasy-jackson2-provider", version = "4.7.6.Final" } 36 | guice = { module = "com.google.inject:guice", version = "5.1.0" } 37 | """.trimIndent()) 38 | val buildFile = p.moduleBuildFile(""" 39 | plugins { 40 | id("org.example.gradle.component.library") 41 | } 42 | 43 | dependencies { 44 | implementation(libs.resteasy.jackson2.provider) 45 | api(libs.resteasy.core) 46 | implementation(libs.guice) 47 | } 48 | """.trimIndent()) 49 | p.file("product/module/src/main/java/foo/Bar.java", """ 50 | package foo; 51 | public class Bar { 52 | private org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider a; 53 | private com.google.inject.Guice b; 54 | public org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap c; 55 | } 56 | """.trimIndent()) 57 | 58 | p.qualityGate() 59 | 60 | assertThat(buildFile).hasContent(""" 61 | plugins { id("org.example.gradle.component.library") } 62 | 63 | dependencies { 64 | api(libs.resteasy.core) 65 | implementation(libs.guice) 66 | implementation(libs.resteasy.jackson2.provider) 67 | } 68 | """.trimIndent()) 69 | } 70 | 71 | @Test 72 | fun `qualityGate fails for wrong dependency scopes`() { 73 | val p = GradleProject().withMinimalStructure() 74 | p.catalog(""" 75 | [libraries] 76 | resteasy-core = { module = "org.jboss.resteasy:resteasy-core", version = "4.7.6.Final" } 77 | resteasy-jackson2-provider = { module = "org.jboss.resteasy:resteasy-jackson2-provider", version = "4.7.6.Final" } 78 | guice = { module = "com.google.inject:guice", version = "5.1.0" } 79 | """.trimIndent()) 80 | p.moduleBuildFile(""" 81 | plugins { 82 | id("org.example.gradle.component.library") 83 | } 84 | 85 | dependencies { 86 | implementation(libs.resteasy.jackson2.provider) 87 | api(libs.resteasy.core) 88 | implementation(libs.guice) 89 | } 90 | """.trimIndent()) 91 | p.file("product/module/src/main/java/foo/Bar.java", """ 92 | package foo; 93 | public class Bar { 94 | private org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap c; 95 | } 96 | """.trimIndent()) 97 | 98 | val result = p.failQualityGate() 99 | 100 | assertThat(result.output).contains(""" 101 | Unused dependencies which should be removed: 102 | implementation(libs.guice) 103 | 104 | Existing dependencies which should be modified to be as indicated: 105 | implementation(libs.resteasy.core) (was api) 106 | 107 | Dependencies which should be removed or changed to runtime-only: 108 | runtimeOnly(libs.resteasy.jackson2.provider) (was implementation) 109 | """.trimIndent()) 110 | } 111 | 112 | companion object { 113 | @JvmStatic 114 | fun pluginIds(): Array { 115 | val pluginList = File("src/main/kotlin").listFiles()!!.filter { it.isFile }.map { 116 | it.name.substringBeforeLast(".gradle.kts") 117 | } 118 | return pluginList.toTypedArray() 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /renderer/renderer-lwjgl/src/main/java/org/example/renderer/lwjgl/textures/TextureManagement.java: -------------------------------------------------------------------------------- 1 | package org.example.renderer.lwjgl.textures; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | import java.nio.IntBuffer; 9 | import org.lwjgl.BufferUtils; 10 | import org.lwjgl.opengl.GL11; 11 | import org.lwjgl.opengl.GL13; 12 | import org.lwjgl.stb.STBImage; 13 | import org.lwjgl.stb.STBImageWrite; 14 | import org.lwjgl.stb.STBTTFontinfo; 15 | import org.lwjgl.stb.STBTruetype; 16 | import org.lwjgl.system.MemoryStack; 17 | import org.lwjgl.system.MemoryUtil; 18 | 19 | /** 20 | * Internal utility class to manage texture loading and rendering. 21 | */ 22 | public class TextureManagement { 23 | public static int loadTexture(byte[] imageData) { 24 | int textureId; 25 | 26 | try (MemoryStack stack = MemoryStack.stackPush()) { 27 | IntBuffer width = stack.mallocInt(1); 28 | IntBuffer height = stack.mallocInt(1); 29 | IntBuffer channels = stack.mallocInt(1); 30 | 31 | ByteBuffer imageBuffer = ByteBuffer.allocateDirect(imageData.length).put(imageData); 32 | imageBuffer.flip(); 33 | 34 | ByteBuffer decodedImage = STBImage.stbi_load_from_memory(imageBuffer, width, height, channels, 4); 35 | if (decodedImage == null) { 36 | throw new RuntimeException("Failed to load image: " + STBImage.stbi_failure_reason()); 37 | } 38 | textureId = loadTextureFromBuffer(decodedImage, width.get(0), width.get(0), true); 39 | } 40 | 41 | return textureId; 42 | } 43 | 44 | public static int renderCharacter(char character) { 45 | byte[] font; 46 | try (var stream = TextureManagement.class.getResourceAsStream("font.ttf")) { 47 | font = requireNonNull(stream).readAllBytes(); 48 | } catch (IOException e) { 49 | throw new RuntimeException(e); 50 | } 51 | 52 | try (MemoryStack stack = MemoryStack.stackPush()) { 53 | ByteBuffer fontBuffer = MemoryUtil.memAlloc(font.length); 54 | fontBuffer.put(font).flip(); 55 | 56 | STBTTFontinfo fontInfo = STBTTFontinfo.create(); 57 | if (!STBTruetype.stbtt_InitFont(fontInfo, fontBuffer)) { 58 | MemoryUtil.memFree(fontBuffer); 59 | throw new RuntimeException("Failed to initialize font."); 60 | } 61 | 62 | int fontSize = 120; 63 | float scale = STBTruetype.stbtt_ScaleForPixelHeight(fontInfo, fontSize); 64 | 65 | IntBuffer widthBuffer = stack.mallocInt(1); 66 | IntBuffer heightBuffer = stack.mallocInt(1); 67 | IntBuffer xOffset = stack.mallocInt(1); 68 | IntBuffer yOffset = stack.mallocInt(1); 69 | 70 | ByteBuffer bitmap = STBTruetype.stbtt_GetCodepointBitmap( 71 | fontInfo, scale, scale, character, widthBuffer, heightBuffer, xOffset, yOffset); 72 | 73 | if (bitmap == null) { 74 | MemoryUtil.memFree(fontBuffer); 75 | throw new RuntimeException("Failed to generate character bitmap."); 76 | } 77 | 78 | int charWidth = widthBuffer.get(0); 79 | int charHeight = heightBuffer.get(0); 80 | 81 | int border = 10; // Border size in pixels 82 | int outWidth = charWidth + border * 2; 83 | int outHeight = charHeight + border * 2; 84 | 85 | ByteBuffer rgbaBuffer = BufferUtils.createByteBuffer(outWidth * outHeight * 4); 86 | 87 | // Fill with transparent pixels 88 | for (int i = 0; i < outWidth * outHeight; i++) { 89 | rgbaBuffer.put((byte) 0); // R 90 | rgbaBuffer.put((byte) 0); // G 91 | rgbaBuffer.put((byte) 0); // B 92 | rgbaBuffer.put((byte) 0); // A 93 | } 94 | 95 | // Center the character bitmap in the new buffer 96 | for (int y = 0; y < charHeight; y++) { 97 | for (int x = 0; x < charWidth; x++) { 98 | int srcIndex = y * charWidth + x; 99 | int dstIndex = ((y + border) * outWidth + (x + border)) * 4; 100 | byte gray = bitmap.get(srcIndex); 101 | rgbaBuffer.put(dstIndex, (byte) 0); // R 102 | rgbaBuffer.put(dstIndex + 1, (byte) 0); // G 103 | rgbaBuffer.put(dstIndex + 2, (byte) 0); // B 104 | rgbaBuffer.put(dstIndex + 3, gray); // A 105 | } 106 | } 107 | rgbaBuffer.position(0); 108 | 109 | return loadTextureFromBuffer(rgbaBuffer, outWidth, outHeight, false); 110 | } 111 | } 112 | 113 | private static int loadTextureFromBuffer(ByteBuffer imageBuffer, int width, int height, boolean freeWithStb) { 114 | int textureId = GL11.glGenTextures(); 115 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId); 116 | GL11.glTexImage2D( 117 | GL11.GL_TEXTURE_2D, 118 | 0, 119 | GL11.GL_RGBA, 120 | width, 121 | height, 122 | 0, 123 | GL11.GL_RGBA, 124 | GL11.GL_UNSIGNED_BYTE, 125 | imageBuffer); 126 | GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); 127 | GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); 128 | 129 | if (freeWithStb) { 130 | STBImage.stbi_image_free(imageBuffer); 131 | } 132 | return textureId; 133 | } 134 | 135 | public static void renderTexture(int textureId, float x, float y, float width, float height) { 136 | GL13.glActiveTexture(GL13.GL_TEXTURE0); 137 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId); 138 | 139 | GL11.glBegin(GL11.GL_QUADS); 140 | GL11.glTexCoord2f(0, 0); 141 | GL11.glVertex2f(x, y); 142 | GL11.glTexCoord2f(1, 0); 143 | GL11.glVertex2f(x + width, y); 144 | GL11.glTexCoord2f(1, 1); 145 | GL11.glVertex2f(x + width, y + height); 146 | GL11.glTexCoord2f(0, 1); 147 | GL11.glVertex2f(x, y + height); 148 | GL11.glEnd(); 149 | } 150 | 151 | public static void saveScreenshot(String folderPath, int width, int height) { 152 | if (folderPath == null) { 153 | return; 154 | } 155 | 156 | ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * 4); 157 | GL11.glFinish(); 158 | GL11.glReadPixels(0, 0, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer); 159 | ByteBuffer flippedBuffer = BufferUtils.createByteBuffer(width * height * 4); 160 | for (int y = 0; y < height; y++) { 161 | for (int x = 0; x < width * 4; x++) { 162 | flippedBuffer.put((height - y - 1) * width * 4 + x, buffer.get(y * width * 4 + x)); 163 | } 164 | } 165 | 166 | File folder = new File(folderPath); 167 | //noinspection ResultOfMethodCallIgnored 168 | folder.mkdirs(); 169 | STBImageWrite.stbi_write_png( 170 | new File(folder, "screen.png").getAbsolutePath(), width, height, 4, flippedBuffer, width * 4); 171 | System.out.println(":screen-update:"); // newline to trigger the listening process 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /engine/javarca-engine/src/main/java/org/example/javarca/engine/Spot.java: -------------------------------------------------------------------------------- 1 | package org.example.javarca.engine; 2 | 3 | import static java.lang.Math.max; 4 | import static java.lang.Math.min; 5 | import static org.example.javarca.model.ActorProperty.BLOCKING; 6 | import static org.example.javarca.model.ActorProperty.DESTRUCTIBLE; 7 | import static org.example.javarca.model.GameConstants.PRECISION; 8 | import static org.example.javarca.model.GameConstants.STAGE_SIZE; 9 | import static org.example.javarca.model.GameConstants.SYMBOL_EMPTY_SPOT; 10 | 11 | import java.util.Arrays; 12 | import java.util.LinkedHashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.stream.IntStream; 16 | import java.util.stream.Stream; 17 | import org.example.javarca.model.ActorCollision; 18 | import org.example.javarca.model.ActorProperty; 19 | import org.example.javarca.model.ActorState; 20 | import org.example.javarca.model.ActorStates; 21 | import org.example.javarca.model.Stage; 22 | 23 | /** 24 | * The implementation of {@link ActorState}. 25 | */ 26 | public class Spot implements ActorState { 27 | private final Map collisionFunctions = new LinkedHashMap<>(); 28 | private final Map initialValues = new LinkedHashMap<>(); 29 | private final Map values = new LinkedHashMap<>(); 30 | 31 | private final char symbol; 32 | private char skin; 33 | private boolean alive; 34 | private int x; 35 | private int y; 36 | 37 | public static Stream render(Stage stage) { 38 | List symbols = Arrays.stream(stage.define().replace(" ", "").split("\n")) 39 | .flatMap(line -> line.chars().boxed()) 40 | .toList(); 41 | 42 | return IntStream.range(0, STAGE_SIZE * STAGE_SIZE) 43 | .mapToObj(p -> new Spot((char) symbols.get(p).intValue(), p)); 44 | } 45 | 46 | public Spot(char symbol, int x, int y) { 47 | this.symbol = symbol; 48 | this.skin = symbol; 49 | this.alive = true; 50 | this.x = PRECISION * x; 51 | this.y = PRECISION * y; 52 | } 53 | 54 | public Spot(char symbol, int posInStream) { 55 | this(symbol, posInStream - (posInStream / STAGE_SIZE) * STAGE_SIZE, posInStream / STAGE_SIZE); 56 | } 57 | 58 | public Spot clone(char newSymbol) { 59 | Spot clone = new Spot(newSymbol, x / PRECISION, y / PRECISION); 60 | clone.init(initialValues, collisionFunctions); 61 | return clone; 62 | } 63 | 64 | public Spot clone(char newSymbol, int x, int y) { 65 | Spot clone = new Spot(newSymbol, x, y); 66 | clone.init(initialValues, collisionFunctions); 67 | return clone; 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return symbol + "(" + (x * 1f) / PRECISION + "|" + (y * 1f) / PRECISION + ")"; 73 | } 74 | 75 | public void init(Map initialValues, Map collisionFunctions) { 76 | this.initialValues.putAll(initialValues); 77 | this.collisionFunctions.putAll(collisionFunctions); 78 | } 79 | 80 | public char getSkin() { 81 | return skin; 82 | } 83 | 84 | public char setSkin(char skin) { 85 | this.skin = skin; 86 | return this.skin; 87 | } 88 | 89 | public char getSymbol() { 90 | return symbol; 91 | } 92 | 93 | @Override 94 | public int getValue(ActorProperty p) { 95 | return values.getOrDefault(p, initialValues.getOrDefault(p, 0)); 96 | } 97 | 98 | @Override 99 | public int setValue(ActorProperty p, int value) { 100 | values.put(p, value); 101 | return getValue(p); 102 | } 103 | 104 | @Override 105 | public int resetValue(ActorProperty p) { 106 | values.remove(p); 107 | return getValue(p); 108 | } 109 | 110 | @Override 111 | public int addToValue(ActorProperty p, int increment) { 112 | values.put(p, getValue(p) + increment); 113 | return getValue(p); 114 | } 115 | 116 | @Override 117 | public int multiplyValue(ActorProperty p, int multiplier) { 118 | values.put(p, getValue(p) * multiplier); 119 | return getValue(p); 120 | } 121 | 122 | @Override 123 | public int getX() { 124 | return x; 125 | } 126 | 127 | @Override 128 | public int setX(int x) { 129 | this.x = x; 130 | return x; 131 | } 132 | 133 | @Override 134 | public int getY() { 135 | return y; 136 | } 137 | 138 | @Override 139 | public int setY(int y) { 140 | this.y = y; 141 | return y; 142 | } 143 | 144 | @Override 145 | public boolean isAlive() { 146 | return alive; 147 | } 148 | 149 | @Override 150 | public void destroy() { 151 | alive = false; 152 | } 153 | 154 | public float getPixelPositionX(int cellWidthInPixel) { 155 | if (!alive) { 156 | return Float.MIN_VALUE; 157 | } 158 | return (x * cellWidthInPixel) * 1f / PRECISION; 159 | } 160 | 161 | public float getPixelPositionY(int cellHeightInPixel) { 162 | if (!alive) { 163 | return Float.MIN_VALUE; 164 | } 165 | return (y * cellHeightInPixel) * 1f / PRECISION; 166 | } 167 | 168 | private boolean blocks() { 169 | return getValue(BLOCKING) == 1 || getValue(DESTRUCTIBLE) == 1; 170 | } 171 | 172 | public void move(int deltaX, int deltaY, List allSpots, ActorStates all) { 173 | if (!alive) { 174 | return; 175 | } 176 | 177 | collideSelf(all); 178 | 179 | if (deltaX == 0 && deltaY == 0) { 180 | return; 181 | } 182 | 183 | int newX = min(max(0, x + deltaX), (STAGE_SIZE - 1) * PRECISION); 184 | int newY = min(max(0, y + deltaY), (STAGE_SIZE - 1) * PRECISION); 185 | 186 | List collisions = allSpots.stream() 187 | .filter(s -> s.alive && s.blocks() && s != this && doesCollide(deltaX, deltaY, s)) 188 | .toList(); 189 | if (!collisions.isEmpty()) { 190 | collisions.forEach(s -> collide(s, all)); 191 | 192 | // maybe we can still move part of the way 193 | if (deltaX != 0) { 194 | x = deltaX < 0 ? snapFloor(x) : snapCeil(x); 195 | } 196 | if (deltaY != 0) { 197 | y = deltaX < 0 ? snapFloor(y) : snapCeil(y); 198 | } 199 | return; 200 | } 201 | 202 | x = newX; 203 | y = newY; 204 | } 205 | 206 | private int snapFloor(int value) { 207 | return (value / PRECISION) * PRECISION; 208 | } 209 | 210 | private int snapCeil(int value) { 211 | int ceil = ((value + PRECISION) / PRECISION) * PRECISION; 212 | if (ceil == value + PRECISION) { 213 | return value; 214 | } 215 | return ceil; 216 | } 217 | 218 | private boolean doesCollide(int deltaX, int deltaY, Spot other) { 219 | return x + deltaX < other.x + PRECISION 220 | && x + deltaX + PRECISION > other.x 221 | && y + deltaY < other.y + PRECISION 222 | && y + deltaY + PRECISION > other.y; 223 | } 224 | 225 | private void collide(Spot other, ActorStates all) { 226 | if (collisionFunctions.containsKey(other.symbol)) { 227 | collisionFunctions.get(other.symbol).collide(this, other, all); 228 | if (symbol == other.symbol) { 229 | return; // do not compute the same collision logic twice 230 | } 231 | } 232 | if (other.collisionFunctions.containsKey(symbol)) { 233 | other.collisionFunctions.get(symbol).collide(other, this, all); 234 | } 235 | } 236 | 237 | private void collideSelf(ActorStates all) { 238 | if (collisionFunctions.containsKey(SYMBOL_EMPTY_SPOT)) { 239 | collisionFunctions.get(SYMBOL_EMPTY_SPOT).collide(this, this, all); 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /renderer/renderer-lwjgl/src/main/java/org/example/renderer/lwjgl/LWJGLRenderer.java: -------------------------------------------------------------------------------- 1 | package org.example.renderer.lwjgl; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | import static org.example.renderer.lwjgl.textures.TextureManagement.saveScreenshot; 5 | import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks; 6 | import static org.lwjgl.glfw.GLFW.GLFW_FALSE; 7 | import static org.lwjgl.glfw.GLFW.GLFW_KEY_DOWN; 8 | import static org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE; 9 | import static org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT; 10 | import static org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT; 11 | import static org.lwjgl.glfw.GLFW.GLFW_KEY_UP; 12 | import static org.lwjgl.glfw.GLFW.GLFW_KEY_X; 13 | import static org.lwjgl.glfw.GLFW.GLFW_PRESS; 14 | import static org.lwjgl.glfw.GLFW.GLFW_RELEASE; 15 | import static org.lwjgl.glfw.GLFW.GLFW_RESIZABLE; 16 | import static org.lwjgl.glfw.GLFW.GLFW_VISIBLE; 17 | import static org.lwjgl.glfw.GLFW.glfwCreateWindow; 18 | import static org.lwjgl.glfw.GLFW.glfwDestroyWindow; 19 | import static org.lwjgl.glfw.GLFW.glfwInit; 20 | import static org.lwjgl.glfw.GLFW.glfwMakeContextCurrent; 21 | import static org.lwjgl.glfw.GLFW.glfwPollEvents; 22 | import static org.lwjgl.glfw.GLFW.glfwSetErrorCallback; 23 | import static org.lwjgl.glfw.GLFW.glfwSetKeyCallback; 24 | import static org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose; 25 | import static org.lwjgl.glfw.GLFW.glfwShowWindow; 26 | import static org.lwjgl.glfw.GLFW.glfwSwapBuffers; 27 | import static org.lwjgl.glfw.GLFW.glfwSwapInterval; 28 | import static org.lwjgl.glfw.GLFW.glfwTerminate; 29 | import static org.lwjgl.glfw.GLFW.glfwWindowHint; 30 | import static org.lwjgl.glfw.GLFW.glfwWindowShouldClose; 31 | import static org.lwjgl.opengl.GL.createCapabilities; 32 | import static org.lwjgl.opengl.GL11.GL_BLEND; 33 | import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; 34 | import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; 35 | import static org.lwjgl.opengl.GL11.GL_MODELVIEW; 36 | import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA; 37 | import static org.lwjgl.opengl.GL11.GL_PROJECTION; 38 | import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA; 39 | import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; 40 | import static org.lwjgl.opengl.GL11.glBlendFunc; 41 | import static org.lwjgl.opengl.GL11.glClear; 42 | import static org.lwjgl.opengl.GL11.glClearColor; 43 | import static org.lwjgl.opengl.GL11.glEnable; 44 | import static org.lwjgl.opengl.GL11.glLoadIdentity; 45 | import static org.lwjgl.opengl.GL11.glMatrixMode; 46 | import static org.lwjgl.opengl.GL11.glOrtho; 47 | import static org.lwjgl.system.MemoryUtil.NULL; 48 | 49 | import java.util.HashMap; 50 | import java.util.Map; 51 | import org.example.javarca.engine.GameLoop; 52 | import org.example.javarca.engine.GameState; 53 | import org.example.javarca.engine.Renderer; 54 | import org.example.javarca.engine.Spot; 55 | import org.example.renderer.lwjgl.textures.TextureManagement; 56 | import org.lwjgl.glfw.GLFWErrorCallback; 57 | import org.lwjgl.system.Configuration; 58 | import org.slf4j.Logger; 59 | import org.slf4j.LoggerFactory; 60 | 61 | /** 62 | * Renderer implementation based on the LWJGL library. 63 | */ 64 | public class LWJGLRenderer implements Renderer { 65 | private static final Logger LOG = LoggerFactory.getLogger(LWJGLRenderer.class); 66 | 67 | private static final int CELL_SIZE = 16; 68 | private static final int STAGE_SIZE = 16; 69 | private static final int GAME_SIZE = STAGE_SIZE * CELL_SIZE; 70 | 71 | private static final String PRESENTATION_FOLDER = System.getenv("PRESENTATION_FOLDER"); 72 | 73 | private final GameLoop gameLoop = new GameLoop(); 74 | private final GameState gameState = new GameState(); 75 | private final Map images = new HashMap<>(); 76 | 77 | private long window; 78 | 79 | public LWJGLRenderer() {} 80 | 81 | @Override 82 | public void run() { 83 | gameLoop.start(gameState); 84 | 85 | init(); 86 | loop(); 87 | 88 | gameLoop.stop(); 89 | 90 | glfwSwapBuffers(window); 91 | 92 | glfwFreeCallbacks(window); 93 | glfwDestroyWindow(window); 94 | glfwTerminate(); 95 | requireNonNull(glfwSetErrorCallback(null)).free(); 96 | } 97 | 98 | private void init() { 99 | int SCALE = 3; 100 | 101 | if (System.getProperty("os.name").toLowerCase().startsWith("mac")) { 102 | Configuration.GLFW_LIBRARY_NAME.set("glfw_async"); 103 | } 104 | GLFWErrorCallback.createPrint(System.err).set(); 105 | if (!glfwInit()) { 106 | LOG.error("Unable to initialize GLFW"); 107 | throw new IllegalStateException("Unable to initialize GLFW"); 108 | } 109 | 110 | glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); 111 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 112 | window = glfwCreateWindow(GAME_SIZE * SCALE, GAME_SIZE * SCALE, "Javarcade", NULL, NULL); 113 | if (window == NULL) { 114 | LOG.error("Failed to create the GLFW window"); 115 | throw new RuntimeException("Failed to create the GLFW window"); 116 | } 117 | 118 | //noinspection resource 119 | glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> { 120 | if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) { 121 | glfwSetWindowShouldClose(window, true); 122 | } 123 | 124 | if (action == GLFW_PRESS) { 125 | switch (key) { 126 | case GLFW_KEY_UP -> gameState.setUp(true); 127 | case GLFW_KEY_DOWN -> gameState.setDown(true); 128 | case GLFW_KEY_LEFT -> gameState.setLeft(true); 129 | case GLFW_KEY_RIGHT -> gameState.setRight(true); 130 | case GLFW_KEY_X -> gameState.action(); 131 | } 132 | } 133 | 134 | if (action == GLFW_RELEASE) { 135 | switch (key) { 136 | case GLFW_KEY_UP -> gameState.setUp(false); 137 | case GLFW_KEY_DOWN -> gameState.setDown(false); 138 | case GLFW_KEY_LEFT -> gameState.setLeft(false); 139 | case GLFW_KEY_RIGHT -> gameState.setRight(false); 140 | } 141 | } 142 | }); 143 | 144 | glfwMakeContextCurrent(window); 145 | glfwSwapInterval(1); 146 | if (PRESENTATION_FOLDER == null) { 147 | glfwShowWindow(window); 148 | } 149 | createCapabilities(); 150 | glEnable(GL_TEXTURE_2D); 151 | glEnable(GL_BLEND); 152 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 153 | glMatrixMode(GL_PROJECTION); 154 | glLoadIdentity(); 155 | glOrtho(0, GAME_SIZE, GAME_SIZE, 0, -1, 1); 156 | glMatrixMode(GL_MODELVIEW); 157 | glLoadIdentity(); 158 | 159 | gameState.getImages().forEach((symbol, content) -> images.put(symbol, TextureManagement.loadTexture(content))); 160 | } 161 | 162 | private void loop() { 163 | glClearColor(1f, 1f, 1f, 0.0f); 164 | 165 | final int targetFPS = PRESENTATION_FOLDER == null ? 60 : 10; 166 | final long frameTime = 1_000_000_000 / targetFPS; 167 | 168 | while (!glfwWindowShouldClose(window)) { 169 | long startTime = System.nanoTime(); 170 | 171 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 172 | gameState.getSpots().forEach(this::drawSpot); 173 | glfwPollEvents(); 174 | glfwSwapBuffers(window); 175 | 176 | long endTime = System.nanoTime(); 177 | long elapsedTime = endTime - startTime; 178 | long sleepTime = frameTime - elapsedTime; 179 | 180 | saveScreenshot(PRESENTATION_FOLDER, 6 * GAME_SIZE, 6 * GAME_SIZE); 181 | if (sleepTime > 0) { 182 | try { 183 | //noinspection BusyWait 184 | Thread.sleep(sleepTime / 1_000_000, (int) (sleepTime % 1_000_000)); 185 | } catch (InterruptedException e) { 186 | Thread.currentThread().interrupt(); 187 | } 188 | } 189 | } 190 | } 191 | 192 | private void drawSpot(Spot s) { 193 | drawSprite(ensureTexture(s), s.getPixelPositionX(CELL_SIZE), s.getPixelPositionY(CELL_SIZE)); 194 | } 195 | 196 | private Integer ensureTexture(Spot spot) { 197 | if (spot.getSkin() == '.') { 198 | return images.get(spot.getSymbol()); 199 | } 200 | return images.computeIfAbsent(spot.getSkin(), TextureManagement::renderCharacter); 201 | } 202 | 203 | private void drawSprite(Integer img, float x, float y) { 204 | if (img == null || x == Float.MIN_VALUE) { 205 | return; 206 | } 207 | TextureManagement.renderTexture(img, x, y, CELL_SIZE, CELL_SIZE); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2019 Louis Jacomet 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How to set up a larger Gradle project 2 | 3 | This repo contains a Gradle project structure with: 4 | 5 | - **Centralized and maintainable** build configuration and custom build logic 6 | - **No dependency hell** through smart dependency management with dependency rules and analysis 7 | 8 | The `main` branch contains everything for a traditional Java project. 9 | The structure though, is good for any kind of project you may build with Gradle 10 | (**Kotlin**, **Groovy**, **Scala**, ...). 11 | 12 | > [!NOTE] 13 | > There are adjustments on other branches of this repo that show how the setup can be varied: 14 | > - 🧩 [**Java Module System**](https://github.com/jjohannes/gradle-project-setup-howto/tree/java_module_system) 15 | > - 📦 [**Versions in BOM/Platform**](https://github.com/jjohannes/gradle-project-setup-howto/tree/java_bom) 16 | > - 🦩 [**Kotlin**](https://github.com/jjohannes/gradle-project-setup-howto/tree/kotlin) 17 | > - 🤖 [**Android**](https://github.com/jjohannes/gradle-project-setup-howto/tree/android) 18 | > - 🍃 [**Java** and **Spring Boot**](https://github.com/jjohannes/gradle-project-setup-howto/tree/spring_boot) 19 | 20 | > [!TIP] 21 | > There are two sister repositories that share the same example with a different focus in the build setup 22 | > - 🧶 [**idiomatic-gradle**](https://github.com/jjohannes/idiomatic-gradle) Focus on structuring builds for both mono-repo or multi-repo setups 23 | > - 🕹️ [**javarcade**](https://github.com/jjohannes/javarcade) Focus on structured dependency management with the JavaRCA recipe 24 | 25 | ## Project Overview 26 | 27 | ### Folder structure 28 | 29 | ``` 30 | ├── settings.gradle.kts - Entry point file for Gradle to work with the project struture 31 | ├── $product-name - Modules of the software are organized in 'product' folders 32 | │ └── $module-name - Each Module of the software has a folder 33 | │ ├── src - Production code and test code 34 | │ └── build.gradle.kts - Defines the type of the Module and its dependencies 35 | ├── gradle - Contains the build configuraton 36 | │ ├── version.txt - Defines the version of the software all Modules share 37 | │ ├── jdk-version.txt - Defines Java version used in the project 38 | │ ├── libs.versions.toml - Version catalog defines versions of 3rd party modules 39 | │ ├── versions/build... - 3rd party version restrictions if needed 40 | │ ├── aggregation/build... - Aggregated reports for the whole project 41 | │ ├── plugins/build... - Define which 3rd party plugins are used and their versions 42 | │ │ └── src/main/kotlin - Individual plugin configurations (aka Convention Plugins) 43 | │ └── wrapper - Define Gradle version used via Gradle Wrapper 44 | ├── gradle.properties - Gradle startup parameters and global switches 45 | ├── build.gradle.kts - Use plugins for checks and auto-formating on the whole project 46 | └── .github - Integrate with GitHub Actions CI system 47 | ``` 48 | 49 | - [Video: The Settings File](https://www.youtube.com/watch?v=Ajs8pTbg8as&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 50 | 51 | ### The $product-name/$module-name/build.gradle.kts files 52 | 53 | Select a _Component Type_ by using the corresponding convention plugin and define the dependencies of the module. 54 | For example: 55 | 56 | ``` 57 | plugins { 58 | id("org.example.gradle.component.library") // This is a 'library' 59 | id("org.example.gradle.feature.publish") // Build feature only in this 'library' Module 60 | } 61 | 62 | dependencies { 63 | api(projects.coruscant) // Depends on another Module of our project 64 | implementation(libs.guava) // Depends on a 3rd party Module 65 | } 66 | ``` 67 | 68 | - [Video: The Build Files](https://www.youtube.com/watch?v=OKjE_Lt_66U&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 69 | - [Video: Declaring Dependencies](https://www.youtube.com/watch?v=igug9tbl4J4&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 70 | 71 | ### The Convention Plugins 72 | 73 | - [Video: (Convention) Plugins](https://www.youtube.com/watch?v=N95YI-szd78&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 74 | 75 | Convention plugins are used to configure each aspect of the build centrally. To keep it structured, we put 76 | them into four categories: _Base_, _Feature_, _Check_, _Report_. Below you find all plugins listed. For more details, 77 | inspect the corresponding plugin files. _Understanding Gradle_ videos that cover related topics are linked below 78 | each plugin file. 79 | 80 | #### Base Plugins 81 | 82 | _Base_ plugins need to be used in all Modules to establish a certain foundation for the setup. 83 | For example, the same dependency management configuration should be applied everywhere to consistently use the same 84 | 3rd party libraries everywhere. 85 | 86 | - [org.example.gradle.base.identity.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.base.identity.gradle.kts) 87 | - [org.example.gradle.base.lifecycle.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.base.lifecycle.gradle.kts) 88 | - [Video: Lifecycle Tasks](https://www.youtube.com/watch?v=sOo0p4Gpjcc&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 89 | - [org.example.gradle.base.lifecycle.root.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.base.lifecycle.root.gradle.kts) 90 | - [org.example.gradle.base.dependency-rules.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.base.dependency-rules.gradle.kts) 91 | - [Video: Dependency Version Conflicts + Consistent Resolution](https://www.youtube.com/watch?v=YYWhfy6c2YQ&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 92 | - [Video: Capability Conflicts + Component Metadata Rules](https://www.youtube.com/watch?v=5g20kbbqBFk&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 93 | 94 | #### Feature Plugins 95 | 96 | Each _feature_ plugin configures one aspect of building the software – like _compiling code_ or _testing code_. 97 | 98 | - [org.example.gradle.feature.repositories.settings.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.feature.repositories.settings.gradle.kts) 99 | - [org.example.gradle.feature.project-structure.settings.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.feature.project-structure.settings.gradle.kts) 100 | - [Video: Settings Plugins](https://www.youtube.com/watch?v=tlx3tzuLSWk&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 101 | - [org.example.gradle.feature.compile-java.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.feature.compile-java.gradle.kts) 102 | - [Video: Source Sets](https://www.youtube.com/watch?v=74PDtHkS_w4&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 103 | - [Video: The JavaCompile Task](https://www.youtube.com/watch?v=wFewehz6rW8&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 104 | - [org.example.gradle.feature.javadoc.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.feature.javadoc.gradle.kts) 105 | - [org.example.gradle.feature.test.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.feature.test.gradle.kts) 106 | - [Video: Configuring Testing](https://www.youtube.com/watch?v=7f_gBvGQN_0&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 107 | - [Video: The Test Task](https://www.youtube.com/watch?v=YJjNQJSaFww&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 108 | - [org.example.gradle.feature.test-end-to-end.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.feature.test-end-to-end.gradle.kts) 109 | - [Video: Feature Variants](https://www.youtube.com/watch?v=XCzyUESaBHQ&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 110 | - [org.example.gradle.feature.test-fixtures.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.feature.test-fixtures.gradle.kts) 111 | - [Video: Test Fixtures](https://www.youtube.com/watch?v=fSRN6YKa5B0&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 112 | - [org.example.gradle.feature.checksum.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.feature.checksum.gradle.kts) 113 | - [Video: Configuring Task Inputs and Outputs](https://www.youtube.com/watch?v=Pj9hSRauiQM&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 114 | - [Video: Implementing Tasks and Extensions](https://www.youtube.com/watch?v=wrgyUKC7vOY&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 115 | - [org.example.gradle.feature.publish.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.feature.publish.gradle.kts) 116 | - [Video: Publishing Libraries](https://www.youtube.com/watch?v=8z5KFCLZDd0&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 117 | - [org.example.gradle.feature.use-all-catalog-versions.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.feature.use-all-catalog-versions.gradle.kts) 118 | - [Video: Centralizing Dependency Versions](https://www.youtube.com/watch?v=8044F5gc1dE&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 119 | - [org.example.gradle.feature.build-cache.settings.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.feature.build-cache.settings.gradle.kts) 120 | - [Video: Caching](https://www.youtube.com/watch?v=nHb0kIcTrFE&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 121 | 122 | #### Report Plugins 123 | 124 | _Report_ plugins add reporting functionality to discover potential issues with the software or the build setup. 125 | They may generate data that is picked up and displayed by external tools like 126 | [Develocity](https://scans.gradle.com/) or [Dependency Track](https://dependencytrack.org/). 127 | More reporting tools may be integrated in this category. 128 | Report plugins are not necessarily needed to build a working software. 129 | 130 | - [org.example.gradle.report.code-coverage.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.report.code-coverage.gradle.kts) 131 | - [Video: Aggregating Custom Artifacts](https://www.youtube.com/watch?v=2gPJD0mAres&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 132 | - [Video: Test and Code Coverage Reporting](https://www.youtube.com/watch?v=uZvzWlP9BYE&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 133 | - [org.example.gradle.report.plugin-analysis.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.report.plugin-analysis.gradle.kts) 134 | - [org.example.gradle.report.sbom.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.report.sbom.gradle.kts) 135 | - [org.example.gradle.report.develocity.settings.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.report.develocity.settings.gradle.kts) 136 | 137 | #### Check Plugins 138 | 139 | Check plugins help with keeping the software maintainable over time. 140 | They check things like the dependency setup or code formatting. 141 | More style checkers or static code analysis tools could be added in this category. 142 | Check plugins are not necessarily needed to build a working software. 143 | 144 | - [org.example.gradle.check.dependencies.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.check.dependencies.gradle.kts) 145 | - [Video: Dependency Analysis](https://www.youtube.com/watch?v=Lipf5piizZc&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 146 | - [Video: Detect and Resolve Collisions on a Classpath](https://www.youtube.com/watch?v=KocTqF0hO_8&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 147 | - [org.example.gradle.check.format-gradle.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.check.format-gradle.gradle.kts) 148 | - [org.example.gradle.check.format-gradle.root.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.check.format-gradle.root.gradle.kts) 149 | - [org.example.gradle.check.format-java.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.check.format-java.gradle.kts) 150 | 151 | #### Component Plugins 152 | 153 | _Component_ plugins combine plugins from all categories above to define _Component Types_ that are then used in the `build.gradle.kts` 154 | files of the individual Modules of our software. 155 | 156 | - [org.example.gradle.component.application.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.component.application.gradle.kts) 157 | - [org.example.gradle.component.library.gradle.kts](gradle/plugins/src/main/kotlin/org.example.gradle.component.library.gradle.kts) 158 | 159 | #### Testing Plugins 160 | 161 | The [Gradle TestKit](https://docs.gradle.org/current/userguide/test_kit.html) can be used to test plugins. 162 | This can be helpful to enforce a certain structure, e.g. by testing if each plugin works on its own. 163 | And if you add custom tasks and advanced logic, you can add tests for that. As example, this project contains one test class: 164 | [ConventionPluginTest.kt](gradle/plugins/src/test/kotlin/org/example/gradle/test/ConventionPluginTest.kt) 165 | 166 | There is also a help task that you can use to get a diagram of the convention plugins defined in the project: 167 | 168 | `./gradlew :aggregation:analysePluginApplicationOrder` 169 | 170 | The task generates a [PlantUML](https://plantuml.com) file that you can render, for example, with the PlantUML IntelliJ plugin. 171 | 172 | ### Continuously build and report using GitHub Actions and Dependabot 173 | 174 | - [build.yaml](.github/workflows/build.yaml) Configure GitHub to run builds and produce reports (👉[inspect](https://github.com/jjohannes/gradle-project-setup-howto/actions/workflows/build.yaml)). Integrates with: 175 | - [Develocity Build Scans](https://scans.gradle.com/) (👉[inspect](https://scans.gradle.com/s/h3odwhbjjd2qm)) 176 | - [Gradle Remote Build Cache](https://docs.gradle.com/develocity/build-cache-node/) 177 | - [Reposilite](https://reposilite.com/) (👉[inspect](https://repo.onepiece.software/#/snapshots)) 178 | - [Dependency Track](https://dependencytrack.org/) 179 | - [renovate.json](renovate.json) Configure [Renovate](https://github.com/apps/renovate) to automatically get version updates (👉[inspect](https://github.com/jjohannes/gradle-project-setup-howto/pulls/app%2Frenovate)) 180 | 181 | ## Notes 182 | 183 | - If you have a question, please ask in an [issue](https://github.com/jjohannes/gradle-project-setup-howto/issues/new). 184 | - The concrete things done in all places (custom tasks, components used in dependencies, additional plugins applied, etc.) are just examples. 185 | If you, for example, need to use additional Gradle plugins you can add these in the corresponding place, keeping the suggested structure. 186 | - There was a different version of this repository I initially published in 2022. The setup was more complex by splitting 187 | the Gradle configuration over more folders which required more boilerplate. After using a setup like this in several 188 | projects, I felt that the setup was overly complex without adding much value. I ended up striping it down to what this 189 | repository is now. The older version is still accessible on the 190 | [2022_java](https://github.com/jjohannes/gradle-project-setup-howto/tree/2022_java) branch. 191 | 192 | ## FAQ 193 | 194 | [List of questions](https://github.com/jjohannes/gradle-project-setup-howto/issues?q=is%3Aissue+label%3Aquestion) asked in issues so far. 195 | 196 | - [How does the consistent resolution setup work and why is there a circular reference?](https://github.com/jjohannes/gradle-project-setup-howto/issues/55) 197 | - [How many convention plugins should be used?](https://github.com/jjohannes/gradle-project-setup-howto/issues/10) 198 | - [How to adjust the setup to work with private Maven repositories? #66](https://github.com/jjohannes/gradle-project-setup-howto/issues/66) 199 | - [How to create a convention plugin for the OpenAPI Generator?](https://github.com/jjohannes/gradle-project-setup-howto/issues/103) 200 | - [How to customize the wrapper task?](https://github.com/jjohannes/gradle-project-setup-howto/issues/12) 201 | - [How to manage plugin versions in the version catalog?](https://github.com/jjohannes/gradle-project-setup-howto/issues/37) 202 | - [What are good practices when publishing libraries (API consistency, multiple variants, transitives)?](https://github.com/jjohannes/gradle-project-setup-howto/issues/17) 203 | - [Why and when to use afterEvaluate or projectsEvaluated?](https://github.com/jjohannes/gradle-project-setup-howto/issues/160) 204 | - [Why is the :app project special?](https://github.com/jjohannes/gradle-project-setup-howto/issues/4) 205 | - [Dependency Analysis: How to ignore dependencies added by plugins?](https://github.com/jjohannes/gradle-project-setup-howto/issues/6) 206 | - [Dependency Analysis: How to remove dependencies added by plugins?](https://github.com/jjohannes/gradle-project-setup-howto/issues/15) 207 | - [Kotlin branch: Why is there a special handling of kotlin-stdlib?](https://github.com/jjohannes/gradle-project-setup-howto/issues/13) 208 | - [Kotlin branch: How to avoid the 'The Kotlin Gradle plugin was loaded multiple times' error?](https://github.com/jjohannes/gradle-project-setup-howto/issues/19) 209 | - [Spring branch: Why does the example not use the Spring Boot Dependency Management plugin?](https://github.com/jjohannes/gradle-project-setup-howto/issues/20) 210 | 211 | More questions or points you would like to discuss? Please [open an issue](https://github.com/jjohannes/gradle-project-setup-howto/issues/new). 212 | --------------------------------------------------------------------------------