<<Previous | ToC
| Next >>
The purpose of this assignment is to learn to use arrays of arrays (multiple-dimensioned arrays) in Java. It's a bit tricky, so first we start with the non-arrays version.
You should understand, ASCII graphics, like teletypes and command lines, is the buggy-whip of computer technology, necessary when the car won't start, but largely replaced by more modern technology like electric cars, full-color graphics monitors and drag'n'drop direct manipulation. So if you want to skip this section, you won't miss much -- except we do some fancy things with arrays that you should know about.
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:
/| _
/ | ( )
/ | _|_
/ | / | \
/ | / | \
/ | |
/ | / \
/ | / \
/________| _/ \_
______________________
\ /
It is important for this to look good, that it be displayed in a
monospace font like Courier, which most Java consoles already are. 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 seaman on his boat as a banner at the front, one more added line. I called mine "SeaAsky" 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 boat) to fully seen = 2 legs + 2 arms + 1 spine + 1 head = 6. 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. But it doesn't affect anything at all. Place this subroutine inside your "class Hello", in the blank line just before your main() declaration, like this:
public Class Hello {Many Java IDEs put a box of some sort around each subroutine (Replit has an open-close arrow in the margin), and this one gets its own.private static void SeaAsky(int nerrs) {
} // end of SeaAsky
public static void main() {
...
Now we can start to fill in the code for this subroutine. If we do the whole seaman first, then it's easier to do the other variations by copy and paste. I did the first and last three lines, you can fill in the rest:
private static void SeaAsky(int nerrs) {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. It shifts the rest of the image out a little, but that's only in your code listing, the printout works as if the two back-slants were one.
if (nerrs >= 6) {
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;}
if (nerrs==5) { // insert a copy of the above,
// ..less the sailor's head
return;}
// etc
} // end of SeaAsky
Let's try it out. In your existing main program, after the variable declarations, but before any other code, insert the single line
SeaAsky(6);
Compile it and run it. Did it work OK? If not, can you figure out
why not?
We now have six more values of nerrs (zero through five) to fill out code for. Here are the first two plays, no errors, then one (the left leg alone), and so on:
/| (0) /| (1)
/ | / |
/ | / |
/ | / |
/ | / |
/ | / |
/ | / | /
/ | / | /
/________| /________| _/
______________________ ______________________
\ / \ /
Zero is just the sail and the boat hull/deck, copy the same print
lines you used for the full seaman, but erase the seaman. Can you do that?
Try it. For each error count, you add more and more of the seaman's body.
The easy way is to work backwards from six, so for five you start with
the same eleven lines, but erase just the head. Then copy the most recent
eleven lines (missing just the head), and erase also the right arm for
number 4. Then copy #4 and erase also (over-type spaces on) the left arm
for #3. You can do that.
Now, to test the creation. Down in your main code, where you print out the number of errors, insert another call to SeaAsky, but giving the number of errors as a parameter instead of 6. Play your game (but lose), and see if the seaman 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.
We could make an array of the eleven lines (quoted strings) to draw the seaman, 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 seaman on his boat. Does that make sense? Here is the beginning (the empty boat) and the last two lines at the end (with the man fully drawn and ready to set sail, and before that missing his head) of that declaration, copied and pasted from my images above:
final String[][] SeaStrs = {
{" /|", " / |", " / |",
" / |", " / |", " / |",
" / |", " / |", "/________|",
"______________________",
"\\ /"},
...
{" /|",
" / |",
" / | _|_",
" / | / | \\",
" / | / | \\",
" / | |",
" / | / \\",
" / | / \\",
"/________| _/ \\_",
"______________________",
"\\ /"},
{" /| _",
" / | ( )",
" / | _|_",
" / | / | \\",
" / | / | \\",
" / | |",
" / | / \\",
" / | / \\",
"/________| _/ \\_",
"______________________",
"\\ /"}};
Notice that for each of seven plays (boat only = no guy, then left
leg, right leg, backbone, left arm, right arm, and finally a head on the
whole guy) you need eleven string lines, a total of 77 string constants,
eleven in each of seven brace pairs, all within a containing brace pair
(you get to fill in the missing eight in the middle, where the three dots
are). 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 SeaAsk2d:
private static void SeaAsk2d(int nerrs) {
final String[][] SeaStrs = {...} // end of SeaAsk2d
Now, instead of 77 print statements, you can do it with one, inside
a for-loop:
for (int nx=0; nx<11; nx++) System.out.println(SeaStrs[nerrs][nx]);
But we still have that big ugly array of strings. We can do better.
final String[] uniques = { "", // 0You get to fill out the legs part of the array. Hmm, it looks like I made only a single line for both shoulders, to be used when the left arm is added and kept there when the right arm is added.
" /|", " / |", " / |", // 1..3
" / |", " / |", " / |", // 4..6
" / |", " / |", "/________|", // 7..9
"______________________", // 10
"\\ /", // 11
" |", " __", " ( )", " _|_", // 12..15
" / |", " / | \\", " / |", " / | \\", // 16..19
... // 20..25
};
We'll call this version SeaAsk3i because we now have a 3-dimension array, but now it's an integer array, and the numbers are the index numbers of the string array, as annotated in the comments. The first dimension is still the error number, zero for no errors (just the empty boat), six for a fully drawn seaman on the boat as before. The third dimension has two elements for each line of each error number, the boat on the left, and then the sailor's body part on the right, or else zero (meaning no body part). The first line of every error number less than six draws just the tip of the sail (uniques[1]) and then nothing (uniques[0]), is a single line, {1,0}. The next line has the next segment of the sail ([2]) and again nothing for six error numbers, {2,0}, and then for error numbers six and seven, the sailor's head {2,14}. I set uniques[12] to be the spine, which we use on line 5 of every error number from three on, and for the rest of the spine up to the sailor's head on error number three.
final int[][][] iSea = {{{1,0},{2,0},...,{9,0},{10,0},{11,0}} // no errorsI left some gaps for you to fill in. The only tricky part is the arms and legs, where you have separate line numbers for the left arm and leg and both arms and both legs. In my numbering above, string line 15 is both shoulders, 16 is the top of the left arm, and 17 is the tops of both arms; similarly 18 is the left hand and 19 is both hands. So for four errors, your index array would show '{3,15},{4,16},{5,18}' in those three positions, and for five or more errors you would put '{3,15},{4,17},{5,19}' there. Does that make sense?
{{1,0},{2,0},...,{9,24},{10,0},{11,0}} // one error
...
{{1,0},{2,14},...,{9,24},{10,0},{11,0}} // six errors
{{1,13},{2,14},...,{9,24},{10,0},{11,0}}}; // seven errors
You need to convince yourself why those are the correct numbers, or you will not be able finish out the table. Remember, you are defining the leg segments, you need to make sure your ASCII graphic leg segments correspond to the index table values you choose for them. Don't worry if you make a mistake, it will be obvious when you go to draw the sailor, and then you can count over to what it drew against what it should have drawn, and correct the miscount.
So your new subroutine starts out like this:
private static void SeaAsk3i(int nerrs) {You get to fill in the missing table data.
final String[] uniques = { "", // 0
...};
final int[][][] iSea = {{{1,0},{2,0},...,
...}};} // end of SeaAsk2i
Now let's build the for-loop carefully. The loop part is the same, but the print statement has a boat part and a sailor part. 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 iSea 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 a pair of numbers, the first is the index of that string in uniques which is the boat segment for that line, and the second number is either zero (no body part to draw) or else the index of that body part in the same array. We collect these two numbers into two integer variables bx and sx, which each then indexes the uniques array to fetch one of the two strings to be drawn on that line. So we could write it like this:
for (int nx=0; nx<11; nx++) {or alternatively:
int bx = iSea[nerrs][nx][0];
int sx = iSea[nerrs][nx][1];
System.out.println(uniques[bx] + uniques[sx]);}
String aLine = "";
for (int nx=0; nx<11; nx++) {
for (int kx=0; kx<2; kx++)
aLine = aLine + uniques[iSea[nerrs][nx][kx]];
System.out.println(aLine);
aLine = "";}
Do you see how that works? Wouldn't you agree, this is a lot more
"elegant" than 77 print lines?
Important! Do not skip to the next page until you understand exactly how this works. If necessary, put the 4-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. If you need help, that's what the mentor is for. If you go into programming, then you need to be able to read (and understand) code like this even if you never write another complicated array indexing program in your life.
Next: Calculator
<<Previous | ToC | Next >>
Revised: 2023 February 3