diff --git a/H02/.editorconfig b/H02/.editorconfig new file mode 100644 index 0000000..38866d3 --- /dev/null +++ b/H02/.editorconfig @@ -0,0 +1,12 @@ +# Editor configuration, see https://editorconfig.org + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[{*.yml,*.json}] +indent_size = 2 diff --git a/H02/.gitignore b/H02/.gitignore new file mode 100644 index 0000000..e3750f8 --- /dev/null +++ b/H02/.gitignore @@ -0,0 +1,86 @@ +### Intellij ### +.idea/ +*.iws +/out/ +*.iml +.idea_modules/ +atlassian-ide-plugin.xml + +### VS-Code ### +.vscode/ +.VSCodeCounter/ + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders +.externalToolBuilders/ +*.launch +.factorypath +.recommenders/ +.apt_generated/ +.project +.classpath + +### Linux ### +*~ +.fuse_hidden* +.directory +.Trash-* +.nfs* + +### macOS ### +.DS_Store +.AppleDouble +.LSOverride +Icon +._* +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db +*.stackdump +[Dd]esktop.ini +$RECYCLE.BIN/ +*.lnk + +### Gradle ### +.gradle +/build/ +out/ +gradle-app.setting +!gradle-wrapper.jar +.gradletasknamecache + +*.hprof +jagr.conf diff --git a/H02/README.md b/H02/README.md new file mode 100644 index 0000000..4eb1ee3 --- /dev/null +++ b/H02/README.md @@ -0,0 +1,4 @@ +# Vorlage zu Hausübung 02 + +Beachten Sie die Hinweise zum Herunterladen, Importieren, Bearbeitern, Exportieren und Hochladen in unserem +[Studierenden-Guide](https://wiki.tudalgo.org/) diff --git a/H02/build.gradle.kts b/H02/build.gradle.kts new file mode 100644 index 0000000..80a992c --- /dev/null +++ b/H02/build.gradle.kts @@ -0,0 +1,37 @@ +import org.sourcegrade.jagr.gradle.task.grader.GraderRunTask + +plugins { + alias(libs.plugins.algomate) +} + +exercise { + assignmentId.set("h02") +} + +submission { + // ACHTUNG! + // Setzen Sie im folgenden Bereich Ihre TU-ID (NICHT Ihre Matrikelnummer!), Ihren Nachnamen und Ihren Vornamen + // in Anführungszeichen (z.B. "ab12cdef" für Ihre TU-ID) ein! + // BEISPIEL: + // studentId = "ab12cdef" + // firstName = "sol_first" + // lastName = "sol_last" + studentId = "" + firstName = "" + lastName = "" + + // Optionally require own tests for mainBuildSubmission task. Default is false + requireTests = false +} + +dependencies { + implementation(libs.fopbot) +} + +tasks { + withType { + doFirst { + throw GradleException("No public tests are provided for this exercise! For more information go to https://moodle.informatik.tu-darmstadt.de/mod/page/view.php?id=68766") + } + } +} diff --git a/H02/gradle/libs.versions.toml b/H02/gradle/libs.versions.toml new file mode 100644 index 0000000..c23692d --- /dev/null +++ b/H02/gradle/libs.versions.toml @@ -0,0 +1,6 @@ +[plugins] +algomate = { id = "org.tudalgo.algomate", version = "0.7.1" } +style = { id = "org.sourcegrade.style", version = "3.0.0" } + +[libraries] +fopbot = { module = "org.tudalgo:fopbot", version = "0.8.1" } diff --git a/H02/gradle/wrapper/gradle-wrapper.jar b/H02/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e644113 Binary files /dev/null and b/H02/gradle/wrapper/gradle-wrapper.jar differ diff --git a/H02/gradle/wrapper/gradle-wrapper.properties b/H02/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..79eb9d0 --- /dev/null +++ b/H02/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/H02/gradlew b/H02/gradlew new file mode 100755 index 0000000..1aa94a4 --- /dev/null +++ b/H02/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/H02/gradlew.bat b/H02/gradlew.bat new file mode 100644 index 0000000..25da30d --- /dev/null +++ b/H02/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/H02/settings.gradle.kts b/H02/settings.gradle.kts new file mode 100644 index 0000000..d98b271 --- /dev/null +++ b/H02/settings.gradle.kts @@ -0,0 +1,11 @@ +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { +// mavenLocal() + maven("https://s01.oss.sonatype.org/content/repositories/snapshots") + maven("https://jitpack.io") + mavenCentral() + } +} + +rootProject.name = "H02-Student" diff --git a/H02/src/main/java/h02/FourWins.java b/H02/src/main/java/h02/FourWins.java new file mode 100644 index 0000000..68654f4 --- /dev/null +++ b/H02/src/main/java/h02/FourWins.java @@ -0,0 +1,345 @@ +package h02; + +import fopbot.Direction; +import fopbot.Robot; +import fopbot.RobotFamily; +import fopbot.World; +import h02.template.InputHandler; +import org.tudalgo.algoutils.student.annotation.DoNotTouch; +import org.tudalgo.algoutils.student.annotation.StudentImplementationRequired; + +import java.util.Optional; + +/** + * The {@link FourWins} class represents the main class of the FourWins game. + */ +public class FourWins { + private final InputHandler inputHandler = new InputHandler(this); + /** + * The width of the game board. + */ + private final int width; + /** + * The height of the game board. + */ + private final int height; + /** + * Indicates whether the game has finished. + */ + @SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"}) + private boolean finished = false; + + /** + * Creates a new {@link FourWins} instance with the given width and height. + * + * @param width the width of the game board + * @param height the height of the game board + */ + FourWins(final int width, final int height) { + this.width = width; + this.height = height; + } + + /** + * Starts the game by setting up the world and executing the game loop. + */ + void startGame() { + setupWorld(); + gameLoop(); + } + + /** + * Sets up the world and installs the {@link InputHandler}. + */ + void setupWorld() { + World.setSize(width, height); + World.setDelay(10); + World.setVisible(true); + inputHandler.install(); + } + + + /** + * Validates if a given column index is within the bounds of the game board and not fully occupied. + * + * @param column The column index to validate. + * @param stones 2D array representing the game board, where each cell contains a RobotFamily color indicating the + * player that has placed a stone in that position. + * @return true if the column is within bounds and has at least one unoccupied cell; false otherwise. + */ + @StudentImplementationRequired("H2.2.1") + public static boolean validateInput(final int column, final RobotFamily[][] stones) { + // TODO: H2.2.1 + return org.tudalgo.algoutils.student.Student.crash("H2.2.1 - Remove if implemented"); + } + + + /** + * Calculates the next unoccupied row index in the specified column. This row index is the next destination for a + * falling stone. + * + * @param column The column index where the stone is to be dropped. + * @param stones 2D array representing the game board, where each cell contains a RobotFamily object indicating the + * player that has placed a stone in that position. + * @return Index of the next unoccupied row index in the specified column. + */ + @StudentImplementationRequired("H2.2.2") + public static int getDestinationRow(final int column, final RobotFamily[][] stones) { + // TODO: H2.2.2 + return org.tudalgo.algoutils.student.Student.crash("H2.2.2 - Remove if implemented"); + } + + /** + * Drops a stone into the specified column of the game board, simulating a falling animation. This method gets the + * destination row for the stone in the specified column with the `getDestinationRow` method. It creates a new Robot + * instance to represent the stone with the currentPlayer's RobotFamily in the given column and the destination row. + * After that it simulates the stone's fall by decrementing its position until it reaches the destination row. Once + * the stone reaches its destination, the method updates the stones array (a 2D array of RobotFamily colors) to + * mark the slot as occupied by the currentPlayer. + * + * @param column The column index where the stone is to be dropped. + * @param stones 2D array representing the game board, where each cell contains a RobotFamily object + * indicating the player that has placed a stone in that position. + * @param currentPlayer The RobotFamily object representing the current player dropping the stone. + */ + @StudentImplementationRequired("H2.2.2") + public static void dropStone(final int column, final RobotFamily[][] stones, final RobotFamily currentPlayer) { + // TODO: H2.2.2 + org.tudalgo.algoutils.student.Student.crash("H2.2.2 - Remove if implemented"); + } + + + /** + * Checks if the current player has won by any condition. The conditions can be a horizontal, vertical, diagonal, or + * anti-diagonal line of at least four stones. + * + * @param stones 2D array representing the game board, where each cell contains a RobotFamily color + * indicating the player that has placed a stone in that position. + * @param currentPlayer The RobotFamily color representing the current player to check for a win. + * @return true if the current player has formed a horizontal line of at least four stones; false otherwise. + */ + @StudentImplementationRequired("H2.2.3") + public static boolean testWinConditions(final RobotFamily[][] stones, final RobotFamily currentPlayer) { + // TODO: H2.2.3 + return org.tudalgo.algoutils.student.Student.crash("H2.2.3 - Remove if implemented"); + } + + /** + * Checks if the current player has won by forming a horizontal line of at least consecutive four stones. + * + * @param stones 2D array representing the game board, where each cell contains a RobotFamily color + * indicating the player that has placed a stone in that position. + * @param currentPlayer The RobotFamily color representing the current player to check for a win. + * @return true if the current player has formed a horizontal line of at least four stones; false otherwise. + */ + @StudentImplementationRequired("H2.2.3") + public static boolean testWinHorizontal(final RobotFamily[][] stones, final RobotFamily currentPlayer) { + // TODO: H2.2.3 + return org.tudalgo.algoutils.student.Student.crash("H2.2.3 - Remove if implemented"); + } + + /** + * Checks if the current player has won by forming a vertical line of at least consecutive four stones. + * + * @param stones 2D array representing the game board, where each cell contains a RobotFamily color + * indicating the player that has placed a stone in that position. + * @param currentPlayer The RobotFamily color representing the current player to check for a win. + * @return true if the current player has formed a vertical line of at least four stones; false otherwise. + */ + @StudentImplementationRequired("H2.2.3") + public static boolean testWinVertical(final RobotFamily[][] stones, final RobotFamily currentPlayer) { + // TODO: H2.2.3 + return org.tudalgo.algoutils.student.Student.crash("H2.2.3 - Remove if implemented"); + } + + /** + * Checks if the current player has won by forming a diagonal line of at least consecutive four stones. + * + * @param stones 2D array representing the game board, where each cell contains a RobotFamily color + * indicating the player that has placed a stone in that position. + * @param currentPlayer The RobotFamily color representing the current player to check for a win. + * @return true if the current player has formed a diagonal line of at least four stones; false otherwise. + */ + @DoNotTouch + public static boolean testWinDiagonal(final RobotFamily[][] stones, final RobotFamily currentPlayer) { + @SuppressWarnings("CheckStyle") final int MAX_STONES = 4; + + @SuppressWarnings("CheckStyle") final int WIDTH = World.getWidth(); + @SuppressWarnings("CheckStyle") final int HEIGHT = World.getHeight(); + int[] direction = new int[]{1, 1}; + + // for every field + for (int y = 0; y < HEIGHT; y++) { + for (int x = 0; x < WIDTH; x++) { + + // for every direction + for (int nthDirection = 0; nthDirection < 4; nthDirection++) { + final int[] pos = {x, y}; + + // test for consecutive coins + int coinCount = 0; // start counting at 0 + while (pos[0] >= 0 && pos[0] < WIDTH + && pos[1] >= 0 && pos[1] < HEIGHT + && stones[pos[1]][pos[0]] == currentPlayer) { + coinCount++; // count every stone that has currentPlayer's color + if (coinCount >= MAX_STONES) { + return true; + } + pos[0] += direction[0]; + pos[1] += direction[1]; + } + + direction = new int[]{direction[1], -direction[0]}; // next direction (rotate by 90 deg) + } + } + } + + return false; + } + + + /** + * Switches the player for each turn. If the current player is SQUARE_BLUE, SQUARE_RED is returned as the next + * player. If the current player is SQUARE_RED, SQUARE_BLUE is returned as the next player. + * + * @param currentPlayer The player color of the current player. + * @return The player color of the next player. + */ + @StudentImplementationRequired("H2.2.4") + public static RobotFamily nextPlayer(final RobotFamily currentPlayer) { + // TODO: H2.2.4 + return org.tudalgo.algoutils.student.Student.crash("H2.2.4 - Remove if implemented"); + } + + /** + * Displays a Message in the console and on the game board indicating the game is drawn. + */ + @StudentImplementationRequired("H2.2.4") + public void writeDrawMessage() { + inputHandler.displayDrawStatus(); + + // TODO: H2.2.4 + org.tudalgo.algoutils.student.Student.crash("H2.2.4 - Remove if implemented"); + } + + /** + * Displays a Message in the console and on the game board indicating the game is won by a player. + * + * @param winner {@link RobotFamily} of the winning player + */ + @StudentImplementationRequired("H2.2.4") + public void writeWinnerMessage(final RobotFamily winner) { + inputHandler.displayWinnerStatus(winner); + + // TODO: H2.2.4 + org.tudalgo.algoutils.student.Student.crash("H2.2.4 - Remove if implemented"); + } + + /** + * Displays the winner of the game by printing the winning color in the console and filling the whole field with + * Robots of the winning color. + * + * @param winner The RobotFamily color of the winner. + */ + @StudentImplementationRequired("H2.2.4") + public static void colorFieldBackground(final RobotFamily winner) { + // TODO: H2.2.4 + org.tudalgo.algoutils.student.Student.crash("H2.2.4 - Remove if implemented"); + } + + /** + * Executes the main game loop, handling player turns, stone drops, and win condition checks. This method + * initializes the game board as a 2D array of RobotFamily colors, representing the slots that can be filled with + * players' stones. It starts with a predefined currentPlayer and continues in a loop until a win condition is met. + * Each iteration of the loop waits for player input to select a column to drop a stone into, switches the current + * player, drops the stone in the selected column, and checks for win conditions. If a win condition is met, the + * loop ends, and the winner is displayed. + */ + @StudentImplementationRequired("H2.2.4") + void gameLoop() { + final RobotFamily[][] stones = new RobotFamily[World.getHeight()][World.getWidth()]; + RobotFamily currentPlayer = RobotFamily.SQUARE_BLUE; + + boolean draw = false; + finished = false; + + while (!finished) { + // TODO: H2.2.4 + // set next player + org.tudalgo.algoutils.student.Student.crash("H2.2.4 - Remove if implemented"); + + // wait for click in column (DO NOT TOUCH) + finished = draw = isGameBoardFull(stones); + if (draw) { + break; + } + final int column = inputHandler.getNextInput(currentPlayer, stones); + + // TODO: H2.2.4 + // let stone drop + // test win conditions + org.tudalgo.algoutils.student.Student.crash("H2.2.4 - Remove if implemented"); + } + + // displaying either draw or winner (DO NOT TOUCH) + if (draw) { + writeDrawMessage(); + colorFieldBackground(getDrawnRobotFamily()); + } else { + writeWinnerMessage(currentPlayer); + colorFieldBackground(currentPlayer); + } + } + + + /** + * Executes the main game loop, handling player turns, stone drops, and win condition checks. Sets the background + * color of a field at the specified coordinates. The color is derived from the {@link RobotFamily} SQUARE_BLUE or + * SQUARE_RED. + * + * @param x the x coordinate of the field + * @param y the y coordinate of the field + * @param color the {@link RobotFamily} corresponding to the field color to set + */ + @DoNotTouch + public static void setFieldColor(final int x, final int y, final RobotFamily color) { + World.getGlobalWorld().setFieldColor(x, y, color.getColor()); + } + + /** + * Returns the {@link RobotFamily} which represents a drawn game. + * + * @return the {@link RobotFamily} which represents a drawn game. + */ + @DoNotTouch + @SuppressWarnings("UnstableApiUsage") + protected static RobotFamily getDrawnRobotFamily() { + return Optional.ofNullable(World.getGlobalWorld().getGuiPanel()).filter(guiPanel -> !guiPanel.isDarkMode()).map(guiPanel -> RobotFamily.SQUARE_ORANGE).orElse(RobotFamily.SQUARE_YELLOW); + } + + /** + * Checks if all columns of the game board are fully occupied. + * + * @param stones 2D array representing the game board, where each cell contains a RobotFamily + * @return true if all columns of the game board are fully occupied; false otherwise. + */ + @DoNotTouch + public static boolean isGameBoardFull(final RobotFamily[][] stones) { + for (int x = 0; x < World.getWidth(); x++) { + if (FourWins.validateInput(x, stones)) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} when the game is finished, {@code false} otherwise. + * + * @return whether the game is finished. + */ + public boolean isFinished() { + return finished; + } + +} diff --git a/H02/src/main/java/h02/Main.java b/H02/src/main/java/h02/Main.java new file mode 100644 index 0000000..a7dc39a --- /dev/null +++ b/H02/src/main/java/h02/Main.java @@ -0,0 +1,99 @@ +package h02; + +import fopbot.RobotFamily; +import fopbot.World; +import org.tudalgo.algoutils.student.annotation.StudentImplementationRequired; + +import static org.tudalgo.algoutils.student.io.PropertyUtils.getIntProperty; +import static org.tudalgo.algoutils.student.test.StudentTestUtils.printTestResults; +import static org.tudalgo.algoutils.student.test.StudentTestUtils.testEquals; + +/** + * Main entry point in executing the program. + */ +public class Main { + /** + * Main entry point in executing the program. + * + * @param args program arguments, currently ignored + */ + public static void main(final String[] args) { + // H1 + sanityChecksH211(); + sanityChecksH212(); + printTestResults(); + + // H2 + sanityChecksH22(); + printTestResults(); + + // starting game (comment out if you just want to run the tests) + final var propFile = "h02.properties"; + new FourWins( + getIntProperty(propFile, "FW_WORLD_WIDTH"), + getIntProperty(propFile, "FW_WORLD_HEIGHT") + ).startGame(); + } + + /** + * Perform sanity checks for exercise H2.1.1. + */ + @StudentImplementationRequired("H2.3") + public static void sanityChecksH211() { + // TODO: H2.3 + org.tudalgo.algoutils.student.Student.crash("H2.3 - Remove if implemented"); + } + + /** + * Perform sanity checks for exercise H2.1.2. + */ + @StudentImplementationRequired("H2.3") + public static void sanityChecksH212() { + // predefined simple test + final String[][] simpleTest = new String[][]{ + "a b c d e f".split(" "), + "a b c d e f".split(" "), + "a b c d e f".split(" "), + }; + // predefined complex test + final String[][] complexTest = new String[][]{ + "a a b b c c".split(" "), + "a b c d e f".split(" "), + "a a a b b b c c c".split(" "), + }; + + // TODO: H2.3 + org.tudalgo.algoutils.student.Student.crash("H2.3 - Remove if implemented"); + } + + /** + * Perform sanity checks for exercise H2.2 + */ + @StudentImplementationRequired("H2.4") + public static void sanityChecksH22() { + // setting world size + World.setSize(4, 5); + + // predefined stones1 array + final RobotFamily[][] stones1 = { + {null, RobotFamily.SQUARE_BLUE, null, RobotFamily.SQUARE_RED}, + {null, null, null, RobotFamily.SQUARE_BLUE}, + {null, null, null, RobotFamily.SQUARE_RED}, + {null, null, null, RobotFamily.SQUARE_BLUE}, + {null, null, null, RobotFamily.SQUARE_RED}, + }; + + // predefined stones2 array + final RobotFamily[][] stones2 = { + {RobotFamily.SQUARE_BLUE, RobotFamily.SQUARE_BLUE, RobotFamily.SQUARE_BLUE, RobotFamily.SQUARE_BLUE}, + {RobotFamily.SQUARE_RED, RobotFamily.SQUARE_RED, RobotFamily.SQUARE_BLUE, RobotFamily.SQUARE_RED}, + {RobotFamily.SQUARE_BLUE, RobotFamily.SQUARE_RED, RobotFamily.SQUARE_BLUE, RobotFamily.SQUARE_BLUE}, + {RobotFamily.SQUARE_BLUE, RobotFamily.SQUARE_RED, RobotFamily.SQUARE_BLUE, RobotFamily.SQUARE_RED}, + {RobotFamily.SQUARE_BLUE, RobotFamily.SQUARE_BLUE, RobotFamily.SQUARE_BLUE, RobotFamily.SQUARE_RED}, + }; + + // TODO: H2.4 + org.tudalgo.algoutils.student.Student.crash("H2.4 - Remove if implemented"); + } + +} diff --git a/H02/src/main/java/h02/OneDimensionalArrayStuff.java b/H02/src/main/java/h02/OneDimensionalArrayStuff.java new file mode 100644 index 0000000..c67d532 --- /dev/null +++ b/H02/src/main/java/h02/OneDimensionalArrayStuff.java @@ -0,0 +1,55 @@ +package h02; + +import org.tudalgo.algoutils.student.annotation.StudentImplementationRequired; + +/** + * This class serves as a container for the methods that are to be implemented by the students for exercise H2.1.1. + */ +public class OneDimensionalArrayStuff { + + /** + * Prevent instantiation of this utility class. + */ + private OneDimensionalArrayStuff() { + throw new IllegalStateException("This class is not meant to be instantiated."); + } + + /** + * Returns a new array that is a copy of the input array with the given value appended at the end. + * + * @param array the input array + * @param value the value to append + * @return a new array that is a copy of the input array with the given value appended at the end + */ + @SuppressWarnings("ManualArrayCopy") + @StudentImplementationRequired("H2.1.1") + public static int[] push(final int[] array, final int value) { + // TODO: H2.1.1 + return org.tudalgo.algoutils.student.Student.crash("H2.1.1 - Remove if implemented"); + } + + /** + * Calculates the next Fibonacci number based on the given array and returns a new array with the next Fibonacci + * number appended at the end. + * + * @param array the input array containing the last two Fibonacci numbers up to the current point + * @return a new array with the next Fibonacci number appended at the end + */ + @StudentImplementationRequired("H2.1.1") + public static int[] calculateNextFibonacci(final int[] array) { + // TODO: H2.1.1 + return org.tudalgo.algoutils.student.Student.crash("H2.1.1 - Remove if implemented"); + } + + /** + * Returns the n-th Fibonacci number. + * + * @param n the index of the Fibonacci number to return + * @return the n-th Fibonacci number + */ + @StudentImplementationRequired("H2.1.1") + public static int fibonacci(final int n) { + // TODO: H2.1.1 + return org.tudalgo.algoutils.student.Student.crash("H2.1.1 - Remove if implemented"); + } +} diff --git a/H02/src/main/java/h02/TwoDimensionalArrayStuff.java b/H02/src/main/java/h02/TwoDimensionalArrayStuff.java new file mode 100644 index 0000000..fe4962e --- /dev/null +++ b/H02/src/main/java/h02/TwoDimensionalArrayStuff.java @@ -0,0 +1,79 @@ +package h02; + +import org.tudalgo.algoutils.student.annotation.DoNotTouch; +import org.tudalgo.algoutils.student.annotation.StudentImplementationRequired; + +import java.util.Arrays; + +/** + * This class serves as a container for the methods that are to be implemented by the students for exercise H2.1.2. + */ +public class TwoDimensionalArrayStuff { + + /** + * Prevent instantiation of this utility class. + */ + private TwoDimensionalArrayStuff() { + throw new IllegalStateException("This class is not meant to be instantiated."); + } + + /** + * Returns an array containing the number of occurrences of the query {@link String} in each line of the input array. + * + * @param input the input array + * @param query the query {@link String} + * @return an array containing the number of occurrences of the query {@link String} in each line of the input array + */ + @StudentImplementationRequired("H2.1.2") + public static int[] occurrences(final String[][] input, final String query) { + // TODO: H2.1.2 + return org.tudalgo.algoutils.student.Student.crash("H2.1.2 - Remove if implemented"); + } + + /** + * Returns the mean of the input array. + * + * @param input the input array + * @return the mean of the input array + */ + @StudentImplementationRequired("H2.1.2") + public static float mean(final int[] input) { + // TODO: H2.1.2 + return org.tudalgo.algoutils.student.Student.crash("H2.1.2 - Remove if implemented"); + } + + /** + * Returns the mean number of occurrences of the query {@link String} in each line of the input array. + * + * @param input the input array + * @param query the query {@link String} + * @return the mean number of occurrences of the query {@link String} in each line of the input array + */ + @DoNotTouch + public static float meanOccurrencesPerLine(final String[][] input, final String query) { + return mean(occurrences(input, query)); + } + + /** + * Overload that splits the input string by lines and spaces, then calls regular meanOccurrencesPerLine. + * + * @param input the input string to split by lines and spaces + * @param query the query {@link String} + * @return the mean number of occurrences of the query {@link String} in each line of the input array + */ + @DoNotTouch + public static float meanOccurrencesPerLine(final String input, final String query) { + // filter out unwanted symbols + final String filteredInput = input.replaceAll("[^\\w\\s]", ""); + // split by lines + final var processedInput = Arrays.stream(filteredInput.split("(\\r\\n|\\r|\\n)")) + // split by spaces + .map(line -> line.split("\\s")) + // collect to 2D array + .toArray(String[][]::new); + /// uncomment the following line to log processed input + // System.out.printf("Processed input: %s%n", Arrays.deepToString(processedInput)); + // call regular meanOccurrencesPerLine + return meanOccurrencesPerLine(processedInput, query); + } +} diff --git a/H02/src/main/java/h02/template/InputHandler.java b/H02/src/main/java/h02/template/InputHandler.java new file mode 100644 index 0000000..cb65bba --- /dev/null +++ b/H02/src/main/java/h02/template/InputHandler.java @@ -0,0 +1,201 @@ +package h02.template; + +import fopbot.RobotFamily; +import fopbot.World; +import h02.FourWins; +import org.tudalgo.algoutils.student.annotation.DoNotTouch; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.beans.PropertyChangeEvent; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + +/** + * The {@link InputHandler} handles the input of the users. + */ +@DoNotTouch +public class InputHandler { + /** + * The input queue. + */ + private final BlockingDeque inputQueue = new LinkedBlockingDeque<>(); + + /** + * The {@link FourWins} instance. + */ + private final FourWins fourWins; + + /** + * Whether the row select mode is active. + */ + private final AtomicBoolean rowSelectMode = new AtomicBoolean(false); + + /** + * The status label. + */ + private final JLabel statusLabel = new JLabel("", SwingConstants.CENTER); + + /** + * Creates a new {@link InputHandler} instance. + * + * @param fourWins the {@link FourWins} instance + */ + public InputHandler(final FourWins fourWins) { + this.fourWins = fourWins; + final int padding = 4; // Padding in pixels + statusLabel.setBorder(new EmptyBorder(padding, padding, padding, padding)); + } + + /** + * Sets the color of the given column to the given color. + * + * @param column the column to set the color of + * @param colorSupplier the color to set + */ + private void setColumnColor(final int column, final Supplier colorSupplier) { + for (int i = 0; i < World.getHeight(); i++) { + final int finalI = i; + SwingUtilities.invokeLater(() -> World.getGlobalWorld().getField(column, finalI).setFieldColor(colorSupplier)); + } + } + + /** + * Executes the given action only if the game is running. + * + * @param action the action to execute + */ + private void whenGameIsRunning(final Runnable action) { + if (!fourWins.isFinished()) { + action.run(); + } + } + + /** + * Installs the input handler to the fopbot world. + */ + @SuppressWarnings("UnstableApiUsage") + public void install() { + final var guiPanel = World.getGlobalWorld().getGuiPanel(); + final var guiFrame = World.getGlobalWorld().getGuiFrame(); + World.getGlobalWorld().getInputHandler().addFieldClickListener( + e -> whenGameIsRunning(() -> addInput(e.getField().getX())) + ); + World.getGlobalWorld().getInputHandler().addFieldHoverListener(e -> whenGameIsRunning(() -> { + // deselect last hovered field, if any + if (e.getPreviousField() != null) { + setColumnColor(e.getPreviousField().getX(), () -> null); + } + if (rowSelectMode.get()) { + // select current hovered field + if (e.getField() != null) { + setColumnColor( + e.getField().getX(), + () -> guiPanel.isDarkMode() + ? Color.yellow + : Color.orange + ); + } + } + })); + statusLabel.setFont(statusLabel.getFont().deriveFont(guiPanel.scale(20.0f))); + guiFrame.add(statusLabel, BorderLayout.NORTH); + guiFrame.pack(); + guiPanel.addDarkModeChangeListener(this::onDarkModeChange); + guiPanel.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(final ComponentEvent e) { + statusLabel.setFont( + statusLabel.getFont().deriveFont( + Math.max(20f, 0.04f * Math.min(guiPanel.getWidth(), guiPanel.getHeight())) + ) + ); + } + }); + // trigger dark mode change to set the correct color + guiPanel.setDarkMode(World.getGlobalWorld().getGuiPanel().isDarkMode()); + } + + /** + * Called when the dark mode changes. + * + * @param e the property change event + */ + @SuppressWarnings("UnstableApiUsage") + public void onDarkModeChange(final PropertyChangeEvent e) { + final var darkMode = (boolean) e.getNewValue(); + statusLabel.setForeground(darkMode ? Color.white : Color.black); + World.getGlobalWorld().getGuiFrame().getContentPane().setBackground(darkMode ? Color.black : Color.white); + } + + /** + * Adds an input to the input queue. When {@link #getNextInput(RobotFamily, RobotFamily[][])} is called, the program + * will wait until this method is called. + * + * @param input the input to add + */ + public void addInput(final int input) { + inputQueue.add(input); + } + + /** + * Returns the next input from the input queue. If the input is invalid, the user will be prompted to enter a new + * input. The program will halt until a valid input is entered. + * + * @param currentPlayer the current player + * @param stones the current state of the game board + * @return the next input from the input queue + */ + public int getNextInput(final RobotFamily currentPlayer, final RobotFamily[][] stones) { + rowSelectMode.set(true); + statusLabel.setText( + "Click on a column to insert a disc.
Current Player: %s".formatted(currentPlayer.getName()) + ); + try { + final int input = inputQueue.take(); + System.out.println("Received column input: " + input); + if (!FourWins.validateInput(input, stones)) { + System.out.println("Invalid column input, please try again."); + return getNextInput(currentPlayer, stones); + } + rowSelectMode.set(false); + return input; + } catch (final InterruptedException e) { + rowSelectMode.set(false); + throw new RuntimeException(e); + } + } + + /** + * Sets a status message, saying that the game has ended in a draw. + */ + public void displayDrawStatus() { + statusLabel.setText("No valid columns found.
Hence, game ends with a draw."); + } + + /** + * Sets a status message, saying that the game has ended with a winner. + * + * @param winner the winner of the game + */ + public void displayWinnerStatus(final RobotFamily winner) { + statusLabel.setText("Player %s has won the game!".formatted(winner.getName())); + } + + /** + * Returns the {@link #statusLabel} of this {@link InputHandler}. + * + *

Use the {@link JLabel#getText()} method to get the current text of the label, and the + * {@link JLabel#setText(String)} method to update the text. + * + * @return the {@link #statusLabel} of this {@link InputHandler} + */ + public JLabel getStatusLabel() { + return statusLabel; + } +} diff --git a/H02/src/main/resources/h02.properties b/H02/src/main/resources/h02.properties new file mode 100644 index 0000000..58ef558 --- /dev/null +++ b/H02/src/main/resources/h02.properties @@ -0,0 +1,2 @@ +FW_WORLD_WIDTH = 7 +FW_WORLD_HEIGHT = 6 diff --git a/H02/src/test/java/h02/ExampleJUnitTest.java b/H02/src/test/java/h02/ExampleJUnitTest.java new file mode 100644 index 0000000..6e4a903 --- /dev/null +++ b/H02/src/test/java/h02/ExampleJUnitTest.java @@ -0,0 +1,16 @@ +package h02; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * An example JUnit test class. + */ +public class ExampleJUnitTest { + + @Test + public void testAddition() { + assertEquals(2, 1 + 1); + } +}