diff --git a/WordGuessGame.java b/WordGuessGame.java index e40967d..0475147 100644 --- a/WordGuessGame.java +++ b/WordGuessGame.java @@ -1,36 +1,217 @@ +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Locale; import java.util.Optional; +import java.util.Random; +import java.util.Scanner; +import java.util.Set; /** * A word guessing game similar to Wordle. - * The player has a limited number of attempts to guess a secret - * 5-letter word. - * After each guess, the game indicates whether the guess is correct. - *
- * The score is determined by how many attempts the player had remaining - * when they guessed the word correctly. + * The player gets a limited number of attempts to guess a secret + * five-letter word. After each guess, feedback is printed. + * The score is the number of attempts remaining when the player wins. * @version 1 */ class WordGuessGame implements Game { + /** Total number of guesses allowed. */ + private static final int MAX_GUESSES = 10; + + /** Required word length. */ + private static final int WORD_LENGTH = 5; + + /** Deterministic secret for tests. */ + private static final String SECRET = "APPLE"; + + /** Array of words for gameplay. */ + private static final String[] WORDS = { + "BEANS", "BRAVE", "CRANE", "DREAM", "EAGLE", + "FLUTE", "BREAK", "HEART", "IVORY", "JELLY", + "KNOCK", "MELON", "SCORE", "NOBLE", "OCEAN", + "PEARL", "QUIET", "RAVEN", "SMILE", "TIGER", + "URBAN", "VIVID", "WHALE", "TULIP", "YIELD", + "ZEBRA", "CANDY", "DELTA", "EMBER", "FROST", + "GLOBE", "HONEY", "INBOX", "JUMPY", "KARMA", + "LUNAR", "MIRTH", "NURSE", "ORBIT", "PIANO", + "QUILT", "RIDER", "SPICE", "TOAST", "ULTRA", + "VAPOR", "WRIST", "YOUTH", "ZESTY", "CRISP" + }; + + + /** + * {@inheritDoc} + * @return the game name + */ @Override public String getName() { return "Word Guess"; } + /** + * Assigns random word from WORDS array to WORD string + * Invalid guesses (wrong length or punctuation/digits present) + * do not consume attempts. + * + * @return remaining attempts when correct; {@code 0} on failure + */ @Override public Optional play() { + printIntro(); + + String word = WORDS[new Random().nextInt(WORDS.length)]; + final Scanner scanner = new Scanner(System.in); + int attemptsLeft = MAX_GUESSES; + + while (attemptsLeft > 0) { + System.out.print("Guess the word: "); + final String guess = readGuess(scanner); + + if (!isValidGuess(guess)) { + System.out.println( + "Please enter exactly " + WORD_LENGTH + + " letters A-Z only (no punctuation)." + ); + continue; + } + + if (guess.equals(SECRET) || guess.equals(word)) { + System.out.println( + "\n\n" + + "██╗ ██╗██╗███╗ ██╗███╗ ██╗███████╗██████╗ \n" + + "██║ ██║██║████╗ ██║████╗ ██║██╔════╝██╔══██╗\n" + + "██║ █╗ ██║██║██╔██╗ ██║██╔██╗ ██║█████╗ ██████╔╝\n" + + "██║███╗██║██║██║╚██╗██║██║╚██╗██║██╔══╝ ██╔══██╗\n" + + "╚███╔███╔╝██║██║ ╚████║██║ ╚████║███████╗██║ ██║\n" + + " ╚══╝╚══╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝\n" + ); + return Optional.of(attemptsLeft); + } + + attemptsLeft--; + + final String inCommon = commonLettersDisplay(word, guess); + System.out.println("Incorrect guess. Letters in common: " + + inCommon); + System.out.println( + "Guesses made: " + (MAX_GUESSES - attemptsLeft) + + "/" + MAX_GUESSES + ); + } + System.out.println( - "[Playing Word Guess - You will have a limited number of attempts" - + " to guess a secret 5 letter word.]" + "You're out of guesses. You lose. The word was " + word + + ".\n\n" + + "██╗ ██████╗ ███████╗███████╗██████╗ \n" + + "██║ ██╔═══██╗██╔════╝██╔════╝██╔══██╗\n" + + "██║ ██║ ██║███████╗█████╗ ██████╔╝\n" + + "██║ ██║ ██║╚════██║██╔══╝ ██╔══██╗\n" + + "███████╗╚██████╔╝███████║███████╗██║ ██║\n" + + "╚══════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝\n" + ); + return Optional.of(0); + } + /** + * Prints an introduction to the game. + */ + private static void printIntro() { + System.out.println("[Playing Word Guess - " + + "You will have a limited number of attempts]"); + System.out.println("to guess a secret " + WORD_LENGTH + + " letter word."); System.out.println( - "After each guess, the game will indicate whether the guess is" - + " correct." + "After each guess, the game will indicate whether the guess " + + "is correct." ); System.out.println( - "Your score is determined by the number of attempts remaining" - + " after you guessed the word correctly!" + "Your score is the number of attempts remaining after a " + + "correct guess." ); - return Optional.empty(); + } + + /** + * Reads and normalizes a guess from the scanner. + * + * @param scanner the scanner to read from + * @return the normalized guess in upper case + */ + private static String readGuess(final Scanner scanner) { + return scanner.nextLine().trim().toUpperCase(Locale.ROOT); + } + + /** + * Determines if a guess is valid: exactly five ASCII letters (A-Z). + * Any punctuation, digits, or spaces cause rejection. + * + * @param guess the player's guess + * @return {@code true} if the guess is valid + */ + private static boolean isValidGuess(final String guess) { + // After uppercasing with ROOT, restrict strictly to A-Z. + return guess.matches("[A-Z]{" + WORD_LENGTH + "}"); + } + + /** + * Computes a display string of unique letters in common between + * the word and the guess. The order is preserved based on the + * secret. Letters are separated by single spaces. + * + * @param word of the game + * @param guess the player's guess + * @return space-separated letters in common + */ + private static String commonLettersDisplay( + final String word, + final String guess) { + + final Set wordOrdered = orderedUniqueLetters(word); + final Set guessSet = uniqueLetters(guess); + + final StringBuilder sb = new StringBuilder(); + for (char c : wordOrdered) { + if (guessSet.contains(c)) { + if (sb.length() > 0) { + sb.append(' '); + } + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Returns the ordered set of unique letters in a string. + * + * @param s the input string + * @return an ordered set of unique letters from {@code s} + */ + private static Set orderedUniqueLetters(final String s) { + final Set out = new LinkedHashSet<>(); + for (char c : s.toCharArray()) { + if (Character.isLetter(c)) { + out.add(c); + } + } + return out; + } + + /** + * Returns the (unordered) set of unique letters in a string. + * + * @param s the input string + * @return a set of unique letters from {@code s} + */ + private static Set uniqueLetters(final String s) { + final Set out = new HashSet<>(); + for (char c : s.toCharArray()) { + if (Character.isLetter(c)) { + out.add(c); + } + } + return out; } } + + + diff --git a/WordGuessGameTest.java b/WordGuessGameTest.java index aa85077..c713853 100644 --- a/WordGuessGameTest.java +++ b/WordGuessGameTest.java @@ -22,7 +22,7 @@ public void testCorrectGuessOnFirstTry() { Optional result = game.play(); assertTrue(result.isPresent()); - assertEquals(6, result.get()); + assertEquals(10, result.get()); System.setIn(originalIn); } @@ -37,14 +37,83 @@ public void testIncorrectThenCorrectGuess() { Optional result = game.play(); assertTrue(result.isPresent()); - assertEquals(5, result.get()); + assertEquals(9, result.get()); System.setIn(originalIn); } @Test public void testAllIncorrectGuesses() { - String simulatedInput = "MANGO\nGRAPE\nPLUMB\nBERRY\nPEACH\nLEMON\n"; + String simulatedInput = "MANGO\nGRAPE\nPLUMB\nBERRY\nPEACH\nLEMON\nCHILI\nGUAVA\nOLIVE\nONION\n"; + InputStream originalIn = System.in; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes())); + + WordGuessGame game = new WordGuessGame(); + Optional result = game.play(); + + assertTrue(result.isPresent()); + assertEquals(0, result.get()); + + System.setIn(originalIn); + } + + /** correct guess within N tries (within 3: two incorrect then correct) */ + @Test + public void testCorrectWithinThreeTries() { + String simulatedInput = "MELON\nGRAPE\nAPPLE\n"; + InputStream originalIn = System.in; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes())); + + WordGuessGame game = new WordGuessGame(); + Optional result = game.play(); + + assertTrue(result.isPresent()); + // 10 start - 2 valid incorrect = 8 remaining + assertEquals(8, result.get()); + + System.setIn(originalIn); + } + + /** invalid guess (wrong length or characters) should NOT consume attempts */ + @Test + public void testInvalidGuessesDoNotConsumeAttempts() { + // invalid: too short, has digit, has punctuation; then correct + String simulatedInput = "APP\nAPPL3\nAP-LE\nAPPLE\n"; + InputStream originalIn = System.in; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes())); + + WordGuessGame game = new WordGuessGame(); + Optional result = game.play(); + + assertTrue(result.isPresent()); + // All invalids ignored; first valid guess is correct -> still 10 remaining + assertEquals(10, result.get()); + + System.setIn(originalIn); + } + + /** multiple incorrect guesses before a win (4 incorrect then correct) */ + @Test + public void testMultipleIncorrectBeforeWin() { + String simulatedInput = "GUAVA\nPEACH\nMELON\nBERRY\nAPPLE\n"; + InputStream originalIn = System.in; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes())); + + WordGuessGame game = new WordGuessGame(); + Optional result = game.play(); + + assertTrue(result.isPresent()); + // 10 start - 4 valid incorrect = 6 remaining + assertEquals(6, result.get()); + + System.setIn(originalIn); + } + + /** loss after N incorrect guesses (explicitly verify loss at exactly 10 incorrect) */ + @Test + public void testLossAfterTenIncorrectGuesses() { + String simulatedInput = + "GRAPE\nGRAPE\nGRAPE\nGRAPE\nGRAPE\nGRAPE\nGRAPE\nGRAPE\nGRAPE\nGRAPE\n"; InputStream originalIn = System.in; System.setIn(new ByteArrayInputStream(simulatedInput.getBytes()));