Squashed 'solution/H03/' content from commit ea0c1f2

git-subtree-dir: solution/H03
git-subtree-split: ea0c1f269eb8d625ec4dee6f9888b6e41db30c81
This commit is contained in:
Oshgnacknak 2025-01-11 16:41:08 +01:00
commit 3826b5af0c
35 changed files with 2298 additions and 0 deletions

12
.editorconfig Normal file
View file

@ -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

88
.gitignore vendored Normal file
View file

@ -0,0 +1,88 @@
### 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
screenshots/
jagr.conf

4
README.md Normal file
View file

@ -0,0 +1,4 @@
# Musterlösung zu Hausübung 03
Beachten Sie die Hinweise zum Herunterladen, Importieren, Bearbeitern, Exportieren und Hochladen in unserem
[Studierenden-Guide](https://wiki.tudalgo.org/)

46
build.gradle.kts Normal file
View file

@ -0,0 +1,46 @@
plugins {
alias(libs.plugins.algomate)
alias(libs.plugins.style)
}
version = file("version").readLines().first()
exercise {
assignmentId.set("h03")
}
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 = "ab12cdef"
firstName = "sol_first"
lastName = "sol_last"
// Optionally require own tests for mainBuildSubmission task. Default is false
requireTests = false
}
dependencies {
implementation(libs.algoutils.student)
implementation(libs.fopbot)
}
jagr {
graders {
val graderPublic by getting {
configureDependencies {
implementation(libs.algoutils.tutor)
}
}
val graderPrivate by creating {
parent(graderPublic)
graderName.set("FOP-2425-H03-Private")
rubricProviderName.set("h03.H03_RubricProvider")
}
}
}

11
gradle/libs.versions.toml Normal file
View file

@ -0,0 +1,11 @@
[versions]
algoutils = "0.9.1-SNAPSHOT"
[plugins]
algomate = { id = "org.tudalgo.algomate", version = "0.7.1" }
style = { id = "org.sourcegrade.style", version = "3.0.0" }
[libraries]
algoutils-student = { module = "org.tudalgo:algoutils-student", version.ref = "algoutils" }
algoutils-tutor = { module = "org.tudalgo:algoutils-tutor", version.ref = "algoutils" }
fopbot = { module = "org.tudalgo:fopbot", version = "0.8.1" }

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

245
gradlew vendored Executable file
View file

@ -0,0 +1,245 @@
#!/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##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && 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
which java >/dev/null 2>&1 || 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
# 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=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=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, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
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" "$@"

92
gradlew.bat vendored Normal file
View file

@ -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.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
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

11
settings.gradle.kts Normal file
View file

@ -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 = "H03-Root"

View file

