cs106a – Assignment #4 – Task #2

The complete specification of assignment #4 can be found as part of the stream at iTunes.

Adding graphics

For Part II, your task is simply to extend the program you have already written so that it now keeps track of the Hangman graphical display. Although you might want to spice things up in your extensions, the simple version of the final picture for the unfortunate user who has run out of guesses looks like this:

cs106a – assignment #4 – task #2

The scaffold and the tiny bit of rope above the head are drawn before the game begins, and then the parts are added in the following order: head, body, left arm, right arm, left leg, right leg, left foot, right foot. Because this picture is simpler than most of the figures you have drawn for section problems, the challenge of this part of the assignment does not lie so much in using the acm.graphics package but rather in implementing the separation of functions between the class that performs the console-based interaction and the class that manages the display. That class is called HangmanCanvas and is included in the starter project in the form of the stub implementation.
This stub is somewhat different from the one shown earlier for HangmanLexicon. That stub actually did something, even if it was only a part of what the complete implementation of the class will actually do. This stub declares several named constants that define the parameters of the picture, but doesn’t actually use them as yet. The three methods in the stub implementation of HangmanCanvas—reset, displayWord, and noteIncorrectGuess—do absolutely nothing. This strategy, however, is also common in programming. The fact that the class exists and exports methods means that you can call those methods from the console-based Hangman class even before you complete their implementation.
The first thing you should do when you begin Part II is to create a new HangmanCanvas— in precisely the do-nothing form in which it has been given to you—and install it in the program window next to the console. The Hangman class itself is an instance of a ConsoleProgram, which means that the startup code in the ACM libraries has installed an IOConsole in the window that fills the entire space. Your next task is to add a HangmanCanvas to the program window as well. You will learn much more about how to do this kind of operation toward the end of the CS 106A, but the code you need for this part is extremely simple. First, in the instance variables section of the Hangman program, you need to declare an instance variable for the canvas by writing

private HangmanCanvas canvas;

and then add the following init method to your program:

public void init() {
         canvas = new HangmanCanvas();
         add(canvas);
}

Note that your Hangman program will have both an init and a run method as a result, and that is perfectly fine. This init method initializes the canvas and adds it to the window prior to the run method being executed; the run method is where the execution of your game will start after the window is initialized. By default, the contents of the program window are given equal amounts of space side by side. Since this is a console program, the console is already installed and will therefore show up in the left column. When you add the HangmanCanvas it will occupy the second column, which means that the console and graphics components of the window will each get half the screen area. Input and output from the Hangman program will continue to appear on the console, and any objects you add to the HangmanCanvas stored in the variable canvas will appear in the area on the right.
You can now go through and add the calls to the methods in HangmanCanvas. Every time you start a game, for example, you will need to call

canvas.reset();

to delete all the body parts from the canvas and redraw the scaffold. Similarly, you will have to call displayWord and noteIncorrectGuess at the appropriate points in your code. As of yet, nothing will actually be displayed on the canvas when you make these calls, but your program should run just the same as it did before, freeing you to concentrate on implementing the methods in HangmanCanvas. Note that you should not add any more public methods to HangmanCanvas (adding private helper methods is fine though).
The implementation of HangmanCanvas should be reasonably straightforward. Although the sizes of the scaffold and the various body parts are given to you, their positions are not specified, so you will have to do some arithmetic to calculate the coordinates. The center line of the body should be centered horizontally on the screen, and the scaffold should be displayed a bit higher than the center so that there is room underneath for two labels: a label in a large font showing the secret word as it currently stands and a label in a smaller font showing the incorrect guesses.

Add – according to the the instruction the canvas instance variable and initialize it in the init() method:

	private HangmanCanvas canvas;

	public void init() {
		canvas = new HangmanCanvas();
		add(canvas);
	}

Reset the canvas together with your other “game status” variables:

        ....
		boolean gameWon = false;
		int numberOfGuesesLeft = 8;
		canvas.reset();
        ....

