diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d134928 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Default formatting Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +# do not trim trailing whitespace in markdown files +[*.md] +trim_trailing_whitespace = false + +# explicit 4 space indentation +[*.py] +indent_size = 4 + +# explicit 2 space indentation +[*.{json,yml,yaml,xml,ddl,sql}] +indent_size = 2 + +# windows specific files +[*.{bat,cmd}] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..00a51af --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore index a1c2a23..9e7c379 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,70 @@ -# Compiled class file +# InelliJ IDEA files +*.iml +*.ipr +*.ids +*.iws +.idea/ + +# Eclipse files +.project +.metadata +.classpath +.settings/ +.loadpath +bin/ + +# Netbeans +nbactions.xml + +# Visual Studio Code +.vscode + +# Maven +target/ + +# gradle files +.gradle +build/ + +# ignore logfiles +*.log* + +# OS dependant files +.DS_Store +.Spotlight-V100 +.Trashes +Thumbs.db +Desktop.ini +*~ +# Thumbnails +._* + +# compiled files +*.com *.class +*.dll +*.exe +*.o +*.so -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz +# packages +*.7z +#*.jar *.rar +*.zip +*.gz +*.bzip +*.xz +*.lzma +*~$* -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* +# package managment formats +*.dmg +*.xpi +*.gem +*.egg +*.deb +*.rpm + +# databases +*.sqlite diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..37dcb87 --- /dev/null +++ b/build.gradle @@ -0,0 +1,56 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java project to get you started. + * For more details take a look at the Java Quickstart chapter in the Gradle + * User Manual available at https://docs.gradle.org/6.1/userguide/tutorial_java_projects.html + */ + +plugins { + // Apply the java plugin to add support for Java + id 'java' + + // Apply the application plugin to add support for building a CLI application. + id 'application' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.beryx:text-io:3.4.1' + + // beryx uses SLF4J. To remove warning, we add the implementation "no operation" + implementation 'org.slf4j:slf4j-nop:2.+' + + // Use JUnit Jupiter API for testing. + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.hamcrest:hamcrest:2.2' + + // Use JUnit Jupiter Engine for testing. + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' +} + +group = 'ch.zhaw.pm2' +version = '2022.1' + +application { + // Define the main class for the application. + mainClass = 'ch.zhaw.pm2.racetrack.ConsoleApp' +} + +run { + standardInput = System.in +} + +test { + // Use junit platform for unit tests + useJUnitPlatform() +} diff --git a/follower/challenge_handout_points.txt b/follower/challenge_handout_points.txt new file mode 100644 index 0000000..4d7fec6 --- /dev/null +++ b/follower/challenge_handout_points.txt @@ -0,0 +1,23 @@ +(X:28, Y:22) +(X:31, Y:22) +(X:34, Y:22) +(X:37, Y:22) +(X:40, Y:22) +(X:43, Y:22) +(X:46, Y:21) +(X:48, Y:19) +(X:48, Y:17) +(X:46, Y:15) +(X:41, Y:13) +(X:41, Y:10) +(X:46, Y:9) +(X:49, Y:4) +(X:40, Y:2) +(X:30, Y:2) +(X:21, Y:3) +(X:16, Y:7) +(X:13, Y:10) +(X:14, Y:14) +(X:11, Y:19) +(X:13, Y:22) +(X:24, Y:22) diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..336465c --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.console=plain diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..41d9927 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..41dfb87 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/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/master/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 + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# 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"' + +# 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*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + 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 + +# 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 \ + "$@" + +# 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/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@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=. +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%" == "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%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/moves/challenge-car-a.txt b/moves/challenge-car-a.txt new file mode 100644 index 0000000..defc08b --- /dev/null +++ b/moves/challenge-car-a.txt @@ -0,0 +1,42 @@ +RIGHT +RIGHT +RIGHT +NONE +NONE +NONE +NONE +UP +LEFT +LEFT +LEFT +LEFT +UP_LEFT +NONE +RIGHT +RIGHT +RIGHT +NONE +LEFT +DOWN_LEFT +DOWN_LEFT +LEFT +LEFT +NONE +RIGHT +NONE +DOWN +DOWN +RIGHT +NONE +RIGHT +DOWN +NONE +UP_RIGHT +RIGHT +UP_RIGHT +UP_RIGHT +RIGHT +RIGHT + + + diff --git a/moves/challenge-car-b.txt b/moves/challenge-car-b.txt new file mode 100644 index 0000000..e5bdf18 --- /dev/null +++ b/moves/challenge-car-b.txt @@ -0,0 +1,40 @@ +RIGHT +RIGHT +RIGHT +NONE +NONE +NONE +UP +LEFT +NONE +UP_LEFT +UP_LEFT +DOWN_LEFT +LEFT +UP_RIGHT +DOWN_RIGHT +NONE +NONE +DOWN_LEFT +DOWN_LEFT +LEFT +LEFT +NONE +RIGHT +NONE +DOWN +DOWN +RIGHT +NONE +RIGHT +DOWN +NONE +UP_RIGHT +RIGHT +UP_RIGHT +UP_RIGHT +RIGHT +RIGHT + + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e00e82b --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/6.1/userguide/multi_project_builds.html + */ + +rootProject.name = 'racetrack' diff --git a/src/main/java/ch/zhaw/pm2/racetrack/Car.java b/src/main/java/ch/zhaw/pm2/racetrack/Car.java new file mode 100644 index 0000000..9b97c3d --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/Car.java @@ -0,0 +1,134 @@ +package ch.zhaw.pm2.racetrack; + +import ch.zhaw.pm2.racetrack.given.CarSpecification; +import ch.zhaw.pm2.racetrack.strategy.MoveStrategy; + +/** + * Class representing a car on the racetrack. + * Uses {@link PositionVector} to store current position on the track grid and current velocity vector. + * Each car has an identifier character which represents the car on the race track board. + * Also keeps the state, if the car is crashed (not active anymore). The state can not be changed back to uncrashed. + * The velocity is changed by providing an acelleration vector. + * The car is able to calculate the endpoint of its next position and on request moves to it. + */ +public class Car implements CarSpecification { + + /** + * Car identifier used to represent the car on the track + */ + private final char id; + + /** + * Current position of the car on the track grid using a {@link PositionVector} + */ + private PositionVector position; + + /** + * Current velocity of the car using a {@link PositionVector} + */ + private PositionVector velocity = new PositionVector(0, 0); + + /** + * Indicator if the car has crashed + */ + private boolean crashed = false; + + /** + * Current move strategy + */ + private MoveStrategy moveStrategy; + + /** + * Constructor for class Car + * @param id unique Car identification + * @param position initial position of the Car + */ + public Car(char id, PositionVector position) { + this.id = id; + setPosition(position); + } + + /** + * Set this Car position directly, regardless of current position and velocity. + * This should only be used by the game controller in rare cases to set the crash or winning position. + * The next position is normaly automatically calculated and set in the {@link #move()} method. + * + * @param position The new position to set the car directly to. + */ + @Override + public void setPosition(final PositionVector position) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Return the position that will apply after the next move at the current velocity. + * Does not complete the move, so the current position remains unchanged. + * + * @return Expected position after the next move + */ + @Override + public PositionVector nextPosition() { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Add the specified amounts to this cars's velocity. + * The only acceleration values allowed are -1, 0 or 1 in both axis + * There are 9 possible acceleration vectors, which are defined in {@link PositionVector.Direction}. + * Changes only velocity, not position. + * + * @param acceleration A Direction vector containing the amounts to add to the velocity in x and y dimension + */ + @Override + public void accelerate(PositionVector.Direction acceleration) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Update this Car's position based on its current velocity. + */ + @Override + public void move() { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Mark this Car as being crashed. + */ + @Override + public void crash() { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Returns whether this Car has been marked as crashed. + * + * @return Returns true if crash() has been called on this Car, false otherwise. + */ + @Override + public boolean isCrashed() { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Set move strategy + * @param moveStrategy + */ + public void setMoveStrategy(MoveStrategy moveStrategy) { + this.moveStrategy = moveStrategy; + } + + /** + * Get current move strategy + * @return MoveStrategy + */ + public MoveStrategy getMoveStrategy() { + return this.moveStrategy; + } +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/Config.java b/src/main/java/ch/zhaw/pm2/racetrack/Config.java new file mode 100644 index 0000000..ad4876f --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/Config.java @@ -0,0 +1,47 @@ +package ch.zhaw.pm2.racetrack; + +import ch.zhaw.pm2.racetrack.given.ConfigSpecification; + +import java.io.File; +import java.util.Objects; + +public class Config implements ConfigSpecification { + + // Directory containing the track files + private File trackDirectory = new File("tracks"); + + // Directory containing the track files + private File moveDirectory = new File("moves"); + + // Directory containing the follower files + private File followerDirectory = new File("follower"); + + public File getMoveDirectory() { + return moveDirectory; + } + + public void setMoveDirectory(File moveDirectory) { + Objects.requireNonNull(moveDirectory); + this.moveDirectory = moveDirectory; + } + + public File getFollowerDirectory() { + return followerDirectory; + } + + public void setFollowerDirectory(File followerDirectory) { + Objects.requireNonNull(followerDirectory); + this.followerDirectory = followerDirectory; + } + + public File getTrackDirectory() { + return trackDirectory; + } + + public void setTrackDirectory(File trackDirectory) { + Objects.requireNonNull(trackDirectory); + this.trackDirectory = trackDirectory; + } + + +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/Game.java b/src/main/java/ch/zhaw/pm2/racetrack/Game.java new file mode 100644 index 0000000..27609ca --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/Game.java @@ -0,0 +1,141 @@ +package ch.zhaw.pm2.racetrack; + +import ch.zhaw.pm2.racetrack.given.GameSpecification; + +import java.util.List; + +import static ch.zhaw.pm2.racetrack.PositionVector.Direction; + +/** + * Game controller class, performing all actions to modify the game state. + * It contains the logic to move the cars, detect if they are crashed + * and if we have a winner. + */ +public class Game implements GameSpecification { + public static final int NO_WINNER = -1; + + /** + * Return the index of the current active car. + * Car indexes are zero-based, so the first car is 0, and the last car is getCarCount() - 1. + * @return The zero-based number of the current car + */ + @Override + public int getCurrentCarIndex() { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Get the id of the specified car. + * @param carIndex The zero-based carIndex number + * @return A char containing the id of the car + */ + @Override + public char getCarId(int carIndex) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Get the position of the specified car. + * @param carIndex The zero-based carIndex number + * @return A PositionVector containing the car's current position + */ + @Override + public PositionVector getCarPosition(int carIndex) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Get the velocity of the specified car. + * @param carIndex The zero-based carIndex number + * @return A PositionVector containing the car's current velocity + */ + @Override + public PositionVector getCarVelocity(int carIndex) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Return the winner of the game. If the game is still in progress, returns NO_WINNER. + * @return The winning car's index (zero-based, see getCurrentCar()), or NO_WINNER if the game is still in progress + */ + @Override + public int getWinner() { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Execute the next turn for the current active car. + *

