diff --git a/exercises/practice/hangman/.meta/config.json b/exercises/practice/hangman/.meta/config.json index 4540d6c3f..d3a75da85 100644 --- a/exercises/practice/hangman/.meta/config.json +++ b/exercises/practice/hangman/.meta/config.json @@ -5,7 +5,8 @@ "contributors": [ "mirkoperillo", "msomji", - "muzimuzhi" + "muzimuzhi", + "thibault2705" ], "files": { "solution": [ diff --git a/exercises/practice/hangman/.meta/src/reference/java/Hangman.java b/exercises/practice/hangman/.meta/src/reference/java/Hangman.java index 3ec7eb87c..49ea71afd 100644 --- a/exercises/practice/hangman/.meta/src/reference/java/Hangman.java +++ b/exercises/practice/hangman/.meta/src/reference/java/Hangman.java @@ -1,76 +1,90 @@ import io.reactivex.Observable; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; class Hangman { - Observable play( - Observable words, + Observable guess( + String word, Observable letters) { - return Observable - .combineLatest( - words, - letters.startWith(""), - (word, letter) -> new AbstractMap.SimpleEntry<>(word, letter)) - .scan( - Output.empty(), - (state, entry) -> { - System.out.println(state + " -> " + entry); - if (state == null || state.status != Status.PLAYING) { - return createNewGame(entry.getKey()); - } else { - return processNewLetter(state, entry.getValue()); + return letters + .startWith("") + .scan(Output.empty(), (state, letter) -> { + if (state == null || state.state == null) { + return createNewGame(word); } + + if (letter.isEmpty()) { + return state; + } + + if (state.state == Status.WIN) { + throw new IllegalStateException("cannot guess after the game is won"); + } + + if (state.state == Status.LOSE) { + throw new IllegalStateException("cannot guess after the game is lost"); + } + + return processNewLetter(state, letter); }) - .skip(1); // Skip the initial state + .skip(1); } private static Output createNewGame(String word) { return Output.initialState(word); } - private static Output processNewLetter( - Output state, - String letter) { + private static Output processNewLetter(Output state, String letter) { if (state.isLetterAlreadyPlayed(letter)) { - throw new IllegalArgumentException("Letter " + letter + " was already played"); + return processIncorrectGuess(state, letter); } + if (state.isLetterInSecret(letter)) { return processCorrectGuess(state, letter); - } else { - return processIncorrectGuess(state, letter); } + + return processIncorrectGuess(state, letter); } private static Output processCorrectGuess(Output state, String letter) { - Set newGuess = new HashSet<>(state.guess); - newGuess.add(letter); - String discovered = Output.getGuessedWord(state.secret, newGuess); - Status newStatus = Output.isWin(state.secret, newGuess) ? Status.WIN : Status.PLAYING; + Set newGuesses = new LinkedHashSet<>(state.guesses); + newGuesses.add(letter); + + String discovered = Output.getGuessedWord(state.word, newGuesses); + Status newStatus = Output.isWin(state.word, newGuesses) ? Status.WIN : Status.ON_GOING; + return new Output( - state.secret, - discovered, - newGuess, - state.misses, - state.parts, - newStatus); + state.word, + discovered, + newGuesses, + state.misses, + state.parts, + newStatus); } private static Output processIncorrectGuess(Output state, String letter) { - Set newMisses = new HashSet<>(state.misses); + Set newMisses = new LinkedHashSet<>(state.misses); newMisses.add(letter); + List newParts = new ArrayList<>(state.parts); - newParts.add(order[newParts.size()]); - Status newStatus = Output.isLoss(newMisses) ? Status.LOSS : Status.PLAYING; + if (newParts.size() < order.length) { + newParts.add(order[newParts.size()]); + } + + Status newStatus = newParts.size() >= order.length ? Status.LOSE : Status.ON_GOING; + return new Output( - state.secret, - state.discovered, - state.guess, + state.word, + state.maskedWord, + state.guesses, newMisses, newParts, newStatus); } static Part[] order = Part.values(); - } diff --git a/exercises/practice/hangman/.meta/src/reference/java/Output.java b/exercises/practice/hangman/.meta/src/reference/java/Output.java index 89b227849..414469dc8 100644 --- a/exercises/practice/hangman/.meta/src/reference/java/Output.java +++ b/exercises/practice/hangman/.meta/src/reference/java/Output.java @@ -1,89 +1,94 @@ -import static java.util.stream.Collectors.joining; - import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import static java.util.stream.Collectors.joining; + class Output { - public final String secret; - public final String discovered; - public final Set guess; + static final int MAX_FAILURES = Part.values().length; + + public final String word; + public final String maskedWord; + public final Set guesses; public final Set misses; public final List parts; - public final Status status; + public final Status state; + public final int remainingFailures; Output( - final String secret, - final String discovered, - final Set guess, - final Set misses, - final List parts, - final Status status) { - this.secret = secret; - this.discovered = discovered; - this.guess = Set.copyOf(guess); + final String word, + final String maskedWord, + final Set guesses, + final Set misses, + final List parts, + final Status state) { + this.word = word; + this.maskedWord = maskedWord; + this.guesses = Set.copyOf(guesses); this.misses = Set.copyOf(misses); this.parts = List.copyOf(parts); - this.status = status; + this.state = state; + this.remainingFailures = Math.max(0, MAX_FAILURES - 1 - parts.size()); } static Output empty() { return new Output( - null, - null, - Collections.emptySet(), - Collections.emptySet(), - Collections.emptyList(), - null); + null, + null, + Collections.emptySet(), + Collections.emptySet(), + Collections.emptyList(), + null); } static Output initialState(final String secret) { return new Output( - secret, - getGuessedWord(secret, Collections.emptySet()), - new HashSet<>(), - new HashSet<>(), - new ArrayList<>(), - Status.PLAYING); + secret, + getGuessedWord(secret, Collections.emptySet()), + new LinkedHashSet<>(), + new LinkedHashSet<>(), + new ArrayList<>(), + Status.ON_GOING); } boolean isLetterAlreadyPlayed(final String letter) { - return guess.contains(letter) || misses.contains(letter); + return guesses.contains(letter) || misses.contains(letter); } boolean isLetterInSecret(final String letter) { - return secret.contains(letter); + return word.contains(letter); } static String getGuessedWord(String secret, Set letters) { return secret.chars() - .mapToObj(i -> String.valueOf((char) i)) - .map(c -> letters.contains(c) ? c : "_") - .collect(joining()); + .mapToObj(i -> String.valueOf((char) i)) + .map(c -> letters.contains(c) ? c : "_") + .collect(joining()); } static boolean isWin(String secret, Set guessedLetters) { return secret.chars() - .mapToObj(i -> String.valueOf((char) i)) - .allMatch(guessedLetters::contains); + .mapToObj(i -> String.valueOf((char) i)) + .allMatch(guessedLetters::contains); } - static boolean isLoss(Set missedLetters) { - return missedLetters.size() >= Part.values().length; + static boolean isLoss(List parts) { + return parts.size() >= MAX_FAILURES; } @Override public String toString() { return "Output{" + - "secret='" + secret + '\'' + - ", discovered='" + discovered + '\'' + - ", guess=" + guess + - ", misses=" + misses + - ", parts=" + parts + - ", status=" + status + - '}'; + "secret='" + word + '\'' + + ", discovered='" + maskedWord + '\'' + + ", guess=" + guesses + + ", misses=" + misses + + ", parts=" + parts + + ", status=" + state + + ", remainingFailures=" + remainingFailures + + '}'; } } diff --git a/exercises/practice/hangman/.meta/src/reference/java/Part.java b/exercises/practice/hangman/.meta/src/reference/java/Part.java index d1d54e60b..28e8a4da0 100644 --- a/exercises/practice/hangman/.meta/src/reference/java/Part.java +++ b/exercises/practice/hangman/.meta/src/reference/java/Part.java @@ -4,5 +4,9 @@ enum Part { LEFT_ARM, RIGHT_ARM, LEFT_LEG, - RIGHT_LEG + RIGHT_LEG, + LEFT_EYE, + RIGHT_EYE, + NOSE, + MOUTH } diff --git a/exercises/practice/hangman/.meta/src/reference/java/Status.java b/exercises/practice/hangman/.meta/src/reference/java/Status.java index 699a34e91..63291130e 100644 --- a/exercises/practice/hangman/.meta/src/reference/java/Status.java +++ b/exercises/practice/hangman/.meta/src/reference/java/Status.java @@ -1,5 +1,5 @@ enum Status { - PLAYING, + ON_GOING, WIN, - LOSS + LOSE } diff --git a/exercises/practice/hangman/.meta/tests.toml b/exercises/practice/hangman/.meta/tests.toml new file mode 100644 index 000000000..12917b7c4 --- /dev/null +++ b/exercises/practice/hangman/.meta/tests.toml @@ -0,0 +1,40 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[2419ffe6-16d8-4059-856a-9a101998a418] +description = "Initially 9 failures are allowed and no letters are guessed" + +[17c4296d-daab-44dc-8155-37c77caa52c1] +description = "After 10 failures the game is over" + +[77c9ae1f-bbc4-4ed4-b67e-08110cbcfc17] +description = "Losing with several correct guesses" + +[25101d8d-9874-405b-9825-193a14b69753] +description = "Feeding a correct letter removes underscores" + +[8e6bd521-bc9b-458f-9cce-f57f4140c173] +description = "Feeding a correct letter twice counts as a failure" + +[5e6971b7-5e5f-49c2-b85d-1dd6aeb53bd5] +description = "Guessing a repeated letter reveals all instances" + +[a6c69d92-01ef-4b81-b9d9-801131e79bbb] +description = "Getting all the letters right makes for a win" + +[2dc47994-b434-4a26-b70c-1eebeff77fe4] +description = "Winning on the last guess is still a win" + +[52801d56-6963-494b-a901-5736e46ddc12] +description = "Guessing after a lose is error" + +[29a874f3-a413-4e1b-9a60-6be018e70b60] +description = "Guessing after a win is error" diff --git a/exercises/practice/hangman/src/main/java/Hangman.java b/exercises/practice/hangman/src/main/java/Hangman.java index abab3240f..6cf7793ce 100644 --- a/exercises/practice/hangman/src/main/java/Hangman.java +++ b/exercises/practice/hangman/src/main/java/Hangman.java @@ -2,7 +2,7 @@ class Hangman { - Observable play(Observable words, Observable letters) { + Observable guess(String word, Observable letters) { throw new UnsupportedOperationException("Delete this statement and write your own implementation."); } diff --git a/exercises/practice/hangman/src/main/java/Output.java b/exercises/practice/hangman/src/main/java/Output.java index c4376f901..29fb436f2 100644 --- a/exercises/practice/hangman/src/main/java/Output.java +++ b/exercises/practice/hangman/src/main/java/Output.java @@ -1,29 +1,33 @@ -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; + +import static java.util.stream.Collectors.joining; class Output { - public final String secret; - public final String discovered; - public final Set guess; + static final int MAX_FAILURES = Part.values().length; + + public final String word; + public final String maskedWord; + public final Set guesses; public final Set misses; public final List parts; - public final Status status; + public final Status state; + public final int remainingFailures; Output( - final String secret, - final String discovered, - final Set guess, + final String word, + final String maskedWord, + final Set guesses, final Set misses, final List parts, - final Status status) { - this.secret = secret; - this.discovered = discovered; - this.guess = Set.copyOf(guess); + final Status state) { + this.word = word; + this.maskedWord = maskedWord; + this.guesses = Set.copyOf(guesses); this.misses = Set.copyOf(misses); this.parts = List.copyOf(parts); - this.status = status; + this.state = state; + this.remainingFailures = Math.max(0, MAX_FAILURES - 1 - parts.size()); } static Output empty() { @@ -36,4 +40,51 @@ static Output empty() { null); } + static Output initialState(final String secret) { + return new Output( + secret, + getGuessedWord(secret, Collections.emptySet()), + new LinkedHashSet<>(), + new LinkedHashSet<>(), + new ArrayList<>(), + Status.ON_GOING); + } + + boolean isLetterAlreadyPlayed(final String letter) { + return guesses.contains(letter) || misses.contains(letter); + } + + boolean isLetterInSecret(final String letter) { + return word.contains(letter); + } + + static String getGuessedWord(String secret, Set letters) { + return secret.chars() + .mapToObj(i -> String.valueOf((char) i)) + .map(c -> letters.contains(c) ? c : "_") + .collect(joining()); + } + + static boolean isWin(String secret, Set guessedLetters) { + return secret.chars() + .mapToObj(i -> String.valueOf((char) i)) + .allMatch(guessedLetters::contains); + } + + static boolean isLoss(List parts) { + return parts.size() >= MAX_FAILURES; + } + + @Override + public String toString() { + return "Output{" + + "secret='" + word + '\'' + + ", discovered='" + maskedWord + '\'' + + ", guess=" + guesses + + ", misses=" + misses + + ", parts=" + parts + + ", status=" + state + + ", remainingFailures=" + remainingFailures + + '}'; + } } diff --git a/exercises/practice/hangman/src/main/java/Part.java b/exercises/practice/hangman/src/main/java/Part.java index d1d54e60b..28e8a4da0 100644 --- a/exercises/practice/hangman/src/main/java/Part.java +++ b/exercises/practice/hangman/src/main/java/Part.java @@ -4,5 +4,9 @@ enum Part { LEFT_ARM, RIGHT_ARM, LEFT_LEG, - RIGHT_LEG + RIGHT_LEG, + LEFT_EYE, + RIGHT_EYE, + NOSE, + MOUTH } diff --git a/exercises/practice/hangman/src/main/java/Status.java b/exercises/practice/hangman/src/main/java/Status.java index 699a34e91..63291130e 100644 --- a/exercises/practice/hangman/src/main/java/Status.java +++ b/exercises/practice/hangman/src/main/java/Status.java @@ -1,5 +1,5 @@ enum Status { - PLAYING, + ON_GOING, WIN, - LOSS + LOSE } diff --git a/exercises/practice/hangman/src/test/java/HangmanTest.java b/exercises/practice/hangman/src/test/java/HangmanTest.java index 6cecef282..9c2f75de7 100644 --- a/exercises/practice/hangman/src/test/java/HangmanTest.java +++ b/exercises/practice/hangman/src/test/java/HangmanTest.java @@ -1,29 +1,15 @@ import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import io.reactivex.disposables.Disposable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; public class HangmanTest { - /** - * Used in {@link #consecutiveGames()} to tell when both are subscribed too. - */ - private static class SubscribedEmitters { - private ObservableEmitter word; - private ObservableEmitter letter; - } - private Hangman hangman; @BeforeEach @@ -32,201 +18,133 @@ public void init() { } @Test - @DisplayName("Initial game state is set correctly") - public void initialization() { - Observable result = hangman.play( - Observable.fromArray("secret"), - Observable.fromArray()); - Output init = result.blockingFirst(); - - assertThat(init).isNotNull(); - assertThat(init.secret).isEqualTo("secret"); - assertThat(init.discovered).isEqualTo("______"); - assertThat(init.guess).isEmpty(); - assertThat(init.misses).isEmpty(); - assertThat(init.parts).isEmpty(); - assertThat(init.status).isEqualTo(Status.PLAYING); + @DisplayName("Initially 9 failures are allowed and no letters are guessed") + void initially9FailuresAreAllowedAndNoLettersAreGuessed() { + Output result = hangman + .guess("loot", Observable.empty()) + .blockingLast(); + + assertEquals(Status.ON_GOING, result.state); + assertEquals("____", result.maskedWord); + assertEquals(9, result.remainingFailures); } @Disabled("Remove to run test") @Test - @DisplayName("First correct guess updates discovered and guess lists") - public void firstGuess() { - Observable result = hangman.play( - Observable.fromArray("secret"), - Observable.fromArray("e")); - - Output last = result.blockingLast(); - assertThat(last.discovered).isEqualTo("_e__e_"); - assertThat(last.guess).containsExactly("e"); - assertThat(last.misses).isEmpty(); - assertThat(last.parts).isEmpty(); - assertThat(last.status).isEqualTo(Status.PLAYING); + @DisplayName("After 10 failures the game is over") + void after10FailuresTheGameIsOver() { + Output result = hangman + .guess("loot", Observable.fromArray("a", "b", "c", "d", "e", "f", "g", "h", "i", "j")) + .blockingLast(); + + assertEquals(Status.LOSE, result.state); + assertEquals("____", result.maskedWord); + assertEquals(0, result.remainingFailures); } @Disabled("Remove to run test") @Test - @DisplayName("First incorrect guess registers a miss and adds a part") - public void firstMiss() { - Observable result = hangman.play( - Observable.fromArray("secret"), - Observable.fromArray("a")); - Output last = result.blockingLast(); - - assertThat(last.discovered).isEqualTo("______"); - assertThat(last.guess).isEmpty(); - assertThat(last.misses).containsExactly("a"); - assertThat(last.parts).containsExactly(Part.HEAD); - assertThat(last.status).isEqualTo(Status.PLAYING); + @DisplayName("Losing with several correct guesses") + void losingWithSeveralCorrectGuesses() { + Output result = hangman + .guess("loot", Observable.fromArray("t", "o", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j")) + .blockingLast(); + + assertEquals(Status.LOSE, result.state); + assertEquals("_oot", result.maskedWord); + assertEquals(0, result.remainingFailures); } @Disabled("Remove to run test") @Test - @DisplayName("Game in progress accumulates guesses, misses and parts correctly") - public void gameInProgress() { - Observable result = hangman.play( - Observable.fromArray("secret"), - Observable.fromArray("a", "e", "o", "s")); - Output last = result.blockingLast(); - - assertThat(last.discovered).isEqualTo("se__e_"); - assertThat(last.guess).containsExactlyInAnyOrder("e", "s"); - assertThat(last.misses).containsExactlyInAnyOrder("a", "o"); - assertThat(last.parts).containsExactlyInAnyOrder(Part.HEAD, Part.BODY); - assertThat(last.status).isEqualTo(Status.PLAYING); + @DisplayName("Feeding a correct letter removes underscores") + void feedingCorrectLetterRemovesUnderscores() { + Output result = hangman + .guess("loot", Observable.fromArray("t")) + .blockingLast(); + + assertEquals(Status.ON_GOING, result.state); + assertEquals("___t", result.maskedWord); + assertEquals(9, result.remainingFailures); } @Disabled("Remove to run test") @Test - @DisplayName("Winning the game results in WIN status") - public void wonGame() { - Observable result = hangman.play( - Observable.fromArray("secret"), - Observable.fromArray("a", "e", "o", "s", "c", "r", "g", "t")); - Output last = result.blockingLast(); - - assertThat(last.discovered).isEqualTo("secret"); - assertThat(last.guess).containsExactlyInAnyOrder("c", "e", "r", "s", "t"); - assertThat(last.status).isEqualTo(Status.WIN); + @DisplayName("Feeding a correct letter twice counts as a failure") + void feedingCorrectLetterTwiceCountAsASuccess() { + Output result = hangman + .guess("loot", Observable.fromArray("t", "t")) + .blockingLast(); + + assertEquals(Status.ON_GOING, result.state); + assertEquals("___t", result.maskedWord); + assertEquals(8, result.remainingFailures); } @Disabled("Remove to run test") @Test - @DisplayName("Losing the game results in LOSS status") - public void lostGame() { - Observable result = hangman.play( - Observable.fromArray("secret"), - Observable.fromArray("a", "b", "c", "d", "e", "f", "g", "h")); - Output last = result.blockingLast(); - - assertThat(last.discovered).isEqualTo("_ec_e_"); - assertThat(last.misses).containsExactlyInAnyOrder("a", "b", "d", "f", "g", "h"); - assertThat(last.status).isEqualTo(Status.LOSS); - assertThat(last.parts).containsExactlyInAnyOrder( - Part.HEAD, - Part.BODY, - Part.LEFT_ARM, - Part.RIGHT_ARM, - Part.LEFT_LEG, - Part.RIGHT_LEG - ); + @DisplayName("Guessing a repeated letter reveals all instances") + void guessingARepeatedLetterRevealsAllInstances() { + Output result = hangman + .guess("loot", Observable.fromArray("t", "t", "o")) + .blockingLast(); + + assertEquals(Status.ON_GOING, result.state); + assertEquals("_oot", result.maskedWord); + assertEquals(8, result.remainingFailures); } @Disabled("Remove to run test") @Test - @DisplayName("Handles consecutive games correctly with ordered emissions") - public void consecutiveGames() { - // This test setup is more complex because we have to order the emission of values in the - // different observers. - // 1. Word observable receives the work to guess - // 2. Letter observable receives the various letters tried - // 3. Word observable receives the new word to guess - // 4. Letter observable receiveds the letters for the second word - - // Emitters respectively for the word and letter observables - SubscribedEmitters emitters = new SubscribedEmitters(); - Runnable emit = () -> { - // Process sending the inputs in the right order - emitters.word.onNext("secret"); - Stream.of("a", "e", "o", "s", "c", "r", "g", "t").forEach(emitters.letter::onNext); - emitters.word.onNext("abba"); - Stream.of("a", "e", "s", "b").forEach(emitters.letter::onNext); - emitters.word.onComplete(); - }; - Observable words = createWordObservable(emitters, emit); - Observable letters = createLetterObservable(emitters, emit); - Observable outputs = hangman.play(words, letters); - - // We collect the results of the game - List results = new ArrayList<>(); - Disposable subscription = outputs.filter(output -> output.status != Status.PLAYING) - .subscribe(results::add); - try { - assertThat(results.size()).isEqualTo(2); - - Output first = results.get(0); - assertThat(first.discovered).isEqualTo("secret"); - assertThat(first.guess).containsExactlyInAnyOrder("s", "e", "c", "r", "t"); - assertThat(first.misses).containsExactlyInAnyOrder("a", "o", "g"); - assertThat(first.status).isEqualTo(Status.WIN); - - Output second = results.get(1); - assertThat(second.discovered).isEqualTo("abba"); - assertThat(second.guess).containsExactlyInAnyOrder("a", "b"); - assertThat(second.misses).containsExactlyInAnyOrder("e", "s"); - assertThat(first.status).isEqualTo(Status.WIN); - } finally { - subscription.dispose(); - } - } - - Observable createWordObservable(SubscribedEmitters emitters, Runnable emit) { - return Observable.create( - emitter -> { - // A new subscription was created for words, record it. - emitters.word = emitter; - if (emitters.letter != null) { - // Start emitting only when both word and letter observable have subscriptions - emit.run(); - } - }); + @DisplayName("Getting all the letters right makes for a win") + void gettingAllTheLettersRightMakesForASuccess() { + Output result = hangman + .guess("loot", Observable.fromArray("t", "t", "o", "l")) + .blockingLast(); + + assertEquals(Status.WIN, result.state); + assertEquals("loot", result.maskedWord); + assertEquals(8, result.remainingFailures); } - Observable createLetterObservable(SubscribedEmitters emitters, Runnable emit) { - return Observable.create( - emitter -> { - // A new subscription was created for letters, record it. - emitters.letter = emitter; - if (emitters.word != null) { - // Start emitting only when both word and letter observable have subscriptions - emit.run(); - } - }); + @Disabled("Remove to run test") + @Test + @DisplayName("Winning on the last guess is still a win") + void winningOnTheLastGuessIsStillAWin() { + Output result = hangman + .guess("loot", Observable.fromArray("a", "b", "c", "d", "e", "f", "g", "h", "i", "t", "o", "l")) + .blockingLast(); + + assertEquals(Status.WIN, result.state); + assertEquals("loot", result.maskedWord); + assertEquals(0, result.remainingFailures); } @Disabled("Remove to run test") @Test - @DisplayName("Cannot play the same guess twice") - public void cannotPlayAGuessTwice() { - Observable result = hangman.play( - Observable.fromArray("secret"), - Observable.fromArray("e", "c", "s", "c")); - - assertThatThrownBy(() -> result.blockingLast()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Letter c was already played"); + @DisplayName("Guessing after a lose is error") + void guessingAfterALoseIsError() { + IllegalStateException ex = assertThrows( + IllegalStateException.class, + () -> hangman + .guess("loot", Observable.fromArray("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k")) + .blockingLast() + ); + + assertEquals("cannot guess after the game is lost", ex.getMessage()); } @Disabled("Remove to run test") @Test - @DisplayName("Cannot play the same miss twice") - public void cannotPlayAMissTwice() { - Observable result = hangman.play( - Observable.fromArray("secret"), - Observable.fromArray("e", "a", "s", "a")); - - assertThatThrownBy(() -> result.blockingLast()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Letter a was already played"); + @DisplayName("Guessing after a win is error") + void guessingAfterAWinIsError() { + IllegalStateException ex = assertThrows( + IllegalStateException.class, + () -> hangman + .guess("loot", Observable.fromArray("t", "o", "l", "l")) + .blockingLast() + ); + + assertEquals("cannot guess after the game is won", ex.getMessage()); } }