From f424bb6eff3680b4f1c370bd22b6f934a67210ac Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 8 Mar 2018 21:03:18 +0300 Subject: [PATCH 01/13] ThreadPool completed --- ThreadPool/.idea/compiler.xml | 9 + ThreadPool/.idea/misc.xml | 24 +++ ThreadPool/.idea/modules.xml | 10 + ThreadPool/.idea/modules/ThreadPool.iml | 13 ++ ThreadPool/.idea/modules/ThreadPool_main.iml | 14 ++ ThreadPool/.idea/modules/ThreadPool_test.iml | 18 ++ ThreadPool/build.gradle | 15 ++ ThreadPool/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54706 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + ThreadPool/gradlew | 172 ++++++++++++++++++ ThreadPool/gradlew.bat | 84 +++++++++ ThreadPool/settings.gradle | 2 + ThreadPool/src/ThreadPoolImpl$Task.class | Bin 0 -> 5441 bytes .../ThreadPool/LightExecutionException.java | 7 + .../mit/kazakov/ThreadPool/LightFuture.java | 32 ++++ .../kazakov/ThreadPool/ThreadPoolImpl.java | 163 +++++++++++++++++ .../ThreadPool/ThreadPoolImplTest.java | 154 ++++++++++++++++ 17 files changed, 723 insertions(+) create mode 100644 ThreadPool/.idea/compiler.xml create mode 100644 ThreadPool/.idea/misc.xml create mode 100644 ThreadPool/.idea/modules.xml create mode 100644 ThreadPool/.idea/modules/ThreadPool.iml create mode 100644 ThreadPool/.idea/modules/ThreadPool_main.iml create mode 100644 ThreadPool/.idea/modules/ThreadPool_test.iml create mode 100644 ThreadPool/build.gradle create mode 100644 ThreadPool/gradle/wrapper/gradle-wrapper.jar create mode 100644 ThreadPool/gradle/wrapper/gradle-wrapper.properties create mode 100755 ThreadPool/gradlew create mode 100644 ThreadPool/gradlew.bat create mode 100644 ThreadPool/settings.gradle create mode 100644 ThreadPool/src/ThreadPoolImpl$Task.class create mode 100644 ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightExecutionException.java create mode 100644 ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightFuture.java create mode 100644 ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java create mode 100644 ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java diff --git a/ThreadPool/.idea/compiler.xml b/ThreadPool/.idea/compiler.xml new file mode 100644 index 0000000..c12b025 --- /dev/null +++ b/ThreadPool/.idea/compiler.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/ThreadPool/.idea/misc.xml b/ThreadPool/.idea/misc.xml new file mode 100644 index 0000000..85cb263 --- /dev/null +++ b/ThreadPool/.idea/misc.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/ThreadPool/.idea/modules.xml b/ThreadPool/.idea/modules.xml new file mode 100644 index 0000000..4e0292a --- /dev/null +++ b/ThreadPool/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ThreadPool/.idea/modules/ThreadPool.iml b/ThreadPool/.idea/modules/ThreadPool.iml new file mode 100644 index 0000000..846c43c --- /dev/null +++ b/ThreadPool/.idea/modules/ThreadPool.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/ThreadPool/.idea/modules/ThreadPool_main.iml b/ThreadPool/.idea/modules/ThreadPool_main.iml new file mode 100644 index 0000000..3968fb2 --- /dev/null +++ b/ThreadPool/.idea/modules/ThreadPool_main.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ThreadPool/.idea/modules/ThreadPool_test.iml b/ThreadPool/.idea/modules/ThreadPool_test.iml new file mode 100644 index 0000000..75f968b --- /dev/null +++ b/ThreadPool/.idea/modules/ThreadPool_test.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ThreadPool/build.gradle b/ThreadPool/build.gradle new file mode 100644 index 0000000..31770fa --- /dev/null +++ b/ThreadPool/build.gradle @@ -0,0 +1,15 @@ +group 'ru.spbau.mit.kazakov.ThreadPool' +version '1.0-SNAPSHOT' + +apply plugin: 'java' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + compile group: 'org.jetbrains', name: 'annotations', version: '13.0' + testCompile group: 'junit', name: 'junit', version: '4.12' +} diff --git a/ThreadPool/gradle/wrapper/gradle-wrapper.jar b/ThreadPool/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..05f5f90a4701ac59bd444c3ad49a5bc1ce944166 GIT binary patch literal 54706 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 literal 0 HcmV?d00001 diff --git a/ThreadPool/gradle/wrapper/gradle-wrapper.properties b/ThreadPool/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..df4e635 --- /dev/null +++ b/ThreadPool/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Mar 08 19:53:28 MSK 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip diff --git a/ThreadPool/gradlew b/ThreadPool/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/ThreadPool/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$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="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/ThreadPool/gradlew.bat b/ThreadPool/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/ThreadPool/gradlew.bat @@ -0,0 +1,84 @@ +@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 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= + +@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 init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +: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/ThreadPool/settings.gradle b/ThreadPool/settings.gradle new file mode 100644 index 0000000..abed208 --- /dev/null +++ b/ThreadPool/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'ThreadPool' + diff --git a/ThreadPool/src/ThreadPoolImpl$Task.class b/ThreadPool/src/ThreadPoolImpl$Task.class new file mode 100644 index 0000000000000000000000000000000000000000..0eac2a1acc30bfe149e48040d7cd4dd0453bd9a1 GIT binary patch literal 5441 zcmcIo`EwK39sjI#tdImY0?ZXaU>n=Au}En`gIy1ffnXv|8xoT?$y#39izTgCtqi1R zj=}dp=bn1S-yQ{+o_L!M!=Iwj?-uFJ= z>TmzO_h$f~#7C>J5zmIP6@x0yhvCDZ>^vur&&cDmDxQzv1-uwW2%78+sYuH_9EOgO z2u3j`_=Y?#s2C3;CPYHW#K_{JKv^mZVWQX(6fqIOC0tfPlI~WFbGK z6_Z)RPL69YY2)TZa$wBTwc%5ynWjlvmnrh3YG3U3tpi|MppD2x;{3WAniC}wO08yr%X zlb5rZ>@sqO-L2qM%Sw@#(z>(t939nb z4%1(?DI=$!C}xLrYd{;ya3_{B(^}@7W*PGCZTdwE1&^&z499O&q<${VEBO-L0}n&6 zWZI^drL=URMAgPK#hh(q^}gJMQ81{~Db3Qdx~*G1xt!?+5|wL6nbv6Xf^H94nvp9c zwaQ`gglV5BW-?Sob)%1l^0f%@Rr=+$o_AyM5m%m!mK#laJP(&niMqPq*3#q0wY+0g zW+*exNwLp4#29(LQJrzNzNNK_(8-|-dRo#fT&6{=&npPDv@(1Zq!u;95ib`yTbDI) z$`~E9j}#?=NzQLO85}Al(*shhOkcr6I`OQNdQFS@utZ#ZIN-u9lg9$2;tLAQuCoUg zTCaOa8-LyAT&NA=jXE5zRq=I^^UWx}fp03< zw;Y?f_Hv4zsF+motth^Yw^V#5itplkWLeSHb*zH6<)~MsXUXQjTgXk~>@MirVmSPlkRGmn8O~rJQ zqHHhZ^Z;+uWQ)Z}sSwkEntT7-y;!U)EJ7;YiQ-+n7ezk?cnhmo-N;Rt<9agXltm_< zHln3%)1s>)?8&7!74OrDDbpM;<`w+CnyE*?&Do<`ZaAYC8ePcFDna^f%gBvZ@616Q z;3eeY4qcW@<5pL2k8Q7($@Ckx&NlTU1x*VKvY0*!sxo_y_@3Sh);2aaT6*5JoC^wH zn0fw|zSeV5{1`t8;~;*jpu5K!EoSwc9Un2R_(AVB6VI3KLGk9MLUY_4i8mEGnhNo3 zv0%rUK=C0xp5wJAgr6%&l+oxWR?GRYmd*=A&V&DD2n``T9YP_5Z6S1o&_Ad6xz17X zJ35&nWi>|U6V*=ky!Tv{L%npZHmwI0#qV(=jO(~LC(`;@5k|F4N%wL&RPo1oS>VwT zqjlhR7`Jdo!H$ZHJW9O3*?JY3yJ1WV=e zCd=Z$Cc84VB+hc!CSw5z)z~i2P4VAR?ckgrUL2EvVC2 zrC^Z^bDvjoKPK<89nQUE9Z#2pwM z+`{@Bq!8i%0^3PtI~^*sYc5x&0}Eu5osvwu7!JV8jLTO@0@-XXPj84_2qAMcUx z4;iHIJ67sIAhc$dF7P?=b4kT1386BJOAf=1^o`Cl)VRcVPfnnz+`8e3{mF^gLX>dVHc_l!}=7db11g0KxyxpFO-!#^zj zh!Eso(@GCB2b8C_d_XBc!@585wyb5ec%vcq3nqB%SCjBL&^x&=KY^9Cj3#gF3{KMb zg!~9wJ<=?)?oZ%l-OeF3Pe%KoFE-gfiFKTR%{jvPH=JEgi_6tUJQ)pxeg`(f`GX~J z1a_Y{9v=+EeoNST&VsW>SO#Jr3i@6A;eq;z%rbV);MVyUrZ7d+8BELa9#QY}h)tf) ze_%tSp9ywdWq2xzl%wm)ESgJe6@QwQR^py4`_}M(fUJ2S&0z|A%pFm>IZM5y j66L6h$#QFYv3?fkc*1;kLLkP~K?Zi5^HpZlHEjML*sEe) literal 0 HcmV?d00001 diff --git a/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightExecutionException.java b/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightExecutionException.java new file mode 100644 index 0000000..4243b08 --- /dev/null +++ b/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightExecutionException.java @@ -0,0 +1,7 @@ +package ru.spbau.mit.kazakov.ThreadPool; + +/** + * Used to indicate that result of computation can't be obtained because of an exception. + */ +public class LightExecutionException extends Exception { +} \ No newline at end of file diff --git a/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightFuture.java b/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightFuture.java new file mode 100644 index 0000000..aee9af4 --- /dev/null +++ b/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightFuture.java @@ -0,0 +1,32 @@ +package ru.spbau.mit.kazakov.ThreadPool; + +import java.util.function.Function; + +/** + * An interface representing a task from {@link ThreadPoolImpl}. + * + * @param type of computation result + */ +public interface LightFuture { + /** + * Returns true if computation is completed, and false otherwise. + */ + boolean isReady(); + + /** + * Returns result of computation. + * + * @throws LightExecutionException if an exception was thrown during computation + */ + T get() throws LightExecutionException, InterruptedException; + + /** + * Creates a new task for {@link ThreadPoolImpl} using specified function, that requires computation result of current task. + * + * @param function specified function + * @param type of computation result of new task + * @return created task + * @throws LightExecutionException if an exception was thrown during computation + */ + LightFuture thenApply(Function function) throws LightExecutionException; +} \ No newline at end of file diff --git a/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java b/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java new file mode 100644 index 0000000..7ca1140 --- /dev/null +++ b/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java @@ -0,0 +1,163 @@ +package ru.spbau.mit.kazakov.ThreadPool; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.xml.ws.Holder; + +import org.jetbrains.annotations.NotNull; + +/** + * A class for computation in multiple threads. + */ +public class ThreadPoolImpl { + private final Thread[] threads; + private final Queue> tasksQueue = new LinkedList<>(); + + /** + * Creates specified number of threads for computation. + */ + public ThreadPoolImpl(int numberOfThreads) { + this.threads = new Thread[numberOfThreads]; + Runnable threadCycle = () -> { + while (!Thread.currentThread().isInterrupted()) { + Task task; + synchronized (tasksQueue) { + while (this.tasksQueue.isEmpty()) { + try { + tasksQueue.wait(); + } catch (InterruptedException exception) { + return; + } + } + task = tasksQueue.remove(); + } + + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (task) { + task.compute(); + task.notify(); + } + } + + }; + + for (int i = 0; i < numberOfThreads; ++i) { + this.threads[i] = new Thread(threadCycle); + this.threads[i].start(); + } + + } + + /** + * Adds a new task to queue. + * + * @param task a computation + * @param type of computation result + * @return added task + */ + @NotNull + public LightFuture addTask(@NotNull Supplier task) { + Task lightFuture = new Task<>(task); + synchronized (tasksQueue) { + tasksQueue.add(lightFuture); + tasksQueue.notify(); + } + return lightFuture; + } + + /** + * Sends terminating signals to all threads. + */ + public void shutdown() { + for (Thread thread : threads) { + thread.interrupt(); + } + } + + /** + * An implementation of LightFuture for using in {@link ThreadPoolImpl}. + * + * @param type of computation result. + */ + private class Task implements LightFuture { + private final List> deferredTasks = new ArrayList<>(); + private Supplier computation; + private boolean isReady; + private boolean isSuccessful = true; + private Holder result; + + /** + * Initializes supplier for computation. + */ + public Task(@NotNull Supplier computation) { + this.computation = computation; + } + + /** + * Makes computation, saves result and adds deferred task to queue. + */ + public void compute() { + try { + result = new Holder<>(computation.get()); + } catch (RuntimeException exception) { + isSuccessful = false; + } + + isReady = true; + synchronized (tasksQueue) { + tasksQueue.addAll(this.deferredTasks); + tasksQueue.notify(); + } + } + + /** + * @see LightFuture#isReady() + */ + public boolean isReady() { + return isReady; + } + + /** + * @see LightFuture#get() + */ + public T get() throws LightExecutionException, InterruptedException { + if (!this.isReady) { + synchronized (this) { + while (!this.isReady) { + this.wait(); + } + } + } + + if (!this.isSuccessful) { + throw new LightExecutionException(); + } else { + return this.result.value; + } + } + + /** + * @see LightFuture#thenApply(Function) + */ + @NotNull + public synchronized LightFuture thenApply(@NotNull Function function) throws LightExecutionException { + if (!isSuccessful) { + throw new LightExecutionException(); + } + + Supplier computation = () -> function.apply(result.value); + if (isReady) { + return addTask(computation); + } else { + Task task = new Task<>(computation); + deferredTasks.add(task); + return task; + } + } + } +} + diff --git a/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java b/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java new file mode 100644 index 0000000..b455837 --- /dev/null +++ b/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java @@ -0,0 +1,154 @@ +package ru.spbau.mit.kazakov.ThreadPool; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.junit.Assert; +import org.junit.Test; + +public class ThreadPoolImplTest { + public ThreadPoolImplTest() { + } + + @Test + public void testConstructorAndShutdown() { + ThreadPoolImpl threadPool = new ThreadPoolImpl(20); + threadPool.shutdown(); + } + + @Test + public void testIsReady() throws LightExecutionException, InterruptedException { + Supplier task = () -> 404; + ThreadPoolImpl threadPool = new ThreadPoolImpl(2); + LightFuture lightFuture = threadPool.addTask(task); + lightFuture.get(); + Assert.assertTrue(lightFuture.isReady()); + } + + @Test + public void testComputeSimple() throws LightExecutionException, InterruptedException { + Supplier task = () -> ":("; + ThreadPoolImpl threadPool = new ThreadPoolImpl(5); + LightFuture lightFuture = threadPool.addTask(task); + Assert.assertEquals(":(", lightFuture.get()); + } + + @Test( + expected = LightExecutionException.class + ) + public void testComputeThrowsException() throws LightExecutionException, InterruptedException { + //noinspection NumericOverflow + Supplier task = () -> 5 / 0; + ThreadPoolImpl threadPool = new ThreadPoolImpl(5); + LightFuture lightFuture = threadPool.addTask(task); + lightFuture.get(); + } + + @Test + public void testComputeMultipleTasks() throws LightExecutionException, InterruptedException { + Object computationResult = new Object(); + Supplier task1 = () -> { + safeSleep(); + return ":("; + }; + Supplier task2 = () -> { + safeSleep(); + return 101; + }; + Supplier task3 = () -> { + safeSleep(); + return computationResult; + }; + Supplier task4 = () -> { + safeSleep(); + return 'n'; + }; + Supplier task5 = () -> { + safeSleep(); + return (byte) 0; + }; + + ThreadPoolImpl threadPool = new ThreadPoolImpl(3); + LightFuture lightFuture1 = threadPool.addTask(task1); + LightFuture lightFuture2 = threadPool.addTask(task2); + LightFuture lightFuture3 = threadPool.addTask(task3); + LightFuture lightFuture4 = threadPool.addTask(task4); + LightFuture lightFuture5 = threadPool.addTask(task5); + + Assert.assertEquals(":(", lightFuture1.get()); + Assert.assertEquals(Integer.valueOf(101), lightFuture2.get()); + Assert.assertEquals(computationResult, lightFuture3.get()); + Assert.assertEquals(Character.valueOf('n'), lightFuture4.get()); + Assert.assertEquals(Byte.valueOf((byte) 0), lightFuture5.get()); + } + + @Test + public void testThenApplySimple() throws LightExecutionException, InterruptedException { + Supplier supplier = () -> { + this.safeSleep(); + return 999; + }; + Function function = String::valueOf; + ThreadPoolImpl threadPool = new ThreadPoolImpl(3); + Assert.assertEquals("999", threadPool.addTask(supplier).thenApply(function).get()); + } + + @Test + public void testThenApplyAfterComputation() throws LightExecutionException, InterruptedException { + Supplier supplier = () -> 999; + Function function = String::valueOf; + ThreadPoolImpl threadPool = new ThreadPoolImpl(3); + LightFuture lightFuture = threadPool.addTask(supplier); + lightFuture.get(); + Assert.assertEquals("999", lightFuture.thenApply(function).get()); + } + + @Test + public void testThenApplyMultiple() throws LightExecutionException, InterruptedException { + Supplier supplier = () -> { + safeSleep(); + return "Genome-wide association"; + }; + Function function1 = s -> s.concat(" study"); + Function function2 = s -> s.replace("d", "ld"); + Function function3 = s -> s.substring(0, 11); + ThreadPoolImpl threadPool = new ThreadPoolImpl(3); + + LightFuture lightFuture = threadPool.addTask(supplier); + Assert.assertEquals("Genome-wide association study", lightFuture.thenApply(function1).get()); + Assert.assertEquals("Genome-wilde association", lightFuture.thenApply(function2).get()); + Assert.assertEquals("Genome-wide", lightFuture.thenApply(function3).get()); + } + + @Test + public void testNumberOfThreads() throws InterruptedException { + Set threadsId = new HashSet<>(); + Supplier supplier = () -> { + synchronized (threadsId) { + threadsId.add(Thread.currentThread().getId()); + } + //noinspection InfiniteLoopStatement,StatementWithEmptyBody + while (true) { + } + }; + ThreadPoolImpl threadPool = new ThreadPoolImpl(15); + + for (int i = 0; i < 100; ++i) { + threadPool.addTask(supplier); + } + + TimeUnit.MILLISECONDS.sleep(1500); + Assert.assertEquals(15, threadsId.size()); + } + + private void safeSleep() { + try { + TimeUnit.SECONDS.sleep(1L); + } catch (InterruptedException var2) { + throw new RuntimeException(); + } + } +} From 9b0d9ff7d5d1ec38de7e0f6305df8da4f5400fe5 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 8 Mar 2018 21:05:17 +0300 Subject: [PATCH 02/13] Delete extra file --- ThreadPool/src/ThreadPoolImpl$Task.class | Bin 5441 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ThreadPool/src/ThreadPoolImpl$Task.class diff --git a/ThreadPool/src/ThreadPoolImpl$Task.class b/ThreadPool/src/ThreadPoolImpl$Task.class deleted file mode 100644 index 0eac2a1acc30bfe149e48040d7cd4dd0453bd9a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5441 zcmcIo`EwK39sjI#tdImY0?ZXaU>n=Au}En`gIy1ffnXv|8xoT?$y#39izTgCtqi1R zj=}dp=bn1S-yQ{+o_L!M!=Iwj?-uFJ= z>TmzO_h$f~#7C>J5zmIP6@x0yhvCDZ>^vur&&cDmDxQzv1-uwW2%78+sYuH_9EOgO z2u3j`_=Y?#s2C3;CPYHW#K_{JKv^mZVWQX(6fqIOC0tfPlI~WFbGK z6_Z)RPL69YY2)TZa$wBTwc%5ynWjlvmnrh3YG3U3tpi|MppD2x;{3WAniC}wO08yr%X zlb5rZ>@sqO-L2qM%Sw@#(z>(t939nb z4%1(?DI=$!C}xLrYd{;ya3_{B(^}@7W*PGCZTdwE1&^&z499O&q<${VEBO-L0}n&6 zWZI^drL=URMAgPK#hh(q^}gJMQ81{~Db3Qdx~*G1xt!?+5|wL6nbv6Xf^H94nvp9c zwaQ`gglV5BW-?Sob)%1l^0f%@Rr=+$o_AyM5m%m!mK#laJP(&niMqPq*3#q0wY+0g zW+*exNwLp4#29(LQJrzNzNNK_(8-|-dRo#fT&6{=&npPDv@(1Zq!u;95ib`yTbDI) z$`~E9j}#?=NzQLO85}Al(*shhOkcr6I`OQNdQFS@utZ#ZIN-u9lg9$2;tLAQuCoUg zTCaOa8-LyAT&NA=jXE5zRq=I^^UWx}fp03< zw;Y?f_Hv4zsF+motth^Yw^V#5itplkWLeSHb*zH6<)~MsXUXQjTgXk~>@MirVmSPlkRGmn8O~rJQ zqHHhZ^Z;+uWQ)Z}sSwkEntT7-y;!U)EJ7;YiQ-+n7ezk?cnhmo-N;Rt<9agXltm_< zHln3%)1s>)?8&7!74OrDDbpM;<`w+CnyE*?&Do<`ZaAYC8ePcFDna^f%gBvZ@616Q z;3eeY4qcW@<5pL2k8Q7($@Ckx&NlTU1x*VKvY0*!sxo_y_@3Sh);2aaT6*5JoC^wH zn0fw|zSeV5{1`t8;~;*jpu5K!EoSwc9Un2R_(AVB6VI3KLGk9MLUY_4i8mEGnhNo3 zv0%rUK=C0xp5wJAgr6%&l+oxWR?GRYmd*=A&V&DD2n``T9YP_5Z6S1o&_Ad6xz17X zJ35&nWi>|U6V*=ky!Tv{L%npZHmwI0#qV(=jO(~LC(`;@5k|F4N%wL&RPo1oS>VwT zqjlhR7`Jdo!H$ZHJW9O3*?JY3yJ1WV=e zCd=Z$Cc84VB+hc!CSw5z)z~i2P4VAR?ckgrUL2EvVC2 zrC^Z^bDvjoKPK<89nQUE9Z#2pwM z+`{@Bq!8i%0^3PtI~^*sYc5x&0}Eu5osvwu7!JV8jLTO@0@-XXPj84_2qAMcUx z4;iHIJ67sIAhc$dF7P?=b4kT1386BJOAf=1^o`Cl)VRcVPfnnz+`8e3{mF^gLX>dVHc_l!}=7db11g0KxyxpFO-!#^zj zh!Eso(@GCB2b8C_d_XBc!@585wyb5ec%vcq3nqB%SCjBL&^x&=KY^9Cj3#gF3{KMb zg!~9wJ<=?)?oZ%l-OeF3Pe%KoFE-gfiFKTR%{jvPH=JEgi_6tUJQ)pxeg`(f`GX~J z1a_Y{9v=+EeoNST&VsW>SO#Jr3i@6A;eq;z%rbV);MVyUrZ7d+8BELa9#QY}h)tf) ze_%tSp9ywdWq2xzl%wm)ESgJe6@QwQR^py4`_}M(fUJ2S&0z|A%pFm>IZM5y j66L6h$#QFYv3?fkc*1;kLLkP~K?Zi5^HpZlHEjML*sEe) From d492b5960163c5c8390b623caa85fd8f9351757b Mon Sep 17 00:00:00 2001 From: DmitryAu Date: Thu, 8 Mar 2018 21:06:30 +0300 Subject: [PATCH 03/13] Delete extra char. --- .../ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java b/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java index b455837..b2e60dd 100644 --- a/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java +++ b/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java @@ -146,7 +146,7 @@ public void testNumberOfThreads() throws InterruptedException { private void safeSleep() { try { - TimeUnit.SECONDS.sleep(1L); + TimeUnit.SECONDS.sleep(1); } catch (InterruptedException var2) { throw new RuntimeException(); } From 86f79e621f63b5faeb05f3337a3960d9181214d1 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 9 Mar 2018 23:54:13 +0300 Subject: [PATCH 04/13] Fix shutdown work --- .../spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java | 12 ++++++++++-- .../mit/kazakov/ThreadPool/ThreadPoolImplTest.java | 5 +---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java b/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java index 7ca1140..2a5be72 100644 --- a/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java +++ b/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java @@ -70,12 +70,20 @@ public LightFuture addTask(@NotNull Supplier task) { } /** - * Sends terminating signals to all threads. + * Dismisses all task in queue, waits for tasks which are computing and terminates all threads. */ - public void shutdown() { + public void shutdown() throws InterruptedException { for (Thread thread : threads) { thread.interrupt(); } + synchronized (tasksQueue) { + while (!tasksQueue.isEmpty()) { + tasksQueue.remove().isSuccessful = false; + } + } + for (Thread thread : threads) { + thread.join(); + } } /** diff --git a/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java b/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java index b455837..3fd9197 100644 --- a/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java +++ b/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java @@ -10,11 +10,8 @@ import org.junit.Test; public class ThreadPoolImplTest { - public ThreadPoolImplTest() { - } - @Test - public void testConstructorAndShutdown() { + public void testConstructorAndShutdown() throws InterruptedException { ThreadPoolImpl threadPool = new ThreadPoolImpl(20); threadPool.shutdown(); } From 13fce15ec3cef631534f734c06274b61e34ae571 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 16 Mar 2018 06:34:41 +0300 Subject: [PATCH 05/13] Implement app logic --- Tic-tac-toe/.idea/compiler.xml | 9 + Tic-tac-toe/.idea/misc.xml | 6 + Tic-tac-toe/.idea/modules.xml | 10 + Tic-tac-toe/.idea/modules/Tic-tac-toe.iml | 13 ++ .../.idea/modules/Tic-tac-toe_main.iml | 14 ++ .../.idea/modules/Tic-tac-toe_test.iml | 18 ++ Tic-tac-toe/.idea/vcs.xml | 6 + Tic-tac-toe/build.gradle | 30 +++ Tic-tac-toe/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54706 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + Tic-tac-toe/gradlew | 172 ++++++++++++++++++ Tic-tac-toe/gradlew.bat | 84 +++++++++ Tic-tac-toe/settings.gradle | 3 + .../ru/spbau/mit/kazakov/TicTacToe/AI.java | 11 ++ .../spbau/mit/kazakov/TicTacToe/AIBoard.java | 143 +++++++++++++++ .../spbau/mit/kazakov/TicTacToe/AILevel.java | 8 + .../mit/kazakov/TicTacToe/AbstractBoard.java | 60 ++++++ .../ru/spbau/mit/kazakov/TicTacToe/Board.java | 20 ++ .../mit/kazakov/TicTacToe/HotSeatBoard.java | 24 +++ .../spbau/mit/kazakov/TicTacToe/Player.java | 22 +++ .../ru/spbau/mit/kazakov/TicTacToe/State.java | 8 + .../mit/kazakov/TicTacToe/TicTacToe.java | 81 +++++++++ .../mit/kazakov/TicTacToe/AIBoardTest.java | 87 +++++++++ .../kazakov/TicTacToe/HotSeatBoardTest.java | 7 + 24 files changed, 842 insertions(+) create mode 100644 Tic-tac-toe/.idea/compiler.xml create mode 100644 Tic-tac-toe/.idea/misc.xml create mode 100644 Tic-tac-toe/.idea/modules.xml create mode 100644 Tic-tac-toe/.idea/modules/Tic-tac-toe.iml create mode 100644 Tic-tac-toe/.idea/modules/Tic-tac-toe_main.iml create mode 100644 Tic-tac-toe/.idea/modules/Tic-tac-toe_test.iml create mode 100644 Tic-tac-toe/.idea/vcs.xml create mode 100644 Tic-tac-toe/build.gradle create mode 100644 Tic-tac-toe/gradle/wrapper/gradle-wrapper.jar create mode 100644 Tic-tac-toe/gradle/wrapper/gradle-wrapper.properties create mode 100755 Tic-tac-toe/gradlew create mode 100644 Tic-tac-toe/gradlew.bat create mode 100644 Tic-tac-toe/settings.gradle create mode 100644 Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AI.java create mode 100644 Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java create mode 100644 Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AILevel.java create mode 100644 Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java create mode 100644 Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java create mode 100644 Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java create mode 100644 Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java create mode 100644 Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/State.java create mode 100644 Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java create mode 100644 Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java create mode 100644 Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java diff --git a/Tic-tac-toe/.idea/compiler.xml b/Tic-tac-toe/.idea/compiler.xml new file mode 100644 index 0000000..bfd4f70 --- /dev/null +++ b/Tic-tac-toe/.idea/compiler.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Tic-tac-toe/.idea/misc.xml b/Tic-tac-toe/.idea/misc.xml new file mode 100644 index 0000000..e208459 --- /dev/null +++ b/Tic-tac-toe/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Tic-tac-toe/.idea/modules.xml b/Tic-tac-toe/.idea/modules.xml new file mode 100644 index 0000000..1d3e77a --- /dev/null +++ b/Tic-tac-toe/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Tic-tac-toe/.idea/modules/Tic-tac-toe.iml b/Tic-tac-toe/.idea/modules/Tic-tac-toe.iml new file mode 100644 index 0000000..397d067 --- /dev/null +++ b/Tic-tac-toe/.idea/modules/Tic-tac-toe.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tic-tac-toe/.idea/modules/Tic-tac-toe_main.iml b/Tic-tac-toe/.idea/modules/Tic-tac-toe_main.iml new file mode 100644 index 0000000..2f865a4 --- /dev/null +++ b/Tic-tac-toe/.idea/modules/Tic-tac-toe_main.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tic-tac-toe/.idea/modules/Tic-tac-toe_test.iml b/Tic-tac-toe/.idea/modules/Tic-tac-toe_test.iml new file mode 100644 index 0000000..4e7335c --- /dev/null +++ b/Tic-tac-toe/.idea/modules/Tic-tac-toe_test.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tic-tac-toe/.idea/vcs.xml b/Tic-tac-toe/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/Tic-tac-toe/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Tic-tac-toe/build.gradle b/Tic-tac-toe/build.gradle new file mode 100644 index 0000000..c892ef2 --- /dev/null +++ b/Tic-tac-toe/build.gradle @@ -0,0 +1,30 @@ +group 'ru.spbau.mit.kazakov.Tic-tac-toe' +version '1.0-SNAPSHOT' + +apply plugin: 'java' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' +} +group 'ru.spbau.mit.kazakov.Tic-tac-toe' +version '1.0-SNAPSHOT' + +apply plugin: 'java' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + compile group: 'org.jetbrains', name: 'annotations', version: '13.0' + +} diff --git a/Tic-tac-toe/gradle/wrapper/gradle-wrapper.jar b/Tic-tac-toe/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..9254496b8f3920f08dbf0367a8d404fd7f43e5fd GIT binary patch literal 54706 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$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="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/Tic-tac-toe/gradlew.bat b/Tic-tac-toe/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/Tic-tac-toe/gradlew.bat @@ -0,0 +1,84 @@ +@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 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= + +@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 init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +: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/Tic-tac-toe/settings.gradle b/Tic-tac-toe/settings.gradle new file mode 100644 index 0000000..861864f --- /dev/null +++ b/Tic-tac-toe/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'Tic-tac-toe' +rootProject.name = 'Tic-tac-toe' + diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AI.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AI.java new file mode 100644 index 0000000..a5f6b9a --- /dev/null +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AI.java @@ -0,0 +1,11 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +/** + * An interface for representation AI in {@link AIBoard} + */ +public interface AI { + /** + * Analyzes the board and makes turn. + */ + void makeTurn(); +} diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java new file mode 100644 index 0000000..5196f2d --- /dev/null +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java @@ -0,0 +1,143 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * Implements logic of game with AI. + */ +public class AIBoard extends AbstractBoard { + private AI AI; + + /** + * Calls parent's constructor and initializes AI. + * @param size of board + * @param level of AI + */ + public AIBoard(int size, @NotNull AILevel level) { + super(size); + switch (level) { + case EASY: + AI = new EasyAI(); + break; + case MEDIUM: + AI = new MediumAI(); + break; + } + } + + /** + * @see Board#move(int, int) + */ + @NotNull + @Override + public String[][] move(int row, int col) { + board[row][col] = player.toString(); + updateState(row, col); + if(state != State.RUNNING) { + return board; + } + player = player.next(); + AI.makeTurn(); + player = player.next(); + return board; + } + + /** + * AI witch makes random moves. + */ + private class EasyAI implements AI { + /** + * Chooses random free cell and makes turn. + */ + @Override + public void makeTurn() { + int freeCellsNumber = 0; + for(String[] row : board) { + for(String cell : row) { + if(cell.equals(" ")) { + freeCellsNumber++; + } + } + } + int turn = ThreadLocalRandom.current().nextInt(0, freeCellsNumber); + for(int i = 0; i < board.length; i++) { + for(int j = 0; j < board.length; j++) { + if(board[i][j].equals(" ") && turn-- == 0) { + board[i][j] = player.toString(); + updateState(i, j); + return; + } + } + } + } + } + + /** + * AI witch prevents losing on the next turn and wins this turn if it's possible. + */ + private class MediumAI implements AI { + /** + * Checks if specified line from the board leads to losing or victory in one turn. + * @param turns specified line from the board + */ + private boolean canLoseOrWin(String turns) { + return turns.matches(player.toString() + "*" + " " + player.toString() + "*") || + turns.matches(player.next().toString() + "*" + " " + player.next().toString() + "*"); + } + + /** + * Tries to win or not to lose this turn. + */ + @Override + public void makeTurn() { + for(int i = 0; i < board.length; i++) { + StringBuilder row = new StringBuilder(); + int rowFreeCell = 0, colFreeCell = 0; + StringBuilder col = new StringBuilder(); + for(int j = 0; j < board.length; j++) { + if(board[i][j].equals(" ")) { + rowFreeCell = j; + } + row.append(board[i][j]); + if(board[j][i].equals(" ")) { + colFreeCell = j; + } + col.append(board[j][i]); + } + if(canLoseOrWin(row.toString())) { + board[i][rowFreeCell] = player.toString(); + updateState(i, rowFreeCell); + return; + } + if(canLoseOrWin(col.toString())) { + board[colFreeCell][i] = player.toString(); + updateState(colFreeCell, i); + return; + } + } + + int firstDiagonalFreeCell = 0, secondDiagonalFreeCell = 0; + StringBuilder firstDiagonal = new StringBuilder(); + StringBuilder secondDiagonal = new StringBuilder(); + for(int i = 0; i < board.length; i++) { + if(board[i][i].equals(" ")) { + firstDiagonalFreeCell = i; + } + if(board[i][board.length - 1 - i].equals(" ")) { + secondDiagonalFreeCell = i; + } + firstDiagonal.append(board[i][i]); + secondDiagonal.append(board[i][board.length - 1 - i]); + } + if(canLoseOrWin(firstDiagonal.toString())) { + move(firstDiagonalFreeCell, firstDiagonalFreeCell); + } + if(canLoseOrWin(secondDiagonal.toString())) { + move(secondDiagonalFreeCell, board.length - 1 - secondDiagonalFreeCell); + } + new EasyAI().makeTurn(); + } + } +} diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AILevel.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AILevel.java new file mode 100644 index 0000000..1a21dd7 --- /dev/null +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AILevel.java @@ -0,0 +1,8 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +/** + * Enum for available difficulty levels. + */ +public enum AILevel { + EASY, MEDIUM +} diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java new file mode 100644 index 0000000..3d2c779 --- /dev/null +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java @@ -0,0 +1,60 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +import org.jetbrains.annotations.NotNull; + +/** + * An abstract class with base functionality of game logic. + */ +public abstract class AbstractBoard implements Board { + protected String[][] board; + protected State state = State.RUNNING; + protected Player player = Player.X; + private int size; + private int turnNumber = 0; + + /** + * Sets board size and creates two-dimensional array for board. + */ + public AbstractBoard(int size) { + board = new String[size][size]; + this.size = size; + for(String[] row : board) { + for(int i = 0; i < size; i++) { + row[i] = " "; + } + } + } + + /** + * @see Board#getState() + */ + @Override + @NotNull + public State getState() { + return state; + } + + /** + * Checks last turn changes and updates game state. + * @param row of last turn + * @param col of last turn + */ + public void updateState(int row, int col) { + turnNumber++; + boolean horizontalWin = true; + boolean verticalWin = true; + boolean firstDiagonalWin = true; + boolean secondDiagonalWin = true; + for(int i = 0; i < size; i++) { + verticalWin &= board[i][col].equals(player.toString()); + horizontalWin &= board[row][i].equals(player.toString()); + firstDiagonalWin &= board[i][i].equals(player.toString()); + secondDiagonalWin &= board[i][size - 1 - i].equals(player.toString()); + } + if(horizontalWin || verticalWin || firstDiagonalWin || secondDiagonalWin) { + state = player.getWinState(); + } else if (turnNumber == size * size) { + state = State.DRAW; + } + } +} diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java new file mode 100644 index 0000000..7d42f75 --- /dev/null +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java @@ -0,0 +1,20 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +import org.jetbrains.annotations.NotNull; + +public interface Board { + /** + * Makes specified turn on the board and updates state of game. + * @param row of turn + * @param col of turn + * @return new state of board + */ + @NotNull + String[][] move(int row, int col); + + /** + * Returns current state of the game. + */ + @NotNull + State getState(); +} diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java new file mode 100644 index 0000000..2959727 --- /dev/null +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java @@ -0,0 +1,24 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +import org.jetbrains.annotations.NotNull; + +public class HotSeatBoard extends AbstractBoard { + /** + * @see AbstractBoard#AbstractBoard(int) + */ + public HotSeatBoard(int size) { + super(size); + } + + /** + * @see Board#move(int, int) + */ + @NotNull + @Override + public String[][] move(int row, int col) { + board[row][col] = player.toString(); + updateState(row, col); + player = player.next(); + return board; + } +} diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java new file mode 100644 index 0000000..6e4f955 --- /dev/null +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java @@ -0,0 +1,22 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +/** + * Enum for describing current turn's owner. + */ +public enum Player { + X, O; + + /** + * Returns owner of the next turn. + */ + public Player next() { + return this == X ? O : X; + } + + /** + * Returns state of game corresponding to victory of current turn's owner. + */ + public State getWinState() { + return this == X ? State.X_WINS : State.O_WINS; + } +} diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/State.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/State.java new file mode 100644 index 0000000..7d4a36f --- /dev/null +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/State.java @@ -0,0 +1,8 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +/** + * Enum for states of game. + */ +public enum State { + RUNNING, X_WINS, O_WINS, DRAW +} diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java new file mode 100644 index 0000000..a9ec802 --- /dev/null +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java @@ -0,0 +1,81 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +import javafx.application.Application; +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.RowConstraints; +import javafx.stage.Stage; + +public class TicTacToe extends Application{ + @Override + public void start(Stage primaryStage) { + Button button1 = new Button("Click me"); + button1.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + button1.setOnAction(value -> Platform.exit()); + Button button2 = new Button("Click me"); + button2.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + button2.setOnAction(value -> Platform.exit()); + Button button3 = new Button("Click me"); + button3.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + button3.setOnAction(value -> Platform.exit()); + Button button4 = new Button("Click me"); + button4.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + button4.setOnAction(value -> Platform.exit()); + Button button5 = new Button("Click me"); + button5.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + button5.setOnAction(value -> Platform.exit()); + Button button6 = new Button("Click me"); + button6.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + button6.setOnAction(value -> Platform.exit()); + Button button7 = new Button("Click me"); + button7.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + button7.setOnAction(value -> Platform.exit()); + Button button8 = new Button("Click me"); + button8.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + button8.setOnAction(value -> Platform.exit()); + Button button9 = new Button("Click me"); + button9.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + button9.setOnAction(value -> Platform.exit()); + + + GridPane pane = new GridPane(); + ColumnConstraints column1 = new ColumnConstraints(); + column1.setPercentWidth(50); + ColumnConstraints column2 = new ColumnConstraints(); + column2.setPercentWidth(50); + ColumnConstraints column3 = new ColumnConstraints(); + column3.setPercentWidth(50); + + RowConstraints row1 = new RowConstraints(); + row1.setPercentHeight(100); + row1.setFillHeight(true); + RowConstraints row2 = new RowConstraints(); + row2.setPercentHeight(100); + row2.setFillHeight(true); + RowConstraints row3 = new RowConstraints(); + row3.setPercentHeight(100); + row3.setFillHeight(true); + pane.getColumnConstraints().addAll(column1, column2, column3); + pane.getRowConstraints().addAll(row1, row2, row3); + + pane.add(button1,0,0); + pane.add(button2,1,0); + pane.add(button3,2,0); + pane.add(button4,0,1); + pane.add(button5,1,1); + pane.add(button6,2,1); + pane.add(button7,0,2); + pane.add(button8,1,2); + pane.add(button9,2,2); + Scene scene = new Scene(pane, 500, 500); + primaryStage.setScene(scene); + primaryStage.show(); + } + + public static void main(String[] args) { + Application.launch(args); + } +} diff --git a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java new file mode 100644 index 0000000..3a972ec --- /dev/null +++ b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java @@ -0,0 +1,87 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class AIBoardTest { + @Test + public void testEasyLevelConstructor() { + new AIBoard(10, AILevel.EASY); + } + + @Test + public void testMediumLevelConstructor() { + new AIBoard(10, AILevel.MEDIUM); + } + + @Test + public void testMakeTurnEasyAI() { + Board board = new AIBoard(3, AILevel.EASY); + String[][] boardState = board.move(2, 1); + int diff = 0; + for (int i = 0; i < boardState.length; i++) { + for (int j = 0; j < boardState.length; j++) { + if (!boardState[i][j].equals(" ") && !(i == 2 && j == 1)) { + assertEquals("O", boardState[i][j]); + diff++; + } + } + } + assertEquals(1, diff); + assertEquals("X", boardState[2][1]); + } + + @Test + public void testMakeTurnMediumAI() { + Board board = new AIBoard(3, AILevel.EASY); + String[][] boardState = board.move(2, 1); + int diff = 0; + for (int i = 0; i < boardState.length; i++) { + for (int j = 0; j < boardState.length; j++) { + if (!boardState[i][j].equals(" ") && !(i == 2 && j == 1)) { + assertEquals("O", boardState[i][j]); + diff++; + } + } + } +/* + for (int i = 0; i < boardState.length; i++) { + for (int j = 0; j < boardState.length; j++) { + System.out.print(boardState[i][j] + "/"); + } + System.out.println(); + } +*/ + assertEquals(1, diff); + assertEquals("X", boardState[2][1]); + } + + @Test + public void testMakeTurnMediumAIPreventLose() { + Board board = new AIBoard(3, AILevel.MEDIUM); + String[][] boardState = board.move(0, 0); + if(boardState[0][1].equals(" ") && boardState[0][2].equals(" ")) { + boardState = board.move(0, 1); + assertEquals("O", boardState[0][2]); + } else { + boardState = board.move(1, 0); + assertEquals("O", boardState[2][0]); + } + } + + @Test + public void testMakeTurnMediumAIWin() { + Board board = new AIBoard(3, AILevel.MEDIUM); + board.move(0, 0); + board.move(0, 1); + String[][] boardState = board.move(1, 1); + if(boardState[0][1].equals(" ")) { + boardState = board.move(0, 1); + + } else { + boardState = board.move(1, 0); + + } + } +} \ No newline at end of file diff --git a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java new file mode 100644 index 0000000..f286fcd --- /dev/null +++ b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java @@ -0,0 +1,7 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +import static org.junit.Assert.*; + +public class HotSeatBoardTest { + +} \ No newline at end of file From 716757415dce34c81f2cf15e4168192605e189e3 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 16 Mar 2018 06:38:45 +0300 Subject: [PATCH 06/13] Delete extra file --- ThreadPool/.idea/compiler.xml | 9 - ThreadPool/.idea/misc.xml | 24 --- ThreadPool/.idea/modules.xml | 10 - ThreadPool/.idea/modules/ThreadPool.iml | 13 -- ThreadPool/.idea/modules/ThreadPool_main.iml | 14 -- ThreadPool/.idea/modules/ThreadPool_test.iml | 18 -- ThreadPool/build.gradle | 15 -- ThreadPool/gradle/wrapper/gradle-wrapper.jar | Bin 54706 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - ThreadPool/gradlew | 172 ------------------ ThreadPool/gradlew.bat | 84 --------- ThreadPool/settings.gradle | 2 - .../ThreadPool/LightExecutionException.java | 7 - .../mit/kazakov/ThreadPool/LightFuture.java | 32 ---- .../kazakov/ThreadPool/ThreadPoolImpl.java | 171 ----------------- .../ThreadPool/ThreadPoolImplTest.java | 151 --------------- Tic-tac-toe/gradlew.bat | 168 ++++++++--------- 17 files changed, 84 insertions(+), 812 deletions(-) delete mode 100644 ThreadPool/.idea/compiler.xml delete mode 100644 ThreadPool/.idea/misc.xml delete mode 100644 ThreadPool/.idea/modules.xml delete mode 100644 ThreadPool/.idea/modules/ThreadPool.iml delete mode 100644 ThreadPool/.idea/modules/ThreadPool_main.iml delete mode 100644 ThreadPool/.idea/modules/ThreadPool_test.iml delete mode 100644 ThreadPool/build.gradle delete mode 100644 ThreadPool/gradle/wrapper/gradle-wrapper.jar delete mode 100644 ThreadPool/gradle/wrapper/gradle-wrapper.properties delete mode 100755 ThreadPool/gradlew delete mode 100644 ThreadPool/gradlew.bat delete mode 100644 ThreadPool/settings.gradle delete mode 100644 ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightExecutionException.java delete mode 100644 ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightFuture.java delete mode 100644 ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java delete mode 100644 ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java diff --git a/ThreadPool/.idea/compiler.xml b/ThreadPool/.idea/compiler.xml deleted file mode 100644 index c12b025..0000000 --- a/ThreadPool/.idea/compiler.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/ThreadPool/.idea/misc.xml b/ThreadPool/.idea/misc.xml deleted file mode 100644 index 85cb263..0000000 --- a/ThreadPool/.idea/misc.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/ThreadPool/.idea/modules.xml b/ThreadPool/.idea/modules.xml deleted file mode 100644 index 4e0292a..0000000 --- a/ThreadPool/.idea/modules.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/ThreadPool/.idea/modules/ThreadPool.iml b/ThreadPool/.idea/modules/ThreadPool.iml deleted file mode 100644 index 846c43c..0000000 --- a/ThreadPool/.idea/modules/ThreadPool.iml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/ThreadPool/.idea/modules/ThreadPool_main.iml b/ThreadPool/.idea/modules/ThreadPool_main.iml deleted file mode 100644 index 3968fb2..0000000 --- a/ThreadPool/.idea/modules/ThreadPool_main.iml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ThreadPool/.idea/modules/ThreadPool_test.iml b/ThreadPool/.idea/modules/ThreadPool_test.iml deleted file mode 100644 index 75f968b..0000000 --- a/ThreadPool/.idea/modules/ThreadPool_test.iml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ThreadPool/build.gradle b/ThreadPool/build.gradle deleted file mode 100644 index 31770fa..0000000 --- a/ThreadPool/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -group 'ru.spbau.mit.kazakov.ThreadPool' -version '1.0-SNAPSHOT' - -apply plugin: 'java' - -sourceCompatibility = 1.8 - -repositories { - mavenCentral() -} - -dependencies { - compile group: 'org.jetbrains', name: 'annotations', version: '13.0' - testCompile group: 'junit', name: 'junit', version: '4.12' -} diff --git a/ThreadPool/gradle/wrapper/gradle-wrapper.jar b/ThreadPool/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 05f5f90a4701ac59bd444c3ad49a5bc1ce944166..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54706 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 diff --git a/ThreadPool/gradle/wrapper/gradle-wrapper.properties b/ThreadPool/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index df4e635..0000000 --- a/ThreadPool/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Thu Mar 08 19:53:28 MSK 2018 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip diff --git a/ThreadPool/gradlew b/ThreadPool/gradlew deleted file mode 100755 index cccdd3d..0000000 --- a/ThreadPool/gradlew +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env sh - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$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="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# 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 - ;; - 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/ThreadPool/gradlew.bat b/ThreadPool/gradlew.bat deleted file mode 100644 index e95643d..0000000 --- a/ThreadPool/gradlew.bat +++ /dev/null @@ -1,84 +0,0 @@ -@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 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= - -@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 init - -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 init - -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 - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -: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 %CMD_LINE_ARGS% - -: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/ThreadPool/settings.gradle b/ThreadPool/settings.gradle deleted file mode 100644 index abed208..0000000 --- a/ThreadPool/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'ThreadPool' - diff --git a/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightExecutionException.java b/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightExecutionException.java deleted file mode 100644 index 4243b08..0000000 --- a/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightExecutionException.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.spbau.mit.kazakov.ThreadPool; - -/** - * Used to indicate that result of computation can't be obtained because of an exception. - */ -public class LightExecutionException extends Exception { -} \ No newline at end of file diff --git a/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightFuture.java b/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightFuture.java deleted file mode 100644 index aee9af4..0000000 --- a/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/LightFuture.java +++ /dev/null @@ -1,32 +0,0 @@ -package ru.spbau.mit.kazakov.ThreadPool; - -import java.util.function.Function; - -/** - * An interface representing a task from {@link ThreadPoolImpl}. - * - * @param type of computation result - */ -public interface LightFuture { - /** - * Returns true if computation is completed, and false otherwise. - */ - boolean isReady(); - - /** - * Returns result of computation. - * - * @throws LightExecutionException if an exception was thrown during computation - */ - T get() throws LightExecutionException, InterruptedException; - - /** - * Creates a new task for {@link ThreadPoolImpl} using specified function, that requires computation result of current task. - * - * @param function specified function - * @param type of computation result of new task - * @return created task - * @throws LightExecutionException if an exception was thrown during computation - */ - LightFuture thenApply(Function function) throws LightExecutionException; -} \ No newline at end of file diff --git a/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java b/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java deleted file mode 100644 index 2a5be72..0000000 --- a/ThreadPool/src/main/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImpl.java +++ /dev/null @@ -1,171 +0,0 @@ -package ru.spbau.mit.kazakov.ThreadPool; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.function.Function; -import java.util.function.Supplier; -import javax.xml.ws.Holder; - -import org.jetbrains.annotations.NotNull; - -/** - * A class for computation in multiple threads. - */ -public class ThreadPoolImpl { - private final Thread[] threads; - private final Queue> tasksQueue = new LinkedList<>(); - - /** - * Creates specified number of threads for computation. - */ - public ThreadPoolImpl(int numberOfThreads) { - this.threads = new Thread[numberOfThreads]; - Runnable threadCycle = () -> { - while (!Thread.currentThread().isInterrupted()) { - Task task; - synchronized (tasksQueue) { - while (this.tasksQueue.isEmpty()) { - try { - tasksQueue.wait(); - } catch (InterruptedException exception) { - return; - } - } - task = tasksQueue.remove(); - } - - //noinspection SynchronizationOnLocalVariableOrMethodParameter - synchronized (task) { - task.compute(); - task.notify(); - } - } - - }; - - for (int i = 0; i < numberOfThreads; ++i) { - this.threads[i] = new Thread(threadCycle); - this.threads[i].start(); - } - - } - - /** - * Adds a new task to queue. - * - * @param task a computation - * @param type of computation result - * @return added task - */ - @NotNull - public LightFuture addTask(@NotNull Supplier task) { - Task lightFuture = new Task<>(task); - synchronized (tasksQueue) { - tasksQueue.add(lightFuture); - tasksQueue.notify(); - } - return lightFuture; - } - - /** - * Dismisses all task in queue, waits for tasks which are computing and terminates all threads. - */ - public void shutdown() throws InterruptedException { - for (Thread thread : threads) { - thread.interrupt(); - } - synchronized (tasksQueue) { - while (!tasksQueue.isEmpty()) { - tasksQueue.remove().isSuccessful = false; - } - } - for (Thread thread : threads) { - thread.join(); - } - } - - /** - * An implementation of LightFuture for using in {@link ThreadPoolImpl}. - * - * @param type of computation result. - */ - private class Task implements LightFuture { - private final List> deferredTasks = new ArrayList<>(); - private Supplier computation; - private boolean isReady; - private boolean isSuccessful = true; - private Holder result; - - /** - * Initializes supplier for computation. - */ - public Task(@NotNull Supplier computation) { - this.computation = computation; - } - - /** - * Makes computation, saves result and adds deferred task to queue. - */ - public void compute() { - try { - result = new Holder<>(computation.get()); - } catch (RuntimeException exception) { - isSuccessful = false; - } - - isReady = true; - synchronized (tasksQueue) { - tasksQueue.addAll(this.deferredTasks); - tasksQueue.notify(); - } - } - - /** - * @see LightFuture#isReady() - */ - public boolean isReady() { - return isReady; - } - - /** - * @see LightFuture#get() - */ - public T get() throws LightExecutionException, InterruptedException { - if (!this.isReady) { - synchronized (this) { - while (!this.isReady) { - this.wait(); - } - } - } - - if (!this.isSuccessful) { - throw new LightExecutionException(); - } else { - return this.result.value; - } - } - - /** - * @see LightFuture#thenApply(Function) - */ - @NotNull - public synchronized LightFuture thenApply(@NotNull Function function) throws LightExecutionException { - if (!isSuccessful) { - throw new LightExecutionException(); - } - - Supplier computation = () -> function.apply(result.value); - if (isReady) { - return addTask(computation); - } else { - Task task = new Task<>(computation); - deferredTasks.add(task); - return task; - } - } - } -} - diff --git a/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java b/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java deleted file mode 100644 index 5c09193..0000000 --- a/ThreadPool/src/test/java/ru/spbau/mit/kazakov/ThreadPool/ThreadPoolImplTest.java +++ /dev/null @@ -1,151 +0,0 @@ -package ru.spbau.mit.kazakov.ThreadPool; - -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.junit.Assert; -import org.junit.Test; - -public class ThreadPoolImplTest { - @Test - public void testConstructorAndShutdown() throws InterruptedException { - ThreadPoolImpl threadPool = new ThreadPoolImpl(20); - threadPool.shutdown(); - } - - @Test - public void testIsReady() throws LightExecutionException, InterruptedException { - Supplier task = () -> 404; - ThreadPoolImpl threadPool = new ThreadPoolImpl(2); - LightFuture lightFuture = threadPool.addTask(task); - lightFuture.get(); - Assert.assertTrue(lightFuture.isReady()); - } - - @Test - public void testComputeSimple() throws LightExecutionException, InterruptedException { - Supplier task = () -> ":("; - ThreadPoolImpl threadPool = new ThreadPoolImpl(5); - LightFuture lightFuture = threadPool.addTask(task); - Assert.assertEquals(":(", lightFuture.get()); - } - - @Test( - expected = LightExecutionException.class - ) - public void testComputeThrowsException() throws LightExecutionException, InterruptedException { - //noinspection NumericOverflow - Supplier task = () -> 5 / 0; - ThreadPoolImpl threadPool = new ThreadPoolImpl(5); - LightFuture lightFuture = threadPool.addTask(task); - lightFuture.get(); - } - - @Test - public void testComputeMultipleTasks() throws LightExecutionException, InterruptedException { - Object computationResult = new Object(); - Supplier task1 = () -> { - safeSleep(); - return ":("; - }; - Supplier task2 = () -> { - safeSleep(); - return 101; - }; - Supplier task3 = () -> { - safeSleep(); - return computationResult; - }; - Supplier task4 = () -> { - safeSleep(); - return 'n'; - }; - Supplier task5 = () -> { - safeSleep(); - return (byte) 0; - }; - - ThreadPoolImpl threadPool = new ThreadPoolImpl(3); - LightFuture lightFuture1 = threadPool.addTask(task1); - LightFuture lightFuture2 = threadPool.addTask(task2); - LightFuture lightFuture3 = threadPool.addTask(task3); - LightFuture lightFuture4 = threadPool.addTask(task4); - LightFuture lightFuture5 = threadPool.addTask(task5); - - Assert.assertEquals(":(", lightFuture1.get()); - Assert.assertEquals(Integer.valueOf(101), lightFuture2.get()); - Assert.assertEquals(computationResult, lightFuture3.get()); - Assert.assertEquals(Character.valueOf('n'), lightFuture4.get()); - Assert.assertEquals(Byte.valueOf((byte) 0), lightFuture5.get()); - } - - @Test - public void testThenApplySimple() throws LightExecutionException, InterruptedException { - Supplier supplier = () -> { - this.safeSleep(); - return 999; - }; - Function function = String::valueOf; - ThreadPoolImpl threadPool = new ThreadPoolImpl(3); - Assert.assertEquals("999", threadPool.addTask(supplier).thenApply(function).get()); - } - - @Test - public void testThenApplyAfterComputation() throws LightExecutionException, InterruptedException { - Supplier supplier = () -> 999; - Function function = String::valueOf; - ThreadPoolImpl threadPool = new ThreadPoolImpl(3); - LightFuture lightFuture = threadPool.addTask(supplier); - lightFuture.get(); - Assert.assertEquals("999", lightFuture.thenApply(function).get()); - } - - @Test - public void testThenApplyMultiple() throws LightExecutionException, InterruptedException { - Supplier supplier = () -> { - safeSleep(); - return "Genome-wide association"; - }; - Function function1 = s -> s.concat(" study"); - Function function2 = s -> s.replace("d", "ld"); - Function function3 = s -> s.substring(0, 11); - ThreadPoolImpl threadPool = new ThreadPoolImpl(3); - - LightFuture lightFuture = threadPool.addTask(supplier); - Assert.assertEquals("Genome-wide association study", lightFuture.thenApply(function1).get()); - Assert.assertEquals("Genome-wilde association", lightFuture.thenApply(function2).get()); - Assert.assertEquals("Genome-wide", lightFuture.thenApply(function3).get()); - } - - @Test - public void testNumberOfThreads() throws InterruptedException { - Set threadsId = new HashSet<>(); - Supplier supplier = () -> { - synchronized (threadsId) { - threadsId.add(Thread.currentThread().getId()); - } - //noinspection InfiniteLoopStatement,StatementWithEmptyBody - while (true) { - } - }; - ThreadPoolImpl threadPool = new ThreadPoolImpl(15); - - for (int i = 0; i < 100; ++i) { - threadPool.addTask(supplier); - } - - TimeUnit.MILLISECONDS.sleep(1500); - Assert.assertEquals(15, threadsId.size()); - } - - private void safeSleep() { - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException var2) { - throw new RuntimeException(); - } - } -} diff --git a/Tic-tac-toe/gradlew.bat b/Tic-tac-toe/gradlew.bat index f955316..e95643d 100644 --- a/Tic-tac-toe/gradlew.bat +++ b/Tic-tac-toe/gradlew.bat @@ -1,84 +1,84 @@ -@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 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= - -@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 init - -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 init - -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 - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -: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 %CMD_LINE_ARGS% - -: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 +@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 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= + +@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 init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +: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 From a9e17056341f1744a9f34438eb491845f644d04c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sat, 17 Mar 2018 00:57:03 +0300 Subject: [PATCH 07/13] Implement UI --- Tic-tac-toe/.idea/misc.xml | 18 + .../spbau/mit/kazakov/TicTacToe/AIBoard.java | 85 +++-- .../mit/kazakov/TicTacToe/AbstractBoard.java | 35 +- .../ru/spbau/mit/kazakov/TicTacToe/Board.java | 6 + .../mit/kazakov/TicTacToe/HotSeatBoard.java | 3 + .../mit/kazakov/TicTacToe/TicTacToe.java | 323 ++++++++++++++---- .../mit/kazakov/TicTacToe/AIBoardTest.java | 182 ++++++++-- .../kazakov/TicTacToe/AbstractBoardTest.java | 101 ++++++ .../kazakov/TicTacToe/HotSeatBoardTest.java | 33 ++ 9 files changed, 654 insertions(+), 132 deletions(-) create mode 100644 Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java diff --git a/Tic-tac-toe/.idea/misc.xml b/Tic-tac-toe/.idea/misc.xml index e208459..85cb263 100644 --- a/Tic-tac-toe/.idea/misc.xml +++ b/Tic-tac-toe/.idea/misc.xml @@ -1,5 +1,23 @@ + + diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java index 5196f2d..ee96ba9 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java @@ -12,7 +12,8 @@ public class AIBoard extends AbstractBoard { /** * Calls parent's constructor and initializes AI. - * @param size of board + * + * @param size of board * @param level of AI */ public AIBoard(int size, @NotNull AILevel level) { @@ -35,7 +36,7 @@ public AIBoard(int size, @NotNull AILevel level) { public String[][] move(int row, int col) { board[row][col] = player.toString(); updateState(row, col); - if(state != State.RUNNING) { + if (state != State.RUNNING) { return board; } player = player.next(); @@ -54,17 +55,17 @@ private class EasyAI implements AI { @Override public void makeTurn() { int freeCellsNumber = 0; - for(String[] row : board) { - for(String cell : row) { - if(cell.equals(" ")) { + for (String[] row : board) { + for (String cell : row) { + if (cell.equals(" ")) { freeCellsNumber++; } } } int turn = ThreadLocalRandom.current().nextInt(0, freeCellsNumber); - for(int i = 0; i < board.length; i++) { - for(int j = 0; j < board.length; j++) { - if(board[i][j].equals(" ") && turn-- == 0) { + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board.length; j++) { + if (board[i][j].equals(" ") && turn-- == 0) { board[i][j] = player.toString(); updateState(i, j); return; @@ -79,12 +80,21 @@ public void makeTurn() { */ private class MediumAI implements AI { /** - * Checks if specified line from the board leads to losing or victory in one turn. + * Checks if specified line from the board leads to lose in one turn. + * * @param turns specified line from the board */ - private boolean canLoseOrWin(String turns) { - return turns.matches(player.toString() + "*" + " " + player.toString() + "*") || - turns.matches(player.next().toString() + "*" + " " + player.next().toString() + "*"); + private boolean canLose(String turns) { + return turns.matches(player.next().toString() + "*" + " " + player.next().toString() + "*"); + } + + /** + * Checks if specified line from the board leads to victory in one turn. + * + * @param turns specified line from the board + */ + private boolean canWin(String turns) { + return turns.matches(player.toString() + "*" + " " + player.toString() + "*"); } /** @@ -92,50 +102,73 @@ private boolean canLoseOrWin(String turns) { */ @Override public void makeTurn() { - for(int i = 0; i < board.length; i++) { + int defendRow = -1; + int defendCol = -1; + + for (int i = 0; i < board.length; i++) { StringBuilder row = new StringBuilder(); int rowFreeCell = 0, colFreeCell = 0; StringBuilder col = new StringBuilder(); - for(int j = 0; j < board.length; j++) { - if(board[i][j].equals(" ")) { + for (int j = 0; j < board.length; j++) { + if (board[i][j].equals(" ")) { rowFreeCell = j; } row.append(board[i][j]); - if(board[j][i].equals(" ")) { + if (board[j][i].equals(" ")) { colFreeCell = j; } col.append(board[j][i]); } - if(canLoseOrWin(row.toString())) { + if (canWin(row.toString())) { board[i][rowFreeCell] = player.toString(); updateState(i, rowFreeCell); return; - } - if(canLoseOrWin(col.toString())) { + } else if (canWin(col.toString())) { board[colFreeCell][i] = player.toString(); updateState(colFreeCell, i); return; + } else if (canLose(row.toString())) { + defendRow = i; + defendCol = rowFreeCell; + } else if (canLose(col.toString())) { + defendRow = colFreeCell; + defendCol = i; } } int firstDiagonalFreeCell = 0, secondDiagonalFreeCell = 0; StringBuilder firstDiagonal = new StringBuilder(); StringBuilder secondDiagonal = new StringBuilder(); - for(int i = 0; i < board.length; i++) { - if(board[i][i].equals(" ")) { + for (int i = 0; i < board.length; i++) { + if (board[i][i].equals(" ")) { firstDiagonalFreeCell = i; } - if(board[i][board.length - 1 - i].equals(" ")) { + if (board[i][board.length - 1 - i].equals(" ")) { secondDiagonalFreeCell = i; } firstDiagonal.append(board[i][i]); secondDiagonal.append(board[i][board.length - 1 - i]); } - if(canLoseOrWin(firstDiagonal.toString())) { - move(firstDiagonalFreeCell, firstDiagonalFreeCell); + if (canWin(firstDiagonal.toString())) { + board[firstDiagonalFreeCell][firstDiagonalFreeCell] = player.toString(); + updateState(firstDiagonalFreeCell, firstDiagonalFreeCell); + return; + } else if (canWin(secondDiagonal.toString())) { + board[secondDiagonalFreeCell][board.length - 1 - secondDiagonalFreeCell] = player.toString(); + updateState(secondDiagonalFreeCell, board.length - 1 - secondDiagonalFreeCell); + return; + } else if (canLose(firstDiagonal.toString())) { + defendCol = firstDiagonalFreeCell; + defendRow = firstDiagonalFreeCell; + } else if (canLose(secondDiagonal.toString())) { + defendCol = board.length - 1 - secondDiagonalFreeCell; + defendRow = secondDiagonalFreeCell; } - if(canLoseOrWin(secondDiagonal.toString())) { - move(secondDiagonalFreeCell, board.length - 1 - secondDiagonalFreeCell); + + if (defendCol != -1) { + board[defendRow][defendCol] = player.toString(); + updateState(defendRow, defendCol); + return; } new EasyAI().makeTurn(); } diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java index 3d2c779..84a5633 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java @@ -7,22 +7,17 @@ */ public abstract class AbstractBoard implements Board { protected String[][] board; - protected State state = State.RUNNING; - protected Player player = Player.X; + protected State state; + protected Player player; private int size; - private int turnNumber = 0; + private int turnNumber; /** * Sets board size and creates two-dimensional array for board. */ public AbstractBoard(int size) { - board = new String[size][size]; this.size = size; - for(String[] row : board) { - for(int i = 0; i < size; i++) { - row[i] = " "; - } - } + reset(); } /** @@ -36,6 +31,7 @@ public State getState() { /** * Checks last turn changes and updates game state. + * * @param row of last turn * @param col of last turn */ @@ -45,16 +41,33 @@ public void updateState(int row, int col) { boolean verticalWin = true; boolean firstDiagonalWin = true; boolean secondDiagonalWin = true; - for(int i = 0; i < size; i++) { + for (int i = 0; i < size; i++) { verticalWin &= board[i][col].equals(player.toString()); horizontalWin &= board[row][i].equals(player.toString()); firstDiagonalWin &= board[i][i].equals(player.toString()); secondDiagonalWin &= board[i][size - 1 - i].equals(player.toString()); } - if(horizontalWin || verticalWin || firstDiagonalWin || secondDiagonalWin) { + if (horizontalWin || verticalWin || firstDiagonalWin || secondDiagonalWin) { state = player.getWinState(); } else if (turnNumber == size * size) { state = State.DRAW; } } + + /** + * @see Board#reset() + */ + @Override + public String[][] reset() { + board = new String[size][size]; + for (String[] row : board) { + for (int i = 0; i < size; i++) { + row[i] = " "; + } + } + state = State.RUNNING; + player = Player.X; + turnNumber = 0; + return board; + } } diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java index 7d42f75..2d203aa 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java @@ -5,6 +5,7 @@ public interface Board { /** * Makes specified turn on the board and updates state of game. + * * @param row of turn * @param col of turn * @return new state of board @@ -17,4 +18,9 @@ public interface Board { */ @NotNull State getState(); + + /** + * Sets board to starting state. + */ + String[][] reset(); } diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java index 2959727..3623192 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java @@ -2,6 +2,9 @@ import org.jetbrains.annotations.NotNull; +/** + * Implements logic of game for two players. + */ public class HotSeatBoard extends AbstractBoard { /** * @see AbstractBoard#AbstractBoard(int) diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java index a9ec802..1696cfe 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java @@ -2,79 +2,274 @@ import javafx.application.Application; import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; -import javafx.scene.layout.ColumnConstraints; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.RowConstraints; +import javafx.scene.control.ComboBox; +import javafx.scene.control.ListView; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; import javafx.stage.Stage; -public class TicTacToe extends Application{ + +/** + * Tic Tac Toe application with single and multi player. + */ +public class TicTacToe extends Application { + private static final int BOARD_SIZE = 3; + private Stage primaryStage; + private Scene mainMenuScene; + private Scene boardScene; + private Button[][] cells; + private Text gameResult; + private Scene statsScene; + private final ObservableList stats = FXCollections.observableArrayList(); + private Board board; + + /** + * @see Application#start(Stage) + */ @Override public void start(Stage primaryStage) { - Button button1 = new Button("Click me"); - button1.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); - button1.setOnAction(value -> Platform.exit()); - Button button2 = new Button("Click me"); - button2.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); - button2.setOnAction(value -> Platform.exit()); - Button button3 = new Button("Click me"); - button3.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); - button3.setOnAction(value -> Platform.exit()); - Button button4 = new Button("Click me"); - button4.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); - button4.setOnAction(value -> Platform.exit()); - Button button5 = new Button("Click me"); - button5.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); - button5.setOnAction(value -> Platform.exit()); - Button button6 = new Button("Click me"); - button6.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); - button6.setOnAction(value -> Platform.exit()); - Button button7 = new Button("Click me"); - button7.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); - button7.setOnAction(value -> Platform.exit()); - Button button8 = new Button("Click me"); - button8.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); - button8.setOnAction(value -> Platform.exit()); - Button button9 = new Button("Click me"); - button9.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); - button9.setOnAction(value -> Platform.exit()); - - - GridPane pane = new GridPane(); - ColumnConstraints column1 = new ColumnConstraints(); - column1.setPercentWidth(50); - ColumnConstraints column2 = new ColumnConstraints(); - column2.setPercentWidth(50); - ColumnConstraints column3 = new ColumnConstraints(); - column3.setPercentWidth(50); - - RowConstraints row1 = new RowConstraints(); - row1.setPercentHeight(100); - row1.setFillHeight(true); - RowConstraints row2 = new RowConstraints(); - row2.setPercentHeight(100); - row2.setFillHeight(true); - RowConstraints row3 = new RowConstraints(); - row3.setPercentHeight(100); - row3.setFillHeight(true); - pane.getColumnConstraints().addAll(column1, column2, column3); - pane.getRowConstraints().addAll(row1, row2, row3); - - pane.add(button1,0,0); - pane.add(button2,1,0); - pane.add(button3,2,0); - pane.add(button4,0,1); - pane.add(button5,1,1); - pane.add(button6,2,1); - pane.add(button7,0,2); - pane.add(button8,1,2); - pane.add(button9,2,2); - Scene scene = new Scene(pane, 500, 500); - primaryStage.setScene(scene); + this.primaryStage = primaryStage; + initializeMainMenuScene(); + initializeBoardScene(); + initializeStatsScene(); + primaryStage.setScene(mainMenuScene); + primaryStage.setTitle("Nevermind"); primaryStage.show(); + primaryStage.requestFocus(); + } + + /** + * Creates and initializes main menu scene. + */ + private void initializeMainMenuScene() { + Text sceneTitle = new Text("Welcome"); + sceneTitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20)); + sceneTitle.setFill(Color.WHITE); + + HBox singlePlayerMenu = new HBox(); + singlePlayerMenu.setPadding(new Insets(0, 0, 0, 0)); + singlePlayerMenu.setSpacing(0); + + ComboBox AILevelComboBox = new ComboBox<>(); + AILevelComboBox.setFocusTraversable(false); + AILevelComboBox.setMaxWidth(Double.MAX_VALUE); + AILevelComboBox.getItems().addAll("Medium", "Easy"); + AILevelComboBox.setValue("Medium"); + + Button singlePlayerButton = new Button("Single player"); + singlePlayerButton.setFocusTraversable(false); + singlePlayerButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + singlePlayerButton.setOnAction(value -> { + switch (AILevelComboBox.getValue()) { + case "Medium": + board = new AIBoard(BOARD_SIZE, AILevel.MEDIUM); + break; + case "Easy": + board = new AIBoard(BOARD_SIZE, AILevel.EASY); + break; + } + gameResult.setText(""); + updateBoardScene(board.reset()); + this.primaryStage.setScene(boardScene); + }); + + HBox.setHgrow(AILevelComboBox, Priority.ALWAYS); + HBox.setHgrow(singlePlayerButton, Priority.ALWAYS); + singlePlayerMenu.getChildren().addAll(singlePlayerButton, AILevelComboBox); + + Button multiPlayerButton = new Button("Multi player"); + multiPlayerButton.setFocusTraversable(false); + multiPlayerButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + multiPlayerButton.setOnAction(value -> { + board = new HotSeatBoard(BOARD_SIZE); + gameResult.setText(""); + updateBoardScene(board.reset()); + primaryStage.setScene(boardScene); + }); + + Button statsButton = new Button("Stats"); + statsButton.setFocusTraversable(false); + statsButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + statsButton.setOnAction(value -> primaryStage.setScene(statsScene)); + + Button exitButton = new Button("Exit"); + exitButton.setFocusTraversable(false); + exitButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + exitButton.setOnAction(value -> Platform.exit()); + + VBox menu = new VBox(); + menu.setPadding(new Insets(25, 25, 25, 25)); + menu.setSpacing(10); + menu.setAlignment(Pos.CENTER); + menu.setStyle("-fx-background-color: #ffad33;"); + menu.getChildren().addAll(sceneTitle, singlePlayerMenu, multiPlayerButton, statsButton, exitButton); + mainMenuScene = new Scene(menu, 300, 275); + } + + /** + * Creates and initializes board scene. + */ + private void initializeBoardScene() { + Button mainMenuButton = new Button("Main menu"); + mainMenuButton.setFocusTraversable(false); + mainMenuButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + mainMenuButton.setOnAction(value -> primaryStage.setScene(mainMenuScene)); + + Button restartButton = new Button("Restart"); + restartButton.setFocusTraversable(false); + restartButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + restartButton.setOnAction(value -> { + gameResult.setText(""); + updateBoardScene(board.reset()); + }); + + Text gameResult = new Text(); + gameResult.setTextAlignment(TextAlignment.CENTER); + gameResult.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20)); + gameResult.setFill(Color.WHITE); + this.gameResult = gameResult; + + HBox navigationBar = new HBox(); + navigationBar.setPadding(new Insets(0, 0, 0, 0)); + navigationBar.setSpacing(0); + navigationBar.setAlignment(Pos.CENTER); + navigationBar.setStyle("-fx-background-color: #ffad33;"); + navigationBar.getChildren().addAll(mainMenuButton, restartButton); + + Button[][] cells = new Button[BOARD_SIZE][BOARD_SIZE]; + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells.length; j++) { + int row = i; + int col = j; + cells[i][j] = new Button(" "); + cells[i][j].setStyle("-fx-text-fill: black; -fx-font-size: 35;"); + cells[i][j].setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + cells[i][j].setFocusTraversable(false); + cells[i][j].setOnAction(value -> move(row, col)); + } + } + this.cells = cells; + + ColumnConstraints[] cols = new ColumnConstraints[BOARD_SIZE]; + for (int i = 0; i < cols.length; i++) { + cols[i] = new ColumnConstraints(); + cols[i].setPercentWidth(50); + } + + RowConstraints[] rows = new RowConstraints[BOARD_SIZE]; + for (int i = 0; i < cols.length; i++) { + rows[i] = new RowConstraints(); + rows[i].setPercentHeight(100); + rows[i].setFillHeight(true); + } + + GridPane grid = new GridPane(); + grid.getColumnConstraints().addAll(cols); + grid.getRowConstraints().addAll(rows); + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells.length; j++) { + grid.add(cells[i][j], j, i); + } + } + + BorderPane border = new BorderPane(); + BorderPane.setAlignment(gameResult, Pos.CENTER); + border.setStyle("-fx-background-color: #ffad33;"); + border.setBottom(navigationBar); + border.setCenter(grid); + border.setTop(gameResult); + boardScene = new Scene(border, 300, 275); + } + + /** + * Creates and initializes stats scene. + */ + private void initializeStatsScene() { + VBox statsPane = new VBox(); + statsPane.setAlignment(Pos.CENTER); + statsPane.setStyle("-fx-background-color: #ffad33;"); + statsPane.setPadding(new Insets(10, 25, 10, 25)); + statsPane.setSpacing(10); + + Text sceneTitle = new Text("Stats"); + sceneTitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20)); + sceneTitle.setFill(Color.WHITE); + + ListView listView = new ListView<>(stats); + listView.setFocusTraversable(false); + + Button mainMenuButton = new Button("Main menu"); + mainMenuButton.setFocusTraversable(false); + mainMenuButton.setOnAction(value -> primaryStage.setScene(mainMenuScene)); + + statsPane.getChildren().addAll(sceneTitle, listView, mainMenuButton); + statsScene = new Scene(statsPane, 300, 275); + } + + /** + * Handles player's turn. + * + * @param row witch player've chose + * @param col witch player've chose + */ + private void move(int row, int col) { + updateBoardScene(board.move(row, col)); + switch (board.getState()) { + case O_WINS: + gameResult.setText("O WINS!"); + stats.add("O's victory"); + break; + case X_WINS: + gameResult.setText("X WINS!"); + stats.add("X's victory"); + break; + case DRAW: + gameResult.setText("DRAW!"); + stats.add("Draw"); + break; + } + if (!board.getState().equals(State.RUNNING)) { + disableBoard(); + } + } + + /** + * Prevents all new turns. + */ + private void disableBoard() { + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells.length; j++) { + cells[i][j].setDisable(true); + } + } + } + + /** + * Reflects specified state of the board for player. + * @param boardState specified state of the board + */ + private void updateBoardScene(String[][] boardState) { + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells.length; j++) { + cells[i][j].setDisable(!boardState[i][j].equals(" ")); + cells[i][j].setText(boardState[i][j]); + } + } } + /** + * Runs application. + */ public static void main(String[] args) { Application.launch(args); } diff --git a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java index 3a972ec..cdd248e 100644 --- a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java +++ b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java @@ -1,10 +1,21 @@ package ru.spbau.mit.kazakov.TicTacToe; +import org.junit.Before; import org.junit.Test; +import java.lang.reflect.Field; + import static org.junit.Assert.*; public class AIBoardTest { + private Field boardField; + + @Before + public void initializeFiled() throws NoSuchFieldException { + boardField = AbstractBoard.class.getDeclaredField("board"); + boardField.setAccessible(true); + } + @Test public void testEasyLevelConstructor() { new AIBoard(10, AILevel.EASY); @@ -23,65 +34,174 @@ public void testMakeTurnEasyAI() { for (int i = 0; i < boardState.length; i++) { for (int j = 0; j < boardState.length; j++) { if (!boardState[i][j].equals(" ") && !(i == 2 && j == 1)) { - assertEquals("O", boardState[i][j]); + assertEquals(Player.O.toString(), boardState[i][j]); diff++; } } } assertEquals(1, diff); - assertEquals("X", boardState[2][1]); + assertEquals(Player.X.toString(), boardState[2][1]); } @Test - public void testMakeTurnMediumAI() { + public void testMakeTurnMediumAISimple() { Board board = new AIBoard(3, AILevel.EASY); String[][] boardState = board.move(2, 1); int diff = 0; for (int i = 0; i < boardState.length; i++) { for (int j = 0; j < boardState.length; j++) { if (!boardState[i][j].equals(" ") && !(i == 2 && j == 1)) { - assertEquals("O", boardState[i][j]); + assertEquals(Player.O.toString(), boardState[i][j]); diff++; } } } -/* - for (int i = 0; i < boardState.length; i++) { - for (int j = 0; j < boardState.length; j++) { - System.out.print(boardState[i][j] + "/"); - } - System.out.println(); - } -*/ + assertEquals(1, diff); - assertEquals("X", boardState[2][1]); + assertEquals(Player.X.toString(), boardState[2][1]); } @Test - public void testMakeTurnMediumAIPreventLose() { + public void testMakeTurnMediumAIPreventHorizontalLose() throws IllegalAccessException { Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = board.move(0, 0); - if(boardState[0][1].equals(" ") && boardState[0][2].equals(" ")) { - boardState = board.move(0, 1); - assertEquals("O", boardState[0][2]); - } else { - boardState = board.move(1, 0); - assertEquals("O", boardState[2][0]); - } + String[][] boardState = new String[][]{{Player.X.toString(), " ", " "}, + {Player.O.toString(), " ", " "}, + {" ", " ", " "}}; + boardField.set(board, boardState); + boardState = board.move(0, 1); + assertArrayEquals(new String[][]{{Player.X.toString(), Player.X.toString(), Player.O.toString()}, + {Player.O.toString(), " ", " "}, {" ", " ", " "}}, boardState); } @Test - public void testMakeTurnMediumAIWin() { + public void testMakeTurnMediumAIPreventVerticalLose() throws IllegalAccessException { Board board = new AIBoard(3, AILevel.MEDIUM); - board.move(0, 0); - board.move(0, 1); - String[][] boardState = board.move(1, 1); - if(boardState[0][1].equals(" ")) { - boardState = board.move(0, 1); + String[][] boardState = new String[][]{{Player.X.toString(), " ", Player.O.toString()}, + {" ", " ", " "}, + {" ", " ", " "}}; + boardField.set(board, boardState); + boardState = board.move(1, 0); + assertArrayEquals(new String[][]{{Player.X.toString(), " ", Player.O.toString()}, + {Player.X.toString(), " ", " "}, {Player.O.toString(), " ", " "}}, boardState); + } - } else { - boardState = board.move(1, 0); + @Test + public void testMakeTurnMediumAIPreventFirstDiagonalLose() throws IllegalAccessException { + Board board = new AIBoard(3, AILevel.MEDIUM); + String[][] boardState = new String[][]{{Player.X.toString(), " ", Player.O.toString()}, + {" ", " ", " "}, + {" ", " ", " "}}; + boardField.set(board, boardState); + boardState = board.move(1, 1); + assertArrayEquals(new String[][]{{Player.X.toString(), " ", Player.O.toString()}, + {" ", Player.X.toString(), " "}, {" ", " ", Player.O.toString()}}, boardState); + } - } + @Test + public void testMakeTurnMediumAIPreventSecondDiagonalLose() throws IllegalAccessException { + Board board = new AIBoard(3, AILevel.MEDIUM); + String[][] boardState = new String[][]{{Player.O.toString(), " ", Player.X.toString()}, + {" ", " ", " "}, + {" ", " ", " "}}; + boardField.set(board, boardState); + boardState = board.move(1, 1); + assertArrayEquals(new String[][]{{Player.O.toString(), " ", Player.X.toString()}, + {" ", Player.X.toString(), " "}, {Player.O.toString(), " ", " "}}, boardState); + } + + @Test + public void testMakeTurnMediumAIHorizontalWin() throws IllegalAccessException { + Board board = new AIBoard(3, AILevel.MEDIUM); + String[][] boardState = new String[][]{{Player.O.toString(), " ", Player.O.toString()}, + {Player.X.toString(), " ", " "}, + {Player.X.toString(), " ", " "}}; + boardField.set(board, boardState); + boardState = board.move(1, 1); + assertArrayEquals(new String[][]{{Player.O.toString(), Player.O.toString(), Player.O.toString()}, + {Player.X.toString(), Player.X.toString(), " "}, {Player.X.toString(), " ", " "}}, boardState); + } + + @Test + public void testMakeTurnMediumAIVerticalWin() throws IllegalAccessException { + Board board = new AIBoard(3, AILevel.MEDIUM); + String[][] boardState = new String[][]{{Player.O.toString(), Player.X.toString(), Player.X.toString()}, + {Player.O.toString(), " ", " "}, + {" ", " ", " "}}; + boardField.set(board, boardState); + boardState = board.move(1, 1); + assertArrayEquals(new String[][]{{Player.O.toString(), Player.X.toString(), Player.X.toString()}, + {Player.O.toString(), Player.X.toString(), " "}, {Player.O.toString(), " ", " "}}, boardState); + } + + @Test + public void testMakeTurnMediumAIFirstDiagonalWin() throws IllegalAccessException { + Board board = new AIBoard(3, AILevel.MEDIUM); + String[][] boardState = new String[][]{{Player.O.toString(), Player.X.toString(), Player.X.toString()}, + {" ", " ", " "}, + {" ", " ", Player.O.toString()}}; + boardField.set(board, boardState); + boardState = board.move(2, 0); + assertArrayEquals(new String[][]{{Player.O.toString(), Player.X.toString(), Player.X.toString()}, + {" ", Player.O.toString(), " "}, {Player.X.toString(), " ", Player.O.toString()}}, boardState); + } + + @Test + public void testMakeTurnMediumAISecondDiagonalWin() throws IllegalAccessException { + Board board = new AIBoard(3, AILevel.MEDIUM); + String[][] boardState = new String[][]{{Player.X.toString(), Player.X.toString(), Player.O.toString()}, + {" ", " ", " "}, + {Player.O.toString(), " ", " "}}; + boardField.set(board, boardState); + boardState = board.move(1, 0); + assertArrayEquals(new String[][]{{Player.X.toString(), Player.X.toString(), Player.O.toString()}, + {Player.X.toString(), Player.O.toString(), " "}, {Player.O.toString(), " ", " "}}, boardState); + } + + @Test + public void testMakeTurnPlayerHorizontalWin() throws IllegalAccessException { + Board board = new AIBoard(3, AILevel.MEDIUM); + String[][] boardState = new String[][]{{Player.X.toString(), " ", Player.X.toString()}, + {Player.O.toString(), " ", Player.O.toString()}, + {" ", " ", " "}}; + boardField.set(board, boardState); + boardState = board.move(0, 1); + assertArrayEquals(new String[][]{{Player.X.toString(), Player.X.toString(), Player.X.toString()}, + {Player.O.toString(), " ", Player.O.toString()}, {" ", " ", " "}}, boardState); + } + + @Test + public void testMakeTurnPlayerVerticalWin() throws IllegalAccessException { + Board board = new AIBoard(3, AILevel.MEDIUM); + String[][] boardState = new String[][]{{Player.X.toString(), " ", Player.O.toString()}, + {Player.X.toString(), " ", Player.O.toString()}, + {" ", " ", " "}}; + boardField.set(board, boardState); + boardState = board.move(2, 0); + assertArrayEquals(new String[][]{{Player.X.toString(), " ", Player.O.toString()}, + {Player.X.toString(), " ", Player.O.toString()}, {Player.X.toString(), " ", " "}}, boardState); + } + + @Test + public void testMakeTurnPlayerFirstDiagonalWin() throws IllegalAccessException { + Board board = new AIBoard(3, AILevel.MEDIUM); + String[][] boardState = new String[][]{{Player.X.toString(), " ", Player.O.toString()}, + {" ", Player.X.toString(), Player.O.toString()}, + {" ", " ", " "}}; + boardField.set(board, boardState); + boardState = board.move(2, 2); + assertArrayEquals(new String[][]{{Player.X.toString(), " ", Player.O.toString()}, + {" ", Player.X.toString(), Player.O.toString()}, {" ", " ", Player.X.toString()}}, boardState); + } + + @Test + public void testMakeTurnPlayerSecondDiagonalWin() throws IllegalAccessException { + Board board = new AIBoard(3, AILevel.MEDIUM); + String[][] boardState = new String[][]{{Player.O.toString(), " ", Player.X.toString()}, + {" ", Player.X.toString(), Player.O.toString()}, + {" ", " ", " "}}; + boardField.set(board, boardState); + boardState = board.move(2, 0); + assertArrayEquals(new String[][]{{Player.O.toString(), " ", Player.X.toString()}, + {" ", Player.X.toString(), Player.O.toString()}, {Player.X.toString(), " ", " "}}, boardState); } } \ No newline at end of file diff --git a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java new file mode 100644 index 0000000..dc0317c --- /dev/null +++ b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java @@ -0,0 +1,101 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class AbstractBoardTest { + @Test + public void testGetStateInitial() { + AbstractBoard board = new HotSeatBoard(3); + assertEquals(State.RUNNING, board.getState()); + } + + @Test + public void testGetStateRunning() { + AbstractBoard board = new HotSeatBoard(3); + board.move(1, 1); + String[][] boardState = board.move(2, 0); + assertEquals(State.RUNNING, board.getState()); + } + + @Test + public void testGetStateHorizontalWin() { + AbstractBoard board = new HotSeatBoard(3); + board.move(0, 0); + board.move(1, 1); + board.move(2, 2); + board.move(0, 2); + board.move(2, 0); + board.move(1, 0); + board.move(2, 1); + assertEquals(State.X_WINS, board.getState()); + } + + @Test + public void testGetStateVerticalWin() { + AbstractBoard board = new HotSeatBoard(3); + board.move(0, 1); + board.move(0, 0); + board.move(1, 2); + board.move(2, 2); + board.move(0, 2); + board.move(2, 0); + board.move(2, 1); + board.move(1, 0); + assertEquals(State.O_WINS, board.getState()); + } + + @Test + public void testGetStateFirstDiagonalWin() { + AbstractBoard board = new HotSeatBoard(3); + board.move(0, 0); + board.move(0, 1); + board.move(1, 1); + board.move(0, 2); + board.move(2, 2); + assertEquals(State.X_WINS, board.getState()); + } + + @Test + public void testGetStateSecondDiagonalWin() { + AbstractBoard board = new HotSeatBoard(3); + board.move(0, 0); + board.move(0, 2); + board.move(0, 1); + board.move(1, 1); + board.move(2, 2); + board.move(2, 0); + assertEquals(State.O_WINS, board.getState()); + } + + @Test + public void testGetStateDraw() { + AbstractBoard board = new HotSeatBoard(3); + board.move(0, 0); + board.move(0, 1); + board.move(0, 2); + board.move(1, 1); + board.move(1, 0); + board.move(2, 0); + board.move(1, 2); + board.move(2, 2); + board.move(2, 1); + assertEquals(State.DRAW, board.getState()); + } + + @Test + public void testReset() { + AbstractBoard board = new HotSeatBoard(3); + board.move(0, 0); + board.move(0, 2); + board.move(0, 1); + board.move(1, 1); + board.move(2, 2); + board.move(2, 0); + board.reset(); + assertArrayEquals(new String[][]{{Player.X.toString(), " ", " "}, + {" ", " ", " "}, {" ", " ", " "}}, board.move(0, 0)); + assertEquals(State.RUNNING, board.getState()); + } +} \ No newline at end of file diff --git a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java index f286fcd..6923eac 100644 --- a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java +++ b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java @@ -1,7 +1,40 @@ package ru.spbau.mit.kazakov.TicTacToe; +import org.junit.Test; + import static org.junit.Assert.*; public class HotSeatBoardTest { + @Test + public void testMakeTurnSimple() { + Board board = new HotSeatBoard(3); + String[][] boardState = board.move(1, 0); + assertArrayEquals(new String[][]{{" ", " ", " "}, {Player.X.toString(), " ", " "}, + {" ", " ", " "}}, boardState); + } + + @Test + public void testMakeTurnTwice() { + Board board = new HotSeatBoard(3); + board.move(1, 1); + String[][] boardState = board.move(2, 0); + assertArrayEquals(new String[][]{{" ", " ", " "}, {" ", Player.X.toString(), " "}, + {Player.O.toString(), " ", " "}}, boardState); + } + + @Test + public void testMakeTurn() { + Board board = new HotSeatBoard(3); + board.move(0, 0); + board.move(1, 1); + board.move(2, 2); + board.move(0, 2); + board.move(2, 0); + board.move(1, 0); + String[][] boardState = board.move(2, 1); + assertArrayEquals(new String[][]{{Player.X.toString(), " ", Player.O.toString()}, + {Player.O.toString(), Player.O.toString(), " "}, + {Player.X.toString(), Player.X.toString(), Player.X.toString()}}, boardState); + } } \ No newline at end of file From a8e303d4435599e740511cc6042e6aff57a8174a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sat, 17 Mar 2018 01:05:46 +0300 Subject: [PATCH 08/13] Codestyle formatting --- .../main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java | 4 ++-- .../ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java index 1696cfe..66ff796 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java @@ -247,9 +247,9 @@ private void move(int row, int col) { * Prevents all new turns. */ private void disableBoard() { - for (int i = 0; i < cells.length; i++) { + for (Button[] cell : cells) { for (int j = 0; j < cells.length; j++) { - cells[i][j].setDisable(true); + cell[j].setDisable(true); } } } diff --git a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java index dc0317c..4038bd3 100644 --- a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java +++ b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java @@ -15,7 +15,7 @@ public void testGetStateInitial() { public void testGetStateRunning() { AbstractBoard board = new HotSeatBoard(3); board.move(1, 1); - String[][] boardState = board.move(2, 0); + board.move(2, 0); assertEquals(State.RUNNING, board.getState()); } From c898907d07468ac41470be1c89925632b3a47e15 Mon Sep 17 00:00:00 2001 From: DmitryAu Date: Sat, 17 Mar 2018 01:09:34 +0300 Subject: [PATCH 09/13] Add missing blank line --- .../src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java index 66ff796..4835466 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java @@ -256,6 +256,7 @@ private void disableBoard() { /** * Reflects specified state of the board for player. + * * @param boardState specified state of the board */ private void updateBoardScene(String[][] boardState) { From adb34a0916f059f677e95749679f24886a35c6c8 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sat, 17 Mar 2018 01:16:07 +0300 Subject: [PATCH 10/13] Change menu entries' names --- .../main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java index 4835466..2717341 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java @@ -66,7 +66,7 @@ private void initializeMainMenuScene() { AILevelComboBox.getItems().addAll("Medium", "Easy"); AILevelComboBox.setValue("Medium"); - Button singlePlayerButton = new Button("Single player"); + Button singlePlayerButton = new Button("SinglePlayer"); singlePlayerButton.setFocusTraversable(false); singlePlayerButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); singlePlayerButton.setOnAction(value -> { @@ -87,7 +87,7 @@ private void initializeMainMenuScene() { HBox.setHgrow(singlePlayerButton, Priority.ALWAYS); singlePlayerMenu.getChildren().addAll(singlePlayerButton, AILevelComboBox); - Button multiPlayerButton = new Button("Multi player"); + Button multiPlayerButton = new Button("MultiPlayer"); multiPlayerButton.setFocusTraversable(false); multiPlayerButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); multiPlayerButton.setOnAction(value -> { From 8a46daa78c577ce741bd77ac66028cd18af115dd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 30 Apr 2018 17:52:38 +0300 Subject: [PATCH 11/13] Applied suggested changes --- .gitignore | 4 +- .../spbau/mit/kazakov/TicTacToe/AIBoard.java | 46 ++-- .../mit/kazakov/TicTacToe/AbstractBoard.java | 18 +- .../ru/spbau/mit/kazakov/TicTacToe/Board.java | 4 +- .../mit/kazakov/TicTacToe/CellContent.java | 14 ++ .../mit/kazakov/TicTacToe/HotSeatBoard.java | 4 +- .../spbau/mit/kazakov/TicTacToe/Player.java | 7 + .../mit/kazakov/TicTacToe/TicTacToe.java | 45 ++-- .../mit/kazakov/TicTacToe/AIBoardTest.java | 230 +++++++++--------- .../kazakov/TicTacToe/AbstractBoardTest.java | 6 +- .../kazakov/TicTacToe/HotSeatBoardTest.java | 20 +- 11 files changed, 211 insertions(+), 187 deletions(-) create mode 100644 Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/CellContent.java diff --git a/.gitignore b/.gitignore index 070e0fe..4b26c77 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,7 @@ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: -**/.idea/**/workspace.xml -**/.idea/**/tasks.xml -**/.idea/dictionaries +**/.idea # Sensitive or high-churn files: **/.idea/**/dataSources/ diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java index ee96ba9..c6527d8 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AIBoard.java @@ -8,7 +8,7 @@ * Implements logic of game with AI. */ public class AIBoard extends AbstractBoard { - private AI AI; + private final AI ai; /** * Calls parent's constructor and initializes AI. @@ -20,11 +20,13 @@ public AIBoard(int size, @NotNull AILevel level) { super(size); switch (level) { case EASY: - AI = new EasyAI(); + ai = new EasyAI(); break; case MEDIUM: - AI = new MediumAI(); + ai = new MediumAI(); break; + default: + throw new IllegalArgumentException(); } } @@ -33,14 +35,14 @@ public AIBoard(int size, @NotNull AILevel level) { */ @NotNull @Override - public String[][] move(int row, int col) { - board[row][col] = player.toString(); + public CellContent[][] move(int row, int col) { + board[row][col] = player.toCellContent(); updateState(row, col); if (state != State.RUNNING) { return board; } player = player.next(); - AI.makeTurn(); + ai.makeTurn(); player = player.next(); return board; } @@ -55,9 +57,9 @@ private class EasyAI implements AI { @Override public void makeTurn() { int freeCellsNumber = 0; - for (String[] row : board) { - for (String cell : row) { - if (cell.equals(" ")) { + for (CellContent[] row : board) { + for (CellContent cell : row) { + if (cell.equals(CellContent.EMPTY)) { freeCellsNumber++; } } @@ -65,8 +67,8 @@ public void makeTurn() { int turn = ThreadLocalRandom.current().nextInt(0, freeCellsNumber); for (int i = 0; i < board.length; i++) { for (int j = 0; j < board.length; j++) { - if (board[i][j].equals(" ") && turn-- == 0) { - board[i][j] = player.toString(); + if (board[i][j].equals(CellContent.EMPTY) && turn-- == 0) { + board[i][j] = player.toCellContent(); updateState(i, j); return; } @@ -85,7 +87,7 @@ private class MediumAI implements AI { * @param turns specified line from the board */ private boolean canLose(String turns) { - return turns.matches(player.next().toString() + "*" + " " + player.next().toString() + "*"); + return turns.matches(player.next().toCellContent() + "*" + CellContent.EMPTY + player.next().toCellContent() + "*"); } /** @@ -94,7 +96,7 @@ private boolean canLose(String turns) { * @param turns specified line from the board */ private boolean canWin(String turns) { - return turns.matches(player.toString() + "*" + " " + player.toString() + "*"); + return turns.matches(player.toCellContent() + "*" + CellContent.EMPTY + player.toCellContent() + "*"); } /** @@ -110,21 +112,21 @@ public void makeTurn() { int rowFreeCell = 0, colFreeCell = 0; StringBuilder col = new StringBuilder(); for (int j = 0; j < board.length; j++) { - if (board[i][j].equals(" ")) { + if (board[i][j].equals(CellContent.EMPTY)) { rowFreeCell = j; } row.append(board[i][j]); - if (board[j][i].equals(" ")) { + if (board[j][i].equals(CellContent.EMPTY)) { colFreeCell = j; } col.append(board[j][i]); } if (canWin(row.toString())) { - board[i][rowFreeCell] = player.toString(); + board[i][rowFreeCell] = player.toCellContent(); updateState(i, rowFreeCell); return; } else if (canWin(col.toString())) { - board[colFreeCell][i] = player.toString(); + board[colFreeCell][i] = player.toCellContent(); updateState(colFreeCell, i); return; } else if (canLose(row.toString())) { @@ -140,21 +142,21 @@ public void makeTurn() { StringBuilder firstDiagonal = new StringBuilder(); StringBuilder secondDiagonal = new StringBuilder(); for (int i = 0; i < board.length; i++) { - if (board[i][i].equals(" ")) { + if (board[i][i].equals(CellContent.EMPTY)) { firstDiagonalFreeCell = i; } - if (board[i][board.length - 1 - i].equals(" ")) { + if (board[i][board.length - 1 - i].equals(CellContent.EMPTY)) { secondDiagonalFreeCell = i; } firstDiagonal.append(board[i][i]); secondDiagonal.append(board[i][board.length - 1 - i]); } if (canWin(firstDiagonal.toString())) { - board[firstDiagonalFreeCell][firstDiagonalFreeCell] = player.toString(); + board[firstDiagonalFreeCell][firstDiagonalFreeCell] = player.toCellContent(); updateState(firstDiagonalFreeCell, firstDiagonalFreeCell); return; } else if (canWin(secondDiagonal.toString())) { - board[secondDiagonalFreeCell][board.length - 1 - secondDiagonalFreeCell] = player.toString(); + board[secondDiagonalFreeCell][board.length - 1 - secondDiagonalFreeCell] = player.toCellContent(); updateState(secondDiagonalFreeCell, board.length - 1 - secondDiagonalFreeCell); return; } else if (canLose(firstDiagonal.toString())) { @@ -166,7 +168,7 @@ public void makeTurn() { } if (defendCol != -1) { - board[defendRow][defendCol] = player.toString(); + board[defendRow][defendCol] = player.toCellContent(); updateState(defendRow, defendCol); return; } diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java index 84a5633..1bd7e90 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoard.java @@ -6,7 +6,7 @@ * An abstract class with base functionality of game logic. */ public abstract class AbstractBoard implements Board { - protected String[][] board; + protected CellContent[][] board; protected State state; protected Player player; private int size; @@ -42,10 +42,10 @@ public void updateState(int row, int col) { boolean firstDiagonalWin = true; boolean secondDiagonalWin = true; for (int i = 0; i < size; i++) { - verticalWin &= board[i][col].equals(player.toString()); - horizontalWin &= board[row][i].equals(player.toString()); - firstDiagonalWin &= board[i][i].equals(player.toString()); - secondDiagonalWin &= board[i][size - 1 - i].equals(player.toString()); + verticalWin &= board[i][col].equals(player.toCellContent()); + horizontalWin &= board[row][i].equals(player.toCellContent()); + firstDiagonalWin &= board[i][i].equals(player.toCellContent()); + secondDiagonalWin &= board[i][size - 1 - i].equals(player.toCellContent()); } if (horizontalWin || verticalWin || firstDiagonalWin || secondDiagonalWin) { state = player.getWinState(); @@ -58,11 +58,11 @@ public void updateState(int row, int col) { * @see Board#reset() */ @Override - public String[][] reset() { - board = new String[size][size]; - for (String[] row : board) { + public CellContent[][] reset() { + board = new CellContent[size][size]; + for (CellContent[] row : board) { for (int i = 0; i < size; i++) { - row[i] = " "; + row[i] = CellContent.EMPTY; } } state = State.RUNNING; diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java index 2d203aa..42a8923 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java @@ -11,7 +11,7 @@ public interface Board { * @return new state of board */ @NotNull - String[][] move(int row, int col); + CellContent[][] move(int row, int col); /** * Returns current state of the game. @@ -22,5 +22,5 @@ public interface Board { /** * Sets board to starting state. */ - String[][] reset(); + CellContent[][] reset(); } diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/CellContent.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/CellContent.java new file mode 100644 index 0000000..0a393ca --- /dev/null +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/CellContent.java @@ -0,0 +1,14 @@ +package ru.spbau.mit.kazakov.TicTacToe; + +/** + * Enum for describing content of a cell. + */ +public enum CellContent { + X, O, + EMPTY { + @Override + public String toString() { + return " "; + } + } +} diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java index 3623192..6db59f2 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoard.java @@ -18,8 +18,8 @@ public HotSeatBoard(int size) { */ @NotNull @Override - public String[][] move(int row, int col) { - board[row][col] = player.toString(); + public CellContent[][] move(int row, int col) { + board[row][col] = player.toCellContent(); updateState(row, col); player = player.next(); return board; diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java index 6e4f955..68f86ca 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java @@ -13,6 +13,13 @@ public Player next() { return this == X ? O : X; } + /** + * Returns content of cell corresponding to current turn's owner. + */ + public CellContent toCellContent() { + return this == X ? CellContent.X : CellContent.O; + } + /** * Returns state of game corresponding to victory of current turn's owner. */ diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java index 2717341..94db018 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/TicTacToe.java @@ -24,6 +24,17 @@ */ public class TicTacToe extends Application { private static final int BOARD_SIZE = 3; + private static final String EASY_AI = "Easy"; + private static final String MEDIUM_AI = "Medium"; + private static final String SINGLE_PLAYER_LABEL = "SinglePlayer"; + private static final String MULTI_PLAYER_LABEL = "MultiPlayer"; + private static final String STATS_LABEL = "Stats"; + private static final String EXIT_LABEL = "Exit"; + private static final String RESTART_LABEL = "Restart"; + private static final String MAIN_MENU_LABEL = "Main menu"; + private static final String APP_TITLE = "Nevermind"; + private static final String MAIN_MENU_TITLE = "Welcome"; + private Stage primaryStage; private Scene mainMenuScene; private Scene boardScene; @@ -43,7 +54,7 @@ public void start(Stage primaryStage) { initializeBoardScene(); initializeStatsScene(); primaryStage.setScene(mainMenuScene); - primaryStage.setTitle("Nevermind"); + primaryStage.setTitle(APP_TITLE); primaryStage.show(); primaryStage.requestFocus(); } @@ -52,7 +63,7 @@ public void start(Stage primaryStage) { * Creates and initializes main menu scene. */ private void initializeMainMenuScene() { - Text sceneTitle = new Text("Welcome"); + Text sceneTitle = new Text(MAIN_MENU_TITLE); sceneTitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20)); sceneTitle.setFill(Color.WHITE); @@ -63,18 +74,18 @@ private void initializeMainMenuScene() { ComboBox AILevelComboBox = new ComboBox<>(); AILevelComboBox.setFocusTraversable(false); AILevelComboBox.setMaxWidth(Double.MAX_VALUE); - AILevelComboBox.getItems().addAll("Medium", "Easy"); - AILevelComboBox.setValue("Medium"); + AILevelComboBox.getItems().addAll(MEDIUM_AI, EASY_AI); + AILevelComboBox.setValue(MEDIUM_AI); - Button singlePlayerButton = new Button("SinglePlayer"); + Button singlePlayerButton = new Button(SINGLE_PLAYER_LABEL); singlePlayerButton.setFocusTraversable(false); singlePlayerButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); singlePlayerButton.setOnAction(value -> { switch (AILevelComboBox.getValue()) { - case "Medium": + case MEDIUM_AI: board = new AIBoard(BOARD_SIZE, AILevel.MEDIUM); break; - case "Easy": + case EASY_AI: board = new AIBoard(BOARD_SIZE, AILevel.EASY); break; } @@ -87,7 +98,7 @@ private void initializeMainMenuScene() { HBox.setHgrow(singlePlayerButton, Priority.ALWAYS); singlePlayerMenu.getChildren().addAll(singlePlayerButton, AILevelComboBox); - Button multiPlayerButton = new Button("MultiPlayer"); + Button multiPlayerButton = new Button(MULTI_PLAYER_LABEL); multiPlayerButton.setFocusTraversable(false); multiPlayerButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); multiPlayerButton.setOnAction(value -> { @@ -97,12 +108,12 @@ private void initializeMainMenuScene() { primaryStage.setScene(boardScene); }); - Button statsButton = new Button("Stats"); + Button statsButton = new Button(STATS_LABEL); statsButton.setFocusTraversable(false); statsButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); statsButton.setOnAction(value -> primaryStage.setScene(statsScene)); - Button exitButton = new Button("Exit"); + Button exitButton = new Button(EXIT_LABEL); exitButton.setFocusTraversable(false); exitButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); exitButton.setOnAction(value -> Platform.exit()); @@ -120,12 +131,12 @@ private void initializeMainMenuScene() { * Creates and initializes board scene. */ private void initializeBoardScene() { - Button mainMenuButton = new Button("Main menu"); + Button mainMenuButton = new Button(MAIN_MENU_LABEL); mainMenuButton.setFocusTraversable(false); mainMenuButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); mainMenuButton.setOnAction(value -> primaryStage.setScene(mainMenuScene)); - Button restartButton = new Button("Restart"); + Button restartButton = new Button(RESTART_LABEL); restartButton.setFocusTraversable(false); restartButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); restartButton.setOnAction(value -> { @@ -201,14 +212,14 @@ private void initializeStatsScene() { statsPane.setPadding(new Insets(10, 25, 10, 25)); statsPane.setSpacing(10); - Text sceneTitle = new Text("Stats"); + Text sceneTitle = new Text(STATS_LABEL); sceneTitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20)); sceneTitle.setFill(Color.WHITE); ListView listView = new ListView<>(stats); listView.setFocusTraversable(false); - Button mainMenuButton = new Button("Main menu"); + Button mainMenuButton = new Button(MAIN_MENU_LABEL); mainMenuButton.setFocusTraversable(false); mainMenuButton.setOnAction(value -> primaryStage.setScene(mainMenuScene)); @@ -259,11 +270,11 @@ private void disableBoard() { * * @param boardState specified state of the board */ - private void updateBoardScene(String[][] boardState) { + private void updateBoardScene(CellContent[][] boardState) { for (int i = 0; i < cells.length; i++) { for (int j = 0; j < cells.length; j++) { - cells[i][j].setDisable(!boardState[i][j].equals(" ")); - cells[i][j].setText(boardState[i][j]); + cells[i][j].setDisable(!boardState[i][j].equals(CellContent.EMPTY)); + cells[i][j].setText(boardState[i][j].toString()); } } } diff --git a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java index cdd248e..03acec4 100644 --- a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java +++ b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AIBoardTest.java @@ -29,179 +29,169 @@ public void testMediumLevelConstructor() { @Test public void testMakeTurnEasyAI() { Board board = new AIBoard(3, AILevel.EASY); - String[][] boardState = board.move(2, 1); - int diff = 0; - for (int i = 0; i < boardState.length; i++) { - for (int j = 0; j < boardState.length; j++) { - if (!boardState[i][j].equals(" ") && !(i == 2 && j == 1)) { - assertEquals(Player.O.toString(), boardState[i][j]); - diff++; - } - } - } - assertEquals(1, diff); - assertEquals(Player.X.toString(), boardState[2][1]); + CellContent[][] boardState = board.move(2, 1); + checkEnemyFirstTurn(boardState); + assertEquals(CellContent.X, boardState[2][1]); } @Test public void testMakeTurnMediumAISimple() { Board board = new AIBoard(3, AILevel.EASY); - String[][] boardState = board.move(2, 1); - int diff = 0; - for (int i = 0; i < boardState.length; i++) { - for (int j = 0; j < boardState.length; j++) { - if (!boardState[i][j].equals(" ") && !(i == 2 && j == 1)) { - assertEquals(Player.O.toString(), boardState[i][j]); - diff++; - } - } - } - - assertEquals(1, diff); - assertEquals(Player.X.toString(), boardState[2][1]); + CellContent[][] boardState = board.move(2, 1); + checkEnemyFirstTurn(boardState); + assertEquals(CellContent.X, boardState[2][1]); } @Test public void testMakeTurnMediumAIPreventHorizontalLose() throws IllegalAccessException { - Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.X.toString(), " ", " "}, - {Player.O.toString(), " ", " "}, - {" ", " ", " "}}; - boardField.set(board, boardState); - boardState = board.move(0, 1); - assertArrayEquals(new String[][]{{Player.X.toString(), Player.X.toString(), Player.O.toString()}, - {Player.O.toString(), " ", " "}, {" ", " ", " "}}, boardState); + CellContent[][] startingState = new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.O, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.X, CellContent.X, CellContent.O}, + {CellContent.O, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}; + checkStateAfterPlayerTurn(startingState, expectedState, 0, 1); } @Test public void testMakeTurnMediumAIPreventVerticalLose() throws IllegalAccessException { - Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.X.toString(), " ", Player.O.toString()}, - {" ", " ", " "}, - {" ", " ", " "}}; - boardField.set(board, boardState); - boardState = board.move(1, 0); - assertArrayEquals(new String[][]{{Player.X.toString(), " ", Player.O.toString()}, - {Player.X.toString(), " ", " "}, {Player.O.toString(), " ", " "}}, boardState); + CellContent[][] startingState = new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.O}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.O}, + {CellContent.X, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.O, CellContent.EMPTY, CellContent.EMPTY}}; + checkStateAfterPlayerTurn(startingState, expectedState, 1, 0); } @Test public void testMakeTurnMediumAIPreventFirstDiagonalLose() throws IllegalAccessException { - Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.X.toString(), " ", Player.O.toString()}, - {" ", " ", " "}, - {" ", " ", " "}}; - boardField.set(board, boardState); - boardState = board.move(1, 1); - assertArrayEquals(new String[][]{{Player.X.toString(), " ", Player.O.toString()}, - {" ", Player.X.toString(), " "}, {" ", " ", Player.O.toString()}}, boardState); + CellContent[][] startingState = new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.O}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.O}, + {CellContent.EMPTY, CellContent.X, CellContent.EMPTY}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.O}}; + checkStateAfterPlayerTurn(startingState, expectedState, 1, 1); } @Test public void testMakeTurnMediumAIPreventSecondDiagonalLose() throws IllegalAccessException { - Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.O.toString(), " ", Player.X.toString()}, - {" ", " ", " "}, - {" ", " ", " "}}; - boardField.set(board, boardState); - boardState = board.move(1, 1); - assertArrayEquals(new String[][]{{Player.O.toString(), " ", Player.X.toString()}, - {" ", Player.X.toString(), " "}, {Player.O.toString(), " ", " "}}, boardState); + CellContent[][] startingState = new CellContent[][]{{CellContent.O, CellContent.EMPTY, CellContent.X}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.O, CellContent.EMPTY, CellContent.X}, + {CellContent.EMPTY, CellContent.X, CellContent.EMPTY}, + {CellContent.O, CellContent.EMPTY, CellContent.EMPTY}}; + checkStateAfterPlayerTurn(startingState, expectedState, 1, 1); } @Test public void testMakeTurnMediumAIHorizontalWin() throws IllegalAccessException { - Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.O.toString(), " ", Player.O.toString()}, - {Player.X.toString(), " ", " "}, - {Player.X.toString(), " ", " "}}; - boardField.set(board, boardState); - boardState = board.move(1, 1); - assertArrayEquals(new String[][]{{Player.O.toString(), Player.O.toString(), Player.O.toString()}, - {Player.X.toString(), Player.X.toString(), " "}, {Player.X.toString(), " ", " "}}, boardState); + CellContent[][] startingState = new CellContent[][]{{CellContent.O, CellContent.EMPTY, CellContent.O}, + {CellContent.X, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.X, CellContent.EMPTY, CellContent.EMPTY}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.O, CellContent.O, CellContent.O}, + {CellContent.X, CellContent.X, CellContent.EMPTY}, + {CellContent.X, CellContent.EMPTY, CellContent.EMPTY}}; + checkStateAfterPlayerTurn(startingState, expectedState, 1, 1); } @Test public void testMakeTurnMediumAIVerticalWin() throws IllegalAccessException { - Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.O.toString(), Player.X.toString(), Player.X.toString()}, - {Player.O.toString(), " ", " "}, - {" ", " ", " "}}; - boardField.set(board, boardState); - boardState = board.move(1, 1); - assertArrayEquals(new String[][]{{Player.O.toString(), Player.X.toString(), Player.X.toString()}, - {Player.O.toString(), Player.X.toString(), " "}, {Player.O.toString(), " ", " "}}, boardState); + CellContent[][] startingState = new CellContent[][]{{CellContent.O, CellContent.X, CellContent.X}, + {CellContent.O, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.O, CellContent.X, CellContent.X}, + {CellContent.O, CellContent.X, CellContent.EMPTY}, + {CellContent.O, CellContent.EMPTY, CellContent.EMPTY}}; + checkStateAfterPlayerTurn(startingState, expectedState, 1, 1); } @Test public void testMakeTurnMediumAIFirstDiagonalWin() throws IllegalAccessException { - Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.O.toString(), Player.X.toString(), Player.X.toString()}, - {" ", " ", " "}, - {" ", " ", Player.O.toString()}}; - boardField.set(board, boardState); - boardState = board.move(2, 0); - assertArrayEquals(new String[][]{{Player.O.toString(), Player.X.toString(), Player.X.toString()}, - {" ", Player.O.toString(), " "}, {Player.X.toString(), " ", Player.O.toString()}}, boardState); + CellContent[][] startingState = new CellContent[][]{{CellContent.O, CellContent.X, CellContent.X}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.O}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.O, CellContent.X, CellContent.X}, + {CellContent.EMPTY, CellContent.O, CellContent.EMPTY}, + {CellContent.X, CellContent.EMPTY, CellContent.O}}; + checkStateAfterPlayerTurn(startingState, expectedState, 2, 0); } @Test public void testMakeTurnMediumAISecondDiagonalWin() throws IllegalAccessException { - Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.X.toString(), Player.X.toString(), Player.O.toString()}, - {" ", " ", " "}, - {Player.O.toString(), " ", " "}}; - boardField.set(board, boardState); - boardState = board.move(1, 0); - assertArrayEquals(new String[][]{{Player.X.toString(), Player.X.toString(), Player.O.toString()}, - {Player.X.toString(), Player.O.toString(), " "}, {Player.O.toString(), " ", " "}}, boardState); + CellContent[][] startingState = new CellContent[][]{{CellContent.X, CellContent.X, CellContent.O}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.O, CellContent.EMPTY, CellContent.EMPTY}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.X, CellContent.X, CellContent.O}, + {CellContent.X, CellContent.O, CellContent.EMPTY}, + {CellContent.O, CellContent.EMPTY, CellContent.EMPTY}}; + checkStateAfterPlayerTurn(startingState, expectedState, 1, 0); } @Test public void testMakeTurnPlayerHorizontalWin() throws IllegalAccessException { - Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.X.toString(), " ", Player.X.toString()}, - {Player.O.toString(), " ", Player.O.toString()}, - {" ", " ", " "}}; - boardField.set(board, boardState); - boardState = board.move(0, 1); - assertArrayEquals(new String[][]{{Player.X.toString(), Player.X.toString(), Player.X.toString()}, - {Player.O.toString(), " ", Player.O.toString()}, {" ", " ", " "}}, boardState); + CellContent[][] startingState = new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.X}, + {CellContent.O, CellContent.EMPTY, CellContent.O}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.X, CellContent.X, CellContent.X}, + {CellContent.O, CellContent.EMPTY, CellContent.O}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}; + checkStateAfterPlayerTurn(startingState, expectedState, 0, 1); } @Test public void testMakeTurnPlayerVerticalWin() throws IllegalAccessException { - Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.X.toString(), " ", Player.O.toString()}, - {Player.X.toString(), " ", Player.O.toString()}, - {" ", " ", " "}}; - boardField.set(board, boardState); - boardState = board.move(2, 0); - assertArrayEquals(new String[][]{{Player.X.toString(), " ", Player.O.toString()}, - {Player.X.toString(), " ", Player.O.toString()}, {Player.X.toString(), " ", " "}}, boardState); + CellContent[][] startingState = new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.O}, + {CellContent.X, CellContent.EMPTY, CellContent.O}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.O}, + {CellContent.X, CellContent.EMPTY, CellContent.O}, + {CellContent.X, CellContent.EMPTY, CellContent.EMPTY}}; + checkStateAfterPlayerTurn(startingState, expectedState, 2, 0); } @Test public void testMakeTurnPlayerFirstDiagonalWin() throws IllegalAccessException { - Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.X.toString(), " ", Player.O.toString()}, - {" ", Player.X.toString(), Player.O.toString()}, - {" ", " ", " "}}; - boardField.set(board, boardState); - boardState = board.move(2, 2); - assertArrayEquals(new String[][]{{Player.X.toString(), " ", Player.O.toString()}, - {" ", Player.X.toString(), Player.O.toString()}, {" ", " ", Player.X.toString()}}, boardState); + CellContent[][] startingState = new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.O}, + {CellContent.EMPTY, CellContent.X, CellContent.O}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.O}, + {CellContent.EMPTY, CellContent.X, CellContent.O}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.X}}; + checkStateAfterPlayerTurn(startingState, expectedState, 2, 2); } @Test public void testMakeTurnPlayerSecondDiagonalWin() throws IllegalAccessException { + CellContent[][] startingState = new CellContent[][]{{CellContent.O, CellContent.EMPTY, CellContent.X}, + {CellContent.EMPTY, CellContent.X, CellContent.O}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}; + CellContent[][] expectedState = new CellContent[][]{{CellContent.O, CellContent.EMPTY, CellContent.X}, + {CellContent.EMPTY, CellContent.X, CellContent.O}, + {CellContent.X, CellContent.EMPTY, CellContent.EMPTY}}; + checkStateAfterPlayerTurn(startingState, expectedState, 2, 0); + } + + private void checkEnemyFirstTurn(CellContent[][] boardState) { + int diff = 0; + for (int i = 0; i < boardState.length; i++) { + for (int j = 0; j < boardState.length; j++) { + if (!boardState[i][j].equals(CellContent.EMPTY) && !(i == 2 && j == 1)) { + assertEquals(CellContent.O, boardState[i][j]); + diff++; + } + } + } + + assertEquals(1, diff); + } + + private void checkStateAfterPlayerTurn(CellContent[][] staringState, CellContent[][] expectedState, int row, int col) throws IllegalAccessException { Board board = new AIBoard(3, AILevel.MEDIUM); - String[][] boardState = new String[][]{{Player.O.toString(), " ", Player.X.toString()}, - {" ", Player.X.toString(), Player.O.toString()}, - {" ", " ", " "}}; - boardField.set(board, boardState); - boardState = board.move(2, 0); - assertArrayEquals(new String[][]{{Player.O.toString(), " ", Player.X.toString()}, - {" ", Player.X.toString(), Player.O.toString()}, {Player.X.toString(), " ", " "}}, boardState); + boardField.set(board, staringState); + CellContent[][] actualState = board.move(row, col); + assertArrayEquals(expectedState, actualState); } } \ No newline at end of file diff --git a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java index 4038bd3..03e1ba9 100644 --- a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java +++ b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/AbstractBoardTest.java @@ -94,8 +94,10 @@ public void testReset() { board.move(2, 2); board.move(2, 0); board.reset(); - assertArrayEquals(new String[][]{{Player.X.toString(), " ", " "}, - {" ", " ", " "}, {" ", " ", " "}}, board.move(0, 0)); + assertArrayEquals(new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}, + board.move(0, 0)); assertEquals(State.RUNNING, board.getState()); } } \ No newline at end of file diff --git a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java index 6923eac..79b8a6f 100644 --- a/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java +++ b/Tic-tac-toe/src/test/java/ru/spbau/mit/kazakov/TicTacToe/HotSeatBoardTest.java @@ -8,18 +8,18 @@ public class HotSeatBoardTest { @Test public void testMakeTurnSimple() { Board board = new HotSeatBoard(3); - String[][] boardState = board.move(1, 0); - assertArrayEquals(new String[][]{{" ", " ", " "}, {Player.X.toString(), " ", " "}, - {" ", " ", " "}}, boardState); + CellContent[][] boardState = board.move(1, 0); + assertArrayEquals(new CellContent[][]{{CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}, {CellContent.X, CellContent.EMPTY, CellContent.EMPTY}, + {CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}}, boardState); } @Test public void testMakeTurnTwice() { Board board = new HotSeatBoard(3); board.move(1, 1); - String[][] boardState = board.move(2, 0); - assertArrayEquals(new String[][]{{" ", " ", " "}, {" ", Player.X.toString(), " "}, - {Player.O.toString(), " ", " "}}, boardState); + CellContent[][] boardState = board.move(2, 0); + assertArrayEquals(new CellContent[][]{{CellContent.EMPTY, CellContent.EMPTY, CellContent.EMPTY}, {CellContent.EMPTY, CellContent.X, CellContent.EMPTY}, + {CellContent.O, CellContent.EMPTY, CellContent.EMPTY}}, boardState); } @Test @@ -31,10 +31,10 @@ public void testMakeTurn() { board.move(0, 2); board.move(2, 0); board.move(1, 0); - String[][] boardState = board.move(2, 1); + CellContent[][] boardState = board.move(2, 1); - assertArrayEquals(new String[][]{{Player.X.toString(), " ", Player.O.toString()}, - {Player.O.toString(), Player.O.toString(), " "}, - {Player.X.toString(), Player.X.toString(), Player.X.toString()}}, boardState); + assertArrayEquals(new CellContent[][]{{CellContent.X, CellContent.EMPTY, CellContent.O}, + {CellContent.O, CellContent.O, CellContent.EMPTY}, + {CellContent.X, CellContent.X, CellContent.X}}, boardState); } } \ No newline at end of file From 127bd037c6cba73871fe8864b960e1003a03e6bf Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 30 Apr 2018 17:58:05 +0300 Subject: [PATCH 12/13] Deleted .idea --- Tic-tac-toe/.idea/compiler.xml | 9 ------- Tic-tac-toe/.idea/misc.xml | 24 ------------------- Tic-tac-toe/.idea/modules.xml | 10 -------- Tic-tac-toe/.idea/modules/Tic-tac-toe.iml | 13 ---------- .../.idea/modules/Tic-tac-toe_main.iml | 14 ----------- .../.idea/modules/Tic-tac-toe_test.iml | 18 -------------- Tic-tac-toe/.idea/vcs.xml | 6 ----- 7 files changed, 94 deletions(-) delete mode 100644 Tic-tac-toe/.idea/compiler.xml delete mode 100644 Tic-tac-toe/.idea/misc.xml delete mode 100644 Tic-tac-toe/.idea/modules.xml delete mode 100644 Tic-tac-toe/.idea/modules/Tic-tac-toe.iml delete mode 100644 Tic-tac-toe/.idea/modules/Tic-tac-toe_main.iml delete mode 100644 Tic-tac-toe/.idea/modules/Tic-tac-toe_test.iml delete mode 100644 Tic-tac-toe/.idea/vcs.xml diff --git a/Tic-tac-toe/.idea/compiler.xml b/Tic-tac-toe/.idea/compiler.xml deleted file mode 100644 index bfd4f70..0000000 --- a/Tic-tac-toe/.idea/compiler.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/Tic-tac-toe/.idea/misc.xml b/Tic-tac-toe/.idea/misc.xml deleted file mode 100644 index 85cb263..0000000 --- a/Tic-tac-toe/.idea/misc.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/Tic-tac-toe/.idea/modules.xml b/Tic-tac-toe/.idea/modules.xml deleted file mode 100644 index 1d3e77a..0000000 --- a/Tic-tac-toe/.idea/modules.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Tic-tac-toe/.idea/modules/Tic-tac-toe.iml b/Tic-tac-toe/.idea/modules/Tic-tac-toe.iml deleted file mode 100644 index 397d067..0000000 --- a/Tic-tac-toe/.idea/modules/Tic-tac-toe.iml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tic-tac-toe/.idea/modules/Tic-tac-toe_main.iml b/Tic-tac-toe/.idea/modules/Tic-tac-toe_main.iml deleted file mode 100644 index 2f865a4..0000000 --- a/Tic-tac-toe/.idea/modules/Tic-tac-toe_main.iml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tic-tac-toe/.idea/modules/Tic-tac-toe_test.iml b/Tic-tac-toe/.idea/modules/Tic-tac-toe_test.iml deleted file mode 100644 index 4e7335c..0000000 --- a/Tic-tac-toe/.idea/modules/Tic-tac-toe_test.iml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tic-tac-toe/.idea/vcs.xml b/Tic-tac-toe/.idea/vcs.xml deleted file mode 100644 index 6c0b863..0000000 --- a/Tic-tac-toe/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 365f03b66f03891fa990bed5ce34c2656d2d9bdd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 30 Apr 2018 18:34:46 +0300 Subject: [PATCH 13/13] Added missing @NotNull --- .../src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java | 1 + .../java/ru/spbau/mit/kazakov/TicTacToe/CellContent.java | 3 +++ .../src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java index 42a8923..188a833 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Board.java @@ -22,5 +22,6 @@ public interface Board { /** * Sets board to starting state. */ + @NotNull CellContent[][] reset(); } diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/CellContent.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/CellContent.java index 0a393ca..95b0223 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/CellContent.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/CellContent.java @@ -1,5 +1,7 @@ package ru.spbau.mit.kazakov.TicTacToe; +import org.jetbrains.annotations.NotNull; + /** * Enum for describing content of a cell. */ @@ -7,6 +9,7 @@ public enum CellContent { X, O, EMPTY { @Override + @NotNull public String toString() { return " "; } diff --git a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java index 68f86ca..99bf0e5 100644 --- a/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java +++ b/Tic-tac-toe/src/main/java/ru/spbau/mit/kazakov/TicTacToe/Player.java @@ -1,5 +1,7 @@ package ru.spbau.mit.kazakov.TicTacToe; +import org.jetbrains.annotations.NotNull; + /** * Enum for describing current turn's owner. */ @@ -9,6 +11,7 @@ public enum Player { /** * Returns owner of the next turn. */ + @NotNull public Player next() { return this == X ? O : X; } @@ -16,6 +19,7 @@ public Player next() { /** * Returns content of cell corresponding to current turn's owner. */ + @NotNull public CellContent toCellContent() { return this == X ? CellContent.X : CellContent.O; } @@ -23,6 +27,7 @@ public CellContent toCellContent() { /** * Returns state of game corresponding to victory of current turn's owner. */ + @NotNull public State getWinState() { return this == X ? State.X_WINS : State.O_WINS; }