├── .github └── FUNDING.yml ├── .gitlab-ci.yml ├── CITATION.cff ├── ConvexMerger ├── .classpath ├── .gitignore ├── .project ├── .settings │ ├── org.eclipse.buildship.core.prefs │ ├── org.eclipse.core.resources.prefs │ ├── org.eclipse.core.runtime.prefs │ ├── org.eclipse.jdt.core.prefs │ └── org.sonarlint.eclipse.core.prefs ├── build.gradle ├── exclude.xml ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── icon.ico ├── resources │ └── assets │ │ ├── fonts │ │ ├── OFL.txt │ │ ├── Pridi-Medium.ttf │ │ └── Pridi-Regular.ttf │ │ ├── icons │ │ ├── add_ai.png │ │ ├── add_human.png │ │ ├── ai.png │ │ ├── chevron.png │ │ ├── crown.png │ │ ├── human.png │ │ └── remove.png │ │ ├── logo │ │ ├── 16.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 48.png │ │ ├── 64.png │ │ └── 96.png │ │ └── text │ │ └── rules.txt ├── settings.gradle ├── sonar.gradle ├── src │ └── dev │ │ └── roanh │ │ └── convexmerger │ │ ├── Constants.java │ │ ├── Main.java │ │ ├── animation │ │ ├── Animation.java │ │ ├── CalliperAnimation.java │ │ ├── ClaimAnimation.java │ │ ├── ExampleAnimation.java │ │ ├── MergeAnimation.java │ │ ├── ProxyAnimation.java │ │ ├── RenderableObject.java │ │ └── ScoreAnimation.java │ │ ├── game │ │ ├── ClaimResult.java │ │ ├── ConvexObject.java │ │ ├── GameConstructor.java │ │ ├── GameState.java │ │ ├── GameStateListener.java │ │ ├── Identity.java │ │ └── PlayfieldGenerator.java │ │ ├── net │ │ ├── ClientConnection.java │ │ ├── Connection.java │ │ ├── InternalServer.java │ │ ├── PlayerProxy.java │ │ └── packet │ │ │ ├── Packet.java │ │ │ ├── PacketGameEnd.java │ │ │ ├── PacketGameInit.java │ │ │ ├── PacketPlayerJoin.java │ │ │ ├── PacketPlayerJoinAccept.java │ │ │ ├── PacketPlayerJoinReject.java │ │ │ ├── PacketPlayerMove.java │ │ │ └── PacketRegistry.java │ │ ├── player │ │ ├── AIRegistry.java │ │ ├── GreedyPlayer.java │ │ ├── HumanPlayer.java │ │ ├── LocalPlayer.java │ │ ├── Player.java │ │ ├── RemotePlayer.java │ │ └── SmallPlayer.java │ │ ├── ui │ │ ├── ComboBox.java │ │ ├── ConvexMerger.java │ │ ├── GamePanel.java │ │ ├── HostMenu.java │ │ ├── InfoMenu.java │ │ ├── JoinMenu.java │ │ ├── MainMenu.java │ │ ├── MessageDialog.java │ │ ├── NewGameMenu.java │ │ ├── ResultOverlay.java │ │ ├── Screen.java │ │ ├── ScreenRenderer.java │ │ ├── TextField.java │ │ └── Theme.java │ │ └── util │ │ ├── ConjugationTree.java │ │ ├── ConvexUtil.java │ │ ├── KDTree.java │ │ ├── PartitionTree.java │ │ ├── Segment.java │ │ ├── SegmentPartitionTree.java │ │ └── VerticalDecomposition.java └── test │ └── dev │ └── roanh │ └── convexmerger │ ├── PerformanceTest.java │ └── util │ ├── ConjugationTreeTest.java │ ├── ConvexUtilTest.java │ ├── KDTreeTest.java │ ├── SegmentPartitionTreeTest.java │ └── VerticalDecompTest.java ├── LICENSE └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: RoanH 2 | ko_fi: RoanHofland 3 | custom: https://www.paypal.me/RoanHofland -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: openjdk:8 2 | 3 | variables: 4 | SUFFIX: ${CI_PIPELINE_ID}-${CI_COMMIT_REF_NAME} 5 | PROJECTNAME: ConvexMerger 6 | 7 | before_script: 8 | - java -version 9 | - cd ${PROJECTNAME} 10 | - ls -l 11 | - chmod -R 755 ./* 12 | 13 | stages: 14 | - check 15 | - compile 16 | - test 17 | - status 18 | - javadoc 19 | 20 | endings: 21 | allow_failure: true 22 | script: curl ${SERVER}ci/lf.sh | bash 23 | stage: check 24 | 25 | sonar: 26 | image: eclipse-temurin:17 27 | allow_failure: true 28 | script: 29 | - ./gradlew -PrefName=${CI_COMMIT_REF_NAME} -PnexusPublic=${NEXUS_PUBLIC} assemble 30 | - ./gradlew -PrefName=${CI_COMMIT_REF_NAME} -PnexusPublic=${NEXUS_PUBLIC} test jacocoTestReport 31 | - ./gradlew -I sonar.gradle -PrefName=${CI_COMMIT_REF_NAME} -PnexusPublic=${NEXUS_PUBLIC} sonar 32 | stage: check 33 | only: 34 | - master 35 | 36 | spotbugs: 37 | allow_failure: true 38 | script: ./gradlew -PrefName=${CI_COMMIT_REF_NAME} -PnexusPublic=${NEXUS_PUBLIC} spotbugsMain 39 | stage: check 40 | artifacts: 41 | name: SpotBugs-${SUFFIX} 42 | expire_in: 1 day 43 | when: always 44 | paths: 45 | - ./${PROJECTNAME}/build/reports/spotbugs/main/spotbugs.html 46 | 47 | pending: 48 | allow_failure: true 49 | script: curl ${SERVER}ci/pending.sh | bash 50 | stage: compile 51 | 52 | success: 53 | allow_failure: true 54 | script: curl ${SERVER}ci/success.sh | bash 55 | when: on_success 56 | stage: status 57 | 58 | failure: 59 | allow_failure: true 60 | script: curl ${SERVER}ci/failure.sh | bash 61 | when: on_failure 62 | stage: status 63 | 64 | verify: 65 | allow_failure: true 66 | script: curl ${SERVER}ci/javadoc.sh | bash 67 | stage: javadoc 68 | coverage: '/\([0-9]{2,3}\.[0-9]{2}%\)/' 69 | 70 | javadoc: 71 | script: 72 | - ./gradlew -PrefName=${CI_COMMIT_REF_NAME} -PnexusPublic=${NEXUS_PUBLIC} javadoc 73 | - mv ./build/docs/javadoc ../ 74 | stage: javadoc 75 | artifacts: 76 | name: Javadoc-${SUFFIX} 77 | expire_in: 1 day 78 | paths: 79 | - javadoc/ 80 | 81 | compile: 82 | script: 83 | - ./gradlew -PrefName=${CI_COMMIT_REF_NAME} -PnexusPublic=${NEXUS_PUBLIC} :shadowJar 84 | - ./gradlew -PrefName=${CI_COMMIT_REF_NAME} -PnexusPublic=${NEXUS_PUBLIC} :createExe 85 | - mv ./build/libs/* .. 86 | - mv ./build/launch4j/*.exe .. 87 | stage: compile 88 | artifacts: 89 | name: ConvexMerger-${SUFFIX} 90 | expire_in: 1 day 91 | paths: 92 | - ConvexMerger-*.jar 93 | - ConvexMerger-*.exe 94 | 95 | test: 96 | script: 97 | - ./gradlew -PrefName=${CI_COMMIT_REF_NAME} -PnexusPublic=${NEXUS_PUBLIC} :test :jacocoTestReport 98 | - cat ./build/reports/jacoco/test/html/index.html 99 | stage: test 100 | coverage: '/Total.*?([0-9]{1,3})%/' 101 | artifacts: 102 | reports: 103 | junit: ./${PROJECTNAME}/build/test-results/test/TEST-*.xml 104 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: ConvexMerger 3 | version: 1.2 4 | date-released: 2023-03-12 5 | message: "If you use this software, please cite it using the metadata from this file." 6 | type: software 7 | contact: 8 | - given-names: Roan 9 | family-names: Hofland 10 | email: roan@roanh.dev 11 | website: 'https://roanh.dev' 12 | authors: 13 | - given-names: Roan 14 | family-names: Hofland 15 | email: roan@roanh.dev 16 | - given-names: Emiliyan 17 | family-names: Greshkov 18 | email: e.greshkov@student.tue.nl 19 | - given-names: Thiam Wai 20 | family-names: Chua 21 | email: t.w.chua@student.tue.nl 22 | - name: RockRoller 23 | repository-code: 'https://github.com/RoanH/ConvexMerger' 24 | abstract: "ConvexMerger is an area maximisation game based on the idea of merging convex shapes" 25 | license: GPL-3.0-or-later 26 | year-original: 2021 27 | references: 28 | - title: "ConvexMerger: Algorithmic Optimisations & Challenges" 29 | authors: 30 | - given-names: Roan 31 | family-names: Hofland 32 | affiliation: "Eindhoven University of Technology" 33 | email: r.w.p.hofland@student.tue.nl 34 | - given-names: Emiliyan 35 | family-names: Greshkov 36 | affiliation: "Eindhoven University of Technology" 37 | email: e.greshkov@student.tue.nl 38 | type: report 39 | url: 'https://research.roanh.dev/ConvexMerger%20Report%20v1.3.pdf' 40 | date-published: 2023-05-14 41 | department: "Department of Mathematics and Computer Science" 42 | institution: 43 | name: "Eindhoven University of Technology" 44 | - title: "Convex Merger 2IMA15 Project Group 23" 45 | authors: 46 | - given-names: Roan 47 | family-names: Hofland 48 | affiliation: "Eindhoven University of Technology" 49 | email: r.w.p.hofland@student.tue.nl 50 | - given-names: Emiliyan 51 | family-names: Greshkov 52 | affiliation: "Eindhoven University of Technology" 53 | email: e.greshkov@student.tue.nl 54 | - given-names: Thiam Wai 55 | family-names: Chua 56 | affiliation: "Eindhoven University of Technology" 57 | email: t.w.chua@student.tue.nl 58 | type: report 59 | date-published: 2022-01-26 60 | department: "Department of Mathematics and Computer Science" 61 | institution: 62 | name: "Eindhoven University of Technology" -------------------------------------------------------------------------------- /ConvexMerger/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ConvexMerger/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /.gradle/ 3 | .idea 4 | /build/ 5 | -------------------------------------------------------------------------------- /ConvexMerger/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ConvexMerger 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /ConvexMerger/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home= 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=false 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /ConvexMerger/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /ConvexMerger/.settings/org.eclipse.core.runtime.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | line.separator=\n 3 | -------------------------------------------------------------------------------- /ConvexMerger/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 3 | org.eclipse.jdt.core.compiler.compliance=1.8 4 | org.eclipse.jdt.core.compiler.source=1.8 5 | -------------------------------------------------------------------------------- /ConvexMerger/.settings/org.sonarlint.eclipse.core.prefs: -------------------------------------------------------------------------------- 1 | autoEnabled=true 2 | bindingSuggestionsDisabled=true 3 | eclipse.preferences.version=1 4 | projectKey=roan_convexmerger_e5f173c5-c0ba-4d49-8439-0dc5c1a6f068 5 | serverId=sonar.roanh.dev 6 | -------------------------------------------------------------------------------- /ConvexMerger/build.gradle: -------------------------------------------------------------------------------- 1 | plugins{ 2 | id 'eclipse' 3 | id 'application' 4 | id 'com.gradleup.shadow' version '8.3.5' 5 | id 'edu.sc.seis.launch4j' version '3.0.6' 6 | id 'com.github.spotbugs' version '6.0.18' 7 | id 'jacoco' 8 | } 9 | 10 | import com.github.spotbugs.snom.Confidence 11 | import com.github.spotbugs.snom.Effort 12 | 13 | sourceSets{ 14 | main{ 15 | java.srcDirs = ['src'] 16 | resources.srcDirs = ['resources'] 17 | } 18 | 19 | test.java.srcDirs = ['test'] 20 | } 21 | 22 | dependencies{ 23 | //Util 24 | implementation 'dev.roanh.util:util:2.5' 25 | 26 | //Testing 27 | testImplementation("org.junit.jupiter:junit-jupiter:5.12.0") 28 | testImplementation("org.junit.platform:junit-platform-launcher") 29 | } 30 | 31 | repositories{ 32 | //Local cache 33 | if(project.hasProperty("nexusPublic")){ 34 | maven{ 35 | allowInsecureProtocol = true 36 | url "$nexusPublic" 37 | } 38 | } 39 | mavenCentral() 40 | } 41 | 42 | version = findProperty("refName") ?: 'SNAPSHOT' 43 | if(version.matches("v\\d+\\.\\d+")){ 44 | version = version.substring(1) 45 | } 46 | 47 | eclipse.classpath.downloadSources = true 48 | eclipse.classpath.downloadJavadoc = true 49 | compileJava.options.encoding = 'UTF-8' 50 | compileTestJava.options.encoding = 'UTF-8' 51 | jacocoTestReport.reports.xml.required = true 52 | sourceCompatibility = 1.8 53 | javadoc.options.memberLevel = JavadocMemberLevel.PRIVATE 54 | javadoc.options.encoding = 'UTF-8' 55 | shadowJar.archiveBaseName = 'ConvexMerger' 56 | shadowJar.archiveVersion = 'v' + version 57 | shadowJar.archiveClassifier = '' 58 | application.mainClass = 'dev.roanh.convexmerger.Main' 59 | 60 | test{ 61 | useJUnitPlatform() 62 | dependsOn 'cleanTest' 63 | 64 | testLogging{ 65 | events "passed", "skipped", "failed" 66 | showStandardStreams = true 67 | } 68 | } 69 | 70 | spotbugs{ 71 | showProgress = true 72 | effort = Effort.valueOf('MAX') 73 | reportLevel = Confidence.valueOf('LOW') 74 | excludeFilter = file("$rootDir/exclude.xml") 75 | } 76 | 77 | tasks.withType(com.github.spotbugs.snom.SpotBugsTask){ 78 | reports{ 79 | html{ 80 | required = true 81 | outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.html") 82 | stylesheet = 'fancy-hist.xsl' 83 | } 84 | } 85 | } 86 | 87 | shadowJar{ 88 | mergeServiceFiles() 89 | } 90 | 91 | launch4j{ 92 | jarTask = project.tasks.shadowJar 93 | mainClassName = application.mainClass 94 | icon = "${projectDir}/icon.ico" 95 | jreMinVersion = project.sourceCompatibility.toString() 96 | bundledJrePath = "%JAVA_HOME%" 97 | outfile = 'ConvexMerger-v' + project.version + '.exe' 98 | fileDescription = "ConvexMerger" 99 | productName = rootProject.name 100 | version = project.version 101 | textVersion = project.version 102 | copyright = "Roan Hofland" 103 | } 104 | -------------------------------------------------------------------------------- /ConvexMerger/exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /ConvexMerger/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ConvexMerger/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /ConvexMerger/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 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 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | org.gradle.wrapper.GradleWrapperMain \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /ConvexMerger/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 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /ConvexMerger/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/icon.ico -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Cadson Demak (info@cadsondemak.com) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/fonts/Pridi-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/fonts/Pridi-Medium.ttf -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/fonts/Pridi-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/fonts/Pridi-Regular.ttf -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/icons/add_ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/icons/add_ai.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/icons/add_human.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/icons/add_human.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/icons/ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/icons/ai.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/icons/chevron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/icons/chevron.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/icons/crown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/icons/crown.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/icons/human.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/icons/human.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/icons/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/icons/remove.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/logo/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/logo/16.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/logo/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/logo/256.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/logo/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/logo/32.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/logo/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/logo/48.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/logo/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/logo/64.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/logo/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoanH/ConvexMerger/6cb0ca1dbffae90998f72784815807cb43c73126/ConvexMerger/resources/assets/logo/96.png -------------------------------------------------------------------------------- /ConvexMerger/resources/assets/text/rules.txt: -------------------------------------------------------------------------------- 1 | The goal of the game is to maximise the area of the playfield you own by claiming and merging objects into new convex objects. In every turn you can do the following: 2 | Click an unowned object to claim it for yourself. 3 | Click an object you already own and then select a second object either owned by you or unowned. If there are no objects on what will become the boundary of the new convex object the merge will succeed. Objects fully contained in the newly created convex object will be stolen from their current owner. 4 | The game ends when the player whose turn it is has no possible moves available. You can visualise merging two objects as spanning an elastic band around both objects, the resulting shape is the new convex object. -------------------------------------------------------------------------------- /ConvexMerger/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement{ 2 | repositories{ 3 | //Local cache 4 | if(settings.ext.find("nexusPublic") != null){ 5 | maven{ 6 | allowInsecureProtocol = true 7 | url "$nexusPublic" 8 | } 9 | } 10 | gradlePluginPortal() 11 | mavenCentral() 12 | } 13 | } 14 | 15 | rootProject.name = 'ConvexMerger' 16 | -------------------------------------------------------------------------------- /ConvexMerger/sonar.gradle: -------------------------------------------------------------------------------- 1 | initscript{ 2 | repositories{ 3 | gradlePluginPortal() 4 | } 5 | 6 | dependencies{ 7 | classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:6.1.0.5360" 8 | } 9 | } 10 | 11 | allprojects{ 12 | apply plugin: org.sonarqube.gradle.SonarQubePlugin 13 | 14 | sonar{ 15 | properties{ 16 | property "sonar.projectKey", "roan_convexmerger_e5f173c5-c0ba-4d49-8439-0dc5c1a6f068" 17 | property "sonar.projectName", "ConvexMerger" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger; 20 | 21 | /** 22 | * Class containing various game constants. 23 | * @author Roan 24 | */ 25 | public final class Constants{ 26 | /** 27 | * Current game version. 28 | */ 29 | public static final String VERSION = "v1.2";//don't forget build.gradle 30 | /** 31 | * Game and window title. 32 | */ 33 | public static final String TITLE = "ConvexMerger"; 34 | /** 35 | * The playfield height (from 0 to {@value #PLAYFIELD_WIDTH}). 36 | */ 37 | public static final int PLAYFIELD_WIDTH = 1600; 38 | /** 39 | * The playfield height (from 0 to {@value #PLAYFIELD_HEIGHT}). 40 | */ 41 | public static final int PLAYFIELD_HEIGHT = 900; 42 | /** 43 | * Minimum frame size scaling factor 44 | */ 45 | public static final int MIN_SIZE = 70; 46 | /** 47 | * Initial frame size scaling factor 48 | */ 49 | public static final int INIT_SIZE = 80; 50 | /** 51 | * Number of milliseconds per animation frame (60FPS). 52 | */ 53 | public static final long ANIMATION_RATE = 33; 54 | /** 55 | * Minimum number of milliseconds any turn should take. 56 | */ 57 | public static final long MIN_TURN_TIME = 650; 58 | /** 59 | * Multiplayer server port. 60 | */ 61 | public static final int PORT = 11111; 62 | /** 63 | * The number of milliseconds an AI has to wait before making a move. 64 | */ 65 | public static final long AI_WAIT_TIME = 400; 66 | } 67 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger; 20 | 21 | import dev.roanh.convexmerger.ui.ConvexMerger; 22 | import dev.roanh.util.Util; 23 | 24 | /** 25 | * Main entry point for the application. 26 | * @author Roan 27 | */ 28 | public class Main{ 29 | 30 | /** 31 | * Main subroutine that starts the game. 32 | * @param args No valid command line arguments. 33 | */ 34 | public static void main(String[] args){ 35 | Util.installUI(); 36 | 37 | ConvexMerger game = new ConvexMerger(); 38 | game.showGame(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/animation/Animation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.animation; 20 | 21 | import java.awt.Graphics2D; 22 | 23 | /** 24 | * Class to represent ongoing animations. 25 | * @author Roan 26 | */ 27 | public abstract class Animation{ 28 | /** 29 | * An 'animation' that renders nothing, effectively 30 | * hiding the object it is applied to. It is also not 31 | * possible to wait on this animation. 32 | */ 33 | public static final Animation EMPTY = new Animation(){ 34 | 35 | @Override 36 | protected boolean render(Graphics2D g){ 37 | return true; 38 | } 39 | 40 | @Override 41 | public synchronized void waitFor() throws InterruptedException{ 42 | } 43 | }; 44 | /** 45 | * When true indicates that the animation has no frames remaining. 46 | */ 47 | private volatile boolean finished; 48 | 49 | /** 50 | * Renders the next frame of the animation. 51 | * @param g The graphics instance to use. 52 | * @return True if the animation has frames 53 | * remaining, false if it finished. 54 | */ 55 | public final boolean run(Graphics2D g){ 56 | boolean remaining = render(g); 57 | if(!remaining && !finished){ 58 | end(); 59 | } 60 | return remaining; 61 | } 62 | 63 | /** 64 | * Forcefully ends this animation. 65 | */ 66 | public synchronized void end(){ 67 | finished = true; 68 | notifyAll(); 69 | } 70 | 71 | /** 72 | * Blocks the current thread until this animation has finished. 73 | * @throws InterruptedException When the current thread is interrupted. 74 | */ 75 | public synchronized void waitFor() throws InterruptedException{ 76 | while(!finished){ 77 | wait(); 78 | } 79 | } 80 | 81 | /** 82 | * Renders the next frame of this animation. 83 | * @param g The graphics context to use. 84 | * @return True if the animation has frames 85 | * remaining, false if it finished. 86 | */ 87 | protected abstract boolean render(Graphics2D g); 88 | } 89 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/animation/CalliperAnimation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.animation; 20 | 21 | import java.awt.Color; 22 | import java.awt.Graphics2D; 23 | import java.awt.geom.Line2D; 24 | import java.awt.geom.Point2D; 25 | import java.util.List; 26 | 27 | import dev.roanh.convexmerger.Constants; 28 | import dev.roanh.convexmerger.game.ConvexObject; 29 | import dev.roanh.convexmerger.ui.Theme; 30 | import dev.roanh.convexmerger.util.ConvexUtil; 31 | 32 | /** 33 | * Animation to visualise the callipers used 34 | * when merging two convex objects. 35 | * @author Roan 36 | */ 37 | public class CalliperAnimation extends Animation{ 38 | /** 39 | * The number of milliseconds the animation runs for. 40 | */ 41 | private static final float DURATION = 5000.0F; 42 | /** 43 | * The epoch millisecond start time of the animation. 44 | */ 45 | private long start; 46 | /** 47 | * Cached current index in the first object's points list. 48 | */ 49 | private int indexFirst = 0; 50 | /** 51 | * Cached current index in the second object's points list. 52 | */ 53 | private int indexSecond = 0; 54 | /** 55 | * The first object in the merge. 56 | */ 57 | private ConvexObject first; 58 | /** 59 | * The second object in the merge. 60 | */ 61 | private ConvexObject second; 62 | /** 63 | * The first merge line. 64 | */ 65 | private Line2D firstLine; 66 | /** 67 | * The second merge line. 68 | */ 69 | private Line2D secondLine; 70 | /** 71 | * The angle of the first merge line. 72 | */ 73 | private double firstAngle; 74 | /** 75 | * The angle of the second merge line. 76 | */ 77 | private double secondAngle; 78 | 79 | /** 80 | * Constructs a new calliper animation for the given objects. 81 | * Note: this animation only renders the callipers and not the objects. 82 | * @param first The first object from the merge. 83 | * @param second The second object from the merge. 84 | */ 85 | public CalliperAnimation(ConvexObject first, ConvexObject second){ 86 | this.first = first; 87 | this.second = second; 88 | 89 | Point2D[] lines = ConvexUtil.computeMergeLines(first.getPoints(), second.getPoints(), false); 90 | firstLine = new Line2D.Double(lines[0], lines[1]); 91 | secondLine = new Line2D.Double(lines[2], lines[3]); 92 | firstAngle = ConvexUtil.angleFromVertical(firstLine); 93 | secondAngle = ConvexUtil.angleFromVertical(secondLine); 94 | 95 | start = System.currentTimeMillis(); 96 | } 97 | 98 | @Override 99 | protected boolean render(Graphics2D g){ 100 | long elapsed = System.currentTimeMillis() - start; 101 | double angle = (Math.PI * 2.0F * elapsed) / DURATION; 102 | 103 | g.setStroke(Theme.POLY_STROKE); 104 | g.setColor(Color.BLUE); 105 | if(firstAngle <= angle){ 106 | g.draw(firstLine); 107 | } 108 | 109 | if(secondAngle <= angle){ 110 | g.draw(secondLine); 111 | } 112 | 113 | g.setColor(Color.RED); 114 | indexFirst = drawCalliper(g, first.getPoints(), angle, indexFirst); 115 | indexSecond = drawCalliper(g, second.getPoints(), angle, indexSecond); 116 | 117 | return elapsed < DURATION; 118 | } 119 | 120 | /** 121 | * Draws the calliper line for the given object at the given angle. 122 | * @param g The graphics context to use. 123 | * @param points The points of the convex object to draw a calliper for. 124 | * @param angle The angle of the calliper line to draw. 125 | * @param index The object point set index left off at during the previous animation frame. 126 | * @return The new object point set index left off at. 127 | */ 128 | private int drawCalliper(Graphics2D g, List points, double angle, int index){ 129 | Point2D base = points.get(index % points.size()); 130 | while(index < points.size()){ 131 | if(ConvexUtil.angleFromVertical(points.get(index % points.size()), points.get((index + 1) % points.size())) >= angle){ 132 | base = points.get(index % points.size()); 133 | break; 134 | } 135 | index++; 136 | } 137 | 138 | angle += Math.PI * 0.5F; 139 | drawLine( 140 | g, 141 | base.getX(), 142 | base.getY(), 143 | base.getX() + Math.cos(angle), 144 | base.getY() + Math.sin(angle) 145 | ); 146 | 147 | return index; 148 | } 149 | 150 | /** 151 | * Draws an 'infinite' line segment through the two given points. 152 | * @param g The graphics context to use. 153 | * @param x1 The x-coordinate of the first point. 154 | * @param y1 The y-coordinate of the first point. 155 | * @param x2 The x-coordinate of the second point. 156 | * @param y2 The y-coordinate of the second point. 157 | */ 158 | private void drawLine(Graphics2D g, double x1, double y1, double x2, double y2){ 159 | if(x1 == x2){ 160 | g.draw(new Line2D.Double(x1, -Constants.PLAYFIELD_HEIGHT, x2, Constants.PLAYFIELD_HEIGHT * 2.0D)); 161 | }else{ 162 | double coef = (y2 - y1) / (x2 - x1); 163 | double base = y1 - x1 * coef; 164 | g.draw(new Line2D.Double(-10.0D * Constants.PLAYFIELD_WIDTH, base - coef * 10.0D * Constants.PLAYFIELD_WIDTH, Constants.PLAYFIELD_WIDTH * 11.0D, base + coef * 11.0D * Constants.PLAYFIELD_WIDTH)); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/animation/ClaimAnimation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.animation; 20 | 21 | import java.awt.Color; 22 | import java.awt.Graphics2D; 23 | import java.awt.RadialGradientPaint; 24 | import java.awt.geom.Point2D; 25 | 26 | import dev.roanh.convexmerger.game.ConvexObject; 27 | import dev.roanh.convexmerger.ui.Theme; 28 | import dev.roanh.convexmerger.ui.Theme.PlayerTheme; 29 | 30 | /** 31 | * Animation shown when claiming a single object. 32 | * @author Roan 33 | */ 34 | public class ClaimAnimation extends Animation{ 35 | /** 36 | * Number of milliseconds the animation plays for. 37 | */ 38 | private static final float DURATION = 250.0F; 39 | /** 40 | * Millisecond time this animation started. 41 | */ 42 | private long start; 43 | /** 44 | * The convex object that was claimed. 45 | */ 46 | private ConvexObject obj; 47 | /** 48 | * The point that was clicked to claim the convex object. 49 | */ 50 | private Point2D loc; 51 | /** 52 | * The largest distance to a vertex from the clicked point. 53 | */ 54 | private float dist = 0.0F; 55 | 56 | /** 57 | * Constructs a new claim animation for the given object. 58 | * @param selected The convex object that was claimed. 59 | * @param location The point that was clicked to claim the object. 60 | */ 61 | public ClaimAnimation(ConvexObject selected, Point2D location){ 62 | obj = selected; 63 | loc = location; 64 | start = System.currentTimeMillis(); 65 | for(Point2D p : selected.getPoints()){ 66 | dist = Math.max(dist, (float)location.distance(p)); 67 | } 68 | } 69 | 70 | @Override 71 | protected boolean render(Graphics2D g){ 72 | long elapsed = System.currentTimeMillis() - start; 73 | 74 | if(elapsed < DURATION){ 75 | g.setPaint(new RadialGradientPaint( 76 | loc, 77 | dist, 78 | new float[]{ 79 | 0.0F, 80 | Math.min(0.998F, (elapsed / DURATION) + 0.001F), 81 | Math.min(0.999F, 2.0F * (elapsed / DURATION) + 0.002F), 82 | 1.0F 83 | }, 84 | new Color[]{ 85 | obj.getOwner().getTheme().getBody(), 86 | obj.getOwner().getTheme().getBody(), 87 | PlayerTheme.UNOWNED.getBody(), 88 | PlayerTheme.UNOWNED.getBody() 89 | } 90 | )); 91 | }else{ 92 | g.setColor(Theme.getPlayerBody(obj)); 93 | } 94 | g.fill(obj.getShape()); 95 | 96 | g.setStroke(Theme.POLY_STROKE); 97 | g.setColor(elapsed * 2.0F > DURATION ? Theme.getPlayerOutline(obj) : PlayerTheme.UNOWNED.getOutline()); 98 | g.draw(obj.getShape()); 99 | 100 | return elapsed <= DURATION; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/animation/ExampleAnimation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.animation; 20 | 21 | import java.awt.AlphaComposite; 22 | import java.awt.Composite; 23 | import java.awt.Graphics2D; 24 | import java.awt.geom.Line2D; 25 | import java.util.Arrays; 26 | 27 | import dev.roanh.convexmerger.game.ConvexObject; 28 | import dev.roanh.convexmerger.player.Player; 29 | import dev.roanh.convexmerger.ui.Theme; 30 | import dev.roanh.convexmerger.ui.Theme.PlayerTheme; 31 | 32 | /** 33 | * The example animation shown on the information menu. 34 | * @author Roan 35 | */ 36 | public class ExampleAnimation extends Animation{ 37 | /** 38 | * Width of the animation. 39 | */ 40 | public static final int WIDTH = 600; 41 | /** 42 | * Height of the animation. 43 | */ 44 | public static final int HEIGHT = 300; 45 | /** 46 | * First player. 47 | */ 48 | private static final Player pink = new DummyPlayer(PlayerTheme.P1); 49 | /** 50 | * Second player. 51 | */ 52 | private static final Player blue = new DummyPlayer(PlayerTheme.P2); 53 | /** 54 | * Leftmost object. 55 | */ 56 | private static final ConvexObject first = new ConvexObject(106, 268, 45, 128, 176, 88, 187, 179); 57 | /** 58 | * Copy of the leftmost object that is always owned by the first player. 59 | */ 60 | private static final ConvexObject firstCopy = new ConvexObject(106, 268, 45, 128, 176, 88, 187, 179); 61 | /** 62 | * Middle object. 63 | */ 64 | private static final ConvexObject second = new ConvexObject(297, 209, 251, 116, 367, 127); 65 | /** 66 | * Rightmost object. 67 | */ 68 | private static final ConvexObject third = new ConvexObject(482, 225, 412, 124, 485, 40, 540, 92); 69 | /** 70 | * The result of merging the first and third object. 71 | */ 72 | private static final ConvexObject result; 73 | /** 74 | * Current animation state. 75 | */ 76 | private int state = 0; 77 | /** 78 | * Saved timestamp for animation. 79 | */ 80 | private long time; 81 | 82 | @Override 83 | protected boolean render(Graphics2D g){ 84 | switch(state){ 85 | default: 86 | case 0: 87 | first.setOwner(null); 88 | second.setOwner(null); 89 | third.setOwner(null); 90 | time = System.currentTimeMillis(); 91 | state = 1; 92 | //$FALL-THROUGH$ 93 | case 1: 94 | first.render(g); 95 | second.render(g); 96 | third.render(g); 97 | 98 | if(System.currentTimeMillis() - time > 1000L){ 99 | first.setOwner(pink); 100 | first.setAnimation(new ClaimAnimation(first, first.getCentroid())); 101 | state++; 102 | } 103 | break; 104 | case 2: 105 | second.render(g); 106 | third.render(g); 107 | 108 | if(first.hasAnimation()){ 109 | first.runAnimation(g); 110 | }else{ 111 | first.render(g); 112 | time = System.currentTimeMillis(); 113 | state++; 114 | } 115 | break; 116 | case 3: 117 | first.render(g); 118 | second.render(g); 119 | third.render(g); 120 | 121 | if(System.currentTimeMillis() - time > 1000L){ 122 | second.setOwner(blue); 123 | second.setAnimation(new ClaimAnimation(second, second.getCentroid())); 124 | state++; 125 | } 126 | break; 127 | case 4: 128 | first.render(g); 129 | third.render(g); 130 | 131 | if(second.hasAnimation()){ 132 | second.runAnimation(g); 133 | }else{ 134 | second.render(g); 135 | time = System.currentTimeMillis(); 136 | state++; 137 | } 138 | break; 139 | case 5: 140 | first.render(g); 141 | second.render(g); 142 | third.render(g); 143 | 144 | if(System.currentTimeMillis() - time > 1000L){ 145 | time = System.currentTimeMillis(); 146 | state++; 147 | } 148 | break; 149 | case 6: 150 | first.render(g); 151 | second.render(g); 152 | third.render(g); 153 | 154 | g.setColor(pink.getTheme().getOutline()); 155 | g.setStroke(Theme.HELPER_STROKE); 156 | long elapsed = System.currentTimeMillis() - time; 157 | double f = Math.min(1.0D, elapsed / 1000.0D); 158 | g.draw(new Line2D.Double(106.0D, 268.0D, 106.0D + (482.0D - 106.0D) * f, 268.0D + (225.0D - 268.0D) * f)); 159 | g.draw(new Line2D.Double(176.0D, 88.0D, 176.0D + (485.0D - 176.0D) * f, 88.0D + (40.0D - 88.0D) * f)); 160 | 161 | if(elapsed > 1000){ 162 | time = System.currentTimeMillis(); 163 | state++; 164 | } 165 | break; 166 | case 7: 167 | first.render(g); 168 | second.render(g); 169 | third.render(g); 170 | 171 | Composite composite = g.getComposite(); 172 | g.setStroke(Theme.HELPER_STROKE); 173 | g.setColor(pink.getTheme().getOutline()); 174 | long ms = System.currentTimeMillis() - time; 175 | g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, Math.max(0.0F, 1.0F - Math.min(1.0F, ms / 500.0F)))); 176 | g.draw(new Line2D.Double(106.0D, 268.0D, 482.0D, 225.0D)); 177 | g.draw(new Line2D.Double(176.0D, 88.0D, 485.0D, 40.0D)); 178 | g.setComposite(composite); 179 | 180 | if(ms > 500){ 181 | result.setAnimation(new MergeAnimation(firstCopy, third, result, Arrays.asList(second))); 182 | state++; 183 | } 184 | break; 185 | case 8: 186 | if(result.hasAnimation()){ 187 | result.runAnimation(g); 188 | }else{ 189 | result.render(g); 190 | time = System.currentTimeMillis(); 191 | state++; 192 | } 193 | break; 194 | case 9: 195 | result.render(g); 196 | if(System.currentTimeMillis() - time > 1500L){ 197 | first.setOwner(null); 198 | second.setOwner(null); 199 | third.setOwner(null); 200 | time = System.currentTimeMillis(); 201 | state++; 202 | } 203 | break; 204 | case 10: 205 | composite = g.getComposite(); 206 | g.setStroke(Theme.HELPER_STROKE); 207 | g.setColor(pink.getTheme().getOutline()); 208 | long spent = System.currentTimeMillis() - time; 209 | g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, Math.max(0.0F, 1.0F - Math.min(1.0F, spent / 1000.0F)))); 210 | result.render(g); 211 | g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, Math.max(0.0F, Math.min(1.0F, spent / 1000.0F)))); 212 | first.render(g); 213 | second.render(g); 214 | third.render(g); 215 | g.setComposite(composite); 216 | 217 | if(spent > 1000){ 218 | state = 0; 219 | } 220 | break; 221 | } 222 | 223 | return true; 224 | } 225 | 226 | static{ 227 | result = first.merge(third); 228 | result.setOwner(pink); 229 | firstCopy.setOwner(pink); 230 | } 231 | 232 | /** 233 | * Dummy player instance to reuse convex object logic. 234 | * @author Roan 235 | */ 236 | private static class DummyPlayer extends Player{ 237 | 238 | /** 239 | * Constructs a new dummy player with the given theme. 240 | * @param theme The theme for this player. 241 | */ 242 | protected DummyPlayer(PlayerTheme theme){ 243 | super(true, true, null); 244 | this.init(null, theme); 245 | } 246 | 247 | @Override 248 | public boolean executeMove(){ 249 | return false; 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/animation/ProxyAnimation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.animation; 20 | 21 | import java.awt.Graphics2D; 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | 26 | /** 27 | * Animation that renders the object it is initialised with. 28 | * @author Roan 29 | * @see RenderableObject 30 | */ 31 | public class ProxyAnimation extends Animation{ 32 | /** 33 | * List of objects to render. 34 | */ 35 | private List objects; 36 | 37 | /** 38 | * Constructs a new proxy animation with the 39 | * given list of objects to render. 40 | * @param objs The list of objects to render. 41 | */ 42 | public ProxyAnimation(RenderableObject... objs){ 43 | objects = Arrays.asList(objs); 44 | } 45 | 46 | /** 47 | * Constructs a new proxy animation with the 48 | * given objects to render. 49 | * @param obj1 The first object to render. 50 | * @param obj2 The second object to render. 51 | * @param other Other objects to render. 52 | */ 53 | public ProxyAnimation(RenderableObject obj1, RenderableObject obj2, List other){ 54 | objects = new ArrayList(2 + other.size()); 55 | objects.add(obj1); 56 | objects.add(obj2); 57 | objects.addAll(other); 58 | } 59 | 60 | @Override 61 | protected boolean render(Graphics2D g){ 62 | for(RenderableObject obj : objects){ 63 | obj.renderOrAnimate(g); 64 | } 65 | 66 | return true; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/animation/RenderableObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.animation; 20 | 21 | import java.awt.Graphics2D; 22 | 23 | /** 24 | * Represents an object that can be rendered and animated. 25 | * @author Roan 26 | * @see Animation 27 | */ 28 | public abstract class RenderableObject{ 29 | /** 30 | * The active animation for this object. 31 | */ 32 | private transient Animation animation = null; 33 | 34 | /** 35 | * Gets the animation for this object. 36 | * @return The animation for this object. 37 | */ 38 | public Animation getAnimation(){ 39 | return animation; 40 | } 41 | 42 | /** 43 | * Checks if this convex object has an active animation. 44 | * @return True if this convex object has an active animation. 45 | */ 46 | public boolean hasAnimation(){ 47 | return animation != null; 48 | } 49 | 50 | /** 51 | * Sets the active animation for this convex object. 52 | * @param animation The new active animation. 53 | */ 54 | public void setAnimation(Animation animation){ 55 | this.animation = animation; 56 | } 57 | 58 | /** 59 | * Renders the animation for this convex object 60 | * using the given graphics instance. 61 | * @param g The graphics instance to use. 62 | * @return True if the animation still has frames 63 | * remaining, false otherwise. 64 | */ 65 | public boolean runAnimation(Graphics2D g){ 66 | if(animation.run(g)){ 67 | return true; 68 | }else{ 69 | animation = null; 70 | return false; 71 | } 72 | } 73 | 74 | /** 75 | * Renders this object or runs its animation 76 | * if it has one set. 77 | * @param g The graphics context to use. 78 | */ 79 | public void renderOrAnimate(Graphics2D g){ 80 | if(hasAnimation()){ 81 | runAnimation(g); 82 | }else{ 83 | render(g); 84 | } 85 | } 86 | 87 | /** 88 | * Renders this object. 89 | * @param g The graphics context to use. 90 | */ 91 | public abstract void render(Graphics2D g); 92 | } 93 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/animation/ScoreAnimation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.animation; 20 | 21 | import java.awt.Graphics2D; 22 | 23 | import dev.roanh.convexmerger.player.Player; 24 | import dev.roanh.convexmerger.ui.Theme; 25 | 26 | /** 27 | * Animation that shows the player score increasing or decreasing. 28 | * @author Roan 29 | */ 30 | public class ScoreAnimation extends Animation{ 31 | /** 32 | * Number of score points to add each millisecond. 33 | */ 34 | private static final int SCORE_PER_MS = 51; 35 | /** 36 | * Player whose score to display. 37 | */ 38 | private Player player; 39 | /** 40 | * Current area claimed by the player as 41 | * shown by the animation. 42 | */ 43 | private double area; 44 | /** 45 | * Timestamp the last animation frame was rendered. 46 | */ 47 | private long last = -1L; 48 | 49 | /** 50 | * Constructs a new score animation for the given player. 51 | * @param player The player whose score to animate. 52 | */ 53 | public ScoreAnimation(Player player){ 54 | this.player = player; 55 | area = player.getArea(); 56 | } 57 | 58 | @Override 59 | protected boolean render(Graphics2D g){ 60 | if(last == -1L){ 61 | last = System.currentTimeMillis(); 62 | } 63 | 64 | long time = System.currentTimeMillis(); 65 | if(area <= player.getArea()){ 66 | area = Math.min(player.getArea(), area + (time - last) * SCORE_PER_MS); 67 | }else{ 68 | area = Math.max(player.getArea(), area - (time - last) * SCORE_PER_MS); 69 | } 70 | 71 | g.drawString(Theme.formatScore(area), 0, 0); 72 | 73 | if(Double.compare(area, player.getArea()) != 0){ 74 | last = time; 75 | }else{ 76 | last = -1L; 77 | } 78 | 79 | return true; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/game/ClaimResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.game; 20 | 21 | import dev.roanh.convexmerger.ui.MessageDialog; 22 | 23 | /** 24 | * Object returned to describe the outcome of an attempted claim. 25 | * @author Roan 26 | */ 27 | public class ClaimResult{ 28 | /** 29 | * Result returned when the claim did not affect the game state. 30 | * This claim result has no message and no result. 31 | */ 32 | public static final ClaimResult EMPTY = new ClaimResult(); 33 | /** 34 | * Message returned for invalid moves. 35 | */ 36 | private MessageDialog message = null; 37 | /** 38 | * The newly claimed or created convex object 39 | * as a result of the claim that was made. 40 | */ 41 | private ConvexObject result = null; 42 | 43 | /** 44 | * Prevent direct instantiation. 45 | */ 46 | private ClaimResult(){ 47 | } 48 | 49 | /** 50 | * Constructs a new claim result with the 51 | * given feedback message. 52 | * @param msg The feedback message. 53 | */ 54 | private ClaimResult(MessageDialog msg){ 55 | message = msg; 56 | } 57 | 58 | /** 59 | * Constructs a new claim result with the 60 | * given claim result. 61 | * @param obj The newly claimed or created object. 62 | */ 63 | private ClaimResult(ConvexObject obj){ 64 | result = obj; 65 | } 66 | 67 | /** 68 | * Checks if this result has a feedback message. 69 | * @return True if this result has a feedback message. 70 | */ 71 | public boolean hasMessage(){ 72 | return message != null; 73 | } 74 | 75 | /** 76 | * Gets the feedback message for this result. 77 | * @return The feedback message or null 78 | * if there is no feedback message. 79 | * @see #hasMessage() 80 | */ 81 | public MessageDialog getMessage(){ 82 | return message; 83 | } 84 | 85 | /** 86 | * Checks if this claim result has a game state result. 87 | * @return True if this result has a game state result. 88 | */ 89 | public boolean hasResult(){ 90 | return result != null; 91 | } 92 | 93 | /** 94 | * Gets the game state result for this claim result. 95 | * This is the convex object that was claimed or created. 96 | * @return The game state result. 97 | */ 98 | public ConvexObject getResult(){ 99 | return result; 100 | } 101 | 102 | /** 103 | * Constructs a new claim result with the 104 | * given feedback message. 105 | * @param msg The feedback message. 106 | * @return The newly constructed claim result. 107 | */ 108 | public static final ClaimResult of(MessageDialog msg){ 109 | return new ClaimResult(msg); 110 | } 111 | 112 | /** 113 | * Constructs a new claim result with the 114 | * given claim result. 115 | * @param obj The newly claimed or created object. 116 | * @return The newly constructed claim result. 117 | */ 118 | public static final ClaimResult of(ConvexObject obj){ 119 | return new ClaimResult(obj); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/game/GameConstructor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.game; 20 | 21 | /** 22 | * A simple interface used to execute the longer 23 | * calculations required to start a game on the 24 | * main game thread. 25 | * @author Roan 26 | */ 27 | public abstract interface GameConstructor{ 28 | 29 | /** 30 | * Constructs the game state for the configured game. 31 | * @return The game state. 32 | */ 33 | public abstract GameState create(); 34 | } 35 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/game/GameStateListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.game; 20 | 21 | import java.util.List; 22 | 23 | import dev.roanh.convexmerger.player.Player; 24 | 25 | /** 26 | * Interface that receives game state updates. 27 | * @author Roan 28 | */ 29 | public abstract interface GameStateListener{ 30 | 31 | /** 32 | * Called when a player claims a new object. 33 | * @param player The player that made the claim. 34 | * @param obj The object that was claimed. 35 | */ 36 | public abstract void claim(Player player, ConvexObject obj); 37 | 38 | /** 39 | * Called when a player performs a merge. 40 | * @param player The player that performed the merge. 41 | * @param source The object the merge was started from. 42 | * @param target The target object of the merge. 43 | * @param result The object resulting from the merge. 44 | * @param absorbed The objects absorbed in the merge. 45 | * @throws InterruptedException When the player was 46 | * interrupted while making its move. Signalling 47 | * that the game was aborted. 48 | */ 49 | public abstract void merge(Player player, ConvexObject source, ConvexObject target, ConvexObject result, List absorbed) throws InterruptedException; 50 | 51 | /** 52 | * Called when the game ends. 53 | */ 54 | public abstract void end(); 55 | 56 | /** 57 | * Called when the game is aborted (forcefully terminated). 58 | */ 59 | public abstract void abort(); 60 | } -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/game/Identity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.game; 20 | 21 | /** 22 | * Interface for objects that can be 23 | * identified via some unique ID. 24 | * @author Roan 25 | */ 26 | public abstract interface Identity{ 27 | 28 | /** 29 | * Gets the ID of this entity. 30 | * @return The ID of this entity. 31 | */ 32 | public abstract int getID(); 33 | 34 | /** 35 | * Sets the ID of this entity. 36 | * @param id The new ID of this 37 | * entity, must be unique. 38 | */ 39 | public abstract void setID(int id); 40 | } 41 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/ClientConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net; 20 | 21 | import java.io.IOException; 22 | import java.net.Socket; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | import dev.roanh.convexmerger.Constants; 27 | import dev.roanh.convexmerger.game.ConvexObject; 28 | import dev.roanh.convexmerger.game.GameState; 29 | import dev.roanh.convexmerger.game.GameStateListener; 30 | import dev.roanh.convexmerger.net.packet.Packet; 31 | import dev.roanh.convexmerger.net.packet.PacketGameEnd; 32 | import dev.roanh.convexmerger.net.packet.PacketGameInit; 33 | import dev.roanh.convexmerger.net.packet.PacketPlayerJoin; 34 | import dev.roanh.convexmerger.net.packet.PacketPlayerJoinAccept; 35 | import dev.roanh.convexmerger.net.packet.PacketPlayerJoinReject; 36 | import dev.roanh.convexmerger.net.packet.PacketPlayerJoinReject.RejectReason; 37 | import dev.roanh.convexmerger.net.packet.PacketPlayerMove; 38 | import dev.roanh.convexmerger.net.packet.PacketRegistry; 39 | import dev.roanh.convexmerger.player.Player; 40 | import dev.roanh.convexmerger.player.RemotePlayer; 41 | 42 | /** 43 | * Represents a client connection to a remote game server. 44 | * @author Roan 45 | */ 46 | public class ClientConnection extends Connection implements GameStateListener{ 47 | /** 48 | * The local player that wants to join. 49 | */ 50 | private Player self; 51 | /** 52 | * The reason a server connection could not be established (if any). 53 | */ 54 | private RejectReason failReason; 55 | 56 | /** 57 | * Constructs a new client connection from the given socket 58 | * connection and with the given local player. 59 | * @param socket The server connection. 60 | * @param self The local player instance. 61 | * @throws IOException When an IOException occurs. 62 | */ 63 | private ClientConnection(Socket socket, Player self) throws IOException{ 64 | super(socket); 65 | this.self = self; 66 | } 67 | 68 | /** 69 | * Checks if this client was connected to the server. 70 | * @return True if this client was connected successfully. 71 | */ 72 | public boolean isConnected(){ 73 | return failReason == null; 74 | } 75 | 76 | /** 77 | * Gets the reason why the connection to the server failed (if any). 78 | * @return The reason a server connection failed. 79 | */ 80 | public RejectReason getRejectReason(){ 81 | return failReason; 82 | } 83 | 84 | /** 85 | * Gets the gamestate for the remote game. This method blocks 86 | * until the game is started and the game state sent. 87 | * @return The remote game state. 88 | * @throws IOException When an IOException occurs. 89 | */ 90 | public GameState getGameState() throws IOException{ 91 | Packet recv = readPacket(); 92 | 93 | if(recv.getRegisteryType() != PacketRegistry.GAME_INIT){ 94 | return null; 95 | } 96 | 97 | PacketGameInit data = (PacketGameInit)recv; 98 | 99 | List players = new ArrayList(4); 100 | for(PlayerProxy player : data.getPlayers()){ 101 | if(player.getID() == self.getID()){ 102 | players.add(self); 103 | }else{ 104 | RemotePlayer remote = new RemotePlayer(this, player.isAI(), player.getName()); 105 | remote.setID(player.getID()); 106 | players.add(remote); 107 | } 108 | } 109 | 110 | GameState state = new GameState(data.getObjects(), data.getSeed(), players); 111 | state.registerStateListener(this); 112 | return state; 113 | } 114 | 115 | @Override 116 | public void claim(Player player, ConvexObject obj){ 117 | if(player.isLocal()){ 118 | try{ 119 | sendPacket(new PacketPlayerMove(player, obj)); 120 | }catch(IOException e){ 121 | close(); 122 | } 123 | } 124 | } 125 | 126 | @Override 127 | public void merge(Player player, ConvexObject source, ConvexObject target, ConvexObject result, List absorbed){ 128 | if(player.isLocal()){ 129 | try{ 130 | sendPacket(new PacketPlayerMove(player, source, target)); 131 | }catch(IOException e){ 132 | close(); 133 | } 134 | } 135 | } 136 | 137 | @Override 138 | public void end(){ 139 | if(!isClosed()){ 140 | try{ 141 | sendPacket(new PacketGameEnd()); 142 | }catch(IOException e){ 143 | close(); 144 | } 145 | } 146 | } 147 | 148 | @Override 149 | public void abort(){ 150 | close(); 151 | } 152 | 153 | /** 154 | * Attempts to establish a new multiplayer connection 155 | * to the given host and with the given local player. 156 | * @param host The host to connect to. 157 | * @param player The local player joining. 158 | * @return The remote connection, possibly containing 159 | * a reason the connection failed. 160 | * @throws IOException When an IOException occured. 161 | */ 162 | public static final ClientConnection connect(String host, Player player) throws IOException{ 163 | ClientConnection con = new ClientConnection(new Socket(host, Constants.PORT), player); 164 | con.sendPacket(new PacketPlayerJoin(player.getName(), Constants.VERSION)); 165 | 166 | Packet recv = con.readPacket(); 167 | if(recv.getRegisteryType() != PacketRegistry.PLAYER_JOIN_ACCEPT){ 168 | if(recv.getRegisteryType() == PacketRegistry.PLAYER_JOIN_REJECT){ 169 | con.failReason = ((PacketPlayerJoinReject)recv).getReason(); 170 | }else{ 171 | con.failReason = RejectReason.UNKNOWN; 172 | } 173 | con.close(); 174 | return con; 175 | } 176 | 177 | player.setID(((PacketPlayerJoinAccept)recv).getID()); 178 | return con; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/Connection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net; 20 | 21 | import java.io.IOException; 22 | import java.io.ObjectInputStream; 23 | import java.io.ObjectOutputStream; 24 | import java.net.Socket; 25 | 26 | import dev.roanh.convexmerger.net.packet.Packet; 27 | 28 | /** 29 | * Base connection class for a server client connection. 30 | * @author Roan 31 | */ 32 | public class Connection{ 33 | /** 34 | * The input stream for this connection. 35 | */ 36 | private ObjectInputStream in; 37 | /** 38 | * The output stream for this connection. 39 | */ 40 | private ObjectOutputStream out; 41 | /** 42 | * The socket for this connection. 43 | */ 44 | private Socket socket; 45 | 46 | /** 47 | * Constructs a new connection from the given socket. 48 | * @param socket The socket for the connection. 49 | * @throws IOException When some IOException occurs. 50 | */ 51 | protected Connection(Socket socket) throws IOException{ 52 | this.socket = socket; 53 | out = new ObjectOutputStream(socket.getOutputStream()); 54 | in = new ObjectInputStream(socket.getInputStream()); 55 | } 56 | 57 | /** 58 | * Reads a new packet from the connection. 59 | * @return The read packet or null 60 | * when something unexpected happened or 61 | * the connection is closed. 62 | * @throws IOException When some IOException occurs. 63 | */ 64 | public Packet readPacket() throws IOException{ 65 | try{ 66 | if(!isClosed()){ 67 | Object data = in.readObject(); 68 | if(data instanceof Packet){ 69 | return (Packet)data; 70 | }else{ 71 | //bad client 72 | close(); 73 | return null; 74 | } 75 | }else{ 76 | return null; 77 | } 78 | }catch(ClassNotFoundException e){ 79 | //all classes should be found otherwise the connection is bad 80 | close(); 81 | return null; 82 | } 83 | } 84 | 85 | /** 86 | * Sends a new packet over this connection. 87 | * @param packet The packet to send. 88 | * @throws IOException When some IOException occurs. 89 | */ 90 | public void sendPacket(Packet packet) throws IOException{ 91 | out.writeObject(packet); 92 | out.flush(); 93 | } 94 | 95 | /** 96 | * Checks if this connection is closed. 97 | * @return True if this connection is closed. 98 | */ 99 | public boolean isClosed(){ 100 | return socket.isClosed(); 101 | } 102 | 103 | /** 104 | * Closes this connection. 105 | */ 106 | public void close(){ 107 | try{ 108 | in.close(); 109 | out.close(); 110 | socket.close(); 111 | }catch(IOException e){ 112 | //not very relevant, we were disconnecting anyway 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/InternalServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net; 20 | 21 | import java.io.IOException; 22 | import java.net.ServerSocket; 23 | import java.net.Socket; 24 | import java.util.HashMap; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.Map.Entry; 28 | import java.util.concurrent.ExecutorService; 29 | import java.util.concurrent.Executors; 30 | 31 | import dev.roanh.convexmerger.Constants; 32 | import dev.roanh.convexmerger.game.ConvexObject; 33 | import dev.roanh.convexmerger.game.GameConstructor; 34 | import dev.roanh.convexmerger.game.GameState; 35 | import dev.roanh.convexmerger.game.GameStateListener; 36 | import dev.roanh.convexmerger.game.PlayfieldGenerator; 37 | import dev.roanh.convexmerger.net.packet.Packet; 38 | import dev.roanh.convexmerger.net.packet.PacketPlayerJoinReject; 39 | import dev.roanh.convexmerger.net.packet.PacketGameInit; 40 | import dev.roanh.convexmerger.net.packet.PacketPlayerJoin; 41 | import dev.roanh.convexmerger.net.packet.PacketPlayerJoinAccept; 42 | import dev.roanh.convexmerger.net.packet.PacketPlayerMove; 43 | import dev.roanh.convexmerger.net.packet.PacketRegistry; 44 | import dev.roanh.convexmerger.net.packet.PacketPlayerJoinReject.RejectReason; 45 | import dev.roanh.convexmerger.player.Player; 46 | import dev.roanh.convexmerger.player.RemotePlayer; 47 | 48 | /** 49 | * Server responsible for hosting multiplayer games. 50 | * @author Roan 51 | */ 52 | public final class InternalServer implements GameStateListener{ 53 | /** 54 | * Thread executing the main server logic. 55 | */ 56 | private ServerThread thread = new ServerThread(); 57 | /** 58 | * Listener to send server events to. 59 | */ 60 | private InternalServerListener handler; 61 | 62 | /** 63 | * Constructs a new server with the given listener. 64 | * @param handler The event listener. 65 | */ 66 | public InternalServer(InternalServerListener handler){ 67 | this.handler = handler; 68 | thread.start(); 69 | } 70 | 71 | /** 72 | * Immediately shuts down this server and closes 73 | * all the connections it had open. 74 | */ 75 | public void shutdown(){ 76 | thread.shutdown(); 77 | thread.close(); 78 | } 79 | 80 | /** 81 | * Gets the game constructor required to start 82 | * a game with the given set of players and with 83 | * the given playfield generator. After this 84 | * is called no new connections will be accepted anymore. 85 | * @param players The list of participating players. 86 | * @param gen The playfield generator. 87 | * @param showDecomp Whether to show the vertical decomposition 88 | * from the start of the game. 89 | * @return The game state constructor for the game. 90 | */ 91 | public GameConstructor startGame(List players, PlayfieldGenerator gen, boolean showDecomp){ 92 | thread.shutdown(); 93 | 94 | return ()->{ 95 | GameState state = new GameState(gen, players); 96 | state.getVerticalDecomposition().setAnimated(showDecomp); 97 | thread.broadCast(new PacketGameInit(state.getObjects(), state.getSeed(), state.getPlayers())); 98 | state.registerStateListener(this); 99 | return state; 100 | }; 101 | } 102 | 103 | @Override 104 | public void claim(Player player, ConvexObject obj){ 105 | thread.broadCast(player, new PacketPlayerMove(player, obj)); 106 | } 107 | 108 | @Override 109 | public void merge(Player player, ConvexObject source, ConvexObject target, ConvexObject result, List absorbed){ 110 | thread.broadCast(player, new PacketPlayerMove(player, source, target)); 111 | } 112 | 113 | @Override 114 | public void end(){ 115 | thread.broadCast(new PacketPlayerMove()); 116 | thread.close(); 117 | } 118 | 119 | @Override 120 | public void abort(){ 121 | thread.close(); 122 | } 123 | 124 | /** 125 | * Main thread managing all connections and initially 126 | * responsible for accepting joining players. 127 | * @author Roan 128 | */ 129 | private class ServerThread extends Thread{ 130 | /** 131 | * Executor service used to handle incoming connections. 132 | */ 133 | private ExecutorService executor = Executors.newFixedThreadPool(4); 134 | /** 135 | * Main server socket. 136 | */ 137 | private ServerSocket server; 138 | /** 139 | * Map of active connections indexed by player ID. 140 | */ 141 | private Map connections = new HashMap(); 142 | /** 143 | * Whether the server is running and accepting new connections. 144 | */ 145 | private volatile boolean running = false; 146 | /** 147 | * The ID to assign to the next valid player. ID 1 is reseved 148 | * for the player hosting the game. 149 | */ 150 | private volatile int nextID = 2; 151 | 152 | /** 153 | * Constructs a new server thread. 154 | */ 155 | private ServerThread(){ 156 | this.setName("InternalServerThread"); 157 | this.setDaemon(true); 158 | } 159 | 160 | /** 161 | * Closes all connections held by this server thread. 162 | */ 163 | private void close(){ 164 | for(Connection con : connections.values()){ 165 | con.close(); 166 | } 167 | } 168 | 169 | /** 170 | * Broadcasts a packet to all connected players 171 | * except for the given source player. 172 | * @param source The player to exclude. 173 | * @param packet The packet to send. 174 | */ 175 | private void broadCast(Player source, Packet packet){ 176 | for(Entry entry : connections.entrySet()){ 177 | if(entry.getKey() != source.getID() && !entry.getValue().isClosed()){ 178 | try{ 179 | entry.getValue().sendPacket(packet); 180 | }catch(IOException e){ 181 | //will be detected on read 182 | entry.getValue().close(); 183 | } 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * Broadcasts a packet to all connected players. 190 | * @param packet The packet to send. 191 | */ 192 | private void broadCast(Packet packet){ 193 | for(Connection con : connections.values()){ 194 | if(!con.isClosed()){ 195 | try{ 196 | con.sendPacket(packet); 197 | }catch(IOException e){ 198 | //will be detected on read 199 | con.close(); 200 | } 201 | } 202 | } 203 | } 204 | 205 | /** 206 | * Shuts down the part of this server responsible 207 | * for accepting incoming players. Connected players 208 | * will still remain connected. 209 | */ 210 | private void shutdown(){ 211 | running = false; 212 | try{ 213 | if(server != null){ 214 | server.close(); 215 | } 216 | }catch(IOException e){ 217 | //not very important, just reject connections 218 | } 219 | } 220 | 221 | /** 222 | * Handles an incoming client connection. 223 | * @param socket The client socket. 224 | */ 225 | private void handleClient(Socket socket){ 226 | try{ 227 | Connection con = new Connection(socket); 228 | Packet packet = con.readPacket(); 229 | 230 | if(packet == null || packet.getRegisteryType() != PacketRegistry.PLAYER_JOIN){ 231 | //bad client 232 | con.close(); 233 | return; 234 | } 235 | 236 | PacketPlayerJoin data = (PacketPlayerJoin)packet; 237 | if(!data.getVersion().equals(Constants.VERSION)){ 238 | con.sendPacket(new PacketPlayerJoinReject(RejectReason.VERSION_MISMATCH)); 239 | con.close(); 240 | return; 241 | } 242 | 243 | synchronized(handler){ 244 | if(nextID <= 4 && running){ 245 | Player player = new RemotePlayer(con, false, data.getName()); 246 | player.setID(nextID++); 247 | con.sendPacket(new PacketPlayerJoinAccept(player.getID())); 248 | connections.put(player.getID(), con); 249 | handler.handlePlayerJoin(player); 250 | }else{ 251 | con.sendPacket(new PacketPlayerJoinReject(RejectReason.FULL)); 252 | } 253 | } 254 | }catch(Throwable t){ 255 | try{ 256 | socket.close(); 257 | }catch(IOException e1){ 258 | //not really relevant, client is bad 259 | } 260 | } 261 | } 262 | 263 | @Override 264 | public void run(){ 265 | try{ 266 | running = true; 267 | server = new ServerSocket(Constants.PORT); 268 | while(running){ 269 | final Socket s = server.accept(); 270 | executor.execute(()->handleClient(s)); 271 | } 272 | }catch(IOException e){ 273 | if(running){ 274 | handler.handleException(e); 275 | } 276 | } 277 | } 278 | } 279 | 280 | /** 281 | * Interface that receives server events. 282 | * @author Roan 283 | */ 284 | public static abstract interface InternalServerListener{ 285 | 286 | /** 287 | * Called when a new player joined the server. 288 | * @param player The player that joined. 289 | */ 290 | public abstract void handlePlayerJoin(Player player); 291 | 292 | /** 293 | * Called when the server encountered a fatal exception 294 | * while accepting incoming player connections. 295 | * @param e The exception that occurred. 296 | */ 297 | public abstract void handleException(Exception e); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/PlayerProxy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net; 20 | 21 | import java.io.Serializable; 22 | import java.util.Objects; 23 | 24 | import dev.roanh.convexmerger.player.Player; 25 | import dev.roanh.convexmerger.player.RemotePlayer; 26 | 27 | /** 28 | * Proxy representative of players across a connection. 29 | * @author Roan 30 | */ 31 | public class PlayerProxy implements Serializable{ 32 | /** 33 | * Serial ID. 34 | */ 35 | private static final long serialVersionUID = -1220844320624428578L; 36 | /** 37 | * The ID of the player this proxy represents. 38 | */ 39 | private int id; 40 | /** 41 | * The name of the player this proxy represents. 42 | */ 43 | private String name; 44 | /** 45 | * Whether the player this proxy represents is an AI. 46 | */ 47 | private boolean ai; 48 | /** 49 | * Whether the connection to the player this proxy represents was lost. 50 | */ 51 | private boolean lost; 52 | 53 | /** 54 | * Constructs a new player proxy from the given player. 55 | * @param player The player to construct a proxy for. 56 | */ 57 | public PlayerProxy(Player player){ 58 | id = player.getID(); 59 | name = player.getName(); 60 | ai = player.isAI(); 61 | if(!player.isLocal() && player instanceof RemotePlayer){ 62 | lost = ((RemotePlayer)player).isLost(); 63 | }else{ 64 | lost = false; 65 | } 66 | } 67 | 68 | /** 69 | * Checks if the connection to the player was lost. 70 | * @return True if the connection to the player was lost. 71 | */ 72 | public boolean isLost(){ 73 | return lost; 74 | } 75 | 76 | /** 77 | * Gets the ID of the player. 78 | * @return The ID of the player. 79 | */ 80 | public int getID(){ 81 | return id; 82 | } 83 | 84 | /** 85 | * Gets the name of the player. 86 | * @return The name of the player. 87 | */ 88 | public String getName(){ 89 | return name; 90 | } 91 | 92 | /** 93 | * Checks if the player is an AI. 94 | * @return True if the player is an AI. 95 | */ 96 | public boolean isAI(){ 97 | return ai; 98 | } 99 | 100 | @Override 101 | public boolean equals(Object other){ 102 | return other instanceof PlayerProxy ? ((PlayerProxy)other).id == id : false; 103 | } 104 | 105 | @Override 106 | public int hashCode(){ 107 | return Objects.hash(id); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/packet/Packet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net.packet; 20 | 21 | import java.io.Serializable; 22 | 23 | /** 24 | * Interface for packets that get sent across the network. 25 | * @author Roan 26 | */ 27 | public abstract interface Packet extends Serializable{ 28 | 29 | /** 30 | * The registry type of this packet. 31 | * @return The type of this packet. 32 | */ 33 | public abstract PacketRegistry getRegisteryType(); 34 | } 35 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/packet/PacketGameEnd.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net.packet; 20 | 21 | /** 22 | * Packet sent when the game ends. 23 | * @author Roan 24 | */ 25 | public class PacketGameEnd implements Packet{ 26 | /** 27 | * Serial ID. 28 | */ 29 | private static final long serialVersionUID = -5196039665681435523L; 30 | 31 | @Override 32 | public PacketRegistry getRegisteryType(){ 33 | return PacketRegistry.GAME_END; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/packet/PacketGameInit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net.packet; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | import dev.roanh.convexmerger.game.ConvexObject; 25 | import dev.roanh.convexmerger.net.PlayerProxy; 26 | import dev.roanh.convexmerger.player.Player; 27 | 28 | /** 29 | * Packet sent when the game starts. 30 | * @author Roan 31 | */ 32 | public class PacketGameInit implements Packet{ 33 | /** 34 | * Serial ID. 35 | */ 36 | private static final long serialVersionUID = 3786951584716643604L; 37 | /** 38 | * The convex objects for this game. 39 | */ 40 | private final List objects; 41 | /** 42 | * The game seed. 43 | */ 44 | private final String seed; 45 | /** 46 | * The players participating in this game. 47 | */ 48 | private final List players = new ArrayList(4); 49 | 50 | /** 51 | * Constructs a new game init packet with the given objects and players. 52 | * @param objects The game objects. 53 | * @param seed The game seed. 54 | * @param players The participating players. 55 | */ 56 | public PacketGameInit(List objects, String seed, List players){ 57 | this.objects = objects; 58 | this.seed = seed; 59 | players.forEach(player->this.players.add(player.getProxy())); 60 | } 61 | 62 | /** 63 | * Gets the game seed. 64 | * @return The game seed. 65 | */ 66 | public String getSeed(){ 67 | return seed; 68 | } 69 | 70 | /** 71 | * Gets the objects for this game. 72 | * @return The game objects. 73 | */ 74 | public List getObjects(){ 75 | return objects; 76 | } 77 | 78 | /** 79 | * Gets the players for this game. 80 | * @return The participating players. 81 | */ 82 | public List getPlayers(){ 83 | return players; 84 | } 85 | 86 | @Override 87 | public PacketRegistry getRegisteryType(){ 88 | return PacketRegistry.GAME_INIT; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/packet/PacketPlayerJoin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net.packet; 20 | 21 | /** 22 | * Packet sent when a players wants to join a game. 23 | * @author Roan 24 | */ 25 | public class PacketPlayerJoin implements Packet{ 26 | /** 27 | * Serial ID. 28 | */ 29 | private static final long serialVersionUID = -6196877761115657750L; 30 | /** 31 | * The name of the player that wants to join. 32 | */ 33 | private final String name; 34 | /** 35 | * The version of the game the player that wants to join is running. 36 | */ 37 | private final String version; 38 | 39 | /** 40 | * Constructs a new player join packet. 41 | * @param name The name of the player that wants to join. 42 | * @param version The version of the game the player is running. 43 | */ 44 | public PacketPlayerJoin(String name, String version){ 45 | this.name = name; 46 | this.version = version; 47 | } 48 | 49 | /** 50 | * Gets the name of the player that wants to join. 51 | * @return The name of the player that wants to join. 52 | */ 53 | public String getName(){ 54 | return name; 55 | } 56 | 57 | /** 58 | * Gets the version of the game the player that wants to 59 | * join is currently running. 60 | * @return The game version the player runs. 61 | */ 62 | public String getVersion(){ 63 | return version; 64 | } 65 | 66 | @Override 67 | public PacketRegistry getRegisteryType(){ 68 | return PacketRegistry.PLAYER_JOIN; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/packet/PacketPlayerJoinAccept.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net.packet; 20 | 21 | /** 22 | * Packet sent when a player is accepted into a game. 23 | * @author Roan 24 | */ 25 | public class PacketPlayerJoinAccept implements Packet{ 26 | /** 27 | * Serial ID. 28 | */ 29 | private static final long serialVersionUID = -5956530603458320833L; 30 | /** 31 | * The assigned player ID. 32 | */ 33 | private final int id; 34 | 35 | /** 36 | * Constructs a new join accept packet with the given 37 | * assigned ID for the joining player. 38 | * @param id The ID for the player. 39 | */ 40 | public PacketPlayerJoinAccept(int id){ 41 | this.id = id; 42 | } 43 | 44 | /** 45 | * Gets the ID assigned to the joining player. 46 | * @return The player ID. 47 | */ 48 | public int getID(){ 49 | return id; 50 | } 51 | 52 | @Override 53 | public PacketRegistry getRegisteryType(){ 54 | return PacketRegistry.PLAYER_JOIN_ACCEPT; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/packet/PacketPlayerJoinReject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net.packet; 20 | 21 | /** 22 | * Packet sent when a player is not accepted into a game. 23 | * @author Roan 24 | */ 25 | public class PacketPlayerJoinReject implements Packet{ 26 | /** 27 | * Serial ID. 28 | */ 29 | private static final long serialVersionUID = -5372738175706062480L; 30 | /** 31 | * They ID of the reject reason. 32 | */ 33 | private final int reason; 34 | 35 | /** 36 | * Constructs a new join reject packet with the given reason. 37 | * @param reason The reason for rejection. 38 | */ 39 | public PacketPlayerJoinReject(RejectReason reason){ 40 | this.reason = reason.id; 41 | } 42 | 43 | /** 44 | * Gets the reason the player was not allowed to join. 45 | * @return The rejection reason. 46 | */ 47 | public RejectReason getReason(){ 48 | for(RejectReason msg : RejectReason.values()){ 49 | if(msg.id == reason){ 50 | return msg; 51 | } 52 | } 53 | return RejectReason.UNKNOWN; 54 | } 55 | 56 | @Override 57 | public PacketRegistry getRegisteryType(){ 58 | return PacketRegistry.PLAYER_JOIN_REJECT; 59 | } 60 | 61 | /** 62 | * Enum of join reject reasons. 63 | * @author Roan 64 | */ 65 | public static enum RejectReason{ 66 | /** 67 | * The exact reason is not known. 68 | */ 69 | UNKNOWN(0, "Unknown reason"), 70 | /** 71 | * The game was already full. 72 | */ 73 | FULL(1, "Game is already full."), 74 | /** 75 | * The server version does not match the client version. 76 | */ 77 | VERSION_MISMATCH(2, "Host game version does not match client version."); 78 | 79 | /** 80 | * Numeric ID of this reason. 81 | */ 82 | private final int id; 83 | /** 84 | * The help text for this reason. 85 | */ 86 | private final String msg; 87 | 88 | /** 89 | * Constructs a new reject reason with the given ID and text. 90 | * @param id The reason ID. 91 | * @param msg The reason help text. 92 | */ 93 | private RejectReason(int id, String msg){ 94 | this.id = id; 95 | this.msg = msg; 96 | } 97 | 98 | /** 99 | * Gets the help text message for this reject reason. 100 | * @return The help text message. 101 | */ 102 | public String getMessage(){ 103 | return msg; 104 | } 105 | 106 | @Override 107 | public String toString(){ 108 | return msg; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/packet/PacketPlayerMove.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net.packet; 20 | 21 | import dev.roanh.convexmerger.game.ConvexObject; 22 | import dev.roanh.convexmerger.net.PlayerProxy; 23 | import dev.roanh.convexmerger.player.Player; 24 | 25 | /** 26 | * Packet sent when a player finishes their move. 27 | * @author Roan 28 | */ 29 | public class PacketPlayerMove implements Packet{ 30 | /** 31 | * Serial ID. 32 | */ 33 | private static final long serialVersionUID = -366297097116291189L; 34 | /** 35 | * The player that made the move. 36 | */ 37 | private final PlayerProxy player; 38 | /** 39 | * The type of move. 40 | */ 41 | private final MoveType type; 42 | /** 43 | * The source object involved (the ID of the claimed 44 | * object or the ID of the object a merge started from). 45 | */ 46 | private final int source; 47 | /** 48 | * The ID of the target object of a merge move. 49 | */ 50 | private final int target; 51 | 52 | /** 53 | * Constructs a new player move no more remaining moves 54 | * being available to the player. 55 | */ 56 | public PacketPlayerMove(){ 57 | this(null, null, null, MoveType.END); 58 | } 59 | 60 | /** 61 | * Constructs a new player move packet for a claim with the given data. 62 | * @param player The player that made the move. 63 | * @param claimed The object that was claimed. 64 | */ 65 | public PacketPlayerMove(Player player, ConvexObject claimed){ 66 | this(player, claimed, null, MoveType.CLAIM); 67 | } 68 | 69 | /** 70 | * Constructs a new player move packet for a merge with the given data. 71 | * @param player The player that made the move. 72 | * @param source The source object the merge started from. 73 | * @param target The merge target object. 74 | */ 75 | public PacketPlayerMove(Player player, ConvexObject source, ConvexObject target){ 76 | this(player, source, target, MoveType.MERGE); 77 | } 78 | 79 | /** 80 | * Constructs a new player move packet with the given data. 81 | * @param player The player that made the move. 82 | * @param source The source object involved (claimed object 83 | * or object a merge started from). 84 | * @param target The merge target object. 85 | * @param type The type of the move. 86 | */ 87 | private PacketPlayerMove(Player player, ConvexObject source, ConvexObject target, MoveType type){ 88 | this.player = player == null ? null : player.getProxy(); 89 | this.source = source == null ? -1 : source.getID(); 90 | this.target = target == null ? -1 : target.getID(); 91 | this.type = type; 92 | } 93 | 94 | /** 95 | * Gets the player that made this move. 96 | * @return The player that made this move. 97 | */ 98 | public PlayerProxy getPlayer(){ 99 | return player; 100 | } 101 | 102 | /** 103 | * Gets the type of move that was made by the player. 104 | * @return The type of move. 105 | */ 106 | public MoveType getType(){ 107 | return type; 108 | } 109 | 110 | /** 111 | * Gets the ID of the source object in this move. This 112 | * is either the ID of the object that was claimed or 113 | * the ID of the object a merge was started from. 114 | * @return The source object ID or -1. 115 | */ 116 | public int getSource(){ 117 | return source; 118 | } 119 | 120 | /** 121 | * Gets the ID of the target object in this move. This 122 | * is the ID of the target object in a merge move. 123 | * @return The target object ID or -1. 124 | */ 125 | public int getTarget(){ 126 | return target; 127 | } 128 | 129 | @Override 130 | public PacketRegistry getRegisteryType(){ 131 | return PacketRegistry.PLAYER_MOVE; 132 | } 133 | 134 | /** 135 | * Player move types. 136 | * @author Roan 137 | */ 138 | public static enum MoveType{ 139 | /** 140 | * Claiming a new convex object. 141 | */ 142 | CLAIM, 143 | /** 144 | * Merging two convex objects. 145 | */ 146 | MERGE, 147 | /** 148 | * No more moves remain for the player. 149 | */ 150 | END 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/net/packet/PacketRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.net.packet; 20 | 21 | /** 22 | * Enum of all packet types. 23 | * @author Roan 24 | */ 25 | public enum PacketRegistry{ 26 | /** 27 | * Sent when a game is started. 28 | */ 29 | GAME_INIT, 30 | /** 31 | * Sent when a player wants to join a game. 32 | */ 33 | PLAYER_JOIN, 34 | /** 35 | * Sent when a player is accepted into a game. 36 | */ 37 | PLAYER_JOIN_ACCEPT, 38 | /** 39 | * Sent when a player make a move. 40 | */ 41 | PLAYER_MOVE, 42 | /** 43 | * Sent when a player is not allowed to join a game. 44 | */ 45 | PLAYER_JOIN_REJECT, 46 | /** 47 | * Sent when the game ends. 48 | */ 49 | GAME_END; 50 | } 51 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/player/AIRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.player; 20 | 21 | import java.util.function.Supplier; 22 | 23 | /** 24 | * Registry of AI players. 25 | * @author Roan 26 | */ 27 | public enum AIRegistry{ 28 | /** 29 | * Isla, a greedy AI that maximises relative area gain. 30 | */ 31 | ISLA("Isla", GreedyPlayer::new), 32 | /** 33 | * Elaina, a greedy AI that maximises the relative area 34 | * gain in a specific part of the map. 35 | */ 36 | ELAINA("Elaina", LocalPlayer::new), 37 | /** 38 | * Shiro, a greedy AI that maximises the relative area 39 | * gain in a specific part of the map from a small object. 40 | */ 41 | SHIRO("Shiro", SmallPlayer::new); 42 | 43 | /** 44 | * The name of this AI. 45 | */ 46 | private String name; 47 | /** 48 | * The construct for this AI. 49 | */ 50 | private Supplier ctor; 51 | 52 | /** 53 | * Constructs a new registry entry. 54 | * @param name The name of the AI. 55 | * @param ctor The construct of the AI. 56 | */ 57 | private AIRegistry(String name, Supplier ctor){ 58 | this.name = name; 59 | this.ctor = ctor; 60 | } 61 | 62 | /** 63 | * Creates a new instance of this AI. 64 | * @return A new AI instance. 65 | */ 66 | public Player createInstance(){ 67 | return ctor.get(); 68 | } 69 | 70 | /** 71 | * Gets the name of this AI. 72 | * @return The name of this AI. 73 | */ 74 | public String getName(){ 75 | return name; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/player/GreedyPlayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.player; 20 | 21 | import java.util.List; 22 | import java.util.stream.Collectors; 23 | 24 | import dev.roanh.convexmerger.game.ConvexObject; 25 | 26 | /** 27 | * Simple AI that follows the greedy 28 | * strategy of maximising relative 29 | * area gain in every turn. 30 | * @author Roan 31 | */ 32 | public class GreedyPlayer extends Player{ 33 | 34 | /** 35 | * Constructs a new greedy player (Isla). 36 | */ 37 | public GreedyPlayer(){ 38 | super(true, true, "Isla"); 39 | } 40 | 41 | /** 42 | * Constructs a new greedy player with the given parameters. 43 | * @param local Whether this AI is executing locally or acting as a remote proxy. 44 | * @param ai Whether this player is an AI or not. 45 | * @param name The name of this player. 46 | */ 47 | protected GreedyPlayer(boolean local, boolean ai, String name){ 48 | super(local, ai, name); 49 | } 50 | 51 | @Override 52 | public boolean executeMove() throws InterruptedException{ 53 | List owned = stream().collect(Collectors.toList()); 54 | 55 | //find the single largest object 56 | ConvexObject max = findLargestUnownedObject(); 57 | 58 | //merge any of our owned objects with something else to get the largest area 59 | MergeOption bestMerge = null; 60 | 61 | for(ConvexObject obj : owned){ 62 | MergeOption option = findBestMergeFrom(obj); 63 | if(option != null && (bestMerge == null || option.getIncrease() > bestMerge.getIncrease())){ 64 | bestMerge = option; 65 | } 66 | } 67 | 68 | if(bestMerge != null && (max == null || bestMerge.getIncrease() > max.getArea())){ 69 | bestMerge.execute(); 70 | return true; 71 | } 72 | 73 | //claiming the largest object is best 74 | if(max != null){ 75 | state.claimObject(max); 76 | return true; 77 | }else{ 78 | return false; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/player/HumanPlayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.player; 20 | 21 | import java.awt.geom.Point2D; 22 | 23 | import dev.roanh.convexmerger.game.ClaimResult; 24 | import dev.roanh.convexmerger.game.ConvexObject; 25 | import dev.roanh.convexmerger.ui.GamePanel; 26 | 27 | /** 28 | * Represents a player controlled by a local human. 29 | * @author Roan 30 | */ 31 | public class HumanPlayer extends Player{ 32 | /** 33 | * The panel visualising the game this player is playing in. 34 | */ 35 | private GamePanel game; 36 | /** 37 | * The objected selected by the player as the next object to claim. 38 | * @see #clickPoint 39 | */ 40 | private volatile ConvexObject nextClaim = null; 41 | /** 42 | * The point clicked to claim the next object to claim. 43 | * @see #nextClaim 44 | */ 45 | private volatile Point2D clickPoint = new Point2D.Double(); 46 | 47 | /** 48 | * Constructs a new human player with the given name. 49 | * @param name The name of the player. 50 | */ 51 | public HumanPlayer(String name){ 52 | super(true, false, name); 53 | } 54 | 55 | /** 56 | * Sets the game panel visualising the game this player is playing in. 57 | * @param game The game panel for this player. 58 | */ 59 | public void setGamePanel(GamePanel game){ 60 | this.game = game; 61 | } 62 | 63 | /** 64 | * Checks if this player currently requires input via the UI. 65 | * @return True if this player requires UI interaction. 66 | */ 67 | public boolean requireInput(){ 68 | return nextClaim == null; 69 | } 70 | 71 | /** 72 | * Handles a new object to try to claim for this player. 73 | * @param claimed The object that was attempted to be claimed. 74 | * @param location The point clicked to claim the given object. 75 | */ 76 | public synchronized void handleClaim(ConvexObject claimed, Point2D location){ 77 | nextClaim = claimed; 78 | clickPoint = location; 79 | notify(); 80 | } 81 | 82 | @Override 83 | public synchronized boolean executeMove() throws InterruptedException{ 84 | if(!state.stream().filter(ConvexObject::canClaim).findAny().isPresent() && !stream().filter(this::hasMergeFrom).findAny().isPresent()){ 85 | return false; 86 | } 87 | 88 | ClaimResult result; 89 | do{ 90 | while(nextClaim == null){ 91 | wait(); 92 | } 93 | 94 | result = state.claimObject(nextClaim, clickPoint); 95 | game.setMessage(result.getMessage()); 96 | nextClaim = null; 97 | }while(!result.hasResult()); 98 | 99 | return true; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/player/LocalPlayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.player; 20 | 21 | import dev.roanh.convexmerger.game.ConvexObject; 22 | 23 | /** 24 | * AI that focuses on maximising local area gain. 25 | * @author Roan 26 | */ 27 | public class LocalPlayer extends Player{ 28 | /** 29 | * Object currently being worked on. 30 | */ 31 | protected ConvexObject target = null; 32 | 33 | /** 34 | * Constructs a new local player (Elaina). 35 | */ 36 | public LocalPlayer(){ 37 | this("Elaina"); 38 | } 39 | 40 | /** 41 | * Constructs a new local player with the given name. 42 | * @param name The player name. 43 | */ 44 | protected LocalPlayer(String name){ 45 | super(true, true, name); 46 | } 47 | 48 | @Override 49 | public boolean executeMove() throws InterruptedException{ 50 | if(target == null){ 51 | return claimNewObject(); 52 | } 53 | 54 | MergeOption merge = findBestMergeFrom(target); 55 | if(merge != null){ 56 | target = merge.execute(); 57 | return true; 58 | } 59 | 60 | return claimNewObject(); 61 | } 62 | 63 | /** 64 | * Selects a new object to claim and start maximising area from. 65 | * @return True if a new object was found, false otherwise (no move left). 66 | * @throws InterruptedException When the player was 67 | * interrupted while making its move. Signalling 68 | * that the game was aborted. 69 | */ 70 | protected boolean claimNewObject() throws InterruptedException{ 71 | target = findLargestUnownedObject(); 72 | if(target == null){ 73 | return false; 74 | }else{ 75 | target = state.claimObject(target).getResult(); 76 | return true; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/player/RemotePlayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.player; 20 | 21 | import java.io.IOException; 22 | 23 | import dev.roanh.convexmerger.net.Connection; 24 | import dev.roanh.convexmerger.net.packet.Packet; 25 | import dev.roanh.convexmerger.net.packet.PacketPlayerMove; 26 | import dev.roanh.convexmerger.net.packet.PacketPlayerMove.MoveType; 27 | import dev.roanh.convexmerger.net.packet.PacketRegistry; 28 | 29 | /** 30 | * Proxy representative for a player playing on a remote system. 31 | * If the remote connection is lost the {@link GreedyPlayer} AI takes over. 32 | * @author Roan 33 | * @see GreedyPlayer 34 | */ 35 | public class RemotePlayer extends GreedyPlayer{ 36 | /** 37 | * The remote player connection. 38 | */ 39 | private Connection con; 40 | /** 41 | * True if the connection to the remote player was lost. 42 | */ 43 | private boolean lost = false; 44 | 45 | /** 46 | * Constructs a new remote player with the given 47 | * player connection, AI status and name. 48 | * @param con The connection to the remote player. 49 | * @param ai Whether the remote player is an AI. 50 | * @param name The name of the remote player. 51 | */ 52 | public RemotePlayer(Connection con, boolean ai, String name){ 53 | super(false, ai, name); 54 | this.con = con; 55 | } 56 | 57 | /** 58 | * Checks if the connection to the remote player was lost. 59 | * @return True if the remote connection was lost. 60 | */ 61 | public boolean isLost(){ 62 | return lost; 63 | } 64 | 65 | /** 66 | * Executes a fallback AI move if the player connection was lost. 67 | * @return True if a move was executed, false if no moves 68 | * are left in the game (signals game end). 69 | * @throws InterruptedException When the player was 70 | * interrupted (signals that the game was aborted). 71 | */ 72 | private boolean fallback() throws InterruptedException{ 73 | con.close(); 74 | lost = true; 75 | if(!getName().endsWith(" [Lost]")){ 76 | this.setName(this.getName() + " [Lost]"); 77 | } 78 | return super.executeMove(); 79 | } 80 | 81 | @Override 82 | public boolean executeMove() throws InterruptedException{ 83 | if(lost){ 84 | return super.executeMove(); 85 | } 86 | 87 | try{ 88 | Packet packet = con.readPacket(); 89 | if(packet == null){ 90 | return fallback(); 91 | } 92 | 93 | if(packet.getRegisteryType() != PacketRegistry.PLAYER_MOVE){ 94 | if(packet.getRegisteryType() == PacketRegistry.GAME_END){ 95 | return false; 96 | }else{ 97 | return fallback(); 98 | } 99 | } 100 | 101 | PacketPlayerMove move = (PacketPlayerMove)packet; 102 | if(move.getType() == MoveType.END){ 103 | con.close(); 104 | return false; 105 | } 106 | 107 | if(move.getPlayer().getID() != getID()){ 108 | return fallback(); 109 | } 110 | 111 | if(move.getPlayer().isLost() && !getName().endsWith(" [Lost]")){ 112 | this.setName(this.getName() + " [Lost]"); 113 | } 114 | 115 | if(move.getType() == MoveType.CLAIM){ 116 | state.claimObject(state.stream().filter(obj->obj.getID() == move.getSource()).findFirst().get()); 117 | return true; 118 | }else if(move.getType() == MoveType.MERGE){ 119 | state.claimObject(state.stream().filter(obj->obj.getID() == move.getSource()).findFirst().get()); 120 | state.claimObject(state.stream().filter(obj->obj.getID() == move.getTarget()).findFirst().get()); 121 | return true; 122 | } 123 | }catch(IOException e){ 124 | return fallback(); 125 | } 126 | 127 | return false; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/player/SmallPlayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.player; 20 | 21 | import java.util.Comparator; 22 | import java.util.Optional; 23 | 24 | import dev.roanh.convexmerger.game.ConvexObject; 25 | 26 | /** 27 | * AI that focuses on maximising local area gain 28 | * starting from small objects. 29 | * @author Roan 30 | */ 31 | public class SmallPlayer extends LocalPlayer{ 32 | 33 | /** 34 | * Constructs a new small player (Shiro). 35 | */ 36 | public SmallPlayer(){ 37 | super("Shiro"); 38 | } 39 | 40 | @Override 41 | protected boolean claimNewObject() throws InterruptedException{ 42 | Optional obj = state.stream().filter(ConvexObject::canClaim).sorted(Comparator.comparingDouble(ConvexObject::getArea)).filter(this::hasMergeFrom).findFirst(); 43 | if(obj.isPresent()){ 44 | target = state.claimObject(obj.get()).getResult(); 45 | return true; 46 | }else{ 47 | return super.claimNewObject(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/ui/ComboBox.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.ui; 20 | 21 | import java.awt.Color; 22 | import java.awt.FontMetrics; 23 | import java.awt.Graphics2D; 24 | import java.awt.geom.Line2D; 25 | import java.awt.geom.Point2D; 26 | import java.awt.geom.Rectangle2D; 27 | import java.util.function.Function; 28 | 29 | /** 30 | * Represents a UI combo box. 31 | * @author Roan 32 | * @param The value data type. 33 | */ 34 | public class ComboBox{ 35 | /** 36 | * Height of the combo box field and drop down item fields. 37 | */ 38 | private static final double CELL_HEIGHT = 20.0D; 39 | /** 40 | * Combo box accent color. 41 | */ 42 | private Color color; 43 | /** 44 | * The bounds for the main field of this combo box. 45 | */ 46 | private Rectangle2D bounds = new Rectangle2D.Double(); 47 | /** 48 | * The bounds for the drop down list of this combo box. 49 | */ 50 | private Rectangle2D list = null; 51 | /** 52 | * The currently selected value. 53 | */ 54 | private T value; 55 | /** 56 | * The list of possible values. 57 | */ 58 | private T[] values; 59 | /** 60 | * The function to use to turn the values into strings. 61 | */ 62 | private Function toString; 63 | /** 64 | * Whether this combo box currently has focus. 65 | */ 66 | private boolean focus = false; 67 | 68 | /** 69 | * Constructs a new combo box with the given values, to string 70 | * function and accent color. The first given value will be 71 | * selected initially. 72 | * @param values The values for this combo box. 73 | * @param toString The function to convert the values to a string. 74 | * @param color The accent color. 75 | */ 76 | public ComboBox(T[] values, Function toString, Color color){ 77 | this.color = color; 78 | this.value = values[0]; 79 | this.values = values; 80 | this.toString = toString; 81 | } 82 | 83 | /** 84 | * Gets the value selected in this combo box. 85 | * @return The selected value. 86 | */ 87 | public T getValue(){ 88 | return value; 89 | } 90 | 91 | /** 92 | * Handles a mouse click on this combo box. 93 | * @param loc The location that was clicked. 94 | */ 95 | public void handleMouseClick(Point2D loc){ 96 | int idx = getSelectedIndex(loc); 97 | if(idx != -1){ 98 | value = values[idx]; 99 | list = null; 100 | } 101 | 102 | if(bounds.contains(loc)){ 103 | focus = true; 104 | }else{ 105 | list = null; 106 | focus = false; 107 | } 108 | } 109 | 110 | /** 111 | * Checks if this combo box currently has focus. 112 | * @return True if this combo box has focus. 113 | */ 114 | public boolean hasFocus(){ 115 | return focus; 116 | } 117 | 118 | /** 119 | * Computes the drop down list selected index given 120 | * the list bounds. 121 | * @param loc The location selected. 122 | * @return The selected index or -1 if none. 123 | */ 124 | private int getSelectedIndex(Point2D loc){ 125 | if(list != null && list.contains(loc)){ 126 | return (int)Math.floor((loc.getY() - list.getY()) / CELL_HEIGHT); 127 | }else{ 128 | return -1; 129 | } 130 | } 131 | 132 | /** 133 | * Renders this combo box according to the given parameters. 134 | * @param g The graphics context to use. 135 | * @param x The x coordinate of the top left corner. 136 | * @param y The y coordinate of the top left corner. 137 | * @param width The width of the combo box. 138 | * @param height The height of the main field of the combo box. 139 | * @param loc The current mouse location. 140 | */ 141 | protected void render(Graphics2D g, double x, double y, double width, double height, Point2D loc){ 142 | g.setColor(Theme.DOUBLE_LIGHTEN); 143 | bounds = new Rectangle2D.Double(x, y, width, height); 144 | g.fill(bounds); 145 | 146 | g.setStroke(Theme.BORDER_STROKE); 147 | g.setColor(Theme.BOX_TEXT_COLOR); 148 | g.setFont(Theme.PRIDI_MEDIUM_14); 149 | FontMetrics fm = g.getFontMetrics(); 150 | g.drawString(toString.apply(value), (float)(x + 4.0F), (float)(y + height - fm.getMaxDescent())); 151 | 152 | if(focus){ 153 | list = new Rectangle2D.Double(x, y + height, width, CELL_HEIGHT * values.length); 154 | g.setColor(Theme.DOUBLE_LIGHTEN); 155 | g.fill(list); 156 | 157 | for(int i = 0; i < values.length; i++){ 158 | double lh = y + height + CELL_HEIGHT * (i + 1); 159 | if(list.contains(loc) && loc.getY() > lh - CELL_HEIGHT && loc.getY() < lh){ 160 | g.setColor(color); 161 | g.fill(new Rectangle2D.Double(x, lh - CELL_HEIGHT, width, CELL_HEIGHT)); 162 | } 163 | g.setColor(Theme.BOX_TEXT_COLOR); 164 | g.drawString(toString.apply(values[i]), (float)(x + 4.0F), (float)(lh - fm.getMaxDescent() + 1.0D)); 165 | } 166 | } 167 | 168 | g.setColor(color); 169 | g.draw(new Line2D.Double(x, y + height - 1, x + width - 1, y + height - 1)); 170 | g.drawImage(Theme.CHEVRON_ICON, (int)(x + width - 1 - Theme.CHEVRON_ICON_SIZE), (int)y, null); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/ui/ConvexMerger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.ui; 20 | 21 | import java.awt.BorderLayout; 22 | import java.awt.Dimension; 23 | import java.awt.Insets; 24 | import java.awt.KeyEventDispatcher; 25 | import java.awt.KeyboardFocusManager; 26 | import java.awt.Point; 27 | import java.awt.Toolkit; 28 | import java.awt.event.KeyEvent; 29 | import java.io.IOException; 30 | import java.lang.reflect.InvocationTargetException; 31 | import java.util.Arrays; 32 | 33 | import javax.imageio.ImageIO; 34 | import javax.swing.JFrame; 35 | import javax.swing.JPanel; 36 | import javax.swing.SwingUtilities; 37 | 38 | import dev.roanh.convexmerger.Constants; 39 | import dev.roanh.convexmerger.game.GameConstructor; 40 | import dev.roanh.convexmerger.game.GameState; 41 | import dev.roanh.convexmerger.player.Player; 42 | 43 | /** 44 | * Main game entry point, manages the main state of the game. 45 | * @author Roan 46 | */ 47 | public class ConvexMerger implements KeyEventDispatcher{ 48 | /** 49 | * Application main frame. 50 | */ 51 | private JFrame frame = new JFrame(Constants.TITLE); 52 | /** 53 | * Window size before switching to full screen. 54 | */ 55 | private Dimension lastSize = null; 56 | /** 57 | * Window location before switching to full screen. 58 | */ 59 | private Point lastLocation = null; 60 | /** 61 | * The renderer rending the active game screen. 62 | */ 63 | private ScreenRenderer renderer = new ScreenRenderer(new MainMenu(this)); 64 | /** 65 | * The thread running the active game (if any). 66 | */ 67 | private GameThread gameThread; 68 | /** 69 | * Cached new game menu to persist settings. 70 | */ 71 | private NewGameMenu newGame = new NewGameMenu(this); 72 | 73 | /** 74 | * Shows the main game window. 75 | */ 76 | public void showGame(){ 77 | JPanel content = new JPanel(new BorderLayout()); 78 | content.add(renderer, BorderLayout.CENTER); 79 | 80 | try{ 81 | frame.setIconImages(Arrays.asList( 82 | ImageIO.read(ClassLoader.getSystemResourceAsStream("assets/logo/16.png")), 83 | ImageIO.read(ClassLoader.getSystemResourceAsStream("assets/logo/32.png")), 84 | ImageIO.read(ClassLoader.getSystemResourceAsStream("assets/logo/48.png")), 85 | ImageIO.read(ClassLoader.getSystemResourceAsStream("assets/logo/64.png")), 86 | ImageIO.read(ClassLoader.getSystemResourceAsStream("assets/logo/96.png")), 87 | ImageIO.read(ClassLoader.getSystemResourceAsStream("assets/logo/256.png")) 88 | )); 89 | }catch(IOException e1){ 90 | //not important and internally resources should load 91 | } 92 | 93 | frame.add(content); 94 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 95 | frame.pack(); 96 | Insets insets = frame.getInsets(); 97 | Dimension minSize = Toolkit.getDefaultToolkit().getScreenSize(); 98 | frame.setMinimumSize(new Dimension( 99 | Math.min(16 * Constants.MIN_SIZE + insets.left + insets.right + 2 * Screen.SIDE_OFFSET, minSize.width), 100 | Math.min(Screen.TOP_SPACE + 9 * Constants.MIN_SIZE + insets.top + insets.bottom + Screen.TOP_OFFSET + Screen.BOTTOM_OFFSET, minSize.height) 101 | )); 102 | frame.setSize(new Dimension( 103 | Math.min(16 * Constants.INIT_SIZE + insets.left + insets.right + 2 * Screen.SIDE_OFFSET, minSize.width), 104 | Math.min(Screen.TOP_SPACE + 9 * Constants.INIT_SIZE + insets.top + insets.bottom + Screen.TOP_OFFSET + Screen.BOTTOM_OFFSET, minSize.height) 105 | )); 106 | frame.setLocationRelativeTo(null); 107 | frame.setVisible(true); 108 | 109 | KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this); 110 | } 111 | 112 | /** 113 | * Exits this game context, possibly exiting 114 | * the application as a whole. 115 | */ 116 | public void exit(){ 117 | frame.dispose(); 118 | } 119 | 120 | /** 121 | * Shows the new game menu. 122 | */ 123 | public void showNewGame(){ 124 | newGame.reset(); 125 | switchScene(newGame); 126 | } 127 | 128 | /** 129 | * Initialises a new game with the given game constructor. 130 | * @param ctor The constructor to build the game. 131 | */ 132 | public void initialiseGame(GameConstructor ctor){ 133 | gameThread = new GameThread(ctor); 134 | gameThread.start(); 135 | } 136 | 137 | /** 138 | * Switches the scene being displayed to the user 139 | * to the given screen. 140 | * @param next The new screen to display. 141 | * @return The previous screen on display. 142 | */ 143 | public Screen switchScene(Screen next){ 144 | return renderer.setScreen(next); 145 | } 146 | 147 | /** 148 | * Aborts the active game, if any. 149 | */ 150 | public void abortGame(){ 151 | if(gameThread != null){ 152 | gameThread.interrupt(); 153 | } 154 | } 155 | 156 | @Override 157 | public boolean dispatchKeyEvent(KeyEvent e){ 158 | if(e.getKeyCode() == KeyEvent.VK_F11 && e.getID() == KeyEvent.KEY_RELEASED){ 159 | if(frame.isUndecorated()){ 160 | frame.setVisible(false); 161 | frame.dispose(); 162 | frame.setUndecorated(false); 163 | frame.setSize(lastSize); 164 | frame.setLocation(lastLocation); 165 | frame.setAlwaysOnTop(false); 166 | frame.setVisible(true); 167 | }else{ 168 | lastSize = frame.getSize(); 169 | lastLocation = frame.getLocation(); 170 | frame.setVisible(false); 171 | frame.dispose(); 172 | frame.setUndecorated(true); 173 | frame.setSize(Toolkit.getDefaultToolkit().getScreenSize()); 174 | frame.setLocationRelativeTo(null); 175 | frame.setAlwaysOnTop(true); 176 | frame.setVisible(true); 177 | } 178 | } 179 | return false; 180 | } 181 | 182 | /** 183 | * Thread responsible for managing the game turns, 184 | * generating the playfield and executing AI moves. 185 | * @author Roan 186 | */ 187 | private final class GameThread extends Thread{ 188 | /** 189 | * The constructor to use to create the game state. 190 | */ 191 | private GameConstructor ctor; 192 | 193 | /** 194 | * Constructs a new game thread with the given game constructor. 195 | * @param ctor The constructor to create the game state with. 196 | */ 197 | private GameThread(GameConstructor ctor){ 198 | this.setName("GameThread"); 199 | this.setDaemon(true); 200 | this.ctor = ctor; 201 | } 202 | 203 | @Override 204 | public void run(){ 205 | GameState state = ctor.create(); 206 | try{ 207 | SwingUtilities.invokeAndWait(()->renderer.setScreen(new GamePanel(ConvexMerger.this, state))); 208 | state.init(); 209 | 210 | if(state.getActivePlayer().isAI()){ 211 | Thread.sleep(Constants.MIN_TURN_TIME); 212 | } 213 | 214 | while(!state.isFinished() && !this.isInterrupted()){ 215 | Player player = state.getActivePlayer(); 216 | if(player.isAI()){ 217 | Thread.sleep(Constants.AI_WAIT_TIME); 218 | } 219 | 220 | long start = System.currentTimeMillis(); 221 | state.executePlayerTurn(); 222 | long duration = System.currentTimeMillis() - start; 223 | player.getStats().addTurnTime(duration); 224 | if(duration < Constants.MIN_TURN_TIME){ 225 | Thread.sleep(Constants.MIN_TURN_TIME - duration); 226 | } 227 | 228 | frame.repaint(); 229 | } 230 | }catch(InvocationTargetException e){ 231 | //never happens 232 | }catch(InterruptedException e){ 233 | //happens when the game is aborted 234 | state.abort(); 235 | } 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/ui/HostMenu.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.ui; 20 | 21 | import java.awt.FontMetrics; 22 | import java.awt.Graphics2D; 23 | import java.awt.geom.Point2D; 24 | import java.util.List; 25 | import java.util.Optional; 26 | 27 | import dev.roanh.convexmerger.game.PlayfieldGenerator; 28 | import dev.roanh.convexmerger.net.InternalServer; 29 | import dev.roanh.convexmerger.net.InternalServer.InternalServerListener; 30 | import dev.roanh.convexmerger.player.Player; 31 | import dev.roanh.convexmerger.ui.Theme.PlayerTheme; 32 | 33 | /** 34 | * Menu used to host a multiplayer match. 35 | * @author Roan 36 | */ 37 | public class HostMenu extends NewGameMenu implements InternalServerListener{ 38 | /** 39 | * Last name used by the host. 40 | */ 41 | private static String lastHostName = null; 42 | /** 43 | * The multiplayer server. 44 | */ 45 | private InternalServer server; 46 | /** 47 | * Error message, if any. 48 | */ 49 | private String error = null; 50 | 51 | /** 52 | * Constructs a new host menu with the given game context. 53 | * @param context The game context. 54 | */ 55 | public HostMenu(ConvexMerger context){ 56 | super(context); 57 | p1 = new HostPanel(PlayerTheme.P1); 58 | server = new InternalServer(this); 59 | } 60 | 61 | @Override 62 | protected void handleStart(List players, PlayfieldGenerator gen){ 63 | this.getContext().initialiseGame(server.startGame(players, gen, showDecomp)); 64 | } 65 | 66 | @Override 67 | public void handlePlayerJoin(Player player){ 68 | synchronized(server){ 69 | if(!p2.hasPlayer()){ 70 | p2 = new RemotePanel(PlayerTheme.P2, player); 71 | }else if(!p3.hasPlayer()){ 72 | p3 = new RemotePanel(PlayerTheme.P3, player); 73 | }else if(!p4.hasPlayer()){ 74 | p4 = new RemotePanel(PlayerTheme.P4, player); 75 | } 76 | } 77 | } 78 | 79 | @Override 80 | protected void handleLeftButtonClick(){ 81 | server.shutdown(); 82 | super.handleLeftButtonClick(); 83 | } 84 | 85 | @Override 86 | public void handleException(Exception e){ 87 | server.shutdown(); 88 | error = e.getClass().getSimpleName() + ": " + e.getMessage(); 89 | } 90 | 91 | @Override 92 | protected String getButtonMessage(){ 93 | return error == null ? super.getButtonMessage() : error; 94 | } 95 | 96 | @Override 97 | protected boolean canStart(){ 98 | return error == null && super.canStart(); 99 | } 100 | 101 | @Override 102 | public void handleMouseRelease(Point2D loc, int width, int height){ 103 | if(error == null){ 104 | synchronized(server){ 105 | super.handleMouseRelease(loc, width, height); 106 | } 107 | }else{ 108 | super.handleMouseRelease(loc, width, height); 109 | } 110 | } 111 | 112 | @Override 113 | protected String getMenuTitle(){ 114 | return "Host Multiplayer"; 115 | } 116 | 117 | /** 118 | * Special player panel that cannot be removed. 119 | * @author Roan 120 | */ 121 | private class HostPanel extends PlayerPanel{ 122 | 123 | /** 124 | * Constructs a new host panel with the given theme. 125 | * @param theme The panel theme. 126 | */ 127 | private HostPanel(PlayerTheme theme){ 128 | super(theme); 129 | setHuman(lastHostName); 130 | } 131 | 132 | @Override 133 | protected Optional getPlayer(){ 134 | Optional player = super.getPlayer(); 135 | player.map(Player::getName).ifPresent(name->lastHostName = name); 136 | return player; 137 | } 138 | 139 | @Override 140 | protected void renderRemoveButton(Graphics2D g, double x, double y, Point2D mouseLoc){ 141 | g.setColor(Theme.DOUBLE_LIGHTEN); 142 | g.setStroke(Theme.BUTTON_STROKE); 143 | g.draw(computeBox(x, y, CONTENT_WIDTH, CONTENT_HEIGHT, 5.0D)); 144 | 145 | g.setFont(Theme.PRIDI_REGULAR_14); 146 | FontMetrics fm = g.getFontMetrics(); 147 | g.setColor(Theme.ADD_COLOR); 148 | g.drawString("Host", (float)(x + (CONTENT_WIDTH - fm.stringWidth("Host")) / 2.0D), (float)(y + CONTENT_HEIGHT - 1.0D - (fm.getAscent() - fm.getDescent() - fm.getLeading()) / 2.0D)); 149 | } 150 | } 151 | 152 | /** 153 | * Special player panel that cannot be removed or edited. 154 | * @author Roan 155 | */ 156 | private class RemotePanel extends PlayerPanel{ 157 | /** 158 | * The remote player associated with this panel. 159 | */ 160 | private Player player; 161 | 162 | /** 163 | * Constructs a new remote panel for the given player. 164 | * @param theme The panel theme. 165 | * @param player The remote player. 166 | */ 167 | private RemotePanel(PlayerTheme theme, Player player){ 168 | super(theme); 169 | this.player = player; 170 | setHuman(player.getName()); 171 | } 172 | 173 | @Override 174 | public Optional getPlayer(){ 175 | return Optional.of(player); 176 | } 177 | 178 | @Override 179 | protected void renderRemoveButton(Graphics2D g, double x, double y, Point2D mouseLoc){ 180 | g.setColor(Theme.DOUBLE_LIGHTEN); 181 | g.setStroke(Theme.BUTTON_STROKE); 182 | g.draw(computeBox(x, y, CONTENT_WIDTH, CONTENT_HEIGHT, 5.0D)); 183 | 184 | g.setFont(Theme.PRIDI_REGULAR_14); 185 | FontMetrics fm = g.getFontMetrics(); 186 | g.setColor(Theme.ADD_COLOR); 187 | g.drawString("Remote", (float)(x + (CONTENT_WIDTH - fm.stringWidth("Remote")) / 2.0D), (float)(y + CONTENT_HEIGHT - 1.0D - (fm.getAscent() - fm.getDescent() - fm.getLeading()) / 2.0D)); 188 | } 189 | 190 | @Override 191 | protected void handleMouseClick(Point2D loc){ 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/ui/JoinMenu.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.ui; 20 | 21 | import java.awt.FontMetrics; 22 | import java.awt.Graphics2D; 23 | import java.awt.event.KeyEvent; 24 | import java.awt.geom.Path2D; 25 | import java.awt.geom.Point2D; 26 | import java.io.IOException; 27 | import java.util.concurrent.ExecutorService; 28 | import java.util.concurrent.Executors; 29 | 30 | import dev.roanh.convexmerger.game.GameState; 31 | import dev.roanh.convexmerger.net.ClientConnection; 32 | import dev.roanh.convexmerger.player.HumanPlayer; 33 | import dev.roanh.convexmerger.player.Player; 34 | import dev.roanh.convexmerger.ui.Theme.PlayerTheme; 35 | 36 | /** 37 | * Menu shown to connect to a remote server. 38 | * @author Roan 39 | */ 40 | public class JoinMenu extends Screen{ 41 | /** 42 | * Maximum width used by the boxes. 43 | */ 44 | private static final int MAX_WIDTH = 1200; 45 | /** 46 | * Height of the connect button. 47 | */ 48 | private static final double CONNECT_HEIGHT = 100.0D; 49 | /** 50 | * Height of the details box. 51 | */ 52 | private static final double boxSize = 100.0D; 53 | /** 54 | * Width of the text fields. 55 | */ 56 | private static final double fieldWidth = 200.0D; 57 | /** 58 | * Height of the text fields. 59 | */ 60 | private static final double fieldHeight = 20.0D; 61 | /** 62 | * Last used host name. 63 | */ 64 | private static String lastHost = ""; 65 | /** 66 | * Last used player name. 67 | */ 68 | private static String lastName = System.getProperty("user.name", "Player 1"); 69 | /** 70 | * Executor used to connect to the remote server. 71 | */ 72 | private ExecutorService executor = Executors.newSingleThreadExecutor(); 73 | /** 74 | * Player name input field. 75 | */ 76 | private TextField name = new TextField(PlayerTheme.P1.getBaseOutline()); 77 | /** 78 | * Host name input field. 79 | */ 80 | private TextField host = new TextField(PlayerTheme.P2.getBaseOutline()); 81 | /** 82 | * Server connection. 83 | */ 84 | private ClientConnection con = null; 85 | /** 86 | * Local player. 87 | */ 88 | private Player self; 89 | /** 90 | * True is currently attempting to establish a connection. 91 | */ 92 | private volatile boolean connecting = false; 93 | /** 94 | * Current status message. 95 | */ 96 | private volatile String msg = "Enter connection details..."; 97 | /** 98 | * Connect button bounds. 99 | */ 100 | private Path2D connect = new Path2D.Double(); 101 | 102 | /** 103 | * Constructs a new join menu with the given game context. 104 | * @param context The game context to use. 105 | */ 106 | protected JoinMenu(ConvexMerger context){ 107 | super(context); 108 | host.setText(lastHost); 109 | name.setText(lastName); 110 | } 111 | 112 | @Override 113 | protected void render(Graphics2D g, int width, int height, Point2D mouseLoc){ 114 | renderMainInterface(g, width, height, null); 115 | renderMenuTitle(g, width, "Join Multiplayer"); 116 | drawTitle(g, width); 117 | 118 | double size = getMaxWidth(width, 0.8D, MAX_WIDTH); 119 | double offset = (width - size) / 2.0D; 120 | double y = TOP_SPACE + TOP_MIDDLE_OFFSET + BOX_SPACING * 2.0D; 121 | drawTitledBox(g, Theme.constructBorderGradient(null, width), offset, y, size, boxSize, "Enter Details"); 122 | 123 | g.setFont(Theme.PRIDI_MEDIUM_14); 124 | FontMetrics fm = g.getFontMetrics(); 125 | name.render(g, offset + size / 2.0D - fieldWidth - BOX_SPACING, y + BOX_HEADER_HEIGHT + BOX_SPACING * 2.0D, fieldWidth, fieldHeight); 126 | host.render(g, offset + size / 2.0D + fm.stringWidth("Host") + SPACING + BOX_SPACING, y + BOX_HEADER_HEIGHT + BOX_SPACING * 2.0D, fieldWidth, fieldHeight); 127 | 128 | g.setColor(Theme.BOX_TEXT_COLOR); 129 | g.drawString("Name", (float)(offset + size / 2.0D - fieldWidth - BOX_SPACING - fm.stringWidth("Name") - SPACING), (float)(y + BOX_HEADER_HEIGHT + BOX_SPACING * 2.0D + fieldHeight - fm.getMaxDescent())); 130 | g.drawString("Host", (float)(offset + size / 2.0D + BOX_SPACING), (float)(y + BOX_HEADER_HEIGHT + BOX_SPACING * 2.0D + fieldHeight - fm.getMaxDescent())); 131 | 132 | y += boxSize + BOX_SPACING; 133 | connect = drawButton(g, "Connect", offset + (size / 3.0D), y, (size / 3.0D), CONNECT_HEIGHT, connecting ? null : mouseLoc); 134 | g.setFont(Theme.PRIDI_REGULAR_12); 135 | fm = g.getFontMetrics(); 136 | g.setColor(Theme.ADD_COLOR); 137 | g.drawString(msg, (float)(offset + (size / 3.0D) + SPACING + ((size / 3.0D) - fm.stringWidth(msg)) / 2.0D), (float)(y + g.getFontMetrics(Theme.PRIDI_REGULAR_18).getHeight() + (CONNECT_HEIGHT - fm.getAscent() + fm.getDescent() + fm.getLeading()) / 2.0D)); 138 | } 139 | 140 | /** 141 | * Attempts to establish a connection to the server. 142 | */ 143 | private void connect(){ 144 | name.removeFocus(); 145 | host.removeFocus(); 146 | self = new HumanPlayer(name.getText()); 147 | connecting = true; 148 | msg = "Connecting..."; 149 | executor.execute(()->{ 150 | try{ 151 | con = ClientConnection.connect(host.getText(), self); 152 | if(con.isConnected()){ 153 | lastHost = host.getText(); 154 | lastName = self.getName(); 155 | msg = "Waiting for the host to start the game..."; 156 | final GameState state = con.getGameState(); 157 | this.getContext().initialiseGame(()->state); 158 | }else{ 159 | msg = con.getRejectReason().getMessage(); 160 | } 161 | }catch(IOException e){ 162 | msg = e.getClass().getSimpleName() + ": " + e.getMessage(); 163 | if(con != null){ 164 | con.close(); 165 | con = null; 166 | } 167 | connecting = false; 168 | } 169 | }); 170 | } 171 | 172 | @Override 173 | public void handleKeyPressed(KeyEvent event){ 174 | if(!connecting){ 175 | if(event.getKeyCode() == KeyEvent.VK_TAB){ 176 | if(name.hasFocus()){ 177 | name.removeFocus(); 178 | host.giveFocus(); 179 | }else if(host.hasFocus()){ 180 | host.removeFocus(); 181 | name.giveFocus(); 182 | } 183 | }else{ 184 | name.handleKeyEvent(event); 185 | host.handleKeyEvent(event); 186 | } 187 | } 188 | } 189 | 190 | @Override 191 | public void handleMouseRelease(Point2D loc, int width, int height){ 192 | super.handleMouseRelease(loc, width, height); 193 | if(!connecting){ 194 | name.handleMouseClick(loc); 195 | host.handleMouseClick(loc); 196 | if(connect.contains(loc)){ 197 | connect(); 198 | } 199 | } 200 | } 201 | 202 | @Override 203 | protected boolean isLeftButtonEnabled(){ 204 | return true; 205 | } 206 | 207 | @Override 208 | protected boolean isRightButtonEnabled(){ 209 | return false; 210 | } 211 | 212 | @Override 213 | protected String getLeftButtonText(){ 214 | return "Back"; 215 | } 216 | 217 | @Override 218 | protected String getRightButtonText(){ 219 | return null; 220 | } 221 | 222 | @Override 223 | protected void handleLeftButtonClick(){ 224 | if(con != null){ 225 | con.close(); 226 | } 227 | executor.shutdownNow(); 228 | switchScene(new MainMenu(this.getContext())); 229 | } 230 | 231 | @Override 232 | protected void handleRightButtonClick(){ 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/ui/MainMenu.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.ui; 20 | 21 | import java.awt.Graphics2D; 22 | import java.awt.Shape; 23 | import java.awt.geom.AffineTransform; 24 | import java.awt.geom.Path2D; 25 | import java.awt.geom.Point2D; 26 | import java.util.concurrent.ThreadLocalRandom; 27 | 28 | import dev.roanh.convexmerger.game.ConvexObject; 29 | import dev.roanh.convexmerger.ui.Theme.PlayerTheme; 30 | 31 | /** 32 | * Game main menu screen. 33 | * @author Roan 34 | */ 35 | public class MainMenu extends Screen{ 36 | /** 37 | * Width of the buttons. 38 | */ 39 | private static final double BUTTON_WIDTH = 250.0D; 40 | /** 41 | * Height of the buttons. 42 | */ 43 | private static final double BUTTON_HEIGHT = 70.0D; 44 | /** 45 | * Bounds of the multiplayer join button. 46 | */ 47 | private Path2D join = new Path2D.Double(); 48 | /** 49 | * Bounds of the multiplayer host button. 50 | */ 51 | private Path2D host = new Path2D.Double(); 52 | /** 53 | * Bounds of the single player button. 54 | */ 55 | private Path2D single = new Path2D.Double(); 56 | /** 57 | * Bounds of the info button. 58 | */ 59 | private Path2D info = new Path2D.Double(); 60 | /** 61 | * Bounds of the quit button. 62 | */ 63 | private Path2D quit = new Path2D.Double(); 64 | /** 65 | * Main menu show case objects. 66 | */ 67 | private Path2D[] objects; 68 | 69 | /** 70 | * Constructs a new main menu with the given game context. 71 | * @param context The game context. 72 | */ 73 | protected MainMenu(ConvexMerger context){ 74 | super(context); 75 | ThreadLocalRandom r = ThreadLocalRandom.current(); 76 | objects = new Path2D[]{ 77 | new ConvexObject( 78 | r.nextDouble(), r.nextDouble(), 79 | 1.0D + r.nextDouble(), r.nextDouble(), 80 | r.nextDouble(), 1.0D + r.nextDouble(), 81 | 1.0D + r.nextDouble(), 1.0D + r.nextDouble() 82 | ).getShape(), 83 | new ConvexObject( 84 | r.nextDouble(), r.nextDouble(), 85 | 1.0D + r.nextDouble(), r.nextDouble(), 86 | r.nextDouble(), 1.0D + r.nextDouble(), 87 | 1.0D + r.nextDouble(), 1.0D + r.nextDouble() 88 | ).getShape(), 89 | new ConvexObject( 90 | r.nextDouble(), r.nextDouble(), 91 | 1.0D + r.nextDouble(), r.nextDouble(), 92 | r.nextDouble(), 1.0D + r.nextDouble(), 93 | 1.0D + r.nextDouble(), 1.0D + r.nextDouble() 94 | ).getShape(), 95 | new ConvexObject( 96 | r.nextDouble(), r.nextDouble(), 97 | 1.0D + r.nextDouble(), r.nextDouble(), 98 | r.nextDouble(), 1.0D + r.nextDouble(), 99 | 1.0D + r.nextDouble(), 1.0D + r.nextDouble() 100 | ).getShape() 101 | }; 102 | } 103 | 104 | @Override 105 | protected void render(Graphics2D g, int width, int height, Point2D mouseLoc){ 106 | renderMainInterface(g, width, height, null); 107 | renderMenuTitle(g, width, "Main Menu"); 108 | drawTitle(g, width); 109 | 110 | single = drawButton(g, "Single Player", (width - BUTTON_WIDTH) / 2.0D, TOP_MIDDLE_OFFSET + BOX_SPACING * 2.0D + TOP_SPACE, BUTTON_WIDTH, BUTTON_HEIGHT, mouseLoc); 111 | host = drawButton(g, "Host Multiplayer", (width - BUTTON_WIDTH) / 2.0D, TOP_MIDDLE_OFFSET + BOX_SPACING * 2.0D + TOP_SPACE + BOX_SPACING + BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT, mouseLoc); 112 | join = drawButton(g, "Join Multiplayer", (width - BUTTON_WIDTH) / 2.0D, TOP_MIDDLE_OFFSET + BOX_SPACING * 2.0D + TOP_SPACE + (BOX_SPACING + BUTTON_HEIGHT) * 2.0D, BUTTON_WIDTH, BUTTON_HEIGHT, mouseLoc); 113 | info = drawButton(g, "Info & Rules", (width - BUTTON_WIDTH) / 2.0D, TOP_MIDDLE_OFFSET + BOX_SPACING * 2.0D + TOP_SPACE + (BOX_SPACING + BUTTON_HEIGHT) * 3.0D, BUTTON_WIDTH, BUTTON_HEIGHT, mouseLoc); 114 | quit = drawButton(g, "Quit", (width - BUTTON_WIDTH) / 2.0D, TOP_MIDDLE_OFFSET + BOX_SPACING * 2.0D + TOP_SPACE + (BOX_SPACING + BUTTON_HEIGHT) * 4.0D, BUTTON_WIDTH, BUTTON_HEIGHT, mouseLoc); 115 | 116 | double w = (width - BUTTON_WIDTH - TOP_SIDE_TRIANGLE * 4.0D) / 2.0D; 117 | double h = (height - TOP_SPACE - TOP_SIDE_TRIANGLE * 2.0D - BOX_INSETS) / 2.0D; 118 | AffineTransform transform = AffineTransform.getScaleInstance(0.5D * w, 0.5D * h); 119 | 120 | g.translate(TOP_SIDE_TRIANGLE, TOP_SPACE + TOP_SIDE_TRIANGLE); 121 | render(g, objects[0].createTransformedShape(transform), PlayerTheme.P1); 122 | 123 | g.translate(0.0D, h); 124 | render(g, objects[1].createTransformedShape(transform), PlayerTheme.P2); 125 | 126 | g.translate(TOP_SIDE_TRIANGLE * 2.0D + BUTTON_WIDTH + w, 0.0D); 127 | render(g, objects[2].createTransformedShape(transform), PlayerTheme.P3); 128 | 129 | g.translate(0.0D, -h); 130 | render(g, objects[3].createTransformedShape(transform), PlayerTheme.P4); 131 | } 132 | 133 | /** 134 | * Renders the given shape in 135 | * the convex object style. 136 | * @param g The graphics context to use. 137 | * @param obj The shape to render. 138 | * @param theme The theme to use. 139 | */ 140 | private void render(Graphics2D g, Shape obj, PlayerTheme theme){ 141 | g.setColor(theme.getBody()); 142 | g.fill(obj); 143 | g.setStroke(Theme.POLY_STROKE); 144 | g.setColor(theme.getOutline()); 145 | g.draw(obj); 146 | } 147 | 148 | @Override 149 | public void handleMouseRelease(Point2D loc, int width, int height){ 150 | super.handleMouseRelease(loc, width, height); 151 | 152 | if(join.contains(loc)){ 153 | this.switchScene(new JoinMenu(this.getContext())); 154 | } 155 | 156 | if(host.contains(loc)){ 157 | this.switchScene(new HostMenu(this.getContext())); 158 | } 159 | 160 | if(single.contains(loc)){ 161 | this.getContext().showNewGame(); 162 | } 163 | 164 | if(info.contains(loc)){ 165 | this.switchScene(new InfoMenu(this.getContext(), null, new MainMenu(this.getContext()))); 166 | } 167 | 168 | if(quit.contains(loc)){ 169 | this.getContext().exit(); 170 | } 171 | } 172 | 173 | @Override 174 | protected boolean isLeftButtonEnabled(){ 175 | return false; 176 | } 177 | 178 | @Override 179 | protected boolean isRightButtonEnabled(){ 180 | return false; 181 | } 182 | 183 | @Override 184 | protected String getLeftButtonText(){ 185 | return null; 186 | } 187 | 188 | @Override 189 | protected String getRightButtonText(){ 190 | return null; 191 | } 192 | 193 | @Override 194 | protected void handleLeftButtonClick(){ 195 | } 196 | 197 | @Override 198 | protected void handleRightButtonClick(){ 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/ui/MessageDialog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.ui; 20 | 21 | import java.util.Arrays; 22 | import java.util.List; 23 | 24 | /** 25 | * Enum of feedback messages shown to the player. 26 | * @author Roan 27 | */ 28 | public enum MessageDialog{ 29 | /** 30 | * Shown when the player tries to claim an object that was 31 | * previously already claimed by a different player. 32 | */ 33 | ALREADY_OWNED("Already Claimed", "This object was already claimed by another player."), 34 | /** 35 | * Shown when a merge attempted by the player intersects 36 | * other objects on its boundary. 37 | */ 38 | MERGE_INTERSECTS("Invalid Merge", "Your merge intersects other objects on its boundary."), 39 | /** 40 | * Shown when the player tries to perform a move when it's not their turn. 41 | */ 42 | NO_TURN("Not Your Turn", "Please wait for the other player(s) to finish their turn."), 43 | /** 44 | * Shown when the player tries to perform a move after the game ended. 45 | */ 46 | GAME_END("Game Ended", "This game has ended, please start a new game."), 47 | /** 48 | * Shown when the player presses the menu button. 49 | */ 50 | QUIT("Quit", " Are you sure you want to quit?"), 51 | /** 52 | * Shown when the game state is not ready to handle the next player action. 53 | */ 54 | NOT_READY("Game Not Ready", "The game is not ready yet to handle the next turn, please wait a bit."); 55 | 56 | /** 57 | * The title for this dialog. 58 | */ 59 | private final String title; 60 | /** 61 | * The message for this dialog. 62 | */ 63 | private final List message; 64 | 65 | /** 66 | * Constructs a new message dialog with the given 67 | * title and feedback message. 68 | * @param title The dialog title. 69 | * @param message The feedback message. 70 | */ 71 | private MessageDialog(String title, String message){ 72 | this.title = title; 73 | this.message = Arrays.asList(message.split(" ")); 74 | } 75 | 76 | /** 77 | * Gets the title for this dialog. 78 | * @return The title for this dialog. 79 | */ 80 | public String getTitle(){ 81 | return title; 82 | } 83 | 84 | /** 85 | * Gets the feedback message for this dialog. 86 | * @return The feedback message for this dialog 87 | * as a list of words. 88 | */ 89 | public List getMessage(){ 90 | return message; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/ui/ScreenRenderer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.ui; 20 | 21 | import java.awt.Color; 22 | import java.awt.Graphics; 23 | import java.awt.Graphics2D; 24 | import java.awt.RenderingHints; 25 | import java.awt.event.KeyEvent; 26 | import java.awt.event.KeyListener; 27 | import java.awt.event.MouseEvent; 28 | import java.awt.event.MouseListener; 29 | import java.awt.event.MouseMotionListener; 30 | import java.awt.geom.AffineTransform; 31 | import java.util.concurrent.Executors; 32 | import java.util.concurrent.ScheduledExecutorService; 33 | import java.util.concurrent.ThreadFactory; 34 | import java.util.concurrent.TimeUnit; 35 | 36 | import javax.swing.JPanel; 37 | 38 | import dev.roanh.convexmerger.Constants; 39 | 40 | /** 41 | * Main game renderer responsible for rendering screens. 42 | * @author Roan 43 | */ 44 | public class ScreenRenderer extends JPanel implements MouseListener, MouseMotionListener, KeyListener, ThreadFactory{ 45 | /** 46 | * Serial ID. 47 | */ 48 | private static final long serialVersionUID = -1921051341650291287L; 49 | /** 50 | * Executor service used to run animations. 51 | */ 52 | private final transient ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(this); 53 | /** 54 | * The active screen to render. 55 | */ 56 | private transient Screen screen; 57 | /** 58 | * Whether to render the FPS counter. 59 | */ 60 | private boolean showFPS = false; 61 | 62 | /** 63 | * Constructs a new screen renderer with the given 64 | * initial screen to render. 65 | * @param screen The screen to render. 66 | */ 67 | public ScreenRenderer(Screen screen){ 68 | this.setFocusable(true); 69 | this.addMouseListener(this); 70 | this.addMouseMotionListener(this); 71 | this.addKeyListener(this); 72 | this.setFocusTraversalKeysEnabled(false); 73 | setScreen(screen); 74 | } 75 | 76 | /** 77 | * Sets the screen to render. 78 | * @param screen The new screen to render. 79 | * @return The previous screen. 80 | */ 81 | public Screen setScreen(Screen screen){ 82 | Screen old = this.screen; 83 | this.screen = screen; 84 | return old; 85 | } 86 | 87 | @Override 88 | public void paintComponent(Graphics g1){ 89 | Graphics2D g = (Graphics2D)g1.create(); 90 | g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 91 | g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 92 | AffineTransform transform = g.getTransform(); 93 | 94 | long start = System.currentTimeMillis(); 95 | screen.render(g, this.getWidth(), this.getHeight()); 96 | long delta = System.currentTimeMillis() - start; 97 | executor.schedule(()->this.repaint(), Math.max(0, Constants.ANIMATION_RATE - delta), TimeUnit.MILLISECONDS); 98 | 99 | if(showFPS){ 100 | g.setTransform(transform); 101 | g.setFont(Theme.PRIDI_MEDIUM_14); 102 | g.setColor(Color.RED); 103 | long max = 1000 / Constants.ANIMATION_RATE; 104 | g.drawString("FPS: " + String.valueOf(delta == 0 ? max : Math.min(max, 1000 / delta)) + "/" + max + " (" + delta + "ms)", 7, g.getFontMetrics().getAscent()); 105 | } 106 | } 107 | 108 | @Override 109 | public void keyTyped(KeyEvent e){ 110 | } 111 | 112 | @Override 113 | public void keyPressed(KeyEvent e){ 114 | screen.handleKeyPressed(e); 115 | if(e.getKeyCode() == KeyEvent.VK_F3){ 116 | showFPS = !showFPS; 117 | } 118 | } 119 | 120 | @Override 121 | public void keyReleased(KeyEvent e){ 122 | screen.handleKeyReleased(e); 123 | } 124 | 125 | @Override 126 | public void mouseDragged(MouseEvent e){ 127 | screen.handleMouseDrag(e.getPoint(), this.getWidth(), this.getHeight()); 128 | } 129 | 130 | @Override 131 | public void mouseMoved(MouseEvent e){ 132 | screen.handleMouseMove(e.getPoint(), this.getWidth(), this.getHeight()); 133 | } 134 | 135 | @Override 136 | public void mouseClicked(MouseEvent e){ 137 | } 138 | 139 | @Override 140 | public void mousePressed(MouseEvent e){ 141 | screen.handleMousePress(e.getPoint(), this.getWidth(), this.getHeight()); 142 | } 143 | 144 | @Override 145 | public void mouseReleased(MouseEvent e){ 146 | screen.handleMouseRelease(e.getPoint(), this.getWidth(), this.getHeight()); 147 | } 148 | 149 | @Override 150 | public void mouseEntered(MouseEvent e){ 151 | } 152 | 153 | @Override 154 | public void mouseExited(MouseEvent e){ 155 | } 156 | 157 | @Override 158 | public Thread newThread(Runnable r){ 159 | Thread thread = Executors.defaultThreadFactory().newThread(r); 160 | thread.setDaemon(true); 161 | thread.setName("AnimationThread-" + thread.getName()); 162 | return thread; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/ui/TextField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.ui; 20 | 21 | import java.awt.Color; 22 | import java.awt.FontMetrics; 23 | import java.awt.Graphics2D; 24 | import java.awt.Toolkit; 25 | import java.awt.datatransfer.DataFlavor; 26 | import java.awt.datatransfer.StringSelection; 27 | import java.awt.datatransfer.UnsupportedFlavorException; 28 | import java.awt.event.KeyEvent; 29 | import java.awt.geom.Line2D; 30 | import java.awt.geom.Point2D; 31 | import java.awt.geom.Rectangle2D; 32 | import java.io.IOException; 33 | 34 | import javax.swing.event.ChangeListener; 35 | 36 | /** 37 | * Text field UI component. 38 | * @author Roan 39 | */ 40 | public class TextField{ 41 | /** 42 | * The accent color for this text field. 43 | */ 44 | private Color color; 45 | /** 46 | * Text field bounds. 47 | */ 48 | private Rectangle2D bounds = new Rectangle2D.Double(); 49 | /** 50 | * Current text field text. 51 | */ 52 | private String text = ""; 53 | /** 54 | * Whether this text field has focus. 55 | */ 56 | private boolean focus = false; 57 | /** 58 | * True if text in this text field is centred. 59 | */ 60 | private boolean center = false; 61 | /** 62 | * Listener called when the text field text changes. 63 | */ 64 | private TextChangeListener changeListener = s->{}; 65 | /** 66 | * Listener called when this text field loses focus. 67 | */ 68 | private FocusLossListener focusListener = ()->{}; 69 | /** 70 | * The foreground (text) colour. 71 | */ 72 | private Color foreground = Theme.BOX_TEXT_COLOR; 73 | 74 | /** 75 | * Constructs a new text field with the given accent color. 76 | * @param color The accent color. 77 | */ 78 | public TextField(Color color){ 79 | this.color = color; 80 | } 81 | 82 | /** 83 | * Sets whether the text in this text field should be centred. 84 | * @param center True to center the text in this text field. 85 | */ 86 | public void setCentred(boolean center){ 87 | this.center = center; 88 | } 89 | 90 | /** 91 | * Sets the change listener for this text field. 92 | * @param listener The new change listener. 93 | * @see ChangeListener 94 | */ 95 | public void setChangeListener(TextChangeListener listener){ 96 | changeListener = listener; 97 | } 98 | 99 | /** 100 | * Sets the focus loss listener for this text field. 101 | * @param listener The new focus listener. 102 | * @see FocusLossListener 103 | */ 104 | public void setFocusListener(FocusLossListener listener){ 105 | focusListener = listener; 106 | } 107 | 108 | /** 109 | * Sets the foreground (text) colour for this text field. 110 | * @param color The new foreground colour. 111 | */ 112 | public void setForegroundColor(Color color){ 113 | foreground = color; 114 | } 115 | 116 | /** 117 | * Gets the text in this text field. 118 | * @return The text in this text field. 119 | */ 120 | public String getText(){ 121 | return text; 122 | } 123 | 124 | /** 125 | * Checks if this text field currently has focus. 126 | * @return True if this text field has focus. 127 | */ 128 | public boolean hasFocus(){ 129 | return focus; 130 | } 131 | 132 | /** 133 | * Sets the text for this text field. 134 | * @param text The new text or 135 | * null to clear the text. 136 | */ 137 | public void setText(String text){ 138 | this.text = text == null ? "" : text; 139 | } 140 | 141 | /** 142 | * Removes the focus from this text field. 143 | */ 144 | public void removeFocus(){ 145 | boolean old = focus; 146 | focus = false; 147 | if(old){ 148 | focusListener.onFocusLost(); 149 | } 150 | } 151 | 152 | /** 153 | * Gives focus to this text field. 154 | */ 155 | public void giveFocus(){ 156 | focus = true; 157 | } 158 | 159 | /** 160 | * Handles a key event on this text field. 161 | * @param event The event to handle. 162 | */ 163 | public void handleKeyEvent(KeyEvent event){ 164 | if(hasFocus()){ 165 | if(event.getKeyCode() == KeyEvent.VK_BACK_SPACE){ 166 | if(!text.isEmpty()){ 167 | text = text.substring(0, text.length() - 1); 168 | changeListener.onTextChange(text); 169 | } 170 | }else if(event.isControlDown() && event.getKeyCode() == KeyEvent.VK_V){ 171 | try{ 172 | text += Toolkit.getDefaultToolkit().getSystemClipboard().getData(DataFlavor.stringFlavor); 173 | changeListener.onTextChange(text); 174 | }catch(IllegalStateException | UnsupportedFlavorException | IOException ignore){ 175 | //copy paste just fails 176 | } 177 | }else if(event.isControlDown() && event.getKeyCode() == KeyEvent.VK_C){ 178 | try{ 179 | Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(text), null); 180 | }catch(IllegalStateException ignore){ 181 | //copy paste just fails 182 | } 183 | }else if(!event.isControlDown() && !event.isAltDown() && event.getKeyChar() != KeyEvent.CHAR_UNDEFINED){ 184 | text += event.getKeyChar(); 185 | changeListener.onTextChange(text); 186 | } 187 | } 188 | } 189 | 190 | /** 191 | * Handles a mouse event on this text field. 192 | * @param loc The location that was clicked. 193 | */ 194 | public void handleMouseClick(Point2D loc){ 195 | boolean old = focus; 196 | focus = bounds.contains(loc); 197 | if(old){ 198 | focusListener.onFocusLost(); 199 | } 200 | } 201 | 202 | /** 203 | * Renders this text field. 204 | * @param g The graphics context to use. 205 | * @param x The x coordinate of the top left corner. 206 | * @param y The y coordinate of the top left corner. 207 | * @param width The width of the text field. 208 | * @param height The height of the text field. 209 | */ 210 | protected void render(Graphics2D g, double x, double y, double width, double height){ 211 | g.setColor(Theme.DOUBLE_LIGHTEN); 212 | bounds = new Rectangle2D.Double(x, y, width, height); 213 | g.fill(bounds); 214 | g.setClip(bounds); 215 | 216 | g.setStroke(Theme.BORDER_STROKE); 217 | g.setColor(foreground); 218 | g.setFont(Theme.PRIDI_MEDIUM_14); 219 | FontMetrics fm = g.getFontMetrics(); 220 | if(center){ 221 | g.drawString(text, (float)(x + (width - fm.stringWidth(text)) / 2.0D), (float)(y + height - fm.getMaxDescent())); 222 | }else{ 223 | g.drawString(text, (float)(x + 4.0F), (float)(y + height - fm.getMaxDescent())); 224 | } 225 | if(focus && ((System.currentTimeMillis() / 600) % 2 == 0)){ 226 | int lx = (int)Math.ceil(x + (center ? (width + fm.stringWidth(text)) / 2.0D : 4.0F + fm.stringWidth(text))); 227 | g.setColor(color); 228 | g.drawLine(lx, (int)(y + 2.0F), lx, (int)(y + height - 4.0F)); 229 | } 230 | 231 | g.setColor(color); 232 | g.draw(new Line2D.Double(x, y + height - 1, x + width - 1, y + height - 1)); 233 | g.setClip(null); 234 | } 235 | 236 | /** 237 | * Listener called when the text field context changes. 238 | * @author Roan 239 | */ 240 | @FunctionalInterface 241 | public static abstract interface TextChangeListener{ 242 | 243 | /** 244 | * Called when the text field content changes. 245 | * @param text The next text field content. 246 | */ 247 | public abstract void onTextChange(String text); 248 | } 249 | 250 | /** 251 | * Listener called when the user stops editing a text field. 252 | * @author Roan 253 | */ 254 | @FunctionalInterface 255 | public static abstract interface FocusLossListener{ 256 | 257 | /** 258 | * Called when the text field loses user focus. 259 | */ 260 | public abstract void onFocusLost(); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/util/KDTree.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.util; 20 | 21 | import java.awt.Color; 22 | import java.awt.Graphics2D; 23 | import java.awt.Shape; 24 | import java.awt.geom.Ellipse2D; 25 | import java.awt.geom.Line2D; 26 | import java.awt.geom.Point2D; 27 | import java.awt.geom.Rectangle2D; 28 | import java.util.ArrayList; 29 | import java.util.Arrays; 30 | import java.util.Collections; 31 | import java.util.Comparator; 32 | import java.util.List; 33 | 34 | import dev.roanh.convexmerger.Constants; 35 | import dev.roanh.convexmerger.ui.Theme; 36 | 37 | /** 38 | * Implementation of a kd-tree for k = 2. 39 | * @author Roan 40 | * @param The per cell item data type. 41 | */ 42 | public class KDTree extends PartitionTree>{ 43 | /** 44 | * The parent kd-tree this kd-tree is a cell in. Will 45 | * be null if this is the root node of the tree. 46 | */ 47 | private KDTree parent = null; 48 | /** 49 | * The low child of this kd-tree. This is the cell 50 | * containing the points lower than the point for this 51 | * KD tree. Will be null if this is a leaf cell. 52 | */ 53 | private KDTree low = null; 54 | /** 55 | * The high child of this kd-tree. This is the cell 56 | * containing the points higher than the point for this 57 | * kd-tree. Will be null if this is a leaf cell. 58 | */ 59 | private KDTree high = null; 60 | /** 61 | * The point defining the line splitting this kd-tree 62 | * cell into a low and high cell. Will be null 63 | * if this is a leaf cell. 64 | */ 65 | private Point2D point = null; 66 | /** 67 | * The way this kd-tree node is split into a low and 68 | * high cell. If true the splitting line is vertical 69 | * and otherwise the splitting line is horizontal. 70 | */ 71 | private boolean xAxis; 72 | /** 73 | * The axis aligned bounding rectangle defining the 74 | * bounds of this kd-tree cell. 75 | */ 76 | private Rectangle2D bounds = null; 77 | 78 | /** 79 | * Constructs a new kd-tree for the given point set. 80 | * @param points The points to build a kd-tree from. 81 | */ 82 | public KDTree(List points){ 83 | this(null, points, true); 84 | } 85 | 86 | /** 87 | * Constructs a new child kd-tree node with the given parent 88 | * node, point set to build a sub tree with and axis to split on. 89 | * @param parent The parent node of this kd-tree node. 90 | * @param points The points to build a kd-subtree from. 91 | * @param xAxis True if this cell should be split based on the 92 | * X coordinate of points, false to split on the Y coordinate. 93 | */ 94 | private KDTree(KDTree parent, List points, boolean xAxis){ 95 | this.parent = parent; 96 | this.xAxis = xAxis; 97 | if(points != null && !points.isEmpty()){ 98 | points.sort(Comparator.comparing(xAxis ? Point2D::getX : Point2D::getY)); 99 | this.point = points.get(points.size() / 2); 100 | 101 | if(points.size() > 1){ 102 | low = new KDTree(this, new ArrayList(points.subList(0, points.size() / 2)), !xAxis); 103 | }else{ 104 | low = new KDTree(this, null, !xAxis); 105 | } 106 | 107 | if(points.size() > 2){ 108 | high = new KDTree(this, new ArrayList(points.subList(points.size() / 2 + 1, points.size())), !xAxis); 109 | }else{ 110 | high = new KDTree(this, null, !xAxis); 111 | } 112 | } 113 | } 114 | 115 | /** 116 | * Gets the point defining the line dividing this kd-tree 117 | * node into two child nodes. 118 | * @return The point dividing this tree node. 119 | */ 120 | public Point2D getPoint(){ 121 | return point; 122 | } 123 | 124 | /** 125 | * Gets the child node containing the low value 126 | * points stored in the children of this node. 127 | * @return The low value child node. 128 | */ 129 | public KDTree getLowNode(){ 130 | return low; 131 | } 132 | 133 | /** 134 | * Gets the child node containing the high value 135 | * points stored in the children of this node. 136 | * @return The high value child node. 137 | */ 138 | public KDTree getHighNode(){ 139 | return high; 140 | } 141 | 142 | /** 143 | * Checks if the given line intersects this kd-tree node. 144 | * @param line The line to check for intersection. 145 | * @return True if the given line intersects this kd-tree node. 146 | */ 147 | public boolean intersects(Line2D line){ 148 | return getBounds().intersectsLine(line); 149 | } 150 | 151 | /** 152 | * Gets the bounding rectangle of this kd-tree node. 153 | * @return The bounding rectangle of this kd-tree node. 154 | */ 155 | public Rectangle2D getBounds(){ 156 | if(bounds == null){ 157 | if(parent == null){ 158 | bounds = new Rectangle2D.Double(0.0D, 0.0D, Constants.PLAYFIELD_WIDTH, Constants.PLAYFIELD_HEIGHT); 159 | }else if(this == parent.low){ 160 | Rectangle2D parentBounds = parent.getBounds(); 161 | if(xAxis){ 162 | bounds = new Rectangle2D.Double( 163 | parentBounds.getMinX(), 164 | parentBounds.getMinY(), 165 | parentBounds.getWidth(), 166 | parent.point.getY() - parentBounds.getMinY() 167 | ); 168 | }else{ 169 | bounds = new Rectangle2D.Double( 170 | parentBounds.getMinX(), 171 | parentBounds.getMinY(), 172 | parent.point.getX() - parentBounds.getMinX(), 173 | parentBounds.getHeight() 174 | ); 175 | } 176 | }else if(this == parent.high){ 177 | Rectangle2D parentBounds = parent.getBounds(); 178 | if(xAxis){ 179 | bounds = new Rectangle2D.Double( 180 | parentBounds.getMinX(), 181 | parent.point.getY(), 182 | parentBounds.getWidth(), 183 | parentBounds.getMaxY() - parent.point.getY() 184 | ); 185 | }else{ 186 | bounds = new Rectangle2D.Double( 187 | parent.point.getX(), 188 | parentBounds.getMinY(), 189 | parentBounds.getMaxX() - parent.point.getX(), 190 | parentBounds.getHeight() 191 | ); 192 | } 193 | } 194 | } 195 | 196 | return bounds; 197 | } 198 | 199 | @Override 200 | public void render(Graphics2D g){ 201 | super.render(g); 202 | 203 | if(!isLeafCell()){ 204 | low.render(g); 205 | high.render(g); 206 | 207 | Rectangle2D bounds = getBounds(); 208 | int c = Math.max(0, 255 - getDepth() * 25); 209 | g.setColor(new Color(0, c, c)); 210 | g.setStroke(Theme.BORDER_STROKE); 211 | if(xAxis){ 212 | g.draw(new Line2D.Double(point.getX(), bounds.getMinY(), point.getX(), bounds.getMaxY())); 213 | }else{ 214 | g.draw(new Line2D.Double(bounds.getMinX(), point.getY(), bounds.getMaxX(), point.getY())); 215 | } 216 | 217 | g.setColor(Color.BLUE); 218 | g.fill(new Ellipse2D.Double(point.getX() - 2.5D, point.getY() - 2.5D, 5.0D, 5.0D)); 219 | } 220 | } 221 | 222 | @Override 223 | public Shape getShape(){ 224 | return getBounds(); 225 | } 226 | 227 | @Override 228 | public boolean isLeafCell(){ 229 | return point == null; 230 | } 231 | 232 | @Override 233 | public List> getChildren(){ 234 | return isLeafCell() ? Collections.emptyList() : Arrays.asList(low, high); 235 | } 236 | 237 | @Override 238 | public KDTree getParent(){ 239 | return parent; 240 | } 241 | 242 | @Override 243 | public KDTree getSelf(){ 244 | return this; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/util/PartitionTree.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.util; 20 | 21 | import java.awt.Color; 22 | import java.awt.Graphics2D; 23 | import java.awt.Shape; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import java.util.stream.Stream; 27 | 28 | import dev.roanh.convexmerger.animation.RenderableObject; 29 | 30 | /** 31 | * Abstract base class for partition tree implementations. 32 | * @author Roan 33 | * @param The metadata type. 34 | * @param The partition tree type. 35 | * @see KDTree 36 | * @see ConjugationTree 37 | */ 38 | public abstract class PartitionTree> extends RenderableObject{ 39 | /** 40 | * The data stored in this cell. 41 | */ 42 | private List data = new ArrayList(); 43 | /** 44 | * Whether this partition tree is marked or not, used for animation. 45 | */ 46 | private boolean marked = false; 47 | 48 | /** 49 | * Sets whether this partition tree is marked or not. 50 | * This is used for animations. 51 | * @param marked True to mark this tree. 52 | */ 53 | public void setMarked(boolean marked){ 54 | this.marked = marked; 55 | } 56 | 57 | /** 58 | * Adds an object to store at this tree node. 59 | * @param obj The object to store. 60 | */ 61 | public void addData(T obj){ 62 | data.add(obj); 63 | } 64 | 65 | /** 66 | * Gets the data stored at this tree node. 67 | * @return The data stored. 68 | */ 69 | public List getData(){ 70 | return data; 71 | } 72 | 73 | /** 74 | * Gets the height of the partition tree this 75 | * cell is a part of. A value of 0 indicates 76 | * that this tree only has a root node. 77 | * @return The height of this partition tree. 78 | */ 79 | public int getHeight(){ 80 | return streamLeafCells().mapToInt(S::getDepth).max().orElse(0); 81 | } 82 | 83 | /** 84 | * Gets the depth of this cell in the partition tree. 85 | * A value of 0 indicates that this cell is the root node. 86 | * @return The depth of this partition tree node. 87 | */ 88 | public int getDepth(){ 89 | S parent = getParent(); 90 | return parent == null ? 0 : 1 + parent.getDepth(); 91 | } 92 | 93 | /** 94 | * Streams all the leaf cells in this tree. 95 | * @return A stream of all partition tree leaf cells. 96 | */ 97 | public Stream streamLeafCells(){ 98 | if(isLeafCell()){ 99 | return Stream.of(getSelf()); 100 | }else{ 101 | Stream stream = Stream.empty(); 102 | for(S child : getChildren()){ 103 | stream = Stream.concat(stream, child.streamLeafCells()); 104 | } 105 | return stream; 106 | } 107 | } 108 | 109 | /** 110 | * Streams all the cells (both leaf and internal) in this tree. 111 | * @return A stream of all partition tree cells. 112 | */ 113 | public Stream streamCells(){ 114 | Stream stream = Stream.of(getSelf()); 115 | for(S child : getChildren()){ 116 | stream = Stream.concat(stream, child.streamCells()); 117 | } 118 | return stream; 119 | } 120 | 121 | /** 122 | * Checks if this tree node is a leaf cell. 123 | * @return True if this tree node is a leaf cell. 124 | */ 125 | public abstract boolean isLeafCell(); 126 | 127 | /** 128 | * Gets the child nodes of this tree node. 129 | * @return The direct child nodes of this node. 130 | */ 131 | public abstract List getChildren(); 132 | 133 | /** 134 | * Gets the parent tree node of this node. 135 | * @return The parent node of this node or 136 | * null if this is node 137 | * is the root node of the tree. 138 | */ 139 | public abstract S getParent(); 140 | 141 | /** 142 | * Gets the shape defining the boundary of this tree cell. 143 | * @return The partition tree cell bounds. 144 | */ 145 | public abstract Shape getShape(); 146 | 147 | /** 148 | * Gets 'this' partition tree. 149 | * @return This partition tree. 150 | */ 151 | public abstract S getSelf(); 152 | 153 | @Override 154 | public void render(Graphics2D g){ 155 | if(marked){ 156 | g.setColor(new Color(255, 0, 0, 50)); 157 | g.fill(getShape()); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /ConvexMerger/src/dev/roanh/convexmerger/util/Segment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.util; 20 | 21 | import java.awt.geom.Line2D; 22 | import java.awt.geom.Point2D; 23 | import java.awt.geom.Rectangle2D; 24 | import java.util.Objects; 25 | 26 | /** 27 | * A line segment instance with equality based 28 | * on its end points. 29 | * @author Roan 30 | */ 31 | public class Segment extends Line2D{ 32 | /** 33 | * First end point of the line. 34 | */ 35 | protected Point2D p1; 36 | /** 37 | * Second end point of the line. 38 | */ 39 | protected Point2D p2; 40 | 41 | /** 42 | * Constructs a new line segment with the given end points. 43 | * @param p1 The first end point of the line. 44 | * @param p2 The second end point of the line. 45 | */ 46 | public Segment(Point2D p1, Point2D p2){ 47 | this.p1 = p1; 48 | this.p2 = p2; 49 | } 50 | 51 | @Override 52 | public Rectangle2D getBounds2D(){ 53 | return new Rectangle2D.Double( 54 | Math.min(p1.getX(), p2.getX()), 55 | Math.min(p1.getY(), p2.getY()), 56 | Math.abs(p1.getX() - p2.getX()), 57 | Math.abs(p1.getY() - p2.getY()) 58 | ); 59 | } 60 | 61 | @Override 62 | public double getX1(){ 63 | return p1.getX(); 64 | } 65 | 66 | @Override 67 | public double getY1(){ 68 | return p1.getY(); 69 | } 70 | 71 | @Override 72 | public Point2D getP1(){ 73 | return p1; 74 | } 75 | 76 | @Override 77 | public double getX2(){ 78 | return p2.getX(); 79 | } 80 | 81 | @Override 82 | public double getY2(){ 83 | return p2.getY(); 84 | } 85 | 86 | @Override 87 | public Point2D getP2(){ 88 | return p2; 89 | } 90 | 91 | @Override 92 | public void setLine(double x1, double y1, double x2, double y2){ 93 | throw new IllegalStateException("Unsupported operation"); 94 | } 95 | 96 | @Override 97 | public int hashCode(){ 98 | return Objects.hash(p1, p2); 99 | } 100 | 101 | @Override 102 | public boolean equals(Object other){ 103 | if(other instanceof Segment){ 104 | Segment line = (Segment)other; 105 | return (line.p1 == p1 && line.p2 == p2) || (line.p1 == p2 && line.p2 == p1); 106 | }else{ 107 | return false; 108 | } 109 | } 110 | 111 | @Override 112 | public String toString(){ 113 | return "Segment[p1=(" + p1.getX() + "," + p1.getY() + "),p2=(" + p2.getX() + "," + p2.getY() + ")]"; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /ConvexMerger/test/dev/roanh/convexmerger/util/ConjugationTreeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.util; 20 | 21 | import static org.junit.jupiter.api.Assertions.*; 22 | 23 | import java.awt.geom.Point2D; 24 | import java.util.Arrays; 25 | import java.util.List; 26 | import java.util.stream.Collectors; 27 | 28 | import org.junit.jupiter.api.Test; 29 | import org.junit.jupiter.api.Timeout; 30 | 31 | import dev.roanh.convexmerger.Constants; 32 | import dev.roanh.convexmerger.game.ConvexObject; 33 | import dev.roanh.convexmerger.game.PlayfieldGenerator; 34 | 35 | public class ConjugationTreeTest{ 36 | private static final List testPoints = Arrays.asList( 37 | new Point2D.Double(100.0D, 100.0D), 38 | new Point2D.Double(200.0D, 300.0D), 39 | new Point2D.Double(300.0D, 400.0D), 40 | new Point2D.Double(400.0D, 200.0D), 41 | new Point2D.Double(500.0D, 500.0D), 42 | new Point2D.Double(600.0D, 600.0D), 43 | new Point2D.Double(150.0D, 700.0D), 44 | new Point2D.Double(800.0D, 200.0D), 45 | new Point2D.Double(750.0D, 450.0D), 46 | new Point2D.Double(300.0D, 800.0D), 47 | new Point2D.Double(1000.0D, 300.0D), 48 | new Point2D.Double(550.0D, 550.0D), 49 | new Point2D.Double(990.0D, 440.0D), 50 | new Point2D.Double(1100.0D, 480.0D) 51 | ); 52 | 53 | @Test 54 | public void constructionPoints(){ 55 | ConjugationTree tree = new ConjugationTree(testPoints); 56 | 57 | assertEquals(29L, tree.streamCells().count()); 58 | assertEquals(15L, tree.streamLeafCells().count()); 59 | tree.streamCells().forEach(cell->{ 60 | if(cell.isLeafCell()){ 61 | assertTrue(cell.getDepth() >= 3 && cell.getDepth() <= 4); 62 | }else{ 63 | assertEquals(1, cell.getPoints().size()); 64 | } 65 | }); 66 | 67 | testTree(tree); 68 | } 69 | 70 | @Test 71 | @Timeout(10) 72 | public void colinTest(){ 73 | SegmentPartitionTree.TYPE_CONJUGATION_TREE.fromObjects(Arrays.asList( 74 | new ConvexObject(ConvexUtil.computeConvexHull(Arrays.asList( 75 | new Point2D.Double(0.0D, 0.0D), 76 | new Point2D.Double(0.0D, 10.0D), 77 | new Point2D.Double(10.0D, 10.0D), 78 | new Point2D.Double(10.0D, 0.0D) 79 | ))), 80 | new ConvexObject(ConvexUtil.computeConvexHull(Arrays.asList( 81 | new Point2D.Double(Constants.PLAYFIELD_WIDTH, Constants.PLAYFIELD_HEIGHT), 82 | new Point2D.Double(Constants.PLAYFIELD_WIDTH, Constants.PLAYFIELD_HEIGHT - 10.0D), 83 | new Point2D.Double(Constants.PLAYFIELD_WIDTH - 10.0D, Constants.PLAYFIELD_HEIGHT - 10.0D), 84 | new Point2D.Double(Constants.PLAYFIELD_WIDTH - 10.0D, Constants.PLAYFIELD_HEIGHT) 85 | ))), 86 | new ConvexObject(ConvexUtil.computeConvexHull(Arrays.asList( 87 | new Point2D.Double(Constants.PLAYFIELD_WIDTH, 0.0D), 88 | new Point2D.Double(Constants.PLAYFIELD_WIDTH, 10.0D), 89 | new Point2D.Double(Constants.PLAYFIELD_WIDTH - 10.0D, 10.0D), 90 | new Point2D.Double(Constants.PLAYFIELD_WIDTH - 10.0D, 0.0D) 91 | ))), 92 | new ConvexObject(ConvexUtil.computeConvexHull(Arrays.asList( 93 | new Point2D.Double(0.0D, Constants.PLAYFIELD_HEIGHT), 94 | new Point2D.Double(0.0D, Constants.PLAYFIELD_HEIGHT - 10.0D), 95 | new Point2D.Double(10.0D, Constants.PLAYFIELD_HEIGHT - 10.0D), 96 | new Point2D.Double(10.0D, Constants.PLAYFIELD_HEIGHT) 97 | ))) 98 | )); 99 | } 100 | 101 | @Test 102 | @Timeout(10) 103 | public void conjugationComputationTest0(){ 104 | testConstructionSeed("3ZGRJD43F20COCERMV59"); 105 | } 106 | 107 | @Test 108 | @Timeout(10) 109 | public void conjugationComputationTest1(){ 110 | testConstructionSeed("3ZGRJD42GZYECMFRN0NQ"); 111 | } 112 | 113 | @Test 114 | @Timeout(10) 115 | public void conjugationComputationTest2(){ 116 | testConstructionSeed("3ZGRJD4163DXEYWINF8G"); 117 | } 118 | 119 | @Test 120 | @Timeout(10) 121 | public void conjugationComputationTest3(){ 122 | testConstructionSeed("3ZGRJD42I9EX87S8Y04P"); 123 | } 124 | 125 | @Test 126 | @Timeout(10) 127 | public void conjugationComputationTest4(){ 128 | testConstructionSeed("3ZGRJD41OQ741AD949KW"); 129 | } 130 | 131 | @Test 132 | @Timeout(10) 133 | public void conjugationComputationTest5(){ 134 | testConstructionSeed("3ZGRJD43QZ02Q4C61DYX"); 135 | } 136 | 137 | @Test 138 | @Timeout(10) 139 | public void conjugationComputationTest6(){ 140 | testConstructionSeed("3ZGRJD433ZR2AVHZ7Y7O"); 141 | } 142 | 143 | @Test 144 | @Timeout(10) 145 | public void conjugationComputationTest7(){ 146 | testConstructionSeed("3ZGRJD40WD57FXXT815Q"); 147 | } 148 | 149 | private void testConstructionSeed(String seed){ 150 | testTree(new ConjugationTree(new PlayfieldGenerator(seed).generatePlayfield().stream().flatMap(obj->{ 151 | return obj.getPoints().stream(); 152 | }).collect(Collectors.toList()))); 153 | } 154 | 155 | private void testTree(ConjugationTree tree){ 156 | //assert that all bisectors are also conjugates and that leaves have no points and inner nodes have points 157 | tree.streamCells().forEach(cell->{ 158 | if(cell.isLeafCell()){ 159 | assertTrue(cell.getPoints().isEmpty()); 160 | }else{ 161 | assertFalse(cell.getPoints().isEmpty()); 162 | if(cell.getDepth() > 0){ 163 | assertNotNull(ConvexUtil.interceptClosed(cell.getBisector(), cell.getParent().getBisector())); 164 | } 165 | } 166 | }); 167 | 168 | //the root is at depth 0 169 | assertEquals(0, tree.getDepth()); 170 | } 171 | } -------------------------------------------------------------------------------- /ConvexMerger/test/dev/roanh/convexmerger/util/KDTreeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvexMerger: An area maximisation game based on the idea of merging convex shapes. 3 | * Copyright (C) 2021 Roan Hofland (roan@roanh.dev), Emiliyan Greshkov and contributors. 4 | * GitHub Repository: https://github.com/RoanH/ConvexMerger 5 | * 6 | * ConvexMerger is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ConvexMerger is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package dev.roanh.convexmerger.util; 20 | 21 | import static org.junit.jupiter.api.Assertions.assertEquals; 22 | import static org.junit.jupiter.api.Assertions.assertTrue; 23 | 24 | import java.awt.geom.Point2D; 25 | import java.util.ArrayList; 26 | import java.util.Arrays; 27 | import java.util.List; 28 | 29 | import org.junit.jupiter.api.Test; 30 | 31 | public class KDTreeTest{ 32 | private static final List testPoints = Arrays.asList( 33 | new Point2D.Double(400.0D, 400.0D), 34 | new Point2D.Double(200.0D, 200.0D), 35 | new Point2D.Double(100.0D, 300.0D) 36 | ); 37 | 38 | @Test 39 | public void simpleConstruction(){ 40 | KDTree tree = new KDTree(new ArrayList(testPoints)); 41 | 42 | assertEquals(testPoints.get(1), tree.getPoint()); 43 | assertEquals(testPoints.get(2), tree.getLowNode().getPoint()); 44 | assertEquals(testPoints.get(0), tree.getHighNode().getPoint()); 45 | 46 | tree.streamLeafCells().forEach(cell->{ 47 | assertTrue(cell.getData().isEmpty()); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ConvexMerger [![](https://img.shields.io/github/release/RoanH/ConvexMerger.svg)](https://github.com/RoanH/ConvexMerger/releases) [![](https://img.shields.io/github/downloads/RoanH/ConvexMerger/total.svg)](#downloads) 2 | ConvexMerger is an area maximisation game based on the idea of merging convex shapes. The goal of the game is to claim as large an area as possible while competing against your opponents that try to do the same. To do this you can either claim new objects for your own or merge already claimed objects with other objects to also claim the area between them. It is possible to play the game with up to 4 players of which some can be AIs and it is also possible to play online multiplayer. The game ends when the active player has no moves left. 3 | 4 | [Jump directly to downloads](#downloads) 5 | 6 | ## Example Game 7 | A short example game between two AIs is shown below. 8 | ![example game](https://media.roanh.dev/convexmerger/example.gif) 9 | 10 | ## Rules 11 | The goal of the game is to maximise the area of the playfield you own by claiming and merging objects into new convex objects. In every turn you can do the following: 12 | 1. Click an unowned object to claim it for yourself. 13 | 2. Click an object you already own and then select a second object either owned by you or unowned. If there are no objects on what will become the boundary of the new convex object the merge will succeed. Objects fully contained in the newly created convex object will be stolen from their current owner. 14 | 15 | The game ends when the player whose turn it is has no possible moves available. You can visualise merging two objects as spanning an elastic band around both objects, the resulting shape is the new convex object. A merge of two objects is shown below: 16 | ![merge](https://media.roanh.dev/convexmerger/merge.gif) 17 | 18 | ## Playfield Options 19 | When generating the playfield you can control the object size, density and spacing. The exact function of these parameters is as follows: 20 | - **Object size**: Controls the size of the playfield objects. 21 | - **Density**: Controls how much of the playfield is initially covered by objects. 22 | - **Spacing**: Controls how much space there is between the objects. The spacing also affects the object size, with a larger spacing resulting in smaller objects. 23 | 24 | ## Downloads 25 | _Requires Java 8 or higher_ 26 | - [Windows executable](https://github.com/RoanH/ConvexMerger/releases/download/v1.2/ConvexMerger-v1.2.exe) 27 | - [Runnable Java Archive](https://github.com/RoanH/ConvexMerger/releases/download/v1.2/ConvexMerger-v1.2.jar) 28 | 29 | All releases: [releases](https://github.com/RoanH/ConvexMerger/releases) 30 | GitHub repository: [here](https://github.com/RoanH/ConvexMerger) 31 | 32 | ## Online Multiplayer 33 | The game has built in support for playing online multiplayer. Here one player will act as the host and all other players will connect to this host. If the host and all other players are on the same local network (e.g. WiFi), then players can connect to the host using the local IPv4 address of the host. If you want to play with remote players, then it is required for the host to portforward port 11111 and players can then connect using the external IP of the host. Please make sure you know what you are doing if you set this up and only play with people you trust. 34 | 35 | ## Algorithms 36 | This project was started for a course on geometric algorithms at the Eindhoven University of Technology and further extended during an algorithms capita selecta. As a result the internal logic for this game was designed around interesting geometric algorithms and most of the major algorithms can be visualised using the keybinds listed on the information screen. Detailed information about the capita selecta project where the game was extended can be found in our report titled [ConvexMerger: Algorithmic Optimisations & Challenges](https://research.roanh.dev/ConvexMerger%20Report%20v1.3.pdf). This report also contains a detailed description of all the visualisations and animations. 37 | 38 | ## Credits 39 | - [Roan Hofland](https://github.com/RoanH): Game Design & Implementation 40 | - [Emiliyan Greshkov](https://github.com/Kroasana): Vertical Decomposition 41 | - [Irina Kostitsyna](https://www.tue.nl/en/research/researchers/irina-kostitsyna): Algorithms Advisor 42 | - [RockRoller](https://github.com/RockRoller01): UI Design & Logo 43 | - [Thiam Wai Chua](https://github.com/CTW121): Playfield Generation 44 | - [Phosphor Icons](https://phosphoricons.com/): UI Icons 45 | - [Cadson Demak](https://fonts.google.com/specimen/Pridi): Pridi Font 46 | 47 | ## Development 48 | This is an [Eclipse](https://www.eclipse.org/) + [Gradle](https://gradle.org/) project with [Util](https://github.com/RoanH/Util) as the only dependency. Development work can be done using the Eclipse IDE (already setup) or using any other Gradle compatible IDE (manual setup). CI will check that all source files use Unix style line endings (LF) and that all functions and fields have valid documentation. 49 | 50 | ## History 51 | Project development started: 20th of November, 2021. 52 | Project due date (2IMA25 course): 30th of January, 2022. 53 | Project release (2IMA05 course): 12th of March, 2023. 54 | --------------------------------------------------------------------------------