Event-Driven Calculator

(Java Code for GameEngine)

<<Previous | ToC | Next

This page continues the construction of a 4-function calculator in GameEngine as part of a course on programming in Java (which starts here). If you have not yet worked through the first page of this construction, you might want to go back and do that.

In Java

After you have built your calculator in GameMaker and clicked the Build button, quit out of GameMaker (close its window) and in the BlueJ dashboard window, right-click the background behind the yellow boxes, which pops up a menu: choose "Add Class from File" and browse over to your (new) ".java" (in my case "CalcGE.java") file and open it. This creates a new yellow box in the dashboard, which you can double-click to open its text.

Verify that this stub version compiles and runs. It won't do anything except put up your calculator buttons and display. Close the window to terminate it. This verification is important, because it establishes the fact that the generated code is good (if the window does not open, you can try the ideas in the "Trouble-Shooting GameEngine" page, or else get help, this may be beyond your ability to fix). Later, after you have added your own code -- basically copying what I show you, this first time -- if it fails to compile, you know it was something you did. Don't feel bad, I make mistakes all the time, that's what the Backspace key is on the keyboard for 

You will see some lines with dollar '$' signs in them. DON'T MESS WITH THE '$' LINES, GameMaker uses them to find where to insert its generated code. Think of GameMaker as your programming partner, you wouldn't want to mess up your partner's code, would you? I mean, you can and nobody gets hurt, except that GameMaker may tell you the file is corrupt and you lose all your work in it. Me, I'm too lazy to keep typing the same thing over and over. Outside the lines enclosed by and including the dollars, this is your program, do anything you want (but it still must be good Java if you want it to compile and run).

In particular, at the top of the file you will see all your widget names declared (between dollar lines); you can declare your own instance variables in the white space below the second dollar line. Mine looks like this after I added the three variables that need to persist between events:

public class CalcGE extends GameEvent {
  JavaGame myGame = null; // soon: reference to the GameEngine
  GameWgt CalcGE = null; // reference to the game board widget
  /// ($) Begin generated class variables (do not modify) ($..)
    GameWgt DisplayTx = null;
  /// (..$) End generated class variables ($) (do not modify this line)

  char optor = ' '; // added instance variables..
  double res = 0.0;
  String aWord = "";

