Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion exercises/practice/hangman/.meta/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"contributors": [
"mirkoperillo",
"msomji",
"muzimuzhi"
"muzimuzhi",
"thibault2705"
],
"files": {
"solution": [
Expand Down
94 changes: 54 additions & 40 deletions exercises/practice/hangman/.meta/src/reference/java/Hangman.java
Original file line number Diff line number Diff line change
@@ -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<Output> play(
Observable<String> words,
Observable<Output> guess(
String word,
Observable<String> 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<String> 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<String> 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<String> newMisses = new HashSet<>(state.misses);
Set<String> newMisses = new LinkedHashSet<>(state.misses);
newMisses.add(letter);

List<Part> 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();

}
95 changes: 50 additions & 45 deletions exercises/practice/hangman/.meta/src/reference/java/Output.java
Original file line number Diff line number Diff line change
@@ -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<String> guess;
static final int MAX_FAILURES = Part.values().length;

public final String word;
public final String maskedWord;
public final Set<String> guesses;
public final Set<String> misses;
public final List<Part> parts;
public final Status status;
public final Status state;
public final int remainingFailures;

Output(
final String secret,
final String discovered,
final Set<String> guess,
final Set<String> misses,
final List<Part> parts,
final Status status) {
this.secret = secret;
this.discovered = discovered;
this.guess = Set.copyOf(guess);
final String word,
final String maskedWord,
final Set<String> guesses,
final Set<String> misses,
final List<Part> 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<String> 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<String> 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<String> missedLetters) {
return missedLetters.size() >= Part.values().length;
static boolean isLoss(List<Part> 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 +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@ enum Part {
LEFT_ARM,
RIGHT_ARM,
LEFT_LEG,
RIGHT_LEG
RIGHT_LEG,
LEFT_EYE,
RIGHT_EYE,
NOSE,
MOUTH
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
enum Status {
PLAYING,
ON_GOING,
WIN,
LOSS
LOSE
}
40 changes: 40 additions & 0 deletions exercises/practice/hangman/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 1 addition & 1 deletion exercises/practice/hangman/src/main/java/Hangman.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

class Hangman {

Observable<Output> play(Observable<String> words, Observable<String> letters) {
Observable<Output> guess(String word, Observable<String> letters) {
Comment thread
thibault2705 marked this conversation as resolved.
Outdated
throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
}

Expand Down
Loading