This method changes the current car's velocity and checks on the path to the next position, + * if it crashes (car state to crashed) or passes the finish line in the right direction (set winner state).

+ *

The steps are as follows

+ *
    + *
  1. Accelerate the current car
  2. + *
  3. Calculate the path from current (start) to next (end) position + * (see {@link Game#calculatePath(PositionVector, PositionVector)})
  4. + *
  5. Verify for each step what space type it hits: + * + *
  6. + *
  7. If the car crashed or wins, set its position to the crash/win coordinates
  8. + *
  9. If the car crashed, also detect if there is only one car remaining, remaining car is the winner
  10. + *
  11. Otherwise move the car to the end position
  12. + *
+ *

The calling method must check the winner state and decide how to go on. If the winner is different + * than {@link Game#NO_WINNER}, or the current car is already marked as crashed the method returns immediately.

+ * + * @param acceleration A Direction containing the current cars acceleration vector (-1,0,1) in x and y direction + * for this turn + */ + @Override + public void doCarTurn(Direction acceleration) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Switches to the next car who is still in the game. Skips crashed cars. + */ + @Override + public void switchToNextActiveCar() { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Returns all of the grid positions in the path between two positions, for use in determining line of sight. + * Determine the 'pixels/positions' on a raster/grid using Bresenham's line algorithm. + * (https://de.wikipedia.org/wiki/Bresenham-Algorithmus) + * Basic steps are + * - Detect which axis of the distance vector is longer (faster movement) + * - for each pixel on the 'faster' axis calculate the position on the 'slower' axis. + * Direction of the movement has to correctly considered + * @param startPosition Starting position as a PositionVector + * @param endPosition Ending position as a PositionVector + * @return Intervening grid positions as a List of PositionVector's, including the starting and ending positions. + */ + @Override + public List calculatePath(PositionVector startPosition, PositionVector endPosition) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Does indicate if a car would have a crash with a WALL space or another car at the given position. + * @param carIndex The zero-based carIndex number + * @param position A PositionVector of the possible crash position + * @return A boolean indicator if the car would crash with a WALL or another car. + */ + @Override + public boolean willCarCrash(int carIndex, PositionVector position) { + // TODO: implementation + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/InvalidFileFormatException.java b/src/main/java/ch/zhaw/pm2/racetrack/InvalidFileFormatException.java new file mode 100644 index 0000000..e7d0c07 --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/InvalidFileFormatException.java @@ -0,0 +1,5 @@ +package ch.zhaw.pm2.racetrack; + +public class InvalidFileFormatException extends Exception { + // TODO: implementation +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/InvalidTrackFormatException.java b/src/main/java/ch/zhaw/pm2/racetrack/InvalidTrackFormatException.java new file mode 100644 index 0000000..25de664 --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/InvalidTrackFormatException.java @@ -0,0 +1,5 @@ +package ch.zhaw.pm2.racetrack; + +public class InvalidTrackFormatException extends Exception { + // TODO: implementation +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/PositionVector.java b/src/main/java/ch/zhaw/pm2/racetrack/PositionVector.java new file mode 100644 index 0000000..10209a3 --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/PositionVector.java @@ -0,0 +1,114 @@ +package ch.zhaw.pm2.racetrack; + +/** + * Holds a position (vector to x,y-position of the car on the track grid) + * or a velocity vector (x,y-components of the velocity vector of a car). + * + * Created by mach 21.01.2020 + */ +public final class PositionVector { + private int x; // horizontal component (position / velocity) + private int y; // vertical component (position / velocity) + + /** + * Enum representing a direction on the track grid. + * Also representing the possible acceleration values. + */ + public enum Direction { + DOWN_LEFT(new PositionVector(-1, 1)), + DOWN(new PositionVector(0, 1)), + DOWN_RIGHT(new PositionVector(1, 1)), + LEFT(new PositionVector(-1, 0)), + NONE(new PositionVector(0, 0)), + RIGHT(new PositionVector(1, 0)), + UP_LEFT(new PositionVector(-1, -1)), + UP(new PositionVector(0, -1)), + UP_RIGHT(new PositionVector(1, -1)); + + public final PositionVector vector; + Direction(final PositionVector v) { + vector = v; + } + } + + /** + * Adds two PositionVectors (e.g. car position and velocity vector or two velocity vectors). + * @param vectorA A position or velocity vector + * @param vectorB A position or velocity vector + * @return A new PositionVector holding the result of the addition. If both + * arguments are positions (not velocity), the result is mathematically + * correct but meaningless. + */ + public static PositionVector add(final PositionVector vectorA, final PositionVector vectorB) { + return new PositionVector(vectorA.getX() + vectorB.getX(), vectorA.getY() + vectorB.getY()); + } + + /** + * Subtracts two PositionVectors (e.g. car position and velocity vector or two velocity vectors). + * @param vectorA A position or velocity vector + * @param vectorB A position or velocity vector + * @return A new PositionVector holding the result of the addition. If both + * arguments are positions (not velocity), the result is mathematically + * correct but meaningless. + */ + public static PositionVector subtract(final PositionVector vectorA, final PositionVector vectorB) { + return new PositionVector(vectorA.getX() - vectorB.getX(), vectorA.getY() - vectorB.getY()); + } + + /** + * Calculates the scalar product (Skalarprodukt) of two 2D vectors. The scalar product + * multiplies the lengths of the parallel components of the vectors. + * @param vectorA A position or velocity vector + * @param vectorB A position or velocity vector + * @return The scalar product (vectorA * vectorB). Since vectorA and + * vectorB are PositionVectors, which hold only integer coordinates, + * the resulting scalar product is an integer. + */ + public static int scalarProduct(final PositionVector vectorA, final PositionVector vectorB) { + return (vectorA.getY() * vectorB.getY()) + (vectorA.getX() * vectorB.getX()); + } + + public PositionVector(final int x, final int y) { + this.y = y; + this.x = x; + } + + /** + * Copy constructor + * @param other + */ + public PositionVector(final PositionVector other) { + this.x = other.getX(); + this.y = other.getY(); + } + + public PositionVector() { + this.x = 0; + this.y = 0; + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + @Override + public boolean equals(final Object other) { + if (!(other instanceof PositionVector)) throw new ClassCastException(); + final PositionVector otherPositionVector = (PositionVector) other; + return this.y == otherPositionVector.getY() && this.x == otherPositionVector.getX(); + } + + @Override + public int hashCode() { + return this.x ^ this.y; + } + + @Override + public String toString() { + return "(X:" + this.x + ", Y:" + this.y + ")"; + } +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/Track.java b/src/main/java/ch/zhaw/pm2/racetrack/Track.java new file mode 100644 index 0000000..61deadf --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/Track.java @@ -0,0 +1,169 @@ +package ch.zhaw.pm2.racetrack; + +import ch.zhaw.pm2.racetrack.given.TrackSpecification; + +import java.io.File; +import java.io.FileNotFoundException; + +/** + * This class represents the racetrack board. + * + *

The racetrack board consists of a rectangular grid of 'width' columns and 'height' rows. + * The zero point of he grid is at the top left. The x-axis points to the right and the y-axis points downwards.

+ *

Positions on the track grid are specified using {@link PositionVector} objects. These are vectors containing an + * x/y coordinate pair, pointing from the zero-point (top-left) to the addressed space in the grid.

+ * + *

Each position in the grid represents a space which can hold an enum object of type {@link Config.SpaceType}.
+ * Possible Space types are: + *

+ *

Beside the board the track contains the list of cars, with their current state (position, velocity, crashed,...)

+ * + *

At initialization the track grid data is read from the given track file. The track data must be a + * rectangular block of text. Empty lines at the start are ignored. Processing stops at the first empty line + * following a non-empty line, or at the end of the file.

+ *

Characters in the line represent SpaceTypes. The mapping of the Characters is as follows:

+ * + * + *

All lines must have the same length, used to initialize the grid width). + * Beginning empty lines are skipped. + * The the tracks ends with the first empty line or the file end.
+ * An {@link InvalidTrackFormatException} is thrown, if + *

+ * + *

The Track can return a String representing the current state of the race (including car positons)

+ */ +public class Track implements TrackSpecification { + + public static final char CRASH_INDICATOR = 'X'; + + // TODO: Add necessary variables + + /** + * Initialize a Track from the given track file. + * + * @param trackFile Reference to a file containing the track data + * @throws FileNotFoundException if the given track file could not be found + * @throws InvalidTrackFormatException if the track file contains invalid data (no tracklines, ...) + */ + public Track(File trackFile) throws FileNotFoundException, InvalidTrackFormatException { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Return the type of space at the given position. + * If the location is outside the track bounds, it is considered a wall. + * + * @param position The coordinates of the position to examine + * @return The type of track position at the given location + */ + @Override + public Config.SpaceType getSpaceType(PositionVector position) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Return the number of cars. + * + * @return Number of cars + */ + @Override + public int getCarCount() { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Get instance of specified car. + * + * @param carIndex The zero-based carIndex number + * @return The car instance at the given index + */ + @Override + public Car getCar(int carIndex) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Get the id of the specified car. + * + * @param carIndex The zero-based carIndex number + * @return A char containing the id of the car + */ + @Override + public char getCarId(int carIndex) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Get the position of the specified car. + * + * @param carIndex The zero-based carIndex number + * @return A PositionVector containing the car's current position + */ + @Override + public PositionVector getCarPos(int carIndex) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Get the velocity of the specified car. + * + * @param carIndex The zero-based carIndex number + * @return A PositionVector containing the car's current velocity + */ + @Override + public PositionVector getCarVelocity(int carIndex) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Gets character at the given position. + * If there is a crashed car at the position, {@link #CRASH_INDICATOR} is returned. + * + * @param y position Y-value + * @param x position X-vlaue + * @param currentSpace char to return if no car is at position (x,y) + * @return character representing position (x,y) on the track + */ + @Override + public char getCharAtPosition(int y, int x, Config.SpaceType currentSpace) { + // TODO: implementation + throw new UnsupportedOperationException(); + } + + /** + * Return a String representation of the track, including the car locations. + * + * @return A String representation of the track + */ + @Override + public String toString() { + // TODO: implementation + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/given/CarSpecification.java b/src/main/java/ch/zhaw/pm2/racetrack/given/CarSpecification.java new file mode 100644 index 0000000..ca350db --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/given/CarSpecification.java @@ -0,0 +1,25 @@ +package ch.zhaw.pm2.racetrack.given; + +import ch.zhaw.pm2.racetrack.PositionVector; +import ch.zhaw.pm2.racetrack.strategy.MoveStrategy; + +/** + * This interface specifies stuff we use to test Racetrack for grading. It shall not be altered! + */ +public interface CarSpecification { + void setPosition(PositionVector position); + + PositionVector nextPosition(); + + void accelerate(PositionVector.Direction acceleration); + + void move(); + + void crash(); + + boolean isCrashed(); + + void setMoveStrategy(MoveStrategy moveStrategy); + + MoveStrategy getMoveStrategy(); +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/given/ConfigSpecification.java b/src/main/java/ch/zhaw/pm2/racetrack/given/ConfigSpecification.java new file mode 100644 index 0000000..b8ba9fe --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/given/ConfigSpecification.java @@ -0,0 +1,54 @@ +package ch.zhaw.pm2.racetrack.given; + +import java.io.File; + +/** + * This interface specifies stuff we use to test Racetrack for grading. It shall not be altered!
+ * It defines how the Game can be configured. + */ +public interface ConfigSpecification { + int MAX_CARS = 9; + + File getMoveDirectory(); + + void setMoveDirectory(File moveDirectory); + + File getFollowerDirectory(); + + void setFollowerDirectory(File followerDirectory); + + File getTrackDirectory(); + + void setTrackDirectory(File trackDirectory); + + /** + * Possible Move Strategies selected by the Console to configure the Cars. (This shall not be altered!) + */ + public enum StrategyType { + DO_NOT_MOVE, USER, MOVE_LIST, PATH_FOLLOWER + } + + /** + * Possible space types of the grid. (This shall not be altered!) + * The char value is used to parse from the track file and represents + * the space type in the text representation created by toString(). + */ + public enum SpaceType { + WALL('#'), + TRACK(' '), + FINISH_UP('^'), + FINISH_DOWN('v'), + FINISH_LEFT('<'), + FINISH_RIGHT('>'); + + public final char value; + + SpaceType(final char c) { + value = c; + } + + public char getValue() { + return value; + } + } +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/given/GameSpecification.java b/src/main/java/ch/zhaw/pm2/racetrack/given/GameSpecification.java new file mode 100644 index 0000000..3596335 --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/given/GameSpecification.java @@ -0,0 +1,28 @@ +package ch.zhaw.pm2.racetrack.given; + +import ch.zhaw.pm2.racetrack.PositionVector; + +import java.util.List; + +/** + * This interface specifies stuff we use to test Racetrack for grading. It shall not be altered! + */ +public interface GameSpecification { + int getCurrentCarIndex(); + + char getCarId(int carIndex); + + PositionVector getCarPosition(int carIndex); + + PositionVector getCarVelocity(int carIndex); + + int getWinner(); + + void doCarTurn(PositionVector.Direction acceleration); + + void switchToNextActiveCar(); + + List calculatePath(PositionVector startPosition, PositionVector endPosition); + + boolean willCarCrash(int carIndex, PositionVector position); +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/given/TrackSpecification.java b/src/main/java/ch/zhaw/pm2/racetrack/given/TrackSpecification.java new file mode 100644 index 0000000..b8621da --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/given/TrackSpecification.java @@ -0,0 +1,25 @@ +package ch.zhaw.pm2.racetrack.given; + +import ch.zhaw.pm2.racetrack.Config; +import ch.zhaw.pm2.racetrack.PositionVector; + +/** + * This interface specifies stuff we use to test Racetrack for grading. It shall not be altered! + */ +public interface TrackSpecification { + Config.SpaceType getSpaceType(PositionVector position); + + int getCarCount(); + + CarSpecification getCar(int carIndex); + + char getCarId(int carIndex); + + PositionVector getCarPos(int carIndex); + + PositionVector getCarVelocity(int carIndex); + + char getCharAtPosition(int y, int x, Config.SpaceType currentSpace); + + String toString(); +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/strategy/DoNotMoveStrategy.java b/src/main/java/ch/zhaw/pm2/racetrack/strategy/DoNotMoveStrategy.java new file mode 100644 index 0000000..24b09e0 --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/strategy/DoNotMoveStrategy.java @@ -0,0 +1,15 @@ +package ch.zhaw.pm2.racetrack.strategy; + +import static ch.zhaw.pm2.racetrack.PositionVector.Direction; + +/** + * Do not accelerate in any direction. + */ +public class DoNotMoveStrategy implements MoveStrategy { + + @Override + public Direction nextMove() { + // TODO: implementation + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/strategy/MoveListStrategy.java b/src/main/java/ch/zhaw/pm2/racetrack/strategy/MoveListStrategy.java new file mode 100644 index 0000000..4432c69 --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/strategy/MoveListStrategy.java @@ -0,0 +1,12 @@ +package ch.zhaw.pm2.racetrack.strategy; + +import ch.zhaw.pm2.racetrack.PositionVector.Direction; + +public class MoveListStrategy implements MoveStrategy { + + @Override + public Direction nextMove() { + // TODO: implementation + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/strategy/MoveStrategy.java b/src/main/java/ch/zhaw/pm2/racetrack/strategy/MoveStrategy.java new file mode 100644 index 0000000..f64ed60 --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/strategy/MoveStrategy.java @@ -0,0 +1,7 @@ +package ch.zhaw.pm2.racetrack.strategy; + +import static ch.zhaw.pm2.racetrack.PositionVector.Direction; + +public interface MoveStrategy { + Direction nextMove(); +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/strategy/PathFollowerMoveStrategy.java b/src/main/java/ch/zhaw/pm2/racetrack/strategy/PathFollowerMoveStrategy.java new file mode 100644 index 0000000..23ad51f --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/strategy/PathFollowerMoveStrategy.java @@ -0,0 +1,15 @@ +package ch.zhaw.pm2.racetrack.strategy; + +import ch.zhaw.pm2.racetrack.PositionVector.Direction; + +/** + * The PathFollowerMoveStrategy class determines the next move based on a file containing points on a path. + */ +public class PathFollowerMoveStrategy implements MoveStrategy { + + @Override + public Direction nextMove() { + // TODO: implementation + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/ch/zhaw/pm2/racetrack/strategy/UserMoveStrategy.java b/src/main/java/ch/zhaw/pm2/racetrack/strategy/UserMoveStrategy.java new file mode 100644 index 0000000..b7df019 --- /dev/null +++ b/src/main/java/ch/zhaw/pm2/racetrack/strategy/UserMoveStrategy.java @@ -0,0 +1,15 @@ +package ch.zhaw.pm2.racetrack.strategy; + +import ch.zhaw.pm2.racetrack.PositionVector.Direction; + +/** + * Let the user decide the next move. + */ +public class UserMoveStrategy implements MoveStrategy { + + @Override + public Direction nextMove() { + // TODO: implementation + throw new UnsupportedOperationException(); + } +} diff --git a/src/test/java/ch/zhaw/pm2/racetrack/PositionVectorTest.java b/src/test/java/ch/zhaw/pm2/racetrack/PositionVectorTest.java new file mode 100644 index 0000000..a0a977a --- /dev/null +++ b/src/test/java/ch/zhaw/pm2/racetrack/PositionVectorTest.java @@ -0,0 +1,29 @@ +package ch.zhaw.pm2.racetrack; + +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PositionVectorTest { + + @Test + void testEquals() { + PositionVector a = new PositionVector(3, 5); + PositionVector b = new PositionVector(3, 5); + assertEquals(a, b); + } + + @Test + void testEqualsWithHashMap() { + Map map = new HashMap<>(); + PositionVector a = new PositionVector(3, 5); + map.put(a, 1); + PositionVector b = new PositionVector(3, 5); + assertTrue(map.containsKey(a), "Test with same object"); + assertTrue(map.containsKey(b), "Test with equal object"); + } +} diff --git a/tracks/.gitignore b/tracks/.gitignore new file mode 100644 index 0000000..e4af982 --- /dev/null +++ b/tracks/.gitignore @@ -0,0 +1 @@ +challenge_points.txt diff --git a/tracks/challenge.txt b/tracks/challenge.txt new file mode 100644 index 0000000..c1a06a3 --- /dev/null +++ b/tracks/challenge.txt @@ -0,0 +1,26 @@ +############################################################### +################# ############# +############### ########### +############### ######### +############### ################## ######### +############### #################### ######### +############## ##################### ######### +############ ###################### ########## +######### ###################### ############ +######### ###################### ############## +######### ##################### ################ +######### ################# ################## +######### ################ ################## +########## ################## ############### +########### #################### ############# +########### ####################### ########## +########## ########################## ######### +######### ############################ ######## +######## ############################# ######## +####### ############################## ######## +###### ############################# ######## +###### ############################ ######### +###### > a ########### +###### > ############## +######## > b ################# +############################################################### diff --git a/tracks/oval-anticlock-right.txt b/tracks/oval-anticlock-right.txt new file mode 100644 index 0000000..f2c85ee --- /dev/null +++ b/tracks/oval-anticlock-right.txt @@ -0,0 +1,14 @@ +################################################## +################################################## +############## ############# +########## ########## +####### ####### +###### ################# ###### +##### ################### ##### +##### ################### ##### +###### ################# ###### +####### > a ####### +########## > ########## +############## > b ############## +################################################## +################################################## \ No newline at end of file diff --git a/tracks/oval-clock-up.txt b/tracks/oval-clock-up.txt new file mode 100644 index 0000000..3c87a82 --- /dev/null +++ b/tracks/oval-clock-up.txt @@ -0,0 +1,14 @@ +################################################## +################################################## +############## ############# +########## ########## +####### ####### +###### a b ################# ###### +#####^^^^^^^^^^################### ##### +##### ################### ##### +###### ################# ###### +####### ####### +########## ########## +############## ############## +################################################## +################################################## diff --git a/tracks/quarter-mile.txt b/tracks/quarter-mile.txt new file mode 100644 index 0000000..97d816b --- /dev/null +++ b/tracks/quarter-mile.txt @@ -0,0 +1,9 @@ +############################################################ +############################################################ +####### < ## +####### < § ## +####### < ## +####### < @ ## +####### < ## +############################################################ +############################################################