Display the current successfully guessed word inside the loop and in addition when the user has won the game:

        ....
		while (!gameWon && (numberOfGuesesLeft > 0)) {
			canvas.displayWord(currentWord);
        ....
			println("You win.");			
			canvas.displayWord(currentWord);
        ....

Display the unsuccessful guesses together with the console message:

        ....

				canvas.noteIncorrectGuess(guess);
				println("There are no " + guess + "'s in the word.");
        ....

To reset the canvas remove all previously drawn objects, draw the scaffold and prepare the labels:

	public void reset() {
		removeAll();
		numberOfWrongGuesses = 0;
		xHangman = getWidth() / 2;
		yHangman = (getHeight() - SCAFFOLD_HEIGHT) * 0.25 + ROPE_LENGTH;
		GPen scaffold = new GPen(xHangman, yHangman);
		scaffold.drawLine(0, -ROPE_LENGTH);
		scaffold.drawLine(-BEAM_LENGTH, 0);
		scaffold.drawLine(0, SCAFFOLD_HEIGHT);
		add(scaffold);
		
		double xLabels = xHangman - BEAM_LENGTH;
		double yLabels = yHangman + SCAFFOLD_HEIGHT;
		currentWord = new GLabel("");
		currentWord.setLocation(xLabels, yLabels + HEAD_RADIUS / 2);
		currentWord.setFont("SansSerif-28");
		add(currentWord);
		wrongGuesses = new GLabel("");
		wrongGuesses.setLocation(xLabels, yLabels + HEAD_RADIUS);
		add(wrongGuesses);
	}

Prepare methods to draw the body parts. Because arms, legs and feet are symmetric it is possible to reuse their functions for both sides:

	private void drawHead() {
		double d = 2 * HEAD_RADIUS;
		GOval head = new GOval(xHangman - HEAD_RADIUS, yHangman, d, d);
		yBody = yHangman + d;
		add(head);		
	}

	private void drawBody() {
		yLeg = yBody + BODY_LENGTH;
		GLine body = new GLine(xHangman, yBody, xHangman, yLeg);
		add(body);		
	}

	private void drawArm(int side) {
		GPen arm = new GPen(xHangman, yBody + ARM_OFFSET_FROM_HEAD);
		arm.drawLine(side * UPPER_ARM_LENGTH, 0);
		arm.drawLine(0, LOWER_ARM_LENGTH);
		add(arm);
	}

	private void drawLeg(int side) {
		GPen leg = new GPen(xHangman, yLeg);
		leg.drawLine(side * HIP_WIDTH, 0);
		leg.drawLine(0, LEG_LENGTH);
		add(leg);
	}

	private void drawFoot(int side) {
		double y = yLeg + LEG_LENGTH;
		double x = xHangman + side * HIP_WIDTH;
		GLine foot = new GLine(x, y, x + side * FOOT_LENGTH, y);
		add(foot);
	}

Displaying the currently guessed word consists only of changing the text of the previously setup label:

	public void displayWord(String word) {
		currentWord.setLabel(word);	
	}

For incorrect guesses draw the current body part and update the label with the wrong guesses:

	public void noteIncorrectGuess(char letter) {
		numberOfWrongGuesses++;
		switch (numberOfWrongGuesses) {
		case 1:
			drawHead();
			break;
		case 2:
			drawBody();
			break;
		case 3:
			drawArm(-1);
			break;
		case 4:
			drawArm(1);
			break;
		case 5:
			drawLeg(-1);
			break;
		case 6:
			drawLeg(1);
			break;
		case 7:
			drawFoot(-1);
			break;
		case 8:
			drawFoot(1);
			break;
		default:
			break;
		}
		
		updateWrongGuesses(letter);
	}

To update the label with the wrong guesses, loop over the currently displayed string. As long as the current letter comes before the guessed letter alphabetically, do not change the string. If the guessed letter is included in the old string, add it. Otherwise just paste the rest of the string to the end:

	private void updateWrongGuesses(char letter) {
		letter = Character.toUpperCase(letter);
		String newString = "";
		String oldString = wrongGuesses.getLabel();
		int length = oldString.length();
		boolean letterAdded = false;
		for (int i = 0; i < length; i++) {
			char ch = oldString.charAt(i);
			if (ch < letter) {
				newString += ch;
			} else {
				if (ch > letter) {
					newString += letter;
				}
				newString += oldString.substring(i);
				letterAdded = true;
				break;
			}
		}
		if (!letterAdded) {
			newString += letter;
		}		
		wrongGuesses.setLabel(newString);
	}

The code for this assignment is available on github.

FacebooktwitterredditpinterestlinkedintumblrmailFacebooktwitterredditpinterestlinkedintumblrmail

Leave a Reply

Your email address will not be published.