-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExtension_Hangman.java
More file actions
493 lines (397 loc) · 15.7 KB
/
Extension_Hangman.java
File metadata and controls
493 lines (397 loc) · 15.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
/*
* File: Extension_Hangman.java
* ------------------
* IMPROVED FEATURES:*******************************************
* (1) Add sound effects.
* (2) Allow player to play G_TURNS of games. Won games do not count towards G_TURNS.
* (3) Karel free falls when player loses all the games.
* (4) Hint available after the 5th guess and when player still hasn't got half the word right.
* (5) For each game player wins, reduce the N_GUESSES by 1 to increase difficulty.
*/
import acm.graphics.*;
import acm.program.*;
import acm.util.*;
import java.applet.AudioClip;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class Extension_Hangman extends ConsoleProgram {
/***********************************************************
* CONSTANTS *
***********************************************************/
/* The number of guesses in one game of Hangman */
private static final int N_GUESSES = 7;
/* The width and the height to make the karel image */
private static final int KAREL_SIZE = 150;
/* The y-location to display karel */
private static final int KAREL_Y = 230;
/* The width and the height to make the parachute image */
private static final int PARACHUTE_WIDTH = 300;
private static final int PARACHUTE_HEIGHT = 130;
/* The y-location to display the parachute */
private static final int PARACHUTE_Y = 50;
/* The y-location to display the partially guessed string */
private static final int PARTIALLY_GUESSED_Y = 430;
/* The y-location to display the incorrectly guessed letters */
private static final int INCORRECT_GUESSES_Y = 460;
/* The fonts of both labels */
private static final String PARTIALLY_GUESSED_FONT = "Courier-36";
private static final String INCORRECT_GUESSES_FONT = "Courier-26";
/* The total number of turns a player can play */
private static final int G_TURNS = 3;
/* Time interval between printing one line and another */
private static final int PRINTLN_DELAY = 500;
/* Time interval between one game and another */
private static final int GAME_DELAY = 3000;
/* Time interval of karel falling */
private static final int FALL_DELAY = 80;
/* Falling speed of karel */
private static final int FALL_SPEED = 30;
/***********************************************************
* Instance Variables *
***********************************************************/
/* An object that can produce pseudo random numbers */
private RandomGenerator rg = new RandomGenerator();
// Instance variables that are going to be called throughout the program:
private GCanvas canvas = new GCanvas();
private int wrongGuesses = 0; // Total number of wrong guesses. Initialized
// as 0.
private String guess = ""; // The result of the player's guess. To be
// initialized when one random word is generated
// for the game, and updated whenever player
// makes a guess.
private String answer = ""; // aka the word generated at the beginning of
// the game.
private String wrongChars = ""; // Shows on the canvas all the previous
// wrong guesses.
private GLabel guessLabel = new GLabel(""); // Shows the value of "guess" on
// the canvas.
private GLabel wrongCharsLabel = new GLabel(""); // Shows the value of
// "wrongChars" on the
// canvas.
private int turnsLeft = G_TURNS; // Shows how many more turns player can
// play.
private int hint = 0; // Holds the value of hints given per game.
private int gamesWon = 0; // Stores the value of games won by player out of G_TURNS.
private int n_Guesses = N_GUESSES; // This value will change if player wins games, to increase difficulty.
private int n_GuessesMin = N_GUESSES / 2; // This value prevents available guesses to become <=1, as long as N_GUESSES is set larger than 2.
// Import and define the images to be shown on the canvas.
private GImage bg = new GImage("background.jpg");
private GImage parachute = new GImage("parachute.png");
private GImage karel = new GImage("karel.png");
private GImage karelFlipped = new GImage("karelFlipped.png");
AudioClip wind = MediaTools.loadAudioClip("wind.au");
AudioClip breaks = MediaTools.loadAudioClip("breaks.au");
// Define two arraylists to include the lines connected to the parachute.
// One is for drawing the lines in order, the other is for breaking them in
// order.
private ArrayList<GLine> lines = new ArrayList<GLine>();
private ArrayList<GLine> linesBreak = new ArrayList<GLine>();
// Create the Arraylist and import the lexicon into it.
private ArrayList<String> lexicon = new ArrayList<String>();
/***********************************************************
* Methods *
***********************************************************/
public void run() {
introduce();
wind.loop();
while (turnsLeft > 0) {
playOneGame();
}
println ("Argh.........");
pause(PRINTLN_DELAY);
println("You killed Karel!!!");
killKarel();
}
private void killKarel() {
canvas.remove(wrongCharsLabel);
canvas.remove(guessLabel);
boolean outOfBorder = parachute.getY() + parachute.getHeight() < 0 && karelFlipped.getY() > canvas.getHeight();
while (!outOfBorder){
parachute.move(0, -FALL_SPEED);
pause(FALL_DELAY);
karelFlipped.move(0, FALL_SPEED);
pause(FALL_DELAY);
}
}
private void introduce() {
println("Welcome to Hangman!");
println("You will play " + G_TURNS + " games of Hangman.");
println("In each game, you will have " + N_GUESSES + " chances to guess.");
println("For each game you win,");
println("the number of available guesses will reduce by 1.");
println("If you lose one game Karel will be revived;");
println("But if you lose all the lives...");
println("T_T..............");
println("Save Karel!");
println("");
pause(PRINTLN_DELAY);
}
private void playOneGame() {
// Initialize the variables before each game.
// If user keeps winning, available guesses should not keep dropping.
if (N_GUESSES - gamesWon < n_GuessesMin ){
n_Guesses = n_GuessesMin;
} else if (N_GUESSES - gamesWon >= n_GuessesMin){
n_Guesses = N_GUESSES - gamesWon;
}
wrongGuesses = 0;
guess = "";
answer = "";
wrongChars = "";
hint = 0;
canvas.removeAll(); // Wipe everything out of the previous game.
setUp();// Set up the canvas and all the images needed for the game.
// Randomly generate a word for
// guess.
// For a single game, the length of the given word is determined.
answer = getRandomWord();
int len = answer.length();
// The initial value before guess would be many dashes '-'.
for (int i = 0; i < len; i++) {
guess = guess + '-';
}
addLabels();// After generating the word, place the label onto the canvas.
showTurnsLeft(); // Tell the player how many more turns they have.
// Game starts. Player can play until guesses run out, or until they win.
while (wrongGuesses < n_Guesses && !win()) {
play();
}
// Define the events on a win or a failure.
if (win()) {
gamesWon ++;
pause(PRINTLN_DELAY);
println("You win.");
println("The word was " + answer + ".");
println("");
pause(GAME_DELAY);
}
if (wrongGuesses == n_Guesses) {
turnsLeft --; // Player loses one turn.
canvas.remove(karel);
canvas.add(karelFlipped, canvas.getWidth() * 0.5 - KAREL_SIZE * 0.5, KAREL_Y);
println("You're completely hung.");
println("The word was: " + answer + ".");
println("");
pause(GAME_DELAY);
}
}
private void showTurnsLeft() {
if (turnsLeft == G_TURNS){
if (gamesWon == 0){
println("You are now in game one.");
}
if (gamesWon == 1){
println("Good job on game one.");
println("Ready to start the second one?");
}
} else if (turnsLeft < G_TURNS && turnsLeft > 1){
println("Now you can play " + turnsLeft + " more times.");
} else if (turnsLeft ==1){
println("Now you only have " + turnsLeft + " more chance.");
}
}
/*
* Method addLabels adds the initial value of the string guess (which is
* "----...") to the canvas below karel. The value of the string is updated
* through other methods later on.
*/
private void addLabels() {
guessLabel.setLabel(guess);
guessLabel.setFont(PARTIALLY_GUESSED_FONT);
guessLabel.setColor(Color.BLACK);
canvas.add(guessLabel, canvas.getWidth() * 0.5 - guessLabel.getWidth() * 0.5, PARTIALLY_GUESSED_Y);
wrongCharsLabel.setFont(INCORRECT_GUESSES_FONT);
}
private void setUp() {
openFile(); // import lexicon.txt
drawBackground(); // import the sky pic
drawParachute(); // import the parachute pic
drawKarel(); // place karel
drawLines(); // attach strings to karel and the parachute
}
/*
* Method openFile imports lexicon.txt into the string arraylist - lexicon.
*/
private void openFile() {
try {
BufferedReader br = new BufferedReader(new FileReader("HangmanLexicon.txt"));
String readLine = br.readLine();
while (readLine != null) {
lexicon.add(readLine);
readLine = br.readLine();
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* Method drawLines draws seven lines (strings attached to karel from the
* parachute). Each line is also added to GLine arraylist lines as an object
* since later they will be removed from canvas separately.
*/
private void drawLines() {
// Clear everything in the two lines arraylists before each game.
lines.clear();
linesBreak.clear();
double x = parachute.getX();
double deltaX = PARACHUTE_WIDTH / (n_Guesses - 1);
double y1 = parachute.getY() + PARACHUTE_HEIGHT;
double x2 = canvas.getWidth() * 0.5;
double y2 = karel.getY();
// Draw seven lines using the coordinates and intervals set above.
for (int i = 0; i < n_Guesses; i++) {
GLine line = new GLine(x + deltaX * i, y1, x2, y2);
canvas.add(line); // add lines to canvas.
lines.add(line); // add lines to arraylist "lines".
}
// Since lines break in a different order from how they are added,
// add the lines to a different arraylist "linesBreak",
// rearranging the order into the right one in which they'll break,
// so that the rightmost line breaks first, then the leftmost one, etc.
for (int i = 0; i <= n_Guesses / 2; i ++){
linesBreak.add(lines.get(n_Guesses - 1 - i));// linesBreak(0)
linesBreak.add(lines.get(i));// linesBreak(1)
}
}
public void init() {
add(canvas); // Initialize canvas.
}
private void drawBackground() {
bg.setSize(canvas.getWidth(), canvas.getHeight());
canvas.add(bg, 0, 0); // Add bluesky image.
}
private void drawParachute() {
parachute.setSize(PARACHUTE_WIDTH, PARACHUTE_HEIGHT);
double x = canvas.getWidth() * 0.5 - PARACHUTE_WIDTH * 0.5;
canvas.add(parachute, x, PARACHUTE_Y); // Add and center parachute.
}
private void drawKarel() {
karel.setSize(KAREL_SIZE, KAREL_SIZE);
double x = canvas.getWidth() * 0.5 - KAREL_SIZE * 0.5;
canvas.add(karel, x, KAREL_Y);
karelFlipped.setSize(KAREL_SIZE, KAREL_SIZE); // Add and center karel.
}
/*
* Boolean win sets the condition that stops the game once the player gets
* the right answer.
*/
private boolean win() {
if (guess.equals(answer))
return true;
return false;
}
/*
* Method play defines the logic of play and the update principles of the
* (instance) variables.
*/
private void play() {
checkIfToGiveHint();
int len = answer.length();
// charCount changes if the player's guess matches at least one
// character in the word.
// It will be zero if player's got a wrong guess.
int charCount = 0;
// Read what player enters.
String guessChar = readLine("Your guess: ");
// What player enters should be a single letter, therefore:
boolean moreDigit = guessChar.length() != 1;
boolean notLetter = guessChar.length() == 1 && (guessChar.charAt(0) < 'A'
|| (guessChar.charAt(0) > 'Z' && guessChar.charAt(0) < 'a') || guessChar.charAt(0) > 'z');
boolean notValid = moreDigit || notLetter;
// While entry is not valid, ask the player to re-enter.
while (notValid) {
guessChar = readLine("Please enter only ONE LETTER: ");
// Update booleans so that notValid will be rechecked at the start
// of the loop.
moreDigit = guessChar.length() != 1;
notLetter = guessChar.length() == 1 && (guessChar.charAt(0) < 'A'
|| (guessChar.charAt(0) > 'Z' && guessChar.charAt(0) < 'a') || guessChar.charAt(0) > 'z');
notValid = moreDigit || notLetter;
}
for (int i = 0; i < len; i++) {
char ch = answer.charAt(i); // Scan each character in the given
// word.
String str = Character.toString(ch); // And make that character a
// string so that it can be
// compared.
// If the validly entered string is the same as some of the
// characters in the word,
// string "guess" is updated to show that correct guess.
// charCount will no longer be zero.
if (guessChar.toLowerCase().equals(str.toLowerCase())) {
guess = guess.substring(0, i) + ch + guess.substring(i + 1);
charCount++;
}
}
// If player didn't get it right, then:
if (charCount == 0) {
println("There are no " + guessChar.toUpperCase() + "'s in the word.");
wrongGuesses++; // One more wrong guess.
// Update wrongChars to include all incorrect guesses.
wrongChars = (wrongChars + guessChar).toUpperCase();
// Set the label value to be string wrongChars
wrongCharsLabel.setLabel(wrongChars);
// It should be repositioned.
wrongCharsLabel.setLocation((canvas.getWidth() - wrongCharsLabel.getWidth()) * 0.5, INCORRECT_GUESSES_Y);
canvas.add(wrongCharsLabel); // Re-add label to canvas.
// The first wrong guess will cause the rightmost line to break,
// that is, the first element in the linesBreak arraylist (index 0).
canvas.remove(linesBreak.get(wrongGuesses - 1));
breaks.play();
} else {
println("That guess is correct.");
guessLabel.setLabel(guess);
}
}
private void checkIfToGiveHint() {
// Set three conditions to decide if to given the player a hint.
// (1) When player arrives at the third last guess;
// (2) And player still hasn't got half the word right.
// (3) And player hasn't arrived at the n_GuessesMin'th game.
// Hint is only given once per game.
int n_CorrectGuesses = 0;
// Create an arraylist to store the indices of the chars unguessed.
ArrayList<Integer> indicesUnguessed = new ArrayList<Integer>();
for (int i = 0; i < guess.length(); i++){
char ch = guess.charAt(i);
if (ch != '-'){
n_CorrectGuesses ++;
} else {
indicesUnguessed.add(i);
}
}
boolean giveHint = n_CorrectGuesses <= guess.length() / 2 && wrongGuesses == n_Guesses - 2;
if (giveHint && hint == 0 && n_Guesses != n_GuessesMin){
pause(PRINTLN_DELAY);
println ("Are you stuck? Here is a HINT for you.");
pause(PRINTLN_DELAY);
// Randomly generate an index from the indicesUnguessed arraylist.
int randomIndex = rg.nextInt(indicesUnguessed.size());
// Get the number (which is i recorded through the for loop above) from the arraylist.
int i = indicesUnguessed.get(randomIndex);
// Apply this i to string "answer" to reveal a hint
guess = guess.substring(0, i) + answer.charAt(i) + guess.substring(i + 1);
hint ++;
println("Your word now looks like this: " + guess);
guessLabel.setLabel(guess);
println("You have " + (n_Guesses - wrongGuesses) + " guess(es) left.");
} else {
// If no hint is given, print regular messages.
println("Your word now looks like this: " + guess);
println("You have " + (n_Guesses - wrongGuesses) + " guess(es) left.");
}
}
/**
* Method: Get Random Word ------------------------- This method returns a
* word to use in the hangman game. It randomly selects from among the
* arrayList "lexicon" that contains the "lexicon.txt".
*/
private String getRandomWord() {
int index = rg.nextInt(lexicon.size());
return lexicon.get(index);
}
}