diff --git a/config.json b/config.json index 92dfd05b1..d85ddef5f 100644 --- a/config.json +++ b/config.json @@ -1275,6 +1275,19 @@ ], "difficulty": 6 }, + { + "slug": "camicia", + "name": "Camicia", + "uuid": "b4f7c3b0-6d3c-45e2-a328-05e09c7467f4", + "practices": [], + "prerequisites": [ + "strings", + "for-loops", + "arrays", + "if-else-statements" + ], + "difficulty": 6 + }, { "slug": "etl", "name": "ETL", diff --git a/exercises/practice/camicia/.docs/instructions.md b/exercises/practice/camicia/.docs/instructions.md new file mode 100644 index 000000000..9da5c14db --- /dev/null +++ b/exercises/practice/camicia/.docs/instructions.md @@ -0,0 +1,84 @@ +# Instructions + +In this exercise, you will simulate a game very similar to the classic card game **CamiciaGame**. +Your program will receive the initial configuration of two players' decks and must simulate the game until it ends (or detect that it will never end). + +## Rules + +- The deck is split between **two players**. + The player's cards are read from left to right, where the leftmost card is the top of the deck. +- A round consists of both players playing at least one card. +- Players take turns placing the **top card** of their deck onto a central pile. +- If the card is a **number card** (2-10), play simply passes to the other player. +- If the card is a **payment card**, a penalty must be paid: + - **J** → opponent must pay 1 card + - **Q** → opponent must pay 2 cards + - **K** → opponent must pay 3 cards + - **A** → opponent must pay 4 cards +- If the player paying a penalty reveals another payment card, that player stops paying the penalty. + The other player must then pay a penalty based on the new payment card. +- If the penalty is fully paid without interruption, the player who placed the **last payment card** collects the central pile and places it at the bottom of their deck. + That player then starts the next round. +- If a player runs out of cards and is unable to play a card (either while paying a penalty or when it is their turn), the other player collects the central pile. +- The moment when a player collects cards from the central pile is called a **trick**. +- If a player has all the cards in their possession after a trick, the game **ends**. +- The game **enters a loop** as soon as the decks are identical to what they were earlier during the game, **not** counting number cards! + +## Examples + +A small example of a match that ends. + +| Round | Player A | Player B | Pile | Penalty Due | +| :---- | :----------- | :------------------------- | :------------------------- | :---------- | +| 1 | 2 A 7 8 Q 10 | 3 4 5 6 K 9 J | | - | +| 1 | A 7 8 Q 10 | 3 4 5 6 K 9 J | 2 | - | +| 1 | A 7 8 Q 10 | 4 5 6 K 9 J | 2 3 | - | +| 1 | 7 8 Q 10 | 4 5 6 K 9 J | 2 3 A | Player B: 4 | +| 1 | 7 8 Q 10 | 5 6 K 9 J | 2 3 A 4 | Player B: 3 | +| 1 | 7 8 Q 10 | 6 K 9 J | 2 3 A 4 5 | Player B: 2 | +| 1 | 7 8 Q 10 | K 9 J | 2 3 A 4 5 6 | Player B: 1 | +| 1 | 7 8 Q 10 | 9 J | 2 3 A 4 5 6 K | Player A: 3 | +| 1 | 8 Q 10 | 9 J | 2 3 A 4 5 6 K 7 | Player A: 2 | +| 1 | Q 10 | 9 J | 2 3 A 4 5 6 K 7 8 | Player A: 1 | +| 1 | 10 | 9 J | 2 3 A 4 5 6 K 7 8 Q | Player B: 2 | +| 1 | 10 | J | 2 3 A 4 5 6 K 7 8 Q 9 | Player B: 1 | +| 1 | 10 | - | 2 3 A 4 5 6 K 7 8 Q 9 J | Player A: 1 | +| 1 | - | - | 2 3 A 4 5 6 K 7 8 Q 9 J 10 | - | +| 2 | - | 2 3 A 4 5 6 K 7 8 Q 9 J 10 | - | - | + +status: `"finished"`, cards: 13, tricks: 1 + +This is a small example of a match that loops. + +| Round | Player A | Player B | Pile | Penalty Due | +| :---- | :------- | :------- | :---- | :---------- | +| 1 | J 2 3 | 4 J 5 | - | - | +| 1 | 2 3 | 4 J 5 | J | Player B: 1 | +| 1 | 2 3 | J 5 | J 4 | - | +| 2 | 2 3 J 4 | J 5 | - | - | +| 2 | 3 J 4 | J 5 | 2 | - | +| 2 | 3 J 4 | 5 | 2 J | Player A: 1 | +| 2 | J 4 | 5 | 2 J 3 | - | +| 3 | J 4 | 5 2 J 3 | - | - | +| 3 | J 4 | 2 J 3 | 5 | - | +| 3 | 4 | 2 J 3 | 5 J | Player B: 1 | +| 3 | 4 | J 3 | 5 J 2 | - | +| 4 | 4 5 J 2 | J 3 | - | - | + +The start of round 4 matches the start of round 2. +Recall, the value of the number cards does not matter. + +status: `"loop"`, cards: 8, tricks: 3 + +## Your Task + +- Using the input, simulate the game following the rules above. +- Determine the following information regarding the game: + - **Status**: `"finished"` or `"loop"` + - **Cards**: total number of cards played throughout the game + - **Tricks**: number of times the central pile was collected + +~~~~exercism/advanced +For those who want to take on a more exciting challenge, the hunt for other records for the longest game with an end is still open. +There are 653,534,134,886,878,245,000 (approximately 654 quintillion) possibilities, and we haven't calculated them all yet! +~~~~ diff --git a/exercises/practice/camicia/.meta/config.json b/exercises/practice/camicia/.meta/config.json new file mode 100644 index 000000000..b8d1c3386 --- /dev/null +++ b/exercises/practice/camicia/.meta/config.json @@ -0,0 +1,23 @@ +{ + "authors": [], + "contributors": [ + "thibault2705" + ], + "files": { + "solution": [ + "src/main/java/Camicia.java" + ], + "test": [ + "src/test/java/CamiciaTest.java" + ], + "example": [ + ".meta/src/reference/java/Camicia.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Simulate the card game and determine whether the match ends or enters an infinite loop.", + "source": "Beggar-My-Neighbour", + "source_url": "https://www.richardpmann.com/beggar-my-neighbour-records.html" +} diff --git a/exercises/practice/camicia/.meta/src/reference/java/Camicia.java b/exercises/practice/camicia/.meta/src/reference/java/Camicia.java new file mode 100644 index 000000000..6b585989b --- /dev/null +++ b/exercises/practice/camicia/.meta/src/reference/java/Camicia.java @@ -0,0 +1,172 @@ +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Camicia { + + private Status status; + private int cards; + private int tricks; + + private enum Player { + PLAYER_A, PLAYER_B + } + + private enum Status { + FINISHED, LOOP + } + + private static int penaltyOf(String card) { + return switch (card) { + case "J" -> 1; + case "Q" -> 2; + case "K" -> 3; + case "A" -> 4; + default -> 0; + }; + } + + private static boolean isPaymentCard(String card) { + return penaltyOf(card) > 0; + } + + /** + * Return a snapshot of the current state + */ + private static String stateKey(Deque deckA, Deque deckB) { + StringBuilder sb = new StringBuilder(); + + for (String card : deckA) { + sb.append(isPaymentCard(card) ? card : "N"); + } + + sb.append('|'); + + for (String card : deckB) { + sb.append(isPaymentCard(card) ? card : "N"); + } + + return sb.toString(); + } + + private static Player otherPlayer(Player player) { + return player == Player.PLAYER_A ? Player.PLAYER_B : Player.PLAYER_A; + } + + private static Deque deckOf(Player player, Deque deckA, Deque deckB) { + return player == Player.PLAYER_A ? deckA : deckB; + } + + public void simulateGame(List playerA, List playerB) { + Deque deckA = new ArrayDeque<>(playerA); + Deque deckB = new ArrayDeque<>(playerB); + + int cardsPlayed = 0; + int tricksCount = 0; + Player current = Player.PLAYER_A; + + Set seenStates = new HashSet<>(); + + while (true) { + String key = stateKey(deckA, deckB); + // Key already exists, which means this is a loop + if (!seenStates.add(key)) { + finishGame(Status.LOOP, cardsPlayed, tricksCount); + return; + } + + // Otherwise, play next round + RoundResult result = playRound(deckA, deckB, current); + + cardsPlayed += result.pileSize(); + tricksCount++; + + // Check if someone wins and finish the game + if (hasWinner(deckA, deckB)) { + finishGame(Status.FINISHED, cardsPlayed, tricksCount); + return; + } + + // Otherwise, play next round + current = result.nextStarter(); + } + } + + private RoundResult playRound(Deque deckA, Deque deckB, Player startingPlayer) { + Deque pile = new ArrayDeque<>(); // cards played in this round + Player currentPlayer = startingPlayer; + int pendingPenalty = 0; + + while (true) { + Deque currentPlayerDeck = deckOf(currentPlayer, deckA, deckB); + Player opponent = otherPlayer(currentPlayer); + Deque opponentDeck = deckOf(opponent, deckA, deckB); + + // Current player deck is empty, opponent collects all pile, end this round + if (currentPlayerDeck.isEmpty()) { + opponentDeck.addAll(pile); + return new RoundResult(opponent, pile.size()); + } + + // Otherwise, current player plays 1 card, add to pile + String card = currentPlayerDeck.poll(); + pile.addLast(card); + + // Current player must pay off pending penalty + if (pendingPenalty > 0) { + // And player reveals a payment card + if (isPaymentCard(card)) { + // reset penalty based on new card, switch turn + pendingPenalty = penaltyOf(card); + currentPlayer = opponent; + } else { + // Otherwise, deduct penalty + pendingPenalty--; + // No pending penalty + if (pendingPenalty == 0) { + // Opponent collects all pile and win the round + deckOf(opponent, deckA, deckB).addAll(pile); + return new RoundResult(opponent, pile.size()); + } + } + } else { + // Normal gameplay, without pending penalty + // If player reveals a payment card, update penalty + if (isPaymentCard(card)) { + pendingPenalty = penaltyOf(card); + } + currentPlayer = opponent; + } + } + } + + private void finishGame(Status status, int cardsPlayed, int tricksCount) { + this.status = status; + this.cards = cardsPlayed; + this.tricks = tricksCount; + } + + private boolean hasWinner(Deque deckA, Deque deckB) { + return deckA.isEmpty() || deckB.isEmpty(); + } + + public String getStatus() { + return status != null ? status.name().toLowerCase() : ""; + } + + public int getCards() { + return cards; + } + + public int getTricks() { + return tricks; + } + + /** + * Immutable round result + */ + private record RoundResult(Player nextStarter, int pileSize) { + } +} \ No newline at end of file diff --git a/exercises/practice/camicia/build.gradle b/exercises/practice/camicia/build.gradle new file mode 100644 index 000000000..d28f35dee --- /dev/null +++ b/exercises/practice/camicia/build.gradle @@ -0,0 +1,25 @@ +plugins { + id "java" +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform("org.junit:junit-bom:5.10.0") + testImplementation "org.junit.jupiter:junit-jupiter" + testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} + +test { + useJUnitPlatform() + + testLogging { + exceptionFormat = "full" + showStandardStreams = true + events = ["passed", "failed", "skipped"] + } +} diff --git a/exercises/practice/camicia/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/camicia/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..e6441136f Binary files /dev/null and b/exercises/practice/camicia/gradle/wrapper/gradle-wrapper.jar differ diff --git a/exercises/practice/camicia/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/camicia/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..2deab89d5 --- /dev/null +++ b/exercises/practice/camicia/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/exercises/practice/camicia/gradlew b/exercises/practice/camicia/gradlew new file mode 100755 index 000000000..1aa94a426 --- /dev/null +++ b/exercises/practice/camicia/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/exercises/practice/camicia/gradlew.bat b/exercises/practice/camicia/gradlew.bat new file mode 100644 index 000000000..25da30dbd --- /dev/null +++ b/exercises/practice/camicia/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/exercises/practice/camicia/src/main/java/Camicia.java b/exercises/practice/camicia/src/main/java/Camicia.java new file mode 100644 index 000000000..7818944a8 --- /dev/null +++ b/exercises/practice/camicia/src/main/java/Camicia.java @@ -0,0 +1,20 @@ +import java.util.List; + +public class Camicia { + + void simulateGame(List playerA, List playerB) { + throw new UnsupportedOperationException("Delete this statement and write your own implementation."); + } + + String getStatus() { + throw new UnsupportedOperationException("Delete this statement and write your own implementation."); + } + + int getCards() { + throw new UnsupportedOperationException("Delete this statement and write your own implementation."); + } + + int getTricks() { + throw new UnsupportedOperationException("Delete this statement and write your own implementation."); + } +} \ No newline at end of file diff --git a/exercises/practice/camicia/src/test/java/CamiciaTest.java b/exercises/practice/camicia/src/test/java/CamiciaTest.java new file mode 100644 index 000000000..f5da6eac1 --- /dev/null +++ b/exercises/practice/camicia/src/test/java/CamiciaTest.java @@ -0,0 +1,518 @@ +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CamiciaTest { + private static final String FINISHED = "finished"; + private static final String LOOP = "loop"; + + private Camicia simulateGame(List playerA, List playerB) { + Camicia camicia = new Camicia(); + camicia.simulateGame(playerA, playerB); + + return camicia; + } + + @Test + @DisplayName("two cards, one trick") + public void twoCardsOneTrick() { + List playerA = List.of("2"); + List playerB = List.of("3"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(2, camicia.getCards()); + assertEquals(1, camicia.getTricks()); + } + + @Test + @DisplayName("three cards, one trick") + public void threeCardsOneTrick() { + List playerA = List.of("2", "4"); + List playerB = List.of("3"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(3, camicia.getCards()); + assertEquals(1, camicia.getTricks()); + } + + @Test + @DisplayName("four cards, one trick") + public void fourCardsOneTrick() { + List playerA = List.of("2", "4"); + List playerB = List.of("3", "5", "6"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(4, camicia.getCards()); + assertEquals(1, camicia.getTricks()); + } + + @Test + @DisplayName("the ace reigns supreme") + public void theAceReignsSupreme() { + List playerA = List.of("2", "A"); + List playerB = List.of("3", "4", "5", "6", "7"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(7, camicia.getCards()); + assertEquals(1, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("the king beats ace") + public void theKingBeatsAce() { + List playerA = List.of("2", "A"); + List playerB = List.of("3", "4", "5", "6", "K"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(7, camicia.getCards()); + assertEquals(1, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("the queen seduces the king") + public void theQueenSeducesTheKing() { + List playerA = List.of("2", "A", "7", "8", "Q"); + List playerB = List.of("3", "4", "5", "6", "K"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(10, camicia.getCards()); + assertEquals(1, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("the jack betrays the queen") + public void theJackBetraysTheQueen() { + List playerA = List.of("2", "A", "7", "8", "Q"); + List playerB = List.of("3", "4", "5", "6", "K", "9", "J"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(12, camicia.getCards()); + assertEquals(1, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("the 10 just wants to put on a show") + public void theTenJustWantsToPutOnAShow() { + List playerA = List.of("2", "A", "7", "8", "Q", "10"); + List playerB = List.of("3", "4", "5", "6", "K", "9", "J"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(13, camicia.getCards()); + assertEquals(1, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("simple loop with decks of 3 cards") + public void simpleLoopWithDecksOfThreeCards() { + List playerA = List.of("J", "2", "3"); + List playerB = List.of("4", "J", "5"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(LOOP, camicia.getStatus()); + assertEquals(8, camicia.getCards()); + assertEquals(3, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("the story is starting to get a bit complicated") + public void theStoryIsStartingToGetAbitComplicated() { + List playerA = List.of( + "2", "6", "6", "J", "4", "K", "Q", "10", "K", "J", "Q", "2", "3", "K", "5", "6", "Q", "Q", "A", "A", + "6", "9", "K", "A", "8", "K", "2", "A", "9", "A", "Q", "4", "K", "K", "K", "3", "5", "K", "8", "Q", + "3", "Q", "7", "J", "K", "J", "9", "J", "3", "3", "K", "K", "Q", "A", "K", "7", "10", "A", "Q", "7", + "10", "J", "4", "5", "J", "9", "10", "Q", "J", "J", "K", "6", "10", "J", "6", "Q", "J", "5", "J", "Q", + "Q", "8", "3", "8", "A", "2", "6", "9", "K", "7", "J", "K", "K", "8", "K", "Q", "6", "10", "J", "10", + "J", "Q", "J", "10", "3", "8", "K", "A", "6", "9", "K", "2", "A", "A", "10", "J", "6", "A", "4", "J", + "A", "J", "J", "6", "2", "J", "3", "K", "2", "5", "9", "J", "9", "6", "K", "A", "5", "Q", "J", "2", + "Q", "K", "A", "3", "K", "J", "K", "2", "5", "6", "Q", "J", "Q", "Q", "J", "2", "J", "9", "Q", "7", + "7", "A", "Q", "7", "Q", "J", "K", "J", "A", "7", "7", "8", "Q", "10", "J", "10", "J", "J", "9", "2", + "A", "2" + ); + List playerB = List.of( + "7", "2", "10", "K", "8", "2", "J", "9", "A", "5", "6", "J", "Q", "6", "K", "6", "5", "A", "4", "Q", + "7", "J", "7", "10", "2", "Q", "8", "2", "2", "K", "J", "A", "5", "5", "A", "4", "Q", "6", "Q", "K", + "10", "8", "Q", "2", "10", "J", "A", "Q", "8", "Q", "Q", "J", "J", "A", "A", "9", "10", "J", "K", "4", + "Q", "10", "10", "J", "K", "10", "2", "J", "7", "A", "K", "K", "J", "A", "J", "10", "8", "K", "A", "7", + "Q", "Q", "J", "3", "Q", "4", "A", "3", "A", "Q", "Q", "Q", "5", "4", "K", "J", "10", "A", "Q", "J", + "6", "J", "A", "10", "A", "5", "8", "3", "K", "5", "9", "Q", "8", "7", "7", "J", "7", "Q", "Q", "Q", + "A", "7", "8", "9", "A", "Q", "A", "K", "8", "A", "A", "J", "8", "4", "8", "K", "J", "A", "10", "Q", + "8", "J", "8", "6", "10", "Q", "J", "J", "A", "A", "J", "5", "Q", "6", "J", "K", "Q", "8", "K", "4", + "Q", "Q", "6", "J", "K", "4", "7", "J", "J", "9", "9", "A", "Q", "Q", "K", "A", "6", "5", "K" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(361, camicia.getCards()); + assertEquals(1, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("two tricks") + public void twoTricks() { + List playerA = List.of("J"); + List playerB = List.of("3", "J"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(5, camicia.getCards()); + assertEquals(2, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("more tricks") + public void moreTricks() { + List playerA = List.of("J", "2", "4"); + List playerB = List.of("3", "J", "A"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(12, camicia.getCards()); + assertEquals(4, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("simple loop with decks of 4 cards") + public void simpleLoopWithDecksOfFourCards() { + List playerA = List.of("2", "3", "J", "6"); + List playerB = List.of("K", "5", "J", "7"); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(LOOP, camicia.getStatus()); + assertEquals(16, camicia.getCards()); + assertEquals(4, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("easy card combination") + public void easyCardCombination() { + List playerA = List.of( + "4", "8", "7", "5", "4", "10", "3", "9", "7", "3", "10", "10", "6", "8", "2", "8", "5", "4", "5", + "9", "6", "5", "2", "8", "10", "9" + ); + List playerB = List.of( + "6", "9", "4", "7", "2", "2", "3", "6", "7", "3", "A", "A", "A", "A", "K", "K", "K", "K", "Q", "Q", + "Q", "Q", "J", "J", "J", "J" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(40, camicia.getCards()); + assertEquals(4, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("easy card combination, inverted decks") + public void easyCardCombinationInvertedDecks() { + List playerA = List.of( + "3", "3", "5", "7", "3", "2", "10", "7", "6", "7", "A", "A", "A", "A", "K", "K", "K", "K", "Q", + "Q", "Q", "Q", "J", "J", "J", "J" + ); + List playerB = List.of( + "5", "10", "8", "2", "6", "7", "2", "4", "9", "2", "6", "10", "10", "5", "4", "8", "4", "8", "6", + "9", "8", "5", "9", "3", "4", "9" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(40, camicia.getCards()); + assertEquals(4, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("mirrored decks") + public void mirroredDecks() { + List playerA = List.of( + "2", "A", "3", "A", "3", "K", "4", "K", "2", "Q", "2", "Q", "10", "J", "5", "J", "6", "10", "2", + "9", "10", "7", "3", "9", "6", "9" + ); + List playerB = List.of( + "6", "A", "4", "A", "7", "K", "4", "K", "7", "Q", "7", "Q", "5", "J", "8", "J", "4", "5", "8", + "9", "10", "6", "8", "3", "8", "5" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(59, camicia.getCards()); + assertEquals(4, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("opposite decks") + public void oppositeDecks() { + List playerA = List.of( + "4", "A", "9", "A", "4", "K", "9", "K", "6", "Q", "8", "Q", "8", "J", "10", "J", "9", "8", "4", + "6", "3", "6", "5", "2", "4", "3" + ); + List playerB = List.of( + "10", "7", "3", "2", "9", "2", "7", "8", "7", "5", "J", "7", "J", "10", "Q", "10", "Q", "3", "K", + "5", "K", "6", "A", "2", "A", "5" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(151, camicia.getCards()); + assertEquals(21, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("random decks #1") + public void randomDecksOne() { + List playerA = List.of( + "K", "10", "9", "8", "J", "8", "6", "9", "7", "A", "K", "5", "4", "4", "J", "5", "J", "4", "3", + "5", "8", "6", "7", "7", "4", "9" + ); + List playerB = List.of( + "6", "3", "K", "A", "Q", "10", "A", "2", "Q", "8", "2", "10", "10", "2", "Q", "3", "K", "9", "7", + "A", "3", "Q", "5", "J", "2", "6" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(542, camicia.getCards()); + assertEquals(76, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("random decks #2") + public void randomDecksTwo() { + List playerA = List.of( + "8", "A", "4", "8", "5", "Q", "J", "2", "6", "2", "9", "7", "K", "A", "8", "10", "K", "8", "10", + "9", "K", "6", "7", "3", "K", "9" + ); + List playerB = List.of( + "10", "5", "2", "6", "Q", "J", "A", "9", "5", "5", "3", "7", "3", "J", "A", "2", "Q", "3", "J", + "Q", "4", "10", "4", "7", "4", "6" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(327, camicia.getCards()); + assertEquals(42, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Kleber 1999") + public void kleber1999() { + List playerA = List.of( + "4", "8", "9", "J", "Q", "8", "5", "5", "K", "2", "A", "9", "8", "5", "10", "A", "4", "J", "3", + "K", "6", "9", "2", "Q", "K", "7" + ); + List playerB = List.of( + "10", "J", "3", "2", "4", "10", "4", "7", "5", "3", "6", "6", "7", "A", "J", "Q", "A", "7", "2", + "10", "3", "K", "9", "6", "8", "Q" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(5790, camicia.getCards()); + assertEquals(805, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Collins 2006") + public void collins2006() { + List playerA = List.of( + "A", "8", "Q", "K", "9", "10", "3", "7", "4", "2", "Q", "3", "2", "10", "9", "K", "A", "8", "7", + "7", "4", "5", "J", "9", "2", "10" + ); + List playerB = List.of( + "4", "J", "A", "K", "8", "5", "6", "6", "A", "6", "5", "Q", "4", "6", "10", "8", "J", "2", "5", + "7", "Q", "J", "3", "3", "K", "9" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(6913, camicia.getCards()); + assertEquals(960, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Mann and Wu 2007") + public void mannAndWu2007() { + List playerA = List.of( + "K", "2", "K", "K", "3", "3", "6", "10", "K", "6", "A", "2", "5", "5", "7", "9", "J", "A", "A", + "3", "4", "Q", "4", "8", "J", "6" + ); + List playerB = List.of( + "4", "5", "2", "Q", "7", "9", "9", "Q", "7", "J", "9", "8", "10", "3", "10", "J", "4", "10", "8", + "6", "8", "7", "A", "Q", "5", "2" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(7157, camicia.getCards()); + assertEquals(1007, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Nessler 2012") + public void nessler2012() { + List playerA = List.of( + "10", "3", "6", "7", "Q", "2", "9", "8", "2", "8", "4", "A", "10", "6", "K", "2", "10", "A", "5", + "A", "2", "4", "Q", "J", "K", "4" + ); + List playerB = List.of( + "10", "Q", "4", "6", "J", "9", "3", "J", "9", "3", "3", "Q", "K", "5", "9", "5", "K", "6", "5", + "7", "8", "J", "A", "7", "8", "7" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(7207, camicia.getCards()); + assertEquals(1015, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Anderson 2013") + public void anderson2013() { + List playerA = List.of( + "6", "7", "A", "3", "Q", "3", "5", "J", "3", "2", "J", "7", "4", "5", "Q", "10", "5", "A", "J", + "2", "K", "8", "9", "9", "K", "3" + ); + List playerB = List.of( + "4", "J", "6", "9", "8", "5", "10", "7", "9", "Q", "2", "7", "10", "8", "4", "10", "A", "6", "4", + "A", "6", "8", "Q", "K", "K", "2" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(7225, camicia.getCards()); + assertEquals(1016, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Rucklidge 2014") + public void rucklidge2014() { + List playerA = List.of( + "8", "J", "2", "9", "4", "4", "5", "8", "Q", "3", "9", "3", "6", "2", "8", "A", "A", "A", "9", + "4", "7", "2", "5", "Q", "Q", "3" + ); + List playerB = List.of( + "K", "7", "10", "6", "3", "J", "A", "7", "6", "5", "5", "8", "10", "9", "10", "4", "2", "7", "K", + "Q", "10", "K", "6", "J", "J", "K" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(7959, camicia.getCards()); + assertEquals(1122, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Nessler 2021") + public void nessler2021() { + List playerA = List.of( + "7", "2", "3", "4", "K", "9", "6", "10", "A", "8", "9", "Q", "7", "A", "4", "8", "J", "J", "A", + "4", "3", "2", "5", "6", "6", "J" + ); + List playerB = List.of( + "3", "10", "8", "9", "8", "K", "K", "2", "5", "5", "7", "6", "4", "3", "5", "7", "A", "9", "J", + "K", "2", "Q", "10", "Q", "10", "Q" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(7972, camicia.getCards()); + assertEquals(1106, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Nessler 2022") + public void nessler2022() { + List playerA = List.of( + "2", "10", "10", "A", "J", "3", "8", "Q", "2", "5", "5", "5", "9", "2", "4", "3", "10", "Q", "A", + "K", "Q", "J", "J", "9", "Q", "K" + ); + List playerB = List.of( + "10", "7", "6", "3", "6", "A", "8", "9", "4", "3", "K", "J", "6", "K", "4", "9", "7", "8", "5", + "7", "8", "2", "A", "7", "4", "6" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(FINISHED, camicia.getStatus()); + assertEquals(8344, camicia.getCards()); + assertEquals(1164, camicia.getTricks()); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Casella 2024, first infinite game found") + public void casella2024FirstInfiniteGameFound() { + List playerA = List.of( + "2", "8", "4", "K", "5", "2", "3", "Q", "6", "K", "Q", "A", "J", "3", "5", "9", "8", "3", "A", + "A", "J", "4", "4", "J", "7", "5" + ); + List playerB = List.of( + "7", "7", "8", "6", "10", "10", "6", "10", "7", "2", "Q", "6", "3", "2", "4", "K", "Q", "10", "J", + "5", "9", "8", "9", "9", "K", "A" + ); + + Camicia camicia = simulateGame(playerA, playerB); + + assertEquals(LOOP, camicia.getStatus()); + assertEquals(474, camicia.getCards()); + assertEquals(66, camicia.getTricks()); + } +} \ No newline at end of file diff --git a/exercises/settings.gradle b/exercises/settings.gradle index 6bf710357..30b009906 100644 --- a/exercises/settings.gradle +++ b/exercises/settings.gradle @@ -45,6 +45,7 @@ include 'practice:bob' include 'practice:book-store' include 'practice:bottle-song' include 'practice:bowling' +include 'practice:camicia' include 'practice:change' include 'practice:circular-buffer' include 'practice:clock'