<*>= <Import List> <Definition of class SimpleMath> <Definition of class Fraction> <Definition of class linearEquation> <Definition of class SolutionWin>
This code is written to a file (or else not used).This code is written to a file (or else not used).
Our import list is pretty standard, except that we import the java.awt.event
package, since we are using JDK version 1.1.1.
<Import List>= import java.awt.*; import java.applet.*; import java.awt.event.*; import java.util.Random; import java.util.StringTokenizer;
Used above.
Class SimpleMath must extend class Applet since we want it to run as an applet too.
It implements interfaces ActionListener and ItemListener
in order to handle the various events. Moreover, it implements the interface Runnable
in order to enable multi-threading. This capability is used when the user opts to
get the complete solution of an equation by pressing the SolveEq button which activates
the SolutionWin. (In our setup we are interested only in button-pressing and
``choice''-making, so implement only these interfaces.)
<Definition of class SimpleMath>=
public class SimpleMath extends Applet
implements ActionListener,
ItemListener
{
<Variable Ddefinitions of Class SimpleMath>
<Method init of Class SimpleMath>
<Method main of Class SimpleMath>
<event handling>
}
Used above.
We provide the user with a Graphical User Interface, so that he can easily operate the
program. For this reason we define many Components which we place on the screen.
<Variable Ddefinitions of Class SimpleMath>= static boolean inApplet=true; // does the program run as an applet or an application? Choice EquationType; // The equation type the user wants to play around with Choice EquationDifficulty; // For each EquationType there are several levels of difficulty Button NewEquation; // If pressed the program produces a new equation Button CheckSolution; // If pressed the program checks the solution the user has produced TextField NewEqDisplay; // Used by the program to display a new equation TextField UserDisplay; // The place where the user writes his solution of an equation TextField ProgDisplay; // The place where the program writes the solution TextField Correct; // Displays the number of correct answers the user has given int corrans=0; // correct answers counter TextField Wrong; // Displays the number of wrong answers the user has given int wrongans=0; // wrong answers counter Button ClearScores; // Once pressed resets the Score Tables Button Quit; // The Quit button Button SolveEq; // When pressed activates the solution window int eqLevel=1; // The current equation level int eqType=1; // The current equation type linearEquation LE; // The linear equation generator and solver SolutionWin SW; // The solution window // AudioClip succClip, failClip; // ``Sound'' variables
Used above.
We have defined all these GUI components, but now we must put them on the screen. Since
things are not so straightforward we must use the GridBaglayout layout manager. (This
layout manager is always "accompanied" by a GridBagConstraints.) We have decided to
put on the first row the EquationType menu, and on the second
row the EquationDifficulty
menu. Along each menu we put a label which "explains" each menu. On the third row we will put a
Label, that will explain the functionality of the
NewEqDisplay text-field next to it. Next to the TextField we put a the
NewEquation button. The forth row is identical to the third row: a label (to be defined
inside method init), a text-field (UserDisplay), and button (CheckSolution).
On the fifth row we place a label (to be defined in method init) and the text-field
ProgDisplay. On the sixth row we put the "statistics" components, e.g., text-fields
Correct and Wrong plus a label in front of them. Finally. on the last row we put a
"Quit" button, so that the user can press it to terminate the program. (Please note,
that each button is added to the ActionListener, so that Java can handle properly
the pressing of button.)
<Method init of Class SimpleMath>=
public void init()
{
LE = new linearEquation();
GridBagConstraints c = new GridBagConstraints();
GridBagLayout gridbag = new GridBagLayout();
setLayout(gridbag);
setFont(new Font("Courier", Font.BOLD, 17));
// Let's start putting components on the screen!
//row 1
Label l0 = new Label("Equation Type");
c.fill = GridBagConstraints.NONE;
c.insets = new Insets(5,0,5,0);
gridbag.setConstraints(l0, c);
add(l0);
EquationType = new Choice();
EquationType.add("Linear Equation");
/* EquationType.add("Non-Linear Equation"); */
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(EquationType,c);
EquationType.addItemListener(this);
add(EquationType);
//row 2
Label l10 = new Label("Difficulty Level");
c.gridwidth = 1;
gridbag.setConstraints(l10, c);
add(l10);
EquationDifficulty = new Choice();
EquationDifficulty.add("One");
EquationDifficulty.add("Two");
EquationDifficulty.add("Three");
EquationDifficulty.add("Four");
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(EquationDifficulty,c);
EquationDifficulty.addItemListener(this);
add(EquationDifficulty);
//row 3
c.gridwidth = 1;
//c.fill = GridBagConstraints.NONE;
c.weightx = 1;
Label l1 = new Label("Solve this Equation!");
gridbag.setConstraints(l1, c);
add(l1);
NewEqDisplay = new TextField(25);
gridbag.setConstraints(NewEqDisplay, c);
add(NewEqDisplay);
c.gridwidth = GridBagConstraints.REMAINDER;
NewEquation = new Button(" New Equation ");
gridbag.setConstraints(NewEquation, c);
add(NewEquation);
NewEquation.addActionListener(this);
//row 4
c.gridwidth = 1;
Label l2 = new Label("Write Your Solution!");
gridbag.setConstraints(l2, c);
add(l2);
UserDisplay = new TextField(25);
gridbag.setConstraints(UserDisplay, c);
add(UserDisplay);
c.gridwidth = GridBagConstraints.REMAINDER;
CheckSolution = new Button("Check Solution");
gridbag.setConstraints(CheckSolution, c);
add(CheckSolution);
CheckSolution.addActionListener(this);
//row 5
c.gridwidth = 1;
Label l3 = new Label("Answer");
gridbag.setConstraints(l3, c);
add(l3);
ProgDisplay = new TextField(25);
gridbag.setConstraints(ProgDisplay, c);
add(ProgDisplay);
c.gridwidth = GridBagConstraints.REMAINDER;
SolveEq = new Button("Solve Equation");
gridbag.setConstraints(SolveEq, c);
add(SolveEq);
SolveEq.addActionListener(this);
//row 6
c.gridwidth = 1;
Label l4 = new Label("Scores");
gridbag.setConstraints(l4, c);
add(l4);
Correct = new TextField("Correct Answers: 0", 22);
gridbag.setConstraints(Correct, c);
add(Correct);
Wrong = new TextField("Wrong Answers: 0", 22);
gridbag.setConstraints(Wrong, c);
add(Wrong);
c.gridwidth = GridBagConstraints.REMAINDER;
ClearScores = new Button("Clear Scores");
gridbag.setConstraints(ClearScores, c);
add(ClearScores);
ClearScores.addActionListener(this);
//row 7, appears only if program runs as an application
if (!inApplet)
{
Quit = new Button("Quit");
c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.CENTER;
gridbag.setConstraints(Quit, c);
add(Quit);
Quit.addActionListener(this);
}
//succClip = getAudioClip(getCodeBase(), "clap.au");
//failClip = getAudioClip(getCodeBase(), "crash.au");
}
Used above.
As we said previously we want our program to run as either an applet or an
application, i.e.,
not be executed within a browser. In order to achieve this we must implement method main.
Since this program is one with a GUI, we must put all components inside a Frame (it is
standard practice, not some novelty of mine!) Once the Frame is created, we create an
instance of class SimpleMath which we add to the Frame, using the default
layout manager. Next we "compactify" the Frame and we make it visible.
<Method main of Class SimpleMath>=
public static void main(String[] args)
{
inApplet = false;
Frame f = new Frame("Simple Mathematics");
SimpleMath sm = new SimpleMath();
sm.init();
f.add("Center", sm);
f.pack();
f.setVisible(true);
}
Used above.
Without the proper handling of events, our program can't do anything at all!
We are interested in two kinds of events: (a) events over buttons and (b) events
over choices. Category (a) is handled by method actionPerformed and
category (b) by method ItemStateChanged.
<event handling>=
<implementation of method actionPerformed>
<implementation of method ItemStateChanged>
Used above.
The code that follows is pretty standard. We get the ``source'' of each event and we
act accordingly. Variable WrongActionType is used to avoid complications when
the user presses the CheckSolution button or the SolveEq button when there is
either no input or the program has not produced an equation.
<implementation of method actionPerformed>=
public void actionPerformed(ActionEvent E)
{
Object src = E.getSource();
boolean WrongAction = false;
<user pressed Quit button>
<user pressed ClearScores button>
<user pressed NewEquation button>
<user pressed CheckSolution button>
<user pressed SolveEq button>
}
Used above.
If our program runs as an application it must exit, if the user presses the quit button.
<user pressed Quit button>=
if (src == Quit)
{
System.exit(0);
}
Used above.
It is possible to reset to score board (maybe your scores aren't so good and you want to forget;-). All we have to do is just to put the initial text on the two associated TextFields.
<user pressed ClearScores button>=
else if (src == ClearScores)
{
Correct.setText("Correct Answers: 0");
corrans=0;
Wrong.setText("Wrong Answers: 0");
wrongans=0;
}
Used above.
If the user requests a new equation one must be created. Depending on the current type
of equation we must generate the analogous equation. This is being done by checking the
current value of variable eqType. Moreover, we must produce an equation with the
specified difficulty level. This is being done by checking the current value of
variable eqLevel.
<user pressed NewEquation button>=
else if (src == NewEquation)
{
switch (eqType)
{
case 1:
LE.level = eqLevel;
LE.setFactors();
NewEqDisplay.setText(LE.Equation);
LE.solveEquation();
UserDisplay.setText("");
WrongAction = false;
break;
/* case 2: not yet implemented */
}
}
Used above.
Verifying the correctness of a solution is a two-fold operation: first we must "read" the users solution, and then we must check it.
<user pressed CheckSolution button>=
else if (src == CheckSolution)
{
<read solution>
<check solution>
}
Used above.
Now, a solution can be either a fraction or just a whole number, and we certainly
do not know that a priori:-( Fortunately, Java provides us with a simple string
tokenizer which can be in elementary scanners, like the one we want to implement.
The try blocks are used to ensure the user types a number and not his name!
In case the user has not provided a solution we are forced to do nothing.
<read solution>=
StringTokenizer tok = new StringTokenizer(UserDisplay.getText(), " / ");
long a=1l, b=1l, TokenCount;
TokenCount = tok.countTokens();
if (TokenCount == 2)
{
try
{
a = Long.parseLong(tok.nextToken());
} catch (NumberFormatException e)
{
a = 1l;
}
try
{
b = Long.parseLong(tok.nextToken());
} catch (NumberFormatException e)
{
b = 1l;
}
WrongAction = false;
}
else if (TokenCount == 1)
{
try
{
a = Long.parseLong(tok.nextToken());
} catch (NumberFormatException e)
{
a = 1l;
}
b = 1L;
WrongAction = false;
}
else
{
// no solution provided
WrongAction = true;
}
Used above.
We have successfully read the solution, we form it and we compare it with the
"official" one. If the solution is correct we advance users correct answers score,
otherwise we are sorry to advance user wrong answers score. This operation is fairly
simple, we just setText the TextField. In case there is a problem, i.e., variable
WrongAction has value different from zero, we produce an error message.
<check solution>=
if(!WrongAction)
{
Fraction f = new Fraction(a, b);
if (f.equal(LE.Solution))
{
corrans++;
ProgDisplay.setText("Correct Answer!!!");
//succClip.play(); //cheer up user
Correct.setText("Correct Answers: " + corrans);
}
else
{
wrongans++;
ProgDisplay.setText("Wrong Answer. Retry!");
//failClip.play();
Wrong.setText("Wrong Answers: " + wrongans);
}
}
else
{
ProgDisplay.setText("Sorry, no input!");
}
Used above.
Once the user presses the SolveEq button we must bring-up a window which
will display the full solution (all steps at one) of the equation. However, the
window must be created only if an equation has been successfully created. Otherwise
just an error message should appear in the ProgDisplay text field.
<user pressed SolveEq button>=
else if (src == SolveEq)
{
if(!WrongAction)
{
if(SW != null) SW.dispose();
SW = new SolutionWin();
SW.SolText.append(LE.solution);
SW.pack();
SW.setVisible(true);
}
else
{
ProgDisplay.setText("Sorry, no equation!");
}
}
Used above.
There are two Choice objects in our program which control the equation difficulty
and the type of equation. What we have to do is to check which Choice object is
being used and what is its current value.
<implementation of method ItemStateChanged>=
public void itemStateChanged(ItemEvent i)
{
Object IS = i.getItemSelectable();
String ITEM = (String)i.getItem();
if (IS == EquationType)
{
if (ITEM == "Linear Equation")
{
eqType = 1;
}
/* else if (ITEM == "Non-Linear Equation")
{
eqType = 2;
} */
}
else if (IS == EquationDifficulty)
{
if (ITEM == "One")
{
eqLevel = 1;
}
else if (ITEM == "Two")
{
eqLevel = 2;
}
else if (ITEM == "Three")
{
eqLevel = 3;
}
if (ITEM == "Four")
{
eqLevel = 4;
}
}
}
Used above.
In principle, any rational number can be expressed as a fraction, e.g., 5=5/1, 0.5=1/2, etc.
Based on this observation, we need to implement a class that will generate and handle fractions.
Each fraction has a nominator and a denominator; moreover each fraction corresponds to some
rational value. This remark leads to the introduction of three variables in class Fraction.
Once, we have a fraction we need to be able to compare with another fraction. Comparing fractions
is easy, once the nominator and the denominator of each fraction have 1 as their only common
divisor.
<Definition of class Fraction>=
class Fraction
{
public long nominator, denominator;
public double RationalValue;
<Constructor of Class Fraction>
<method equal>
<method gcd>
<method printFraction>
}
Used above.
The constructor method of class Fraction is fairly easy--- we simply follow the rules of
elementary mathematics to form a fractions whose nominator and denominator have no common divisor
other that the number 1.
<Constructor of Class Fraction>=
public Fraction(long a, long b)
{
long GCD=gcd(Math.abs(a),Math.abs(b));
long sign;
sign = ((a*b) > 0) ? 1l : -1l;
nominator = sign*Math.abs(a) / GCD;
denominator = Math.abs(b) / GCD;
RationalValue = (denominator == 0) ? ( Double.POSITIVE_INFINITY ) :
((double)nominator) / ((double)denominator);
}
Used above.
Comparing to fractions of the kind our class handles is simple: just compare the nominators and the denominators and if they are unequal, the fractions are not equal.
<method equal>=
public boolean equal(Fraction any)
{
return this.nominator == any.nominator &&
this.denominator == any.denominator;
}
Used above.
The final aspect of class Fraction is the definition of the method that computes the
greatest common divisor of two positive integers. As usual(?), we implement Euclid's
algorithm.
<method gcd>=
private long gcd(long x, long y)
{
while (x != y)
{
if (x >= y)
{
x -= y;
}
else
{
y -= x;
}
}
return x;
}
Used above.
The following method makes it possible to print a Fraction in a ``descent'' way,
i.e., if the denominator is equal to 1 we must print only the nominator.
<method printFraction>=
public String FractionToString()
{
if(denominator == 1)
{
return " " + nominator;
}
else
{
return nominator + "/" + denominator;
}
}
Used above.
Class linearEquation is the mechanism that produces a new linear equation and
solves it. The most general form of the equation it deals with is: a*x+b=c*x+d, where
a, b, c, d are integer numbers and x is the unknown. However, depending on the difficulty
level, certain factors are set to zero; for level 1 b and c , for level 2 c and d,
for level 3 c, and for level 4 all factors are not equal to zero. All factors are randomly
generated by the language's class Random.
<Definition of class linearEquation>=
class linearEquation
{
long a, b, c, d; // equation parameters
int level; // difficulty level
String Equation; // String representation of an equation
Fraction Solution; // The solution of the equation
String solution; // holds the solution of the current equation
<random factor generator>
<method to set factors>
<method that solves the new equation>
<method that provides the complete solution of an equation>
}
Used above.
A correct random factor is one that is not equal to zero and moreover, it is
within a specific range. Parameters a and b, specify the range within the number
must lie.
<random factor generator>=
long rnd(long a, long b)
{
long temp;
long range=b-a;
while ((temp = a+(long)(Math.random()*range)) == 0)
;
return temp;
}
Used above.
Setting the factors is an easy job; we just have to decide on the range of values a factor can assume for each level. For level 1, factors are in the ranger -16..16, for level 2 in the range -32..32, for level 3 in the range -64..64, and for level 4 in the range -128..128. The following method as a side effect creates a string that contains a textual representation of the generated equation. Please note, that for case 4 we make sure that a!=c, so that our equation has a real solution. Moreover, we make certain tests in order to assure that the equation appears correctly, i.e., if b<0, e.g., b=-3, we print "-3" instead of "+-3" which is at least misleading.
<method to set factors>=
void setFactors()
{
switch (level)
{
case 1: a=rnd(-16,16);
b=0l; c=0l;
d=rnd(-16,16);
Equation = a + "*x = " + d;
break;
case 2: a=rnd(-32, 32);
b=rnd(-32, 32);
c=0l;
d=0l;
Equation = a +"*x " +
((b>0) ? ("+ " + b) : ("- " + Math.abs(b))) +
" = 0";
break;
case 3: a=rnd(-64, 64);
b=rnd(-64, 64);
c=0l;
d=rnd(-64, 64);
Equation = a + "*x " +
((b>0) ? ("+ " + b) : ("- " + Math.abs(b))) +
" =" +
((d>0) ? (" " + d) : (" -" + Math.abs(d)));
break;
case 4: a=rnd(-128, 128);
b=rnd(-128, 128);
while ((c=rnd(-128, 128)) == a)
;
d=rnd(-128, 128);
Equation = a + "*x " +
((b>0) ? ("+ " + b) : ("- " + Math.abs(b))) +
" =" +
((c>0) ? (" " + c) : (" -" + Math.abs(c))) +
"*x " +
((d>0) ? ("+ " + d) : ("- " + Math.abs(d)));
break;
}
}
Used above.
Linear equations of the first degree are very easy to solve (okay, they are easy for me!). The basic technique is to separate the known from the unknown quantities, and then to divide the known by the factor of the unknown. The solution to the most general equation ax+b=cx+d, is x=(d-b)/(a-c). This solution is a valid number if a-c != 0, and consequently a!=c.
<method that solves the new equation>=
void solveEquation()
{
Solution = new Fraction(d-b, a-c);
solution = printSolution();
}
Used above.
The following method is used to create a String which will hold the complete
solution of any equation. We follow, as expected, the ordinary way of solving the
equations of this type. We initially separate the known from the unknown values, then
we perform the various calculations and finally we produce the solution. Pretty simple
stuff, eh? But a few explanation are in order for every case.
<method that provides the complete solution of an equation>=
String printSolution()
{
String temp, temp1, temp2;
Fraction tmpFraction;
String SolutionStr="";
long X, Y;
switch (level)
{
<case for level one>
<case for levels two and three>
<case for level four>
}
return SolutionStr;
}
Used above.
In case we have an equation of level 1 the solution goes like this: a*x=d <=> x=d/a.
In case the gcd(d,a) is other that 1 we must then simplify the fraction, so this
means one more step is needed: the ``fraction simplification step''. We initially
produce the solution of the equation, by creating the Fraction(d,a). Then we
compare the nominator and the denominator of the fraction with d and a respectively.
In case they are the same, we don't need the simplification step, otherwise it is
needed. Then we produce the complete solution.
<case for level one>=
case 1: tmpFraction = new Fraction(d,a);
if(tmpFraction.nominator == d &&
tmpFraction.denominator == a)
{
temp = "";
}
else
{
temp = "x = " + d + "/" + a + " <=>\n";
}
SolutionStr = Equation +
" <=>\n" + //print the equivalence symbol
temp +
"x = " + (tmpFraction.FractionToString());
break;
Used above.
Equations of level two and three are alike: a*x+b=d. The solution is more
``complicated'', compared to that of an equation of level one:
a*x+b=d <=> a*x=d-b <=> x=(d-b)/a. In String temp we keep the representation
of b as it must appear on the right side of the equation, i.e., if it less that
zero it should appear as a positive integer and vice versa. Next, we follow the
technique adopted for the solution of equations of level one.
<case for levels two and three>=
case 2:
case 3: temp = (b>0) ? (" - " + b) : (" + " + Math.abs(b));
X = d-b;
tmpFraction = new Fraction(X,a);
if(tmpFraction.nominator == X &&
tmpFraction.denominator == a)
{
temp2 = "";
}
else
{
temp2 = "x = " + X + "/" + a + " <=>\n";
}
SolutionStr = Equation + " <=>\n" +
a + "*x = " + d + temp + " <=>\n" +
a + "*x = " + X + " <=>\n" +
temp2 +
"x = " + (tmpFraction.FractionToString());
break;
Used above.
Equation of level four are the most difficult to solve (o.k., if you are first grade in
high school): a*x + b = c*x + d <=> a*x - c*x = d - b <=> (a-c)*x = (d-b) <=>
x=(d-b)/(a-c). The solution that follows is actually an extension of the technique
explained above, so I guess there is nothing left to say (?).
<case for level four>=
case 4: temp = (b>0) ? (" - " + b) : (" + " + Math.abs(b));
temp1 = (c>0) ? (" - " + c) : (" + " + Math.abs(c));
X = d-b;
Y = a-c;
tmpFraction = new Fraction(X,Y);
if(tmpFraction.nominator == X &&
tmpFraction.denominator == Y)
{
temp2 = "";
}
else
{
temp2 = "x = " + X + "/" + Y + " <=>\n";
}
SolutionStr = Equation + "<=>\n" +
a + "*x " + temp1 + "*x = " + d + temp + " <=>\n" +
Y + "*x = " + X + " <=>\n" +
temp2 +
"x = " + (tmpFraction.FractionToString());
break;
Used above.
Class SolutionWin brings up the solution window. It is a simple window with a
TextArea used to display the solution and a Button used to destroy the window.
The class implements the ActionListener interface, to make sure it will handle
the button-pressed event properly.
<Definition of class SolutionWin>=
class SolutionWin extends Frame
implements ActionListener
{
TextArea SolText;
Button TerminationButton;
<Class Constructor>
<event handling2>
}
Used above.
The constructor of the class crates a Dialog and puts on it a TextArea and
a Button. We use the settings of the main Frame for placing the two objects.
<Class Constructor>=
SolutionWin()
{
super("Solution Window");
GridBagConstraints c = new GridBagConstraints();
GridBagLayout gridbag = new GridBagLayout();
setLayout(gridbag);
setFont(new Font("Courier", Font.BOLD, 17));
// create TextArea
SolText = new TextArea(10,40);
c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.CENTER;
c.insets = new Insets(5,0,5,0);
c.gridwidth = GridBagConstraints.REMAINDER;
gridbag.setConstraints(SolText, c);
add(SolText);
//create button
TerminationButton = new Button("ok");
gridbag.setConstraints(TerminationButton, c);
add(TerminationButton);
TerminationButton.addActionListener(this);
}
Used above.
Event handling is very simple: once the button is pressed we destroy the window.
<event handling2>=
public void actionPerformed(ActionEvent E)
{
Object src = E.getSource();
if (src == TerminationButton)
{
this.dispose();
}
}
Used above.