Learn Programming in Java


<<Previous | ToC | Next >>

ASCII Graphics & Subroutine(s)

After you have your HangMan game working with just keeping score, we can work on displaying the hung man. Traditionally, as a paper&pencil game, this is drawn with stick figures. Later on we can do this with GameEngine graphics, but for now we'll use the traditional ASCII (pronounced "ASS-key") graphics, placing characters on the page arranged so the result resembles the desired picture.

Here is my simple image, when the word was not guessed and the guy is fully rendered. You are not stuck with this, you can do your own, but my explanation will go better if you just use mine for now:

_______
|     |
|    ( )
|    _|_
|   / | \
|  /  |  \
|     |
|    / \
|   /   \
| _/     \_
|__________


Notice I used only six different characters, the vertical bar and back slant (both on the same key, near the backspace or enter key on most keyboards), the regular slant (next to the right shift key), the underscore (shift-hyphen, next to the zero), and left and right parentheses (shift-9 and -0). It is important for this to look good, that it be displayed in a monospace font like Courier, which the BlueJ console window already is. There are eleven lines of text in this graphic, and the simplest way to display it would be with eleven println commands, one for each line. We'll start with that.

Keep your existing game, we'll make this a separate subroutine, then call it from a single line in the main program. Each time we rewrite this subroutine, we can give it a new name, then change just that one line in the main program. When I did this, I also printed the whole hung man as a banner at the front, one more added line. I called mine "HungAsky" and gave it a single integer parameter, the number of body parts to draw, which is how many wrong guesses so far. That would range from zero (just the gallows) to fully hung = 2 legs + 2 arms + 1 head + 1 spine + 1 noose = 7. The subroutine returns no value -- that's called "void" in Java -- and is not Object-oriented (yet), which is spelled "static" in Java. Normally also you would declare it "private" because it's only being used here locally. Place this subroutine inside the green box that is your "class Hello", in the blank line just before the yellow box that is your main() declaration, like this:

public Class Hello {

  private static void HungAsky(int nerrs) {

    } // end of HungAsky

  public static void main() {
...

Notice that BlueJ puts a second (solid) yellow box around your new subroutine.

Now we can start to fill in the code for this subroutine. If we do the whole hung man first, then it's easier to do the other variations by copy and paste. I did the first three lines and the last two, you can fill in the rest:

private static void HungAsky(int nerrs) {
  if (nerrs >= 7) {
    System.out.println("_______");
    System.out.println("|     |");
    System.out.println("|    ( )");
    System.out.println("");
    System.out.println("");
    System.out.println("");
    System.out.println("");
    System.out.println("");
    System.out.println("");
    System.out.println("| _/     \\_");
    System.out.println("|__________");
    return;}
  } // end of HungAsky
Notice that I doubled up the back slant. It's an escape character in Java, it means "take the next character literally" (or alternatively "interpret the following letter as the name of a control character"), so a pair of them together takes the second one literally, which is what we want. You need to do it for each of your back slants.

Let's try it out. In your existing main program, after the variable declarations, but before any other code, insert the single line

HungAsky(7);


Compile it and run it. Did it work OK? If not, can you figure out why not?

We now have seven more values of nerrs (zero through six) to fill out code for. Here are the first three plays, no errors, then one (the noose alone), then two errors (noose + head), and so on:

_______ (0)        _______ (1)        _______ (2)
|                  |     |            |     |
|                  |                  |    ( )
|                  |                  |
|                  |                  |
|                  |                  |
|                  |                  |
|                  |                  |
|                  |                  |
|                  |                  |
|__________        |__________        |__________


Zero is easy, it's the same first and last lines, but the other nine lines are all the same, just one vertical bar for the gallows support post. Can you do that? Try it. For the first error, we draw the noose (well, actually, just the little vertical line from the gallows overhang down to where the head will be), the first two lines the same as seven, but all the rest the same as zero. Two errors picks up the convict's head. I did three errors as the spine, four vertical lines identical to the noose, then three empty (just the gallows post). We add the arms and legs one at a time. You can do that.

Now, to test the creation. Down in your main code, where you print out the number of errors, replace that with another call to HungAsky, but giving the number of errors as a parameter instead of 7. Play your game (but lose), and see if the hung man is built correctly.

You could show this program to your family and friends, and they will all say nice things to you. Show it to a programmer, and he will yawn. That's because you have a hundred lines of code, when it could be much smaller and more elegant. Arrays are more elegant than straight code. Let's do that.
 

String Arrays

We could make an array of the eleven lines (quoted strings) to draw the hung man, or I think I would do a two-dimensioned array (array of String arrays), so that the first index is how many misses (added body parts) so far, and the second index is the eleven lines of text for that view of the hanged man. Does that make sense? Here is the beginning (first five plays) and end (with the man hung) of that declaration, copied and pasted from my images above:
final String[][] hung = {
    {"_______", "|", "|", "|", "|", "|", "|", "|", "|", "|", "|__________"},
    {"_______", "|     |", "|", "|", "|", "|", "|", "|", "|", "|", "|__________"},
    {"_______", "|     |", "|    ( )", "|", "|",
        "|", "|", "|", "|", "|", "|__________"},
    {"_______", "|     |", "|    ( )", "|     |", "|     |", "|     |",
        "|     |", "|", "|", "|", "|__________"},
    {"_______", "|     |", "|    ( )", "|    _|", "|   / |", "|  /  |",
        "|     |", "|", "|", "|", "|__________"}};
    ...
    {"_______", "|     |", "|    ( )", "|    _|_", "|   / | \\", "|  /  |  \\",
        "|     |", "|    / \\", "|   /   \\", "| _/     \\_", "|__________"}};


Notice that for each of eight plays (no guy, then noose, head, backbone, left arm, right arm, left leg and finally the whole guy) you need eleven string lines, a total of 88 string constants, eleven in each of eight brace pairs, all within a containing brace pair (you get to fill in the missing two in the middle). Don't forget to double (escape) the back slants. Whew! Later, I'll show you how to do this in the Game Engine, which looks nicer and has other advantages.

I would make a new subroutine HungAsk2d:

private static void HungAsk2d(int nerrs) {
  final String[][] hung = {...

  } // end of HungAsk2d


Now, instead of 88 print statements, you can do it with one, inside a for-loop:

for (int nx=0; nx<11; nx++) System.out.println(hung[nerrs][nx]);


But we still have that big ugly array of strings. We can do better.
 

Index Arrays

Notice how many times you repeated that one single-character vertical post "|". The top and bottom lines are repeated eight times each, the head six times, and the noose segment another dozen or so. Suppose we made an array of string literals containing just one copy of each unique string. I counted sixteen unique string segments. Can you make that? We'll call this version HungAsk2i because we still have a double-dimensioned array, but now it's an integer array, and the numbers are the index numbers of the string array:
private static void HungAsk2i(int nerrs) {
  final String[] uniques = {"_______", "|", "|__________", "|     |", "|    ( )",
     ...};
  final int[][] ihung = {{0,1,1,1,1,1,1,1,1,1,2},
      {0,3,1,1,1,1,1,1,1,1,2}, {0,3,4,1,1,1,1,1,1,1,2},
      {0,3,4,3,3,3,3,1,1,1,2}, ...}};

  } // end of HungAsk2i

You get to fill in the missing table data.

Now let's build the for-loop carefully. The loop part is the same, as is the print statement. What's different is how we got to the text being printed. Recall it's somewhere in the new uniques array, but where? The new array ihung is still an array of arrays, and the first index is still the number of errors so far -- are you still with me? -- and the second index is still the control variable of the for-loop, but each entry in the array, instead of being an actual string to print, it is the index of that string in uniques. So we could write it like this:

for (int nx=0; nx<11; nx++) {
  int sx = ihung[nerrs][nx];
  System.out.println(uniques[sx]);}
or more simply:
for (int nx=0; nx<11; nx++)
  System.out.println(uniques[ihung[nerrs][nx]]);


Do you see how that works? Wouldn't you agree, this is a lot more "elegant" than 88 print lines?

Important! Do not skip to the next page until you understand exactly how this works. If necessary, put the 3-line version of the for-loop in your program and step through it in the debugger, so you can see where the numbers come and go.

Next: Calculator

<<Previous | ToC | Next >>

Revised: 2020 November 16