Finalizing Seaman for GameMaker


<<Previous | ToC | Next >>
 

Spoiler Alert!

Some people say you can only learn to program by programming. Others say you can learn better by looking at other people's code. I think it takes both. So if you want to learn Java best, you should try to do your own version of Seaman before scrolling down to see my version on this page. But the motivation is yours alone, you alone decide how into this you want to get.

There are several little issues that need to be cleaned up before your Seaman can run properly. We'll look at them now in case and while you are contemplating whether to write before looking.
 

Input Widget

The GameEngine only sends keyboard input to widgets that are defined to be able to accept that input, and that have keyboard focus at the time of input. For Seaman we don't care about focus or differentiating who has it, we just want to see the keystrokes. In GameEngine we accomplish this purpose relatively painlessly. We still need some widget to be designated as accepting keyboard input, so the GameEngine can give its reference to KeyFld. We'll let that be the game board, and we do it in GameMaker. Save and close your Seaman class window, and right-click the GameMaker rectangle in the BlueJ dashboard to run its main(). It will re-open the same setup you had when we left it. If not already selected, click on the game name in the widget list, then check the Key checkbox, then hit Enter on the keyboard twice, once to click OK (and accept your change) and the second time to reBuild your Java template -- don't worry, if you only entered code outside those "$" line regions, your new code is safe. To be extra safe, before starting up GameMaker, make a copy of your Seaman.java (or whatever you called it) in another folder. Bad Things Happen, so "Save early, save often."

Then double-click your Seaman class rectangle again. You can go back and forth like this as often as necessary as you think of (or remember) things better doen in GameMaker or in Java. I don't think BlueJ knows about GameMaker rewriting its java files, so I always close and re-open them.

Back in the Startup() method, you need to tell the GameEngine that the game board is the default recipient of keystrokes. The first line of the method, before the method's first "$" line, is where it makes an assignment to the class copy of the game board widget, which has a name something like "mySeaman" (or whatever you called your game). Add one more line there, either before that first "$" line or else after the second,

DefaultKeyFocus(mySeaman); // or whatever your game is called

Enter Input

We wrote all of our input event handling in the single predefined method KeyFld, but GameEngine only sends printable characters to KeyFld. In particular, to see the enter key input in the same method, we need to forward it from GotEnterKey to KeyFld, like this:
public boolean GotEnterKey(GameWgt whom) {
  KeyFld(whom,'\n');
  return false;} //~GotEnterKey

Greetings

When I first ran my program, I had several programming errors. Three of them prevented compiling, so obviously I had to fix them (no need to bore you with the details). A couple more relate to the user interface. If you go back and look at our English program design, there's a line that tells the user what's going on. Maybe you noticed; I forgot. So my program started up with nothing there. I added a couple lines in the Startup method to correct that problem:
if (Hmsg != null)
  zx = Hmsg.PutTextLn("Seaman: choose a word...");} //~StartUp
You probably gave your message widget a different name than I did. Use the name you chose. Obviously, after the first player has chosen their word, this message becomes obsolete. I added a couple more lines like it within the section of code where we set playing=true
if (Hmsg != null)
  zx = Hmsg.PutTextLn("Playing Seaman: guess a letter...");

Termination

The other problem occurred at the end. Maybe you were more clever than I, but when my program got to the end and just stopped (exited the program) -- which was fine, except the user didn't have any time to see any clever message I displayed. I decided I need another state variable (if we had a number instead of a boolean for playing, we could just assign another value, and I usually do that). Because this is a state variable (tells the program what "state" the program is in, that is what part of the game is active, in this case, the game ended and we are merely hanging on until the user has finished reading our G'bye message) it needs to be defined out at the class level, next to playing. I called mine "ended" because when it's true, it means the game ended. Initialized false, and set to true when either the body parts are fully visible, or else the word is fully guessed. The first thing into the KeyFld method, before we do anything else,
if (ended) System.exit(0); // quit out of this program
That way any keystroke after the message goes up ends the program. In the next page we can think about restarting and playing again.
 
 

My KeyFld method

This is my code for the (predefined) method KeyFld. If your code works, you can look at mine for entertainment. If some part of your program fails, you can compare what you have to what I did to see what's different. If you didn't try, you can look at my code for entertainment. But like riding a bicycle, you learn by doing.
public void KeyFld(GameWgt whom, char info) {
  boolean gotit = false;
  int zx;
  if (ended) System.exit(0); // quit out of this program
  if (!playing) {
    if (info < 'a') {
    if (Hmsg != null)
      zx = Hmsg.PutTextLn("Seaman: guess a letter...");
    playing = true;
      sofar = nLetts;} //~if
    else {
      guessword[nLetts] = info;
      dashes[nLetts].HideSho(true);
      nLetts++;}} //~else //~if
  else if (info >= 'a') if (info <= 'z') { // (if playing is true)...
    for (int nx=0; nx<nLetts; nx++) { // try each letter
      char letter = guessword[nx];
      if (letter == info) { // do all this..
        gotit = true;
        whom = dashes[nx];
        if (whom != null) {
          whom.HideSho(true);
          zx = whom.PutTextLn(""+info);} // PuTxLn needs String
        sofar--;} //~if
      } //~for
    if (!gotit) {
      wrong++;
      switch (wrong) { // show the body part for wrong..
        case 1:
          if (LefLeg != null) LefLeg.HideSho(true);
          break;
        case 2:
          if (RitLeg != null) RitLeg.HideSho(true);
          break;
        case 3:
          if (Spine != null) Spine.HideSho(true);
          break;
        case 4:
          if (Shoulder != null) Shoulder.HideSho(true);
          break;
        case 5:
          if (LefArm != null) LefArm.HideSho(true);
          break;
        case 6:
          if (RitArm != null) RitArm.HideSho(true);
          break;
        case 7:
          if (Head != null) Head.HideSho(true);
          if (Face != null) Face.HideSho(true);
          break;} // this brace closes the switch
      } //~if
    whom = Hmsg;
    if (whom == null) return;
    if (sofar == 0)
      zx = whom.PutTextLn("You got it!");
    else if (wrong >= 7)
      zx = whom.PutTextLn("Better luck next time");
    else return;
    ended = true;} //~if (playing is true)
  } //~KeyFld


In the next page we'll look at some graphical extras and other improvements you might be interested in.

<<Previous | ToC | Next >>

[2022 November 19]