/**
* Hangman.java
*
* Assignment 2 for COMP285.
*
* Play the game of Hangman.
*
* Show your Surname, Name and Student ID.
* e-mail coopes@liv.ac.uk – show your e-mail where to send feedback.
*
* Yiting Qin ID201062266
* email: 774044960@qq.com
*
* Some methods of this Java file are either wrongly implemented or not implemented.
* A correct implementation is required to pass all test methods in the test case
* {@link HangmanTest}.
* So, currently the play does not function as required until fully implemented and tested.
*
*/
import java.util.Enumeration;
import java.util.Random;
import java.util.Scanner;
import java.util.Vector;
/**
*
* Play the game of Hangman. The computer chooses a word (the
* “word-to-be-guessed”), and displays a star sign * for each letter in the word
* (the *-form of the word).
*
* The user guesses a letter: if that letter occurs in the word, then every
* occurrence of that latter in the word is shown (i.e., the letter is written
* instead of the *).
*
* If the letter does not occur in the word or is chosen repeatedly, the user
* loses a life. Play continues in this way until either
*
* – all the letters in the word-to-be-guessed have been guessed; the user has
* won, or
*
* – the user has lost all their lives (10); the computer has won.
*
* After each round, the user has the option of quitting or playing another
* round.
*
* If quitting, the number of rounds played is shown and the round numbers are
* enumerated in which the user has won.
*
*/
public class Hangman {
/**
* Boolean test (default value false) is used in {@link #input_next()}
* method to choose between two alternative versions of inputting a string:
*
* – either from the keyboard,
*
* – or from a given {@link #INPUT} (which is a Vector
*
* Take {@link test} = true to use {@link #INPUT}; useful in the case of
* testing.
*
* Equivalently, add {@link test} = true in setUp method of the test case.
* In this case INPUT should be somehow initialised there.
*/
static boolean test = false; // default value false.
/**
* The ordinary user input from the keyboard.
*
* We use input.next() for getting the next input from the keyboard.
*/
private static Scanner input = new Scanner(System.in);
/**
* Vector
* Used in {@link #input_next()}.
*
* Useful to aid testing and to be initialised in the test case.
*/
static Vector
/**
* INPUT_ENUMERATION is the Vector
* {@link java.util.Enumeration} to allow using the methods
* hasMoreElements() and nextElement().
*/
static Enumeration
/**
* The list of words to choose from. This list is used in
* {@link #initialiseRound()}.
*/
static String[] WORDS = new String[] { “syzygy”, “erythropoeia”,
“quicksilver” };
/**
* Random number generator used in {@link #chooseWord(String[] words)} for
* choosing the word-to-be-guessed from the available array of
* {@link #WORDS}.
*
*/
private final static Random rng = new Random();
/**
* String to hold the current word to be guessed; will be chosen by
* {@link #chooseWord(String[] words)} which uses the random number
* generator {@link #rng}.
*/
static String word;
/**
* Current word with each letter L shown either as “* ” or as “L “; will be
* obtained by {@link #starForm(word) starForm(word)}. During the game
* letters will be guessed and substituted for corresponding stars.
*/
static String word_form;
/**
* The number of lives (for unluckily or repeatedly chosen letters) given to
* the user.
*/
private static final int NUM_LIVES = 10;
/**
* The number of rounds in a play until user quits; initially 0.
*/
static int numRounds = 0;
/**
* The play round numbers where user has won.
*/
static Vector
/**
* The letters that the user has unluckily or repeatedly guessed in a round.
*
* (Each such an unlucky guess costs a life. Typing mistakenly a string of
* several letters costs nothing and will be asked to re-type.)
*/
static Vector
/**
* Play the game. Pick a word, let the user guess it, and repeat until the
* user chooses to quit the game.
*
* Calls methods: {@link #initialiseRound() initialiseRound()},
* {@link #letUserGuessWord(word, word_form, NUM_LIVES)
* letUserGuessWord(word, word_form, NUM_LIVES)}, {@link #input_y_n_Char()
* input_y_n_Char()}.
*
* (This is the only method that should be public; the others are
* package-visible for testing purposes.)
*
*/
public static void playHangman() {
// TODO Auto-generated method stub
/*
* boolean to indicate if user has won the round
*/
boolean hasUserWon;
/*
* boolean to indicate if user has opted to quit
*/
boolean userNotDone = true;
/*
* Now, repeatedly choose a word and let the user guess it, until the
* user opts to quit.
*/
while (userNotDone) {
// set up a new round (in particularly a word to guess, its
// word_form, etc.)
initialiseRound();
// let the user guess word step-by-step by observing word_form;
// a method call letUserGuessWord(word, word_form, NUM_LIVES) should
// be used here and appropriately implemented in this class.
System.out.println(“Guess the word: “);
hasUserWon = letUserGuessWord(word, word_form, NUM_LIVES);
// print result of game depending on the value of hasUserWon
System.out.println(hasUserWon ? word + “\nWell done!”
: “I win, ha ha ha.\nThe word was ” + word);
// play again?
System.out.println(“Do you want to play again? (y/n)”);
// amend numRounds and PlayResults
numRounds++;
if (hasUserWon) {
PlayResults.addElement(numRounds);
}
// get user response (y/n) and treat it as boolean value of
// userNotDone
char c = input_y_n_Char();
if (c == ‘y’) {
userNotDone = true;
} else {
userNotDone = false;
}
}
// Show (i) total number of rounds played and (ii) a list of round
// numbers in which the user has won.
System.out.println(“Total number of rounds played was: ” + numRounds);
System.out.println(“Round numbers which the user has won: ”
+ PlayResults);
}
/**
* Set up a round of Hangman. Pick a {@link #word word} in {@link #WORDS
* WORDS}; set up the {@link #word_form word_form}; clear Vector
* {@link #unluckyGuesses unluckyGuesses}.
*
* @return the word to be guessed
*/
static String initialiseRound() {
// TODO Auto-generated method stub
// return null;
// pick a word for the user to guess
word = chooseWord(WORDS);
// Transform it to the *-form **…* to be shown to the user.
word_form = starForm(word);
// clear the bad guesses
unluckyGuesses.clear();
return word;
}
/**
* Play a round of Hangman.
*
* The method could have no parameters, but they are added to aid testing:
*
* Calls: {@link #inputChar() inputChar()},
* {@link #occursAsNew(userGuess, wrd, w_form) occursAsNew(userGuess, wrd,
* w_form)}, {@link #amend(wrd, w_form, userGuess) amend(wrd, w_form,
* userGuess)} *
*
* @param wrd
* the word to be guessed
* @param wrd_form
* the visible form of the word
* @param num_lives
* user lives remaining
* @return true, if the user wins (AllGuessed); false, otherwise
* (!AllGuessed).
*/
static boolean letUserGuessWord(String wrd, String wrd_form, int num_lives) {
// Word *-form; will repeatedly be amended in this method
String w_form = wrd_form;
// value to return.
// Initially false since wrd_form should initially consist of stars.
boolean AllGuessed = false; // occurs(‘*’, w_form);
// how many unlucky guesses the user has left; originally = num_lives.
int numLivesLeft = num_lives;
// user’s guess
char userGuess;
// was it lucky?
boolean isUserLucky;
/*
* repeatedly:
*
* 1) show (updated) word *-form to user 2) prompt user to guess a
* letter 3) read the letter 4) update the word *-form, list of
* unluckily or repeatedly guessed letters, etc.
*
* until either all letters are guessed, or user has no more lives left
* (which is actually the end of a round).
*/
while (!AllGuessed && numLivesLeft > 0) {
// 1) show the user the current word *-form
System.out.println(w_form);
// 2) prompt user (show all unluckily or repeatedly guessed letters,
// if any; how many lives are left; suggest to pick a letter)
if (numLivesLeft < num_lives) // if some lives had been lost. // (initially false) { System.out .println("Leters you have unluckily or repeatedly guessed: " + unluckyGuesses); } System.out.println("You have " + numLivesLeft + " life(s) remaining. Pick a letter."); // 3) get user's guess (and show it) userGuess = inputChar(); // 4) update isUserLucky, w-form, notAllGuessed, numLivesLeft and // unluckyGuesses. isUserLucky = occursAsNew(userGuess, wrd, w_form); w_form = amend(wrd, w_form, userGuess); AllGuessed = !occurs('*', w_form); if (!isUserLucky) // if user is unlucky { numLivesLeft--; unluckyGuesses.addElement(userGuess); } } // End the round (i.e. the method letUserGuessWord()) by showing // how many lives the user has remaining. System.out.println("You have " + numLivesLeft + " life(s) remaining."); // State the Boolean verdict. return AllGuessed; // This could be true or false // Note that appropriate verdict messages to the user (based on this // Boolean value) will be done by playHangman() after calling the // current method letUserGuessWord(). } /** * The question "Will user play again?" expects inputs "y" or "n". The * method repeats requesting to input one letter y/n by {@link #inputChar()} * until y/n is indeed obtained from the user. * * See more comments to the test method * {@link HangmanTest#testInput_y_n_Char()}. * * @return the input letter 'y' or 'n'. */ static char input_y_n_Char() { // TODO Auto-generated method stub // return '?'; // Not yet implemented. // NEXT LINES - EXPECTED SOLUTION BY STUDENTS // Costs 7 marks while (true) { String r = input_next(); if (r.equals("y") || r.equals("n")) { return r.charAt(0); } } } /** * Pick a random word from an array of words for the user to guess. * * Uses {@link #rng rng}. * * @param String * [] words * @return the word the user has to guess in this game */ static String chooseWord(String[] words) { // TODO Auto-generated method stub // return null; return words[rng.nextInt(words.length)]; } /** * @return the given word w with each letter L shown as '*' * * @param String */ static String starForm(String w) { for (int i = 0; i < w.length(); i++) { char c1 = w.charAt(i); w = w.replace(c1, '*'); } return w; } /** * Occurrence of a character c in a word. */ static boolean occurs(char c, String word) { boolean b = false; for (int i = 0; i < word.length(); i++) { if (word.charAt(i) == c) { b = true; } } return b; } /** * Whether a character c occurs in a {@link #word word} but does not occur * in {@link #word_form word_form}. Calls {@link #occurs(c, word) occurs(c, * word)} and {@link #occurs(c, word_form) occurs(c, word_form)}. */ static boolean occursAsNew(char c, String word, String word_form) { return occurs(c, word) && !occurs(c, word_form); // EXPECTED SOLUTION BY STUDENTS. // Costs 5 marks } /** * Amending a {@link #word_form word_form} by substituting all occurrences * of a given character c in a {@link #word word} into {@link #word_form * word_form} in same positions. */ static String amend(String word, String word_form, char c) { char[] word_form_char_array = word_form.toCharArray(); for (int i = 0; i < word_form.length(); i++) { if (word.charAt(i) == c) { word_form_char_array[i] = c; } } word_form = String.valueOf(word_form_char_array); return word_form; } /** * Inputting a character/string by user. * * If the user's input {@link #input_next() input_next()} (in general - a * string) is of length > 1, it is requested to input just one letter until
* the user really does that.
*
* @return the first character so obtained. It should be a character, not a
* one letter string!
*/
static char inputChar() {
while (true) {
String r = input_next();
if (r.length() == 1) {
return r.charAt(0);
}
}
// EXPECTED SOLUTION BY STUDENTS.
// Costs 10 marks
}
/**
* In the case of {@link test} == false, {@link #input_next()} has the same
* effect as the usual {@link input#next()} reading an input string from the
* console, plus showing the input on the console.
*
* In the case of {@link test} == true, it behaves as a “mock” of
* {@link input#next()} actually taking inputs from the Vector
* {@link #INPUT INPUT} and can be used for testing purposes. (Actually, it
* is more convenient to use {@link java.util.Enumeration}
* {@link #INPUT_ENUMERATION INPUT_ENUMERATION} obtained from Vector
* {@link #INPUT INPUT}). In this case, when {@link #INPUT INPUT} (or
* {@link #INPUT_ENUMERATION INPUT_ENUMERATION}) is finished, the default
* input “n” is assumed. This will eventually halt the game since “n” is the
* answer to question whether the user opts to quit. Again, after getting a
* next input it should be shown on the console.
*
* @return the next input string
*/
static String input_next() {
if (!test) {
String YOUR_INPUT = input.next();
System.out.println(“Your input: ” + YOUR_INPUT);
return YOUR_INPUT;
} else {
/**
* Vector INPUT considered as Enumeration to allow using
* hasMoreElements() and nextElement().
*
* Enumeration
* ABOVE
*/
if (INPUT_ENUMERATION.hasMoreElements()) {
String YOUR_INPUT = INPUT_ENUMERATION.nextElement();
System.out.println(“Your input: ” + YOUR_INPUT);
return YOUR_INPUT;
} else {
System.out.println(“Your input: ” + “n”);
return “n”; // “n” is used here to eventually answer negatively
// the question “Do you want to play again?”
// and so quit the game avoiding infinite loop.
}
}
}
/**
* The main method which consists just in calling {@link #playHangman()
* playHangman()}
*
* @param args
* not used
*/
public static void main(String[] args) {
playHangman();
}
}