  public String toString() {return "(CalcGE)";} //~toString

Some lines farther down there is a Startup method with a couple more dollar lines, where the generated variable(s) are initialized. Again, you may insert your own initialization code after the second dollar line. I added one line, to initialize the display to "0.0":
public void StartUp() {
  if (myGame != null) myRips = myGame.FindListWgt("{CalcGE}");
  /// [$] Begin generated StartUp code (do not modify) [$..]
  if (myGame==null) return;
  DisplayTx = myGame.FindListWgt("{DisplayTx}");
  /// [..$] End generated StartUp code [$] (do not modify this line)
  if (DisplayTx != null) {int tmp = DisplayTx.PutTextLn("0.0");}} //~StartUp

Most of the remaining methods are "stubbed out" (comments only). You will be using one of these, and you need to remove the double slants from the front of its lines (so they are no longer comments, but real code). The methods you don't need you can let the defaults do their thing, and either leave them commented out, or else remove those methods entirely. How do you know which ones you don't need? We're coming to that.

The one method you need is ClickEvt. Every time the user clicks on a button, this method is activated (called), and the whom parameter will be a reference to the widget they clicked on. The obvious most straight-forward way to do this is to make a long list of if-commands, comparing each known widget to the clicked whom. When you find a match, either you add that digit to the growing input number, or else if it's a function key, you first look to see if you have input and a function pending (and do it), then save the new function and wait for its input value, or if it's a function key with no input value (like equal, but you will have already done the pending function key) or Clear, then just do it.

But there's an easier way to do this. All the buttons are Rectangle widgets with identifier values you gave in GameMaker, so inside the ClickEvt method, we can ask what identifier number is associated with the button that got clicked, and we need only a few lines, one for digits, and a few more for function keys:

public boolean ClickEvt(GameWgt whom, int vert, int horz) {
  int tmp, whazzat = whom.GetInfo();
and for the function keys, you can cast (convert) it to a character:
char btnchr = (char)whazzat; // cast the saved int to original char
which is really convenient when deciding what to do with it, which happens next:
if ((btnchr == '+')||(btnchr == '-')||(btnchr == '*')
    || (btnchr == '/') || (btnchr == '=')) {
  // got function key, see if we have one pending..
  if (optor > ' ') if (aWord != "") { // do pending operation..
    double inval = Double.parseDouble(aWord);
/// f. if command is '+' add
    if (optor == '+') res = res+inval;
/// g. else if '-' subtract
    else if (optor == '-') res = res-inval;
/// h. else if '*' multiply
/// i. else if '/' divide
    ... // and so on for each operator
    } // end of doing pending op
  if (btnchr == '=') optor = ' '; // already did it
    else optor = btnchr;
  aWord = ""; // prepare for input
  } // end of got function key
/// e. accept a value (one digit at a time)
else if (whazzat <= 9) aWord = aWord + whazzat;
if (DisplayTx == null) return false;
/// a. display the current result
if (aWord != "") // we have partial input, display it..
  tmp = DisplayTx.PutTextLn(aWord);
else tmp = DisplayTx.PutTextLn("" + res); // otherwise show the result
return false;} //~ClickEvt

Recall that aWord is the String you are accumulating the input digits into and optor is a character variable, both declared with the result value res at the class level (after the second dollar line, but before the first method, the three blue lines above) so they hold their value between events.

I also have three local variables, declared inside this method, temporary values for looking at the button identifier before deciding what to do with it, and one temporary to accept (and discard) the result from the widget method PutTextLn, which puts a text line into the result display widget DisplayTx -- you may have given yours a different name, use yours when you add this code to your copy of ClickEvt -- and returns the actual (pixel) width in case you need it (which you don't, not today). When the user is pressing digit keys, the partial input (in variable aWord) is displayed; otherwise we show the result res.

A couple of remarks about the green line. Recall that res is a (double) number, not text, but PutTextLn is expecting a String parameter. You can know this by searching the GameWgt class for the PutTextLn method, or by clicking the PutTextLn link, or else in BlueJ, when you hover the mouse over the method name, it will pop up the JavaDoc documentation for that method, and you can read it there. In any case, Java will automatically convert a double value to String in some places, but not here as a parameter; however, concatenating it to an empty String (constant, "") is one of the places where it will convert, so we do that before passing it as a parameter. PutTextLn returns an integer result. I think Java lets you ignore the result of a function call, but doing so is a bad habit that will result in bugs that the compiler could have caught, but does not. The Java designer(s) got that one wrong. So I put the result into an otherwise unused variable tmp.

Everything else here probably looks familiar from the text-based calculator you already did.

Does your program compile? If not, look for the first red bar in the margin, or click on the errors message in the lower right corner of the program window. The message it gives you may be wrong (it often is), but it might help you to find the true problem. When you fix the first problem, others may also go away. The compiler is easily confused, it was written by a programmer just like you (only a little farther along) and they make mistakes too. If you can't fix the problem, get help, that's what the instructors are here for.

When all else fails, delete the class (from the pop-up menu in the BlueJ dashboard, then go back to GameMaker and click Build again -- you may need to make some trivial change (like check and uncheck a checkbox) to get the OK button to reappear. Then start over with the fresh copy of your program. Verify that the fresh copy compiles and runs before you add anything. Keep testing after you add each line (you will need to add the matching right brace for each left brace before the compiler will accept it).

When it runs (after you have added your code and successfully compiled it) you can click on digits and see them show up on the display. At least that's what they are supposed to do. If not, go to the next page and we'll work through the problem.

Once you have the digits working, you can click a function key followed by a number, and it will do the math. But if you start it up fresh and click the digits of a number without a preceeding function, that number is lost. That's A Bad Thing, never lose the user's data. Also, when the user clicks a function key, they must remember that they did that, there's no visual indication that they did it. We can do better.

In the next page we will look at these problems, and also program the Clear button and the decimal point.

<<Previous | ToC | Next >>

Revised: 2020 November 4