@ -0,0 +1,295 @@
package h03;
import h03.h3_1.HackingRobotTest;
import h03.h3_1.MovementTypeTest;
import h03.h3_2.DoublePowerRobotTest;
import h03.h3_2.VersatileRobotTest;
import h03.h3_3.RobotsChallengeTest;
import org.sourcegrade.jagr.api.rubric.*;
import org.sourcegrade.jagr.api.testing.RubricConfiguration;
import org.tudalgo.algoutils.transform.SolutionMergingClassTransformer;
import org.tudalgo.algoutils.transform.util.headers.MethodHeader;
import org.tudalgo.algoutils.tutor.general.json.JsonParameterSet;
import static org.tudalgo.algoutils.tutor.general.jagr.RubricUtils.criterion;
import static org.tudalgo.algoutils.tutor.general.jagr.RubricUtils.manualGrader;
public class H03_RubricProvider implements RubricProvider {
private static final Criterion H3_1_1 = Criterion.builder()
.shortDescription("H3.1.1 | Movement types")
.maxPoints(1)
.addChildCriteria(
criterion(
"Die Enumeration MovementType ist korrekt deklariert und umfasst DIAGONAL, OVERSTEP, TELEPORT.",
JUnitTestRef.ofMethod(() -> MovementTypeTest.class.getDeclaredMethod("testEnum")),
JUnitTestRef.ofMethod(() -> MovementTypeTest.class.getDeclaredMethod("testEnumConstants"))
)
)
.build();
private static final Criterion H3_1_2 = Criterion.builder()
.shortDescription("H3.1.2 | First class")
.maxPoints(1)
.addChildCriteria(
criterion(
"Die Klasse HackingRobot ist korrekt deklariert mit den Attributen type und robotTypes.",
JUnitTestRef.ofMethod(() -> HackingRobotTest.class.getDeclaredMethod("testClassHeader")),
JUnitTestRef.ofMethod(() -> HackingRobotTest.class.getDeclaredMethod("testFields"))
)
)
.build();
private static final Criterion H3_1_3 = Criterion.builder()
.shortDescription("H3.1.3 | Robot under construction")
.maxPoints(3)
.addChildCriteria(
criterion(
"Der Konstruktor von HackingRobot ist korrekt deklariert.",
JUnitTestRef.ofMethod(() -> HackingRobotTest.class.getDeclaredMethod("testConstructorHeader"))
),
criterion(
"Der Konstruktor ruft den Konstruktor der Basisklasse Robot korrekt auf.",
JUnitTestRef.ofMethod(() -> HackingRobotTest.class.getDeclaredMethod("testConstructorSuperCall"))
),
criterion(
"Das Attribut robotTypes ist korrekt initialisiert und die Elemente korrekt nach order verschoben.",
JUnitTestRef.ofMethod(() -> HackingRobotTest.class.getDeclaredMethod("testConstructorSetsFields", boolean.class))
)
)
.build();
private static final Criterion H3_1_4 = Criterion.builder()
.shortDescription("H3.1.4 | Access to robot types")
.maxPoints(3)
.addChildCriteria(
criterion(
"Die Methode getType gibt den aktuellen Robotertyp korrekt zurück.",
JUnitTestRef.ofMethod(() -> HackingRobotTest.class.getDeclaredMethod("testGetType"))
),
criterion(
"Die Methode getNextType gibt den nächsten Typ korrekt zurück, wenn nicht zum Index 0 zurückgesprungen wird.",
JUnitTestRef.ofMethod(() -> HackingRobotTest.class.getDeclaredMethod("testGetNextTypeNoMod", int.class))
),
criterion(
"Die Methode getNextType gibt den nächsten Typ korrekt zurück, wenn zum Index 0 zurückgesprungen werden muss.",
JUnitTestRef.ofMethod(() -> HackingRobotTest.class.getDeclaredMethod("testGetNextTypeMod", int.class))
)
)
.build();
private static final Criterion H3_1_5 = Criterion.builder()
.shortDescription("H3.1.5 | Swap type")
.addChildCriteria(
criterion(
"Die Methode shuffle(int itNr) funktioniert korrekt und ändert den Robotertyp zufällig.",
JUnitTestRef.ofMethod(() -> HackingRobotTest.class.getDeclaredMethod("testShuffleWithParams_SetField", int.class))
),
criterion(
"Die Methode gibt true zurück, wenn der Typ geändert wurde, sonst false.",
JUnitTestRef.ofMethod(() -> HackingRobotTest.class.getDeclaredMethod("testShuffleWithParams_ReturnValue", int.class))
)
)
.build();
private static final Criterion H3_1_6 = Criterion.builder()
.shortDescription("H3.1.6 | Are you sure of the swap?")
.addChildCriteria(
criterion(
"Die Methode shuffle() ist korrekt überladen und garantiert, dass der Typ des Roboters geändert wird.",
2,
JUnitTestRef.ofMethod(() -> HackingRobotTest.class.getDeclaredMethod("testShuffleNoParams"))
)
)
.build();
private static final Criterion H3_1 = Criterion.builder()
.shortDescription("H3.1 | HackingRobot")
.maxPoints(12).addChildCriteria(
H3_1_1,
H3_1_2,
H3_1_3,
H3_1_4,
H3_1_5,
H3_1_6
)
.build();
private static final Criterion H3_2_1 = Criterion.builder()
.shortDescription("H3.2.1 | DoublePowerRobot")
.maxPoints(4).addChildCriteria(
criterion(
"Die Klasse DoublePowerRobot ist korrekt deklariert mit den Attributen und Methoden.",
JUnitTestRef.ofMethod(() -> DoublePowerRobotTest.class.getDeclaredMethod("testClassHeader")),
JUnitTestRef.ofMethod(() -> DoublePowerRobotTest.class.getDeclaredMethod("testFields")),
JUnitTestRef.ofMethod(() -> DoublePowerRobotTest.class.getDeclaredMethod("testMethodHeaders"))
),
criterion(
"Der Konstruktor initialisiert doublePowerTypes korrekt mit den aktuellen und nächsten Typen.",
JUnitTestRef.ofMethod(() -> DoublePowerRobotTest.class.getDeclaredMethod("testConstructorSetsField", boolean.class))
),
criterion(
"Die Methode shuffle(int itNr) für DoublePowerRobot funktioniert korrekt.",
JUnitTestRef.ofMethod(() -> DoublePowerRobotTest.class.getDeclaredMethod("testShuffleWithParams", int.class))
),
criterion(
"Die Methode shuffle() für DoublePowerRobot aktualisiert den zweiten Typ korrekt.",
JUnitTestRef.ofMethod(() -> DoublePowerRobotTest.class.getDeclaredMethod("testShuffleNoParams", int.class))
)
)
.build();
private static final Criterion H3_2_2 = Criterion.builder()
.shortDescription("H3.2.2 | VersatileRobot")
.maxPoints(4).addChildCriteria(
criterion(
"Die Klasse VersatileRobot ist korrekt deklariert.",
JUnitTestRef.ofMethod(() -> VersatileRobotTest.class.getDeclaredMethod("testClassHeader"))
),
criterion(
"Der Konstruktor der Klasse VersatileRobot setzt y = x, wenn der Typ DIAGONAL ist.",
JUnitTestRef.ofMethod(() -> VersatileRobotTest.class.getDeclaredMethod("testConstructor"))
),
criterion(
"Die Methode shuffle(int itNr) funktioniert korrekt.",
JUnitTestRef.ofMethod(() -> VersatileRobotTest.class.getDeclaredMethod("testShuffleWithParams"))
),
criterion(
"Die Methode shuffle() setzt korrekt die y-Koordinate, wenn der Typ DIAGONAL ist.",
JUnitTestRef.ofMethod(() -> VersatileRobotTest.class.getDeclaredMethod("testShuffleNoParams"))
)
)
.build();
private static final Criterion H3_2 = Criterion.builder()
.shortDescription("H3.2 | Special Hacking Robots")
.maxPoints(8).addChildCriteria(H3_2_1, H3_2_2)
.build();
private static final Criterion H3_3_1 = Criterion.builder()
.shortDescription("H3.3.1 | First things first")
.maxPoints(1)
.addChildCriteria(
criterion(
"Die Klasse RobotsChallenge ist korrekt deklariert.",
JUnitTestRef.ofMethod(() -> RobotsChallengeTest.class.getDeclaredMethod("testClassHeader"))
)
)
.build();
private static final Criterion H3_3_2 = Criterion.builder()
.shortDescription("H3.3.2 | Participators over here")
.maxPoints(2)
.addChildCriteria(
criterion(
"Der Konstruktor von RobotsChallenge weist korrekt die Parameter begin, goal, und robots zu.",
JUnitTestRef.ofMethod(() -> RobotsChallengeTest.class.getDeclaredMethod("testConstructor", int.class))
),
criterion(
"Der Konstruktor sorgt dafür, dass winThreshold auf 2 gesetzt wird (direkt oder indirekt).",
JUnitTestRef.ofMethod(() -> RobotsChallengeTest.class.getDeclaredMethod("testWinThreshold"))
)
)
.build();
private static final Criterion H3_3_3 = Criterion.builder()
.shortDescription("H3.3.3 | Quick maths")
.maxPoints(3)
.addChildCriteria(
criterion(
"Die Methode calculateStepsDiagonal ist korrekt implementiert und berechnet die Schritte für den Typ DIAGONAL.",
JUnitTestRef.ofMethod(() -> RobotsChallengeTest.class.getDeclaredMethod("testCalculateStepsDiagonal", JsonParameterSet.class))
),
criterion(
"Die Methode calculateStepsOverstep ist korrekt implementiert und berechnet die Schritte für den Typ OVERSTEP.",
JUnitTestRef.ofMethod(() -> RobotsChallengeTest.class.getDeclaredMethod("testCalculateStepsOverstep", JsonParameterSet.class))
),
criterion(
"Die Methode calculateStepsTeleport ist korrekt implementiert und berechnet die Schritte für den Typ TELEPORT.",
JUnitTestRef.ofMethod(() -> RobotsChallengeTest.class.getDeclaredMethod("testCalculateStepsTeleport", JsonParameterSet.class))
)
)
.build();
private static final Criterion H3_3_4 = Criterion.builder()
.shortDescription("H3.3.4 | Let the show begin")
.maxPoints(3)
.addChildCriteria(
criterion(
"Die Methode findWinners berechnet korrekt die Schritte für jeden Roboter.",
JUnitTestRef.ofMethod(() -> RobotsChallengeTest.class.getDeclaredMethod("testFindWinnersCalc"))
),
criterion(
"Die Methode verwendet Math.min korrekt, um die minimalen Schritte zu berechnen.",
JUnitTestRef.ofMethod(() -> RobotsChallengeTest.class.getDeclaredMethod("testFindWinnersMin"))
),
criterion(
"Gewinner werden korrekt in der Liste winners gespeichert.",
JUnitTestRef.ofMethod(() -> RobotsChallengeTest.class.getDeclaredMethod("testFindWinnersReturn"))
)
)
.build();
private static final Criterion H3_3 = Criterion.builder()
.shortDescription("H3.3 | Let Robots Compete!")
.maxPoints(9)
.addChildCriteria(
H3_3_1,
H3_3_2,
H3_3_3,
H3_3_4
)
.build();
private static final Criterion H3_4 = Criterion.builder()
.shortDescription("H3.4 | Documentation")
.addChildCriteria(
Criterion.builder()
.shortDescription("Alle öffentlichen Klassen, Methoden und Konstruktoren sind mit JavaDoc korrekt dokumentiert.")
.maxPoints(3)
.grader(manualGrader(3))
.build()
)
.build();
private static final Rubric RUBRIC = Rubric.builder()
.title("H03 | Mission Robots: The Ultimate Grid Race")
.addChildCriteria(
H3_1,
H3_2,
H3_3,
H3_4
)
.build();
@Override
public Rubric getRubric() {
return RUBRIC;
}
@Override
public void configure(RubricConfiguration configuration) {
configuration.addTransformer(() -> new SolutionMergingClassTransformer.Builder("h03")
.addSolutionClass("h03.Main")
.addSolutionClass("h03.RobotsChallenge", "h03.robots.RobotsChallenge", "robots.RobotsChallenge")
.addSolutionClass("h03.robots.DoublePowerRobot", "h03.DoublePowerRobot", "robot.DoublePowerRobot")
.addSolutionClass("h03.robots.HackingRobot", "h03.HackingRobot", "robot.HackingRobot")
.addSolutionClass("h03.robots.MovementType", "h03.MovementType", "robot.MovementType")
.addSolutionClass("h03.robots.VersatileRobot", "h03.VersatileRobot", "robot.VersatileRobot")
.addMethodReplacement(
MethodHeader.of(Math.class, "min", int.class, int.class),
MethodHeader.of(MathMinMock.class, "min", int.class, int.class))
.addMethodReplacement(
MethodHeader.of(Math.class, "min", float.class, float.class),
MethodHeader.of(MathMinMock.class, "min", float.class, float.class))
.addMethodReplacement(
MethodHeader.of(Math.class, "min", long.class, long.class),
MethodHeader.of(MathMinMock.class, "min", long.class, long.class))
.addMethodReplacement(
MethodHeader.of(Math.class, "min", double.class, double.class),
MethodHeader.of(MathMinMock.class, "min", double.class, double.class))
.setSimilarity(0.80)
.build());
}
}

View file

@ -0,0 +1,31 @@
package h03;
import kotlin.Pair;
import java.util.ArrayList;
import java.util.List;
public class MathMinMock {
public static final List<Pair<Integer, Integer>> MIN_INVOCATIONS = new ArrayList<>();
public static int min(int a, int b) {
MIN_INVOCATIONS.add(new Pair<>(a, b));
return Math.min(a, b);
}
public static long min(long a, long b) {
MIN_INVOCATIONS.add(new Pair<>((int) a, (int) b));
return Math.min(a, b);
}
public static float min(float a, float b) {
MIN_INVOCATIONS.add(new Pair<>((int) a, (int) b));
return Math.min(a, b);
}
public static double min(double a, double b) {
MIN_INVOCATIONS.add(new Pair<>((int) a, (int) b));
return Math.min(a, b);
}
}

View file

@ -0,0 +1,11 @@
package h03;
import java.util.concurrent.ThreadLocalRandom;
public class TestConstants {
public static long RANDOM_SEED = ThreadLocalRandom.current().nextLong();
public static final int TEST_TIMEOUT_IN_SECONDS = 5;
public static final int TEST_ITERATIONS = 5;
}

View file

@ -0,0 +1,69 @@
package h03;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import java.io.IOException;
import static h03.TestConstants.TEST_ITERATIONS;
@DisabledIf("org.tudalgo.algoutils.tutor.general.Utils#isJagrRun()")
public class TestJsonGenerators {
@Test
public void generateCalculateStepsDiagonalDataSet() throws IOException {
TestUtils.generateJsonTestData(
(mapper, index, rnd) -> {
int beginOrig = rnd.nextInt(10);
int begin = beginOrig / 2;
int goal = rnd.nextInt(20);
int expected = Math.abs(begin - goal);
return mapper.createObjectNode()
.put("begin", beginOrig)
.put("goal", goal)
.put("expected", expected);
},
TEST_ITERATIONS,
"CalculateStepsDiagonalDataSet"
);
}
@Test
public void generateCalculateStepsOverstepDataSet() throws IOException {
TestUtils.generateJsonTestData(
(mapper, index, rnd) -> {
int beginOrig = rnd.nextInt(10);
int begin = beginOrig / 2;
int goal = rnd.nextInt(20);
int expected = (Math.abs(begin - goal) % 2 == 0) ? Math.abs(begin - goal) : Math.abs(begin - goal) + 1;
return mapper.createObjectNode()
.put("begin", beginOrig)
.put("goal", goal)
.put("expected", expected);
},
TEST_ITERATIONS,
"CalculateStepsOverstepDataSet"
);
}
@Test
public void generateCalculateStepsTeleportDataSet() throws IOException {
TestUtils.generateJsonTestData(
(mapper, index, rnd) -> {
int beginOrig = rnd.nextInt(10);
int begin = beginOrig / 2;
int goal = rnd.nextInt(20);
int expected = (Math.abs(begin - goal) % 2 == 0) ? Math.abs(begin - goal) / 2 : (Math.abs(begin - goal) / 2) + 2;
return mapper.createObjectNode()
.put("begin", beginOrig)
.put("goal", goal)
.put("expected", expected);
},
TEST_ITERATIONS,
"CalculateStepsTeleportDataSet"
);
}
}

View file

@ -0,0 +1,60 @@
package h03;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Random;
import static h03.TestConstants.RANDOM_SEED;
public abstract class TestUtils {
/**
* A generator for JSON test data.
*/
public interface JsonGenerator {
/**
* Generates a JSON object node.
*
* @param mapper The object mapper to use.
* @param index The index of the object node.
* @param rnd The random number generator to use.
* @return The generated JSON object node.
*/
ObjectNode generateJson(ObjectMapper mapper, int index, Random rnd);
}
/**
* Generates and saves JSON test data.
*
* @param generator The generator to use.
* @param amount The amount of test data to generate.
* @param fileName The file name to save the test data to (without extension).
* @throws IOException If an I/O error occurs.
*/
public static void generateJsonTestData(final JsonGenerator generator, final int amount, final String fileName) throws IOException {
final var seed = RANDOM_SEED;
final var random = new java.util.Random(seed);
final ObjectMapper mapper = new ObjectMapper();
final ArrayNode arrayNode = mapper.createArrayNode();
System.out.println("Generating test data with seed: " + seed);
for (int i = 0; i < amount; i++) {
arrayNode.add(generator.generateJson(mapper, i, random));
}
final var path = Paths.get(
"src",
"graderPrivate",
"resources",
"h03",
fileName + ".generated.json"
).toAbsolutePath();
System.out.printf("Saving to file: %s%n", path);
final var file = path.toFile();
file.createNewFile();
mapper.writerWithDefaultPrettyPrinter().writeValue(file, arrayNode);
}
}

View file

@ -0,0 +1,326 @@
package h03.h3_1;
import fopbot.Robot;
import fopbot.World;
import h03.robots.HackingRobot;
import h03.robots.MovementType;
import kotlin.Triple;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.objectweb.asm.Type;
import org.sourcegrade.jagr.api.rubric.TestForSubmission;
import org.tudalgo.algoutils.transform.util.headers.ClassHeader;
import org.tudalgo.algoutils.transform.util.headers.FieldHeader;
import org.tudalgo.algoutils.transform.util.headers.MethodHeader;
import org.tudalgo.algoutils.tutor.general.assertions.Context;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import static org.tudalgo.algoutils.transform.SubmissionExecutionHandler.*;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.*;
@TestForSubmission
public class HackingRobotTest {
@BeforeAll
public static void setup() {
World.setSize(5, 5);
}
@AfterEach
public void tearDown() {
resetAll();
}
@Test
public void testClassHeader() {
ClassHeader originalClassHeader = getOriginalClassHeader(HackingRobot.class);
assertTrue(Modifier.isPublic(originalClassHeader.access()), emptyContext(), result ->
"Class HackingRobot is not declared public");
assertEquals(Type.getInternalName(Robot.class), originalClassHeader.superName(), emptyContext(), result ->
"Class HackingRobot does not have correct superclass");
}
@Test
public void testFields() {
FieldHeader type = assertNotNull(getOriginalFieldHeader(HackingRobot.class, "type"), emptyContext(),
result -> "Field 'type' does not exist");
assertTrue(Modifier.isPrivate(type.modifiers()), emptyContext(), result ->
"Field 'type' in HackingRobot is not declared private");
assertFalse(Modifier.isStatic(type.modifiers()), emptyContext(), result ->
"Field 'type' in HackingRobot is declared static");
assertEquals(Type.getDescriptor(MovementType.class), type.descriptor(), emptyContext(), result ->
"Field 'type' in HackingRobot does not have correct type");
FieldHeader robotTypes = assertNotNull(getOriginalFieldHeader(HackingRobot.class, "robotTypes"), emptyContext(),
result -> "Field 'robotTypes' does not exist");
assertTrue(Modifier.isPrivate(robotTypes.modifiers()), emptyContext(), result ->
"Field robotTypes in HackingRobot is not declared private");
assertFalse(Modifier.isStatic(robotTypes.modifiers()), emptyContext(), result ->
"Field robotTypes in HackingRobot is declared static");
assertEquals(Type.getDescriptor(MovementType[].class), robotTypes.descriptor(), emptyContext(), result ->
"Field robotTypes in HackingRobot does not have correct type");
// NOTE: it's impossible to test for default value when field is modified in constructor
}
@Test
public void testConstructorHeader() {
MethodHeader constructor = assertNotNull(getOriginalMethodHeader(HackingRobot.class, int.class, int.class, boolean.class),
emptyContext(),
result -> "Constructor 'HackingRobot(int, int, boolean)' does not exist");
assertTrue(Modifier.isPublic(constructor.modifiers()), emptyContext(), result ->
"Constructor 'HackingRobot(int, int, boolean)' is not declared public");
}
@Test
public void testConstructorSuperCall() {
Delegation.disable(MethodHeader.of(HackingRobot.class, int.class, int.class, boolean.class));
int x = 2;
int y = 2;
Context.Builder<?> contextBuilder = contextBuilder()
.add("x", x)
.add("y", y);
Robot hackingRobotInstance = getHackingRobotInstance(x, y, false, contextBuilder);
Context context = contextBuilder.add("HackingRobot instance", hackingRobotInstance).build();
assertEquals(x, hackingRobotInstance.getX(), context, result ->
"Incorrect value for parameter x passed to super constructor");
assertEquals(y, hackingRobotInstance.getY(), context, result ->
"Incorrect value for parameter y passed to super constructor");
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testConstructorSetsFields(boolean order) {
Delegation.disable(MethodHeader.of(HackingRobot.class, int.class, int.class, boolean.class));
List<String> expectedRobotTypes = order ?
List.of("DIAGONAL", "TELEPORT", "OVERSTEP") :
List.of("OVERSTEP", "DIAGONAL", "TELEPORT");
int x = 2;
int y = 2;
Context.Builder<?> contextBuilder = contextBuilder()
.add("x", x)
.add("y", y);
Robot hackingRobotInstance = getHackingRobotInstance(x, y, order, contextBuilder);
Context context = contextBuilder.add("HackingRobot instance", hackingRobotInstance).build();
assertEquals(expectedRobotTypes,
Arrays.stream(FieldHeader.of(HackingRobot.class, "robotTypes").<MovementType[]>getValue(hackingRobotInstance))
.map(Enum::name)
.toList(),
context,
result -> "The values of array robotTypes in HackingRobot were not shifted correctly");
}
@Test
public void testGetType() {
Delegation.disable(MethodHeader.of(HackingRobot.class, "getType"));
int x = 2;
int y = 2;
Context.Builder<?> contextBuilder = contextBuilder()
.add("x", x)
.add("y", y);
HackingRobot hackingRobotInstance = getHackingRobotInstance(x, y, null, contextBuilder);
Context baseContext = contextBuilder.add("HackingRobot instance", hackingRobotInstance).build();
for (MovementType movementType : MovementType.values()) {
FieldHeader.of(HackingRobot.class, "type").setValue(hackingRobotInstance, movementType);
Context context = contextBuilder()
.add(baseContext)
.add("Field 'type'", movementType)
.build();
assertCallEquals(movementType, hackingRobotInstance::getType, context, result ->
"The enum constant returned by 'getType()' is incorrect");
}
}
@ParameterizedTest
@ValueSource(ints = {0, 1, 2})
public void testGetNextTypeNoMod(int offset) {
testGetNextTypeMod(offset);
}
@ParameterizedTest
@ValueSource(ints = {3, 4, 5, 6})
public void testGetNextTypeMod(int offset) {
testGetNextType(offset);
}
/*
@Test
public void testGetRandom() throws Throwable {
// Header
assertTrue((HACKING_ROBOT_GET_RANDOM_LINK.get().modifiers() & Modifier.PUBLIC) != 0, emptyContext(), result ->
"Method getRandom(int) in HackingRobot was not declared public");
assertEquals(int.class, HACKING_ROBOT_GET_RANDOM_LINK.get().returnType().reflection(), emptyContext(), result ->
"Method getRandom(int) has incorrect return type");
// Code
Object hackingRobotInstance = Mockito.mock(HACKING_ROBOT_LINK.get().reflection(), Mockito.CALLS_REAL_METHODS);
List<Integer> returnedInts = new LinkedList<>();
for (int i = 50; i <= 100; i++) {
int result = HACKING_ROBOT_GET_RANDOM_LINK.get().invoke(hackingRobotInstance, i);
final int finalI = i;
assertTrue(result >= 0 && result < i, contextBuilder().add("limit", i).build(), r ->
"Result of getRandom(%d) is not within bounds [0, %d]".formatted(finalI, finalI - 1));
returnedInts.add(result);
}
assertTrue(returnedInts.stream().anyMatch(i -> i >= 3), emptyContext(), result ->
"50 invocations of getRandom(int) didn't return any number > 2, which is extremely unlikely");
}
*/
@ParameterizedTest
@ValueSource(ints = {0, 1, 2})
public void testShuffleWithParams_SetField(int index) {
MovementType[] movementTypeConstants = MovementType.values();
Triple<Context, HackingRobot, Boolean> invocationResult = testShuffleWithParams(index);
assertEquals(movementTypeConstants[index],
FieldHeader.of(HackingRobot.class, "type").getValue(invocationResult.getSecond()),
invocationResult.getFirst(),
result -> "Field 'type' in HackingRobot was not set to the correct value");
}
@ParameterizedTest
@ValueSource(ints = {0, 1, 2})
public void testShuffleWithParams_ReturnValue(int index) {
Triple<Context, HackingRobot, Boolean> invocationResult = testShuffleWithParams(index);
assertEquals(index != 0, invocationResult.getThird(), invocationResult.getFirst(), result ->
"Method 'shuffle(int)' in HackingRobot did not return the expected value");
}
@Test
public void testShuffleNoParams() {
// Header
MethodHeader shuffle = assertNotNull(getOriginalMethodHeader(HackingRobot.class, "shuffle"), emptyContext(),
result -> "Method 'shuffle()' does not exist");
assertTrue(Modifier.isPublic(shuffle.modifiers()), emptyContext(), result ->
"Method 'shuffle()' in HackingRobot was not declared public");
assertEquals(Type.VOID_TYPE, Type.getReturnType(shuffle.descriptor()), emptyContext(), result ->
"Method 'shuffle()' has incorrect return type");
// Body
int limit = 5;
AtomicInteger counter = new AtomicInteger(0);
Delegation.disable(MethodHeader.of(HackingRobot.class, "shuffle"));
Substitution.enable(MethodHeader.of(HackingRobot.class, "shuffle", int.class),
invocation -> counter.incrementAndGet() >= limit);
Context.Builder<?> contextBuilder = contextBuilder()
.add("x", 0)
.add("y", 0);
HackingRobot hackingRobotInstance = getHackingRobotInstance(0, 0, null, contextBuilder);
Context context = contextBuilder.add("HackingRobot instance", hackingRobotInstance).build();
call(hackingRobotInstance::shuffle, context, result ->
"An exception occurred while invoking 'shuffle()' in HackingRobot");
assertEquals(limit, counter.get(), context, result ->
"Method 'shuffle()' in HackingRobot did not return after 'shuffle(int)' returned true / was invoked %d times".formatted(limit));
}
/**
* Create a new HackingRobot instance.
*
* @param x the x coordinate
* @param y the y coordinate
* @param order the order parameter. May be {@code null}, in which case the constructor is called with
* {@code false} first and then {@code true} if an exception was thrown
* @param contextBuilder an optional context builder to append the {@code order} parameter to
* @return a new HackingRobot instance
*/
private HackingRobot getHackingRobotInstance(int x, int y, @Nullable Boolean order, @Nullable Context.Builder<?> contextBuilder) {
Consumer<Boolean> appendContext = b -> {
if (contextBuilder != null) {
contextBuilder.add("order", b);
}
};
HackingRobot hackingRobotInstance;
if (order != null) {
hackingRobotInstance = new HackingRobot(x, y, order);
appendContext.accept(order);
} else {
try {
hackingRobotInstance = new HackingRobot(x, y, false);
appendContext.accept(false);
} catch (Throwable t1) {
System.err.printf("Could not invoke HackingRobot's constructor with params (%d, %d, false):%n%s%n", x, y, t1.getMessage());
try {
hackingRobotInstance = new HackingRobot(x, y, true);
appendContext.accept(true);
} catch (Throwable t2) {
System.err.printf("Could not invoke HackingRobot's constructor with params (%d, %d, true):%n%s%n", x, y, t2.getMessage());
throw new RuntimeException("Could not create an instance of HackingRobot");
}
}
}
return hackingRobotInstance;
}
private void testGetNextType(int offset) {
Delegation.disable(MethodHeader.of(HackingRobot.class, "getNextType"));
int x = 2;
int y = 2;
Context.Builder<?> contextBuilder = contextBuilder()
.add("x", x)
.add("y", y);
HackingRobot hackingRobotInstance = getHackingRobotInstance(x, y, null, contextBuilder);
MovementType[] movementTypeConstants = MovementType.values();
Context context = contextBuilder
.add("HackingRobot instance", hackingRobotInstance)
.add("Field 'type'", movementTypeConstants[offset % movementTypeConstants.length])
.add("Field 'robotTypes'", movementTypeConstants)
.build();
FieldHeader.of(HackingRobot.class, "type")
.setValue(hackingRobotInstance, movementTypeConstants[offset % movementTypeConstants.length]);
FieldHeader.of(HackingRobot.class, "robotTypes")
.setValue(hackingRobotInstance, movementTypeConstants);
assertCallEquals(movementTypeConstants[(offset + 1) % movementTypeConstants.length],
hackingRobotInstance::getNextType,
context,
result -> "The value returned by 'getNextType()' is incorrect");
}
private Triple<Context, HackingRobot, Boolean> testShuffleWithParams(int index) {
Substitution.enable(MethodHeader.of(HackingRobot.class, "getRandom", int.class), invocation -> index);
Delegation.disable(MethodHeader.of(HackingRobot.class, "shuffle", int.class));
MovementType[] movementTypeConstants = MovementType.values();
Context.Builder<?> contextBuilder = contextBuilder()
.add("x", 0)
.add("y", 0);
HackingRobot hackingRobotInstance = getHackingRobotInstance(0, 0, null, contextBuilder);
contextBuilder.add("HackingRobot instance", hackingRobotInstance);
FieldHeader.of(HackingRobot.class, "type").setValue(hackingRobotInstance, movementTypeConstants[0]);
FieldHeader.of(HackingRobot.class, "robotTypes").setValue(hackingRobotInstance, movementTypeConstants);
Context context = contextBuilder
.add("Field 'type'", movementTypeConstants[0])
.add("Field 'robotTypes'", movementTypeConstants)
.add("getRandom(int) return value", index)
.build();
return new Triple<>(context, hackingRobotInstance, callObject(() -> hackingRobotInstance.shuffle(1), context, result ->
"An exception occurred while invoking shuffle(int)"));
}
}

View file

@ -0,0 +1,35 @@
package h03.h3_1;
import h03.robots.MovementType;
import org.junit.jupiter.api.Test;
import org.sourcegrade.jagr.api.rubric.TestForSubmission;
import org.tudalgo.algoutils.transform.util.headers.ClassHeader;
import java.lang.reflect.Modifier;
import java.util.Set;
import static org.tudalgo.algoutils.transform.SubmissionExecutionHandler.*;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.*;
@TestForSubmission
public class MovementTypeTest {
@Test
public void testEnum() {
ClassHeader orignalClassHeader = getOriginalClassHeader(MovementType.class);
assertTrue(Modifier.isPublic(orignalClassHeader.access()), emptyContext(), result ->
"Enum MovementType was not declared public");
}
@Test
public void testEnumConstants() {
Set<String> expectedConstants = Set.of("DIAGONAL", "OVERSTEP", "TELEPORT");
assertEquals(expectedConstants.size(), getOriginalEnumConstants(MovementType.class).size(), emptyContext(),
result -> "Enum MovementType does not have the correct number of constants");
for (String expected : expectedConstants) {
assertNotNull(getOriginalEnumConstant(MovementType.class, expected), emptyContext(),
result -> "Enum constant %s not found in MovementType".formatted(expected));
}
}
}

View file

@ -0,0 +1,155 @@
package h03.h3_2;
import fopbot.World;
import h03.robots.DoublePowerRobot;
import h03.robots.HackingRobot;
import h03.robots.MovementType;
import net.bytebuddy.jar.asm.Type;
import org.apache.commons.lang3.function.TriConsumer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.sourcegrade.jagr.api.rubric.TestForSubmission;
import org.tudalgo.algoutils.transform.util.headers.ClassHeader;
import org.tudalgo.algoutils.transform.util.headers.FieldHeader;
import org.tudalgo.algoutils.transform.util.headers.MethodHeader;
import org.tudalgo.algoutils.tutor.general.assertions.Context;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import static org.tudalgo.algoutils.transform.SubmissionExecutionHandler.*;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.*;
@TestForSubmission
public class DoublePowerRobotTest {
@BeforeAll
public static void setup() {
World.setSize(5, 5);
}
@AfterEach
public void tearDown() {
resetAll();
}
@Test
public void testClassHeader() {
ClassHeader originalClassHeader = getOriginalClassHeader(DoublePowerRobot.class);
assertTrue(Modifier.isPublic(originalClassHeader.access()), emptyContext(), result ->
"Class DoublePowerRobot is not declared public");
assertEquals(Type.getInternalName(HackingRobot.class), originalClassHeader.superName(), emptyContext(), result ->
"Class DoublePowerRobot does not have correct superclass");
}
@Test
public void testFields() {
FieldHeader doublePowerTypes = assertNotNull(getOriginalFieldHeader(DoublePowerRobot.class, "doublePowerTypes"), emptyContext(),
result -> "Field 'doublePowerTypes' does not exist");
assertEquals(Type.getDescriptor(MovementType[].class), doublePowerTypes.descriptor(), emptyContext(),
result -> "Field 'doublePowerTypes' in DoublePowerRobot does not have correct type");
}
@Test
public void testMethodHeaders() {
assertNotNull(getOriginalMethodHeader(DoublePowerRobot.class, "shuffle"), emptyContext(),
result -> "Method 'shuffle()' does not exist");
assertNotNull(getOriginalMethodHeader(DoublePowerRobot.class, "shuffle", int.class), emptyContext(),
result -> "Method 'shuffle(int)' does not exist");
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testConstructorSetsField(boolean order) {
Delegation.disable(MethodHeader.of(DoublePowerRobot.class, int.class, int.class, boolean.class));
List<String> expectedDoublePowerTypes = order ?
List.of("DIAGONAL", "TELEPORT") :
List.of("OVERSTEP", "DIAGONAL");
int x = 2;
int y = 2;
Context context = contextBuilder()
.add("x", x)
.add("y", y)
.add("order", order)
.build();
DoublePowerRobot instance = callObject(() -> new DoublePowerRobot(x, y, order), context, result ->
"An exception occurred while invoking constructor of class DoublePowerRobot");
List<String> actualDoublePowerTypes = Arrays.stream(FieldHeader.of(DoublePowerRobot.class, "doublePowerTypes")
.<MovementType[]>getValue(instance))
.map(Enum::name)
.toList();
assertEquals(expectedDoublePowerTypes, actualDoublePowerTypes, context, result ->
"Array doublePowerTypes does not contain the correct values");
}
@ParameterizedTest
@ValueSource(ints = {0, 1, 2, 3})
public void testShuffleWithParams(int offset) {
testShuffle(offset, MethodHeader.of(DoublePowerRobot.class, "shuffle", int.class),
(instance, context, shuffleReturnValue) -> {
boolean returnValue = callObject(() -> instance.shuffle(1), context, result ->
"An exception occurred while invoking 'shuffle(int)'");
assertEquals(shuffleReturnValue, returnValue, context, result -> "Return value of 'shuffle(int)' is incorrect");
});
}
@ParameterizedTest
@ValueSource(ints = {0, 1, 2, 3})
public void testShuffleNoParams(int offset) {
testShuffle(offset, MethodHeader.of(DoublePowerRobot.class, "shuffle"),
(instance, context, ignored) -> {
call(instance::shuffle, context, result -> "An exception occurred while invoking 'shuffle()'");
});
}
private void testShuffle(int offset, MethodHeader shuffleMethod, TriConsumer<DoublePowerRobot, Context, Boolean> instanceConsumer) {
MovementType[] movementTypes = MovementType.values();
MovementType getTypeReturnValue = movementTypes[offset % movementTypes.length];
MovementType getNextTypeReturnValue = movementTypes[(offset + 1) % movementTypes.length];
int getRandomReturnValue = 1;
boolean shuffleReturnValue = false;
Substitution.enable(MethodHeader.of(HackingRobot.class, "getType"), invocation -> getTypeReturnValue);
Substitution.enable(MethodHeader.of(HackingRobot.class, "getNextType"), invocation -> getNextTypeReturnValue);
Substitution.enable(MethodHeader.of(HackingRobot.class, "getRandom", int.class), invocation -> getRandomReturnValue);
Substitution.enable(MethodHeader.of(HackingRobot.class, "shuffle", int.class), invocation -> shuffleReturnValue);
Substitution.enable(MethodHeader.of(HackingRobot.class, "shuffle"), invocation -> null);
Delegation.disable(shuffleMethod);
int x = 2;
int y = 2;
boolean order = false;
Context context = contextBuilder()
.add("x", x)
.add("y", y)
.add("order", order)
.add("super.getType() return value", getTypeReturnValue)
.add("super.getNextType() return value", getNextTypeReturnValue)
.add("super.getRandom(int) return value", getRandomReturnValue)
.add("super.shuffle(int) return value", shuffleReturnValue)
.build();
DoublePowerRobot instance = callObject(() -> new DoublePowerRobot(x, y, order), context, result ->
"An exception occurred while invoking constructor of class DoublePowerRobot");
instanceConsumer.accept(instance, context, shuffleReturnValue);
FieldHeader doublePowerTypes = FieldHeader.of(DoublePowerRobot.class, "doublePowerTypes");
assertEquals(getTypeReturnValue,
doublePowerTypes.<MovementType[]>getValue(instance)[0],
context,
result -> "Value of doublePowerTypes[0] is incorrect");
assertEquals(getNextTypeReturnValue,
doublePowerTypes.<MovementType[]>getValue(instance)[1],
context,
result -> "Value of doublePowerTypes[1] is incorrect");
}
}

View file

@ -0,0 +1,109 @@
package h03.h3_2;
import fopbot.World;
import h03.robots.HackingRobot;
import h03.robots.MovementType;
import h03.robots.VersatileRobot;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.Type;
import org.sourcegrade.jagr.api.rubric.TestForSubmission;
import org.tudalgo.algoutils.transform.util.headers.ClassHeader;
import org.tudalgo.algoutils.transform.util.headers.MethodHeader;
import org.tudalgo.algoutils.tutor.general.assertions.Context;
import java.lang.reflect.Modifier;
import static org.tudalgo.algoutils.transform.SubmissionExecutionHandler.*;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.*;
@TestForSubmission
public class VersatileRobotTest {
private final MovementType getTypeReturnValue = MovementType.DIAGONAL;
private final MovementType getNextTypeReturnValue = MovementType.OVERSTEP;
private final int getRandomReturnValue = 1;
private final boolean shuffleReturnValue = false;
private final int x = 0;
private final int y = 4;
private final boolean order = false;
private final boolean exchange = false;
private final Context context = contextBuilder()
.add("x", x)
.add("y", y)
.add("order", order)
.add("exchange", exchange)
.add("super.getType() return value", getTypeReturnValue)
.add("super.getNextType() return value", getNextTypeReturnValue)
.add("super.getRandom(int) return value", getRandomReturnValue)
.add("super.shuffle(int) return value", shuffleReturnValue)
.build();
@BeforeAll
public static void setup() {
World.setSize(5, 5);
}
private void setupEnvironment(MethodHeader methodHeader) {
Substitution.enable(MethodHeader.of(HackingRobot.class, "getType"), invocation -> getTypeReturnValue);
Substitution.enable(MethodHeader.of(HackingRobot.class, "getNextType"), invocation -> getNextTypeReturnValue);
Substitution.enable(MethodHeader.of(HackingRobot.class, "getRandom", int.class), invocation -> getRandomReturnValue);
Substitution.enable(MethodHeader.of(HackingRobot.class, "shuffle", int.class), invocation -> shuffleReturnValue);
Substitution.enable(MethodHeader.of(HackingRobot.class, "shuffle"), invocation -> null);
Delegation.disable(methodHeader);
}
@AfterEach
public void tearDown() {
resetAll();
}
@Test
public void testClassHeader() {
ClassHeader originalClassHeader = getOriginalClassHeader(VersatileRobot.class);
assertTrue(Modifier.isPublic(originalClassHeader.access()), emptyContext(), result ->
"Class VersatileRobot was not declared public");
assertEquals(Type.getInternalName(HackingRobot.class), originalClassHeader.superName(), emptyContext(), result ->
"Class VersatileRobot does not extend HackingRobot");
}
@Test
public void testConstructor() {
setupEnvironment(MethodHeader.of(VersatileRobot.class, int.class, int.class, boolean.class, boolean.class));
VersatileRobot instance = callObject(() -> new VersatileRobot(x, y, order, exchange), context, result ->
"An exception occurred while invoking constructor of class VersatileRobot");
assertEquals(x, instance.getX(), context, result -> "The x-coordinate of this VersatileRobot is incorrect");
assertEquals(x, instance.getY(), context, result -> "The y-coordinate of this VersatileRobot is incorrect");
}
@Test
public void testShuffleWithParams() {
setupEnvironment(MethodHeader.of(VersatileRobot.class, "shuffle", int.class));
VersatileRobot instance = callObject(() -> new VersatileRobot(x, y, order, exchange), context, result ->
"An exception occurred while invoking constructor of class VersatileRobot");
instance.setY(y);
call(() -> instance.shuffle(1), context, result -> "An exception occurred while invoking shuffle(int)");
assertEquals(x, instance.getX(), context, result -> "The x-coordinate of this VersatileRobot is incorrect");
assertEquals(x, instance.getY(), context, result -> "The y-coordinate of this VersatileRobot is incorrect");
}
@Test
public void testShuffleNoParams() {
setupEnvironment(MethodHeader.of(VersatileRobot.class, "shuffle"));
VersatileRobot instance = callObject(() -> new VersatileRobot(x, y, order, exchange), context, result ->
"An exception occurred while invoking constructor of class VersatileRobot");
instance.setY(y);
call(instance::shuffle, context, result -> "An exception occurred while invoking shuffle()");
assertEquals(x, instance.getX(), context, result -> "The x-coordinate of this VersatileRobot is incorrect");
assertEquals(x, instance.getY(), context, result -> "The y-coordinate of this VersatileRobot is incorrect");
}
}

View file

@ -0,0 +1,268 @@
package h03.h3_3;
import fopbot.World;
import h03.RobotsChallenge;
import h03.MathMinMock;
import h03.robots.DoublePowerRobot;
import h03.robots.HackingRobot;
import h03.robots.MovementType;
import kotlin.Pair;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.sourcegrade.jagr.api.rubric.TestForSubmission;
import org.tudalgo.algoutils.transform.util.headers.ClassHeader;
import org.tudalgo.algoutils.transform.util.Invocation;
import org.tudalgo.algoutils.transform.util.headers.FieldHeader;
import org.tudalgo.algoutils.transform.util.headers.MethodHeader;
import org.tudalgo.algoutils.tutor.general.assertions.Context;
import org.tudalgo.algoutils.tutor.general.json.JsonParameterSet;
import org.tudalgo.algoutils.tutor.general.json.JsonParameterSetTest;
import java.lang.reflect.*;
import java.util.*;
import static org.tudalgo.algoutils.transform.SubmissionExecutionHandler.*;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.*;
@TestForSubmission
public class RobotsChallengeTest {
@BeforeAll
public static void setup() {
World.setSize(5, 5);
World.setDelay(0);
}
@AfterEach
public void tearDown() {
resetAll();
}
@Test
public void testClassHeader() {
ClassHeader originalClassHeader = getOriginalClassHeader(RobotsChallenge.class);
assertTrue(Modifier.isPublic(originalClassHeader.access()), emptyContext(), result ->
"Class RobotsChallenge was not declared public");
}
@ParameterizedTest
@ValueSource(ints = {10, 15})
public void testConstructor(int begin) {
Delegation.disable(MethodHeader.of(RobotsChallenge.class, int.class, int.class, DoublePowerRobot[].class));
int goal = 5;
DoublePowerRobot[] robots = new DoublePowerRobot[0];
Context context = contextBuilder()
.add("begin", begin)
.add("goal", goal)
.add("robots", robots)
.build();
Object instance = callObject(() -> new RobotsChallenge(begin, goal, robots), context, result ->
"An exception occurred while invoking constructor of class RobotsChallenge");
assertEquals(begin / 2,
FieldHeader.of(RobotsChallenge.class, "begin").getValue(instance),
context,
result -> "Value of field 'begin' is incorrect");
assertEquals(goal,
FieldHeader.of(RobotsChallenge.class, "goal").getValue(instance),
context,
result -> "Value of field 'goal' is incorrect");
assertSame(robots,
FieldHeader.of(RobotsChallenge.class, "robots").getValue(instance),
context,
result -> "Value of field 'robots' is incorrect");
}
@Test
public void testWinThreshold() {
Delegation.disable(MethodHeader.of(RobotsChallenge.class, int.class, int.class, DoublePowerRobot[].class));
int begin = 10;
int goal = 5;
DoublePowerRobot[] robots = new DoublePowerRobot[0];
Context context = contextBuilder()
.add("begin", begin)
.add("goal", goal)
.add("robots", robots)
.build();
Object instance = callObject(() -> new RobotsChallenge(begin, goal, robots), context, result ->
"An exception occurred while invoking constructor of class RobotsChallenge");
assertEquals(2,
FieldHeader.of(RobotsChallenge.class, "winThreshold").getValue(instance),
context,
result -> "Value of field 'winThreshold' is incorrect");
}
@ParameterizedTest
@JsonParameterSetTest("/h03/CalculateStepsDiagonalDataSet.generated.json")
public void testCalculateStepsDiagonal(JsonParameterSet params) throws NoSuchMethodException {
testCalculateStepsAllTypes(params, RobotsChallenge.class.getDeclaredMethod("calculateStepsDiagonal"));
}
@ParameterizedTest
@JsonParameterSetTest("/h03/CalculateStepsOverstepDataSet.generated.json")
public void testCalculateStepsOverstep(JsonParameterSet params) throws NoSuchMethodException {
testCalculateStepsAllTypes(params, RobotsChallenge.class.getDeclaredMethod("calculateStepsOverstep"));
}
@ParameterizedTest
@JsonParameterSetTest("/h03/CalculateStepsTeleportDataSet.generated.json")
public void testCalculateStepsTeleport(JsonParameterSet params) throws NoSuchMethodException {
testCalculateStepsAllTypes(params, RobotsChallenge.class.getDeclaredMethod("calculateStepsTeleport"));
}
@Test
public void testFindWinnersCalc() {
MethodHeader calculateSteps = MethodHeader.of(RobotsChallenge.class, "calculateSteps", MovementType.class);
Delegation.disable(MethodHeader.of(RobotsChallenge.class, "findWinners"));
int begin = 2;
int goal = 5;
MovementType[] movementTypes = MovementType.values();
for (int i = 0; i < 3; i++) {
final int finalI = i;
Substitution.enable(MethodHeader.of(HackingRobot.class, "getType"),
invocation -> movementTypes[finalI % movementTypes.length]);
Substitution.enable(MethodHeader.of(HackingRobot.class, "getNextType"),
invocation -> movementTypes[(finalI + 1) % movementTypes.length]);
Logging.reset();
Logging.enable(calculateSteps);
DoublePowerRobot[] robots = new DoublePowerRobot[] {new DoublePowerRobot(0, 0, false)};
Context context = contextBuilder()
.add("begin", begin)
.add("goal", goal)
.add("robots", robots)
.build();
RobotsChallenge robotsChallengeInstance = new RobotsChallenge(begin * 2, goal, robots);
call(robotsChallengeInstance::findWinners, context, result -> "An exception occurred while invoking findWinners");
List<Invocation> invocations = Logging.getInvocations(calculateSteps);
assertEquals(2, invocations.size(), context, result -> "calculateSteps was not called exactly twice");
assertEquals(List.of(movementTypes[i % movementTypes.length], movementTypes[(i + 1) % movementTypes.length]),
invocations.stream()
.map(invocation -> invocation.getParameter(0, MovementType.class))
.toList(),
context,
result -> "calculateSteps was not called with <robot>.getType() and <robot>.getNextType()");
}
}
@Test
public void testFindWinnersMin() {
Delegation.disable(MethodHeader.of(RobotsChallenge.class, "findWinners"));
Substitution.enable(MethodHeader.of(RobotsChallenge.class, "calculateSteps", MovementType.class),
invocation -> invocation.getParameter(0, MovementType.class).ordinal());
int begin = 2;
int goal = 5;
MovementType[] movementTypes = MovementType.values();
for (int i = 0; i < 3; i++) {
final int finalI = i;
Substitution.enable(MethodHeader.of(HackingRobot.class, "getType"),
invocation -> movementTypes[finalI % movementTypes.length]);
Substitution.enable(MethodHeader.of(HackingRobot.class, "getNextType"),
invocation -> movementTypes[(finalI + 1) % movementTypes.length]);
DoublePowerRobot[] robots = new DoublePowerRobot[] {new DoublePowerRobot(0, 0, false)};
Context context = contextBuilder()
.add("begin", begin)
.add("goal", goal)
.add("robots", robots)
.build();
RobotsChallenge robotsChallengeInstance = new RobotsChallenge(begin * 2, goal, robots);
MathMinMock.MIN_INVOCATIONS.clear();
call(robotsChallengeInstance::findWinners, context, result -> "An exception occurred while invoking findWinners");
List<Pair<Integer, Integer>> minInvocations = new ArrayList<>(MathMinMock.MIN_INVOCATIONS);
assertTrue(!minInvocations.isEmpty(), context, result -> "Math.min was not called at least once");
Pair<Integer, Integer> expectedArgs = new Pair<>(finalI % movementTypes.length, (finalI + 1) % movementTypes.length);
assertTrue(
minInvocations.stream()
.anyMatch(pair -> pair.getFirst().equals(expectedArgs.getFirst()) && pair.getSecond().equals(expectedArgs.getSecond()) ||
pair.getFirst().equals(expectedArgs.getSecond()) && pair.getSecond().equals(expectedArgs.getFirst())),
contextBuilder()
.add(context)
.add("expected", "Math.min(%d, %d) or Math.min(%d, %d)".formatted(expectedArgs.getFirst(), expectedArgs.getSecond(), expectedArgs.getSecond(), expectedArgs.getFirst()))
.build(),
result -> "Math.min was not called with the expected arguments"
);
}
}
@Test
public void testFindWinnersReturn() {
MovementType[] movementTypes = MovementType.values();
DoublePowerRobot[] robots = new DoublePowerRobot[] {
new DoublePowerRobot(0, 0, false),
new DoublePowerRobot(0, 0, false),
new DoublePowerRobot(0, 0, false)
};
Substitution.enable(MethodHeader.of(HackingRobot.class, "getType"), invocation -> {
if (invocation.getInstance() == robots[0]) {
return movementTypes[0];
} else if (invocation.getInstance() == robots[1]) {
return movementTypes[1];
} else if (invocation.getInstance() == robots[2]) {
return movementTypes[2];
} else {
return null;
}
});
Substitution.enable(MethodHeader.of(HackingRobot.class, "getNextType"), invocation -> {
if (invocation.getInstance() == robots[0]) {
return movementTypes[1];
} else if (invocation.getInstance() == robots[1]) {
return movementTypes[2];
} else if (invocation.getInstance() == robots[2]) {
return movementTypes[0];
} else {
return null;
}
});
Substitution.enable(MethodHeader.of(RobotsChallenge.class, "calculateSteps", MovementType.class),
invocation -> invocation.getParameter(0, MovementType.class).ordinal() * 3);
Delegation.disable(MethodHeader.of(RobotsChallenge.class, "findWinners"));
int begin = 2;
int goal = 5;
Context context = contextBuilder()
.add("begin", begin)
.add("goal", goal)
.add("robots", robots)
.build();
RobotsChallenge robotsChallengeInstance = new RobotsChallenge(begin * 2, goal, robots);
DoublePowerRobot[] returnValue = callObject(robotsChallengeInstance::findWinners, context, result ->
"An exception occurred while invoking findWinners");
assertEquals(robots.length, returnValue.length, context, result -> "Returned array has incorrect length");
int a = 0;
for (DoublePowerRobot robot : robots) {
if (robot.getType() == MovementType.DIAGONAL || robot.getNextType() == MovementType.DIAGONAL) {
assertSame(robot, returnValue[a++], context, result -> "Robot was not found in array / at wrong index");
}
}
for (; a < robots.length; a++) {
assertNull(returnValue[a], context, result -> "Found unexpected robots in array");
}
}
private void testCalculateStepsAllTypes(JsonParameterSet params, Method method) {
Delegation.disable(method);
Context context = params.toContext("expected");
DoublePowerRobot[] robots = new DoublePowerRobot[0];
RobotsChallenge instance = callObject(() -> new RobotsChallenge(params.getInt("begin"), params.getInt("goal"), robots),
context, result -> "An exception occurred while invoking constructor of class RobotsChallenge");
assertCallEquals(params.getInt("expected"), () -> method.invoke(instance), context, result ->
result.cause() == null ? method.getName() + " returned an incorrect value" : result.cause().getCause().getMessage());
}
}

Binary file not shown.

View file

@ -0,0 +1 @@
*.generated.json

View file

@ -0,0 +1,70 @@
package h03;
import fopbot.World;
import h03.robots.DoublePowerRobot;
import h03.robots.HackingRobot;
import h03.robots.MovementType;
import h03.robots.VersatileRobot;
/**
* 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(String[] args) {
// Create a 5x5 world and make it visible
World.setSize(5, 5);
World.setVisible(true);
// Create at least one Hacking Robot with different positions and both cases for the array shift
HackingRobot hackingRobot1 = new HackingRobot(1, 1, true);
HackingRobot hackingRobot2 = new HackingRobot(2, 2, false);
// Change the type of the Hacking Robot and check the current and next type
hackingRobot1.shuffle();
System.out.println("HackingRobot1 current type: " + hackingRobot1.getType());
System.out.println("HackingRobot1 next type: " + hackingRobot1.getNextType());
hackingRobot2.shuffle();
System.out.println("HackingRobot2 current type: " + hackingRobot2.getType());
System.out.println("HackingRobot2 next type: " + hackingRobot2.getNextType());
// Create at least two Versatile Robots with both cases for coordinate exchange
VersatileRobot versatileRobot1 = new VersatileRobot(1, 2, true, false);
VersatileRobot versatileRobot2 = new VersatileRobot(3, 4, false, true);
// Change the type of the Versatile Robot until the type is DIAGONAL and check coordinates
while (versatileRobot1.getType() != MovementType.DIAGONAL) {
versatileRobot1.shuffle();
}
System.out.println("VersatileRobot1 type is DIAGONAL. x: " + versatileRobot1.getX() + ", y: " + versatileRobot1.getY());
while (versatileRobot2.getType() != MovementType.DIAGONAL) {
versatileRobot2.shuffle();
}
System.out.println("VersatileRobot2 type is DIAGONAL. x: " + versatileRobot2.getX() + ", y: " + versatileRobot2.getY());
// Create at least three Double Power Robots and change their types to get all movement types
DoublePowerRobot doublePowerRobot1 = new DoublePowerRobot(0, 0, true);
DoublePowerRobot doublePowerRobot2 = new DoublePowerRobot(1, 1, false);
DoublePowerRobot doublePowerRobot3 = new DoublePowerRobot(2, 2, true);
// Create a RobotsChallenge with previously created Double Power Robots
DoublePowerRobot[] robots = {doublePowerRobot1, doublePowerRobot2, doublePowerRobot3};
RobotsChallenge challenge = new RobotsChallenge(0, 2, robots);
// Find and display the winning Double Power Robots
DoublePowerRobot[] winners = challenge.findWinners();
System.out.println("Winning DoublePowerRobots:");
for (DoublePowerRobot winner : winners) {
if (winner != null) {
//print the winner robot's coordinates
System.out.println("Winner robot coordinates: x: " + winner.getX() + ", y: " + winner.getY());
}
}
}
}

View file

@ -0,0 +1,88 @@
package h03;
import h03.robots.DoublePowerRobot;
import h03.robots.MovementType;
/**
* The {@code RobotsChallenge} class performs a challenge between robots of the {@code DoublePowerRobot} class.
*/
public class RobotsChallenge {
private final DoublePowerRobot[] robots;
private final int goal;
private final int begin;
private final int winThreshold = 2;
/**
* Constructs a new {@code RobotsChallenge} with the specified starting position, goal, and array of robots.
*
* @param begin The starting position of the robots.
* @param goal The target coordinates.
* @param robots The array of {@code DoublePowerRobot} objects participating in the challenge.
*/
public RobotsChallenge(int begin, int goal, final DoublePowerRobot[] robots) {
this.begin = begin / 2;
this.goal = goal;
this.robots = robots;
}
/**
* Calculates the number of steps needed for a robot to reach the goal for the diagonal type.
*
* @return The number of steps required to reach the goal.
*/
public int calculateStepsDiagonal() {
return Math.abs(begin - goal);
}
/**
* Calculates the number of steps needed for a robot to reach the goal for the overstep type.
*
* @return The number of steps required to reach the goal.
*/
public int calculateStepsOverstep() {
return (Math.abs(begin - goal) % 2 == 0) ? Math.abs(begin - goal) : Math.abs(begin - goal) + 1;
}
/**
* Calculates the number of steps needed for a robot to reach the goal for the teleport type.
*
* @return The number of steps required to reach the goal.
*/
public int calculateStepsTeleport() {
return (Math.abs(begin - goal) % 2 == 0) ? Math.abs(begin - goal) / 2 : (Math.abs(begin - goal) / 2) + 2;
}
/**
* Calculates the number of steps needed for a robot to reach the goal based on its movement type.
*
* @param type The {@code MovementType} of the robot.
* @return The number of steps required to reach the goal.
*/
public int calculateSteps(MovementType type) {
return type == MovementType.DIAGONAL ? calculateStepsDiagonal() : type == MovementType.OVERSTEP ? calculateStepsOverstep() : calculateStepsTeleport();
}
/**
* Finds the winning robots in the challenge based on their movement types and the number of steps required to reach the goal.
*
* @return An array of {@code DoublePowerRobot} objects that are the winners of the challenge.
*/
public DoublePowerRobot[] findWinners() {
int winnerCount = 0;
DoublePowerRobot[] winners = new DoublePowerRobot[robots.length];
for (DoublePowerRobot robot : robots) {
int stepsFirstType = calculateSteps(robot.getType());
int stepsSecondType = calculateSteps(robot.getNextType());
int steps = Math.min(stepsFirstType, stepsSecondType);
if (steps <= winThreshold) {
winners[winnerCount] = robot;
winnerCount++;
}
}
return winners;
}
}

View file

@ -0,0 +1,57 @@
package h03.robots;
/**
* Subclass DoublePowerRobot, which inherits from the {@code HackingRobot} class and allows the robot to have two types simultaneously.
*/
public class DoublePowerRobot extends HackingRobot {
/**
* Private array doublePowerTypes containing the two types for the DoublePowerRobot.
*/
private MovementType[] doublePowerTypes = new MovementType[2];
/**
* Constructor of the DoublePowerRobot class with parameters x, y, and order.
* Initializes the robot and assigns two movement types to the robot.
*
* @param x The x-coordinate of the robot.
* @param y The y-coordinate of the robot.
* @param order If true, the movement types are shifted to the right by one index, otherwise to the left by one index.
*/
public DoublePowerRobot(int x, int y, boolean order) {
super(x, y, order);
// Assigning the two types to doublePowerTypes
doublePowerTypes[0] = getType();
doublePowerTypes[1] = getNextType();
}
/**
* Overrides the shuffle method of the superclass.
* Shuffles the robot's type a specified number of times and updates the types in doublePowerTypes.
*
* @param itNr The number of iterations to shuffle the type.
* @return True if the types have changed, false otherwise.
*/
@Override
public boolean shuffle(int itNr) {
boolean changed = super.shuffle(itNr);
// Updating the types in doublePowerTypes based on the new value of type
doublePowerTypes[0] = getType();
doublePowerTypes[1] = getNextType();
return changed;
}
/**
* Overrides the shuffle method of the superclass.
* Shuffles the robot's type until the type is different from the current type and updates the types in doublePowerTypes.
*/
@Override
public void shuffle() {
super.shuffle();
doublePowerTypes[0] = getType();
doublePowerTypes[1] = getNextType();
}
}

View file

@ -0,0 +1,111 @@
package h03.robots;
import fopbot.Robot;
import java.util.Random;
/**
* The HackingRobot class extends the Robot class and provides additional methods for movement in the grid.
* The robot can have different types of movements which can be shuffled.
*/
public class HackingRobot extends Robot {
/**
* Private array "robotTypes" containing the elements of the enumeration MovementType in reverse alphabetical order.
*/
private MovementType[] robotTypes = {MovementType.TELEPORT, MovementType.OVERSTEP, MovementType.DIAGONAL};
/**
* Private variable that contains the type of the robot.
*/
private MovementType type;
/**
* Constructs a new HackingRobot at the specified coordinates.
* The order parameter determines the initial order of the movement types.
*
* @param x The x-coordinate of the robot.
* @param y The y-coordinate of the robot.
* @param order If true, the movement types are shifted to the right by one index, otherwise to the left by one index.
*/
public HackingRobot(int x, int y, boolean order) {
super(x, y);
if (order) {
// Move elements to the right by 1 index
MovementType lastElement = robotTypes[robotTypes.length - 1];
for (int i = robotTypes.length - 1; i > 0; i--) {
robotTypes[i] = robotTypes[i - 1];
}
robotTypes[0] = lastElement;
} else {
// Move elements to the left by 1 index
MovementType firstElement = robotTypes[0];
for (int i = 0; i < robotTypes.length - 1; i++) {
robotTypes[i] = robotTypes[i + 1];
}
robotTypes[robotTypes.length - 1] = firstElement;
}
this.type = robotTypes[0];
}
/**
* Returns the current type of the robot.
*
* @return The current MovementType of the robot.
*/
public MovementType getType() {
return type;
}
/**
* Returns the movement type located 1 index to the right of the current type of the robot.
*
* @return The next MovementType of the robot.
*/
public MovementType getNextType() {
int currentIndex = -1;
for (int i = 0; i < robotTypes.length; i++) {
if (robotTypes[i] == type) {
currentIndex = i;
break;
}
}
return robotTypes[(currentIndex + 1) % robotTypes.length];
}
/**
* Generates a random number between zero (inclusive) and the specified limit (exclusive).
*
* @param limit The upper bound (exclusive) for the random number.
* @return A random integer between 0 (inclusive) and the specified limit (exclusive).
*/
public int getRandom(int limit) {
Random random = new Random();
return random.nextInt(limit);
}
/**
* Randomly changes the type of the robot a specified number of times.
*
* @param itNr The number of iterations to shuffle the type.
* @return True if the type changed after shuffling, false otherwise.
*/
public boolean shuffle(int itNr) {
MovementType previousType = this.type;
for (int i = 0; i < itNr; i++) {
int randomIndex = getRandom(robotTypes.length);
this.type = robotTypes[randomIndex];
}
return this.type != previousType;
}
/**
* Randomly changes the type of the robot until the type is different from the current type.
*/
public void shuffle() {
while (!shuffle(1)) {
}
}
}

View file

@ -0,0 +1,21 @@
package h03.robots;
/**
* The {@code MovementType} enum represents the different types of movements that a robot can perform.
*/
public enum MovementType {
/**
* Represents diagonal movement.
*/
DIAGONAL,
/**
* Represents overstepping movement.
*/
OVERSTEP,
/**
* Represents teleportation movement.
*/
TELEPORT
}

View file

@ -0,0 +1,59 @@
package h03.robots;
/**
* Subclass VersatileRobot, which inherits from the class {@code HackingRobot}.
* This robot can switch its coordinates and has specific behavior when its type is DIAGONAL.
*/
public class VersatileRobot extends HackingRobot {
/**
* Constructor of the VersatileRobot class with the parameters x, y, order, and exchange.
* Initializes the robot and optionally exchanges its coordinates.
*
* @param x The x-coordinate of the robot.
* @param y The y-coordinate of the robot.
* @param order If true, the movement types are shifted to the right by one index, otherwise to the left by one index.
* @param exchange If true, the coordinates x and y are exchanged.
*/
public VersatileRobot(int x, int y, boolean order, boolean exchange) {
super(x, y, order);
if (exchange) {
int aux = x;
setX(y);
setY(aux);
}
if (getType() == MovementType.DIAGONAL) {
setY(getX());
}
}
/**
* Overrides the shuffle method of the superclass.
* Shuffles the robot's type a specified number of times and adjusts the y-coordinate if the type is DIAGONAL.
*
* @param itNr The number of iterations to shuffle the type.
* @return True if the types have changed, false otherwise.
*/
@Override
public boolean shuffle(int itNr) {
boolean changed = super.shuffle(itNr);
if (getType() == MovementType.DIAGONAL) {
setY(getX());
}
return changed;
}
/**
* Overrides the shuffle method of the superclass.
* Shuffles the robot's type until the type is different from the current type and adjusts the y-coordinate if the type is DIAGONAL.
*/
@Override
public void shuffle() {
super.shuffle();
if (getType() == MovementType.DIAGONAL) {
setY(getX());
}
}
}

View file

@ -0,0 +1,16 @@
package h03;
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);
}
}

1
version Normal file
View file

@ -0,0 +1 @@
0.1.0-SNAPSHOT