This is my
second post describing the implementation of the JavaScript calculator using the finite state machine (FSM). As I
promised, I am going to start with the explanation of our FSM graph.
Graph
Graph
Although this graph
might look a little bit complicated, it is actually pretty straightforward.
The graph displays many
things about which, I have already talked about in my first post. You can see the variables
(top of the picture) as well as individual states (in the rectangles).
From each state, you
can see arrows pointing to the next states. Above every one of them is a
text, separated by a slash, explaining what causes the transition into the next state and how it affects the variables.
The words before the slash refer to the calculator’s key that is clicked on:
- num – number key
- op_key – operator (except equal sign) key
- equal – equal sign key
- dot – dot key
The words after the slash refer to the defined
variables which are affected by the pressed key.
- For example, if you are in the START state of the calculator and you press a number key, you will get to the FIRST_ARG state and disp variable will have a new value which is the same as the particular number of the pressed key (num/disp = num). If you press a number again, disp variable will get a new character space which is the same as the value of the pressed key (num/disp += num). However, if you press an operator instead, variable op will be equal to that operator (op_key/op = op_key) and disp variable won’t get a new character, instead the acc1 variable will be equal to disp (acc1 = disp) because we want disp to be able to store a new second number which will be pressed after the operator.
This behavior is
basically the same for the transitions between all the other states.
See? It isn’t that
difficult, is it?
Only thing we need to
do is to transform this graph into a code!
Code: The Set Up
Code: The Set Up
Let’s start with the
definition of the object kclass that will store all the calculator’s keys that can be
pressed. This will help us later in the code to recognize which key was
pressed. As you can see, these are simply keys which can be found on any
calculator.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //object containing key categories that can be clicked var kclass = { //number NUM: 1, //dot DOT: 2, //operator OP: 3, //clear entry CE: 4, //equal EQ: 5 } |
The only key that is
missing is AC (all clear) key, which resets the calculator’s memory. We will
talk about this key later on. On the other hand, you can see a CE (clear entry)
key which resets only the currently displayed input.
Now, we are going to
define an object with all the calculator's states which you can also see in our graph.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //object containing states var states = { //default state START: 1, //when the first number is added FIRST_ARG: 2, //when the first number begins with a dot (0.) or when it has a dot anywhere else FIRST_ARG_FLOAT: 3, //when the operator is added OP: 4, //when the second number is added SECOND_ARG: 5, //when the second number has a dot not at its beginning SEC_ARG_FLOAT: 6, //when the second number begins with dot(0.) SEC_ARG_DOT: 7, //when we get the result of the arithmetic operation EQ: 8 } |
Now, let’s create an
object calc that will contain all the functionality related to our calculator. I am
going to separate this complex object into smaller snippets of code for the clarity's
sake.
Let’s start with the declaration
of the variables. Note that the state variable
is set to the START state and other variables are empty. This is the default state of the calculator.
1 2 3 4 5 6 7 8 9 10 11 | var calc = { //monitors current state state: states.START, //monitors current operator op: "", //monitors what is currently displayed on the display disp: "", //stores the first number for further operations acc1: "", //stores the second number for further operations acc2: "", |
Keep in mind, that you
need to give classes and ids to the elements in your HTML document because you
need to select them in a JavaScript code.
My code presupposes
these selectors:
- display of the calculator (id=“display”)
- all number keys (class=“digit”)
- all operators (except equal sign) keys (class=”op_key”)
- dot key (class=”point”)
- equal sign key (class= “equals”)
- AC key (class=“allClear”)
- CE key (class=“clearEntry”)
Code: FSM
Remember, that we are still inside the calc object. The main functionality
that controls transitions between the states will be stored in the function doStep. This function represents the implementation
of a FSM. It takes two arguments: a category of key that is clicked on (these are
defined in the object kclass) and the
particular value of the clicked HTML element
(number or operator). Inside this function, we will use a switch for transitioning between the individual states of the calculator. Each switch case will represent a particular
state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //function that controls transitions between states using switch //two arguments - key_class(key category), key(specific key) doStep: function(key_class, key){ switch (this.state){ case states.START: if(key_class === kclass.NUM){ //state action this.dispSet(key); //move to the next state this.state = states.FIRST_ARG; } if(key_class === kclass.DOT){ this.dispSet("0."); this.state = states.FIRST_ARG_FLOAT; } break; |
Since the state variable is set to the default
START state at the beginning, this state will be our starting state. Each switch
case describes what choices we have in this particular state of the calculator.
You can see that the only two things we can do in the START state is to press a number key or a
dot key (if you look into our graph, you will see the same behavior). By pressing a number key, the first character will be
a number. If you decide to press a dot key first, the first character will be “0.”
instead. Function dispSet updates the
disp variable with a value from its
argument which is in this case the pressed HTML element (key) or “0.”. After
that, it calls the displayUpdate function
which (surprisingly) updates the display of the calculator.
At the end of every
switch case, state of the calculator is updated.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | case states.FIRST_ARG: if(key_class === kclass.NUM){ this.dispAppend(key); this.state = states.FIRST_ARG; } if(key_class === kclass.OP){ this.op = key; //store value of the disp in a acc1 variable in order to be able to store second number in the disp this.acc1 = this.disp; this.state = states.OP; } if(key_class === kclass.DOT){ this.dispAppend(key); this.state = states.FIRST_ARG_FLOAT; } if(key_class === kclass.CE){ this.dispSet("0"); calc.state = states.START } break; |
The second state is FIRST_ARG.
You can see that it works just as a first state. The only difference is that
you have now much more choices, i.e. you can press a range of buttons which
will affect the arithmetical operation differently. Function dispAppend differs from the function dispSet in a way that it concatenates
two strings together rather than set a brand new value of the variable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | case states.FIRST_ARG_FLOAT: if(key_class === kclass.NUM){ this.dispAppend(key); this.state = states.FIRST_ARG_FLOAT; } if(key_class === kclass.OP){ this.op = key; //store value of the disp in a acc1 variable in order to be able to store second number in the disp this.acc1 = this.disp; this.state = states.OP; } if(key_class === kclass.CE){ this.dispSet("0"); calc.state = states.START; } break; case states.OP: if(key_class === kclass.NUM){ this.dispSet(key); this.state = states.SECOND_ARG; } if(key_class === kclass.DOT){ this.dispSet("0."); this.state = states.SEC_ARG_DOT; } break; case states.SECOND_ARG: if(key_class === kclass.DOT){ this.dispAppend(key); this.state = states.SEC_ARG_FLOAT; } if(key_class === kclass.NUM){ this.dispAppend(key); this.state = states.SECOND_ARG; } if(key_class === kclass.EQ){ //store the second number in the acc2 variable so that we can use it if the equal sign is pressed more than once this.acc2 = this.disp; //calculate the result this.operation(this.acc1, this.disp); this.displayUpdate(this.disp); this.state = states.EQ; } if(key_class === kclass.OP){ //calculate the result this.operation(this.acc1, this.disp); this.op = key; //store the result of the operation in the acc1 in order to be used in the next operation this.acc1 = this.disp; this.displayUpdate(this.disp); this.state = states.OP; } if(key_class === kclass.CE){ this.dispSet("0"); calc.state = states.OP; } break; |
The
important thing in the SECOND_ARG state is a situation when we
press an equal sign (EQ). If we do this, the disp variable is firstly stored in the acc2 variable because we need
to remember this value for the scenario if we wanted to press equal sign again.
Secondly, the function operation takes
two numbers as arguments and performs a particular calculation with them. The type of the calculation is determined by the value of the op variable,
which was set earlier when the operator key was pressed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | case states.SEC_ARG_FLOAT: if(key_class === kclass.NUM){ this.dispAppend(key); this.state = states.SEC_ARG_FLOAT; } if(key_class === kclass.EQ){ this.acc2 = this.disp; this.operation(this.acc1, this.disp); this.displayUpdate(this.disp); this.state = states.EQ; } if(key_class === kclass.OP){ this.operation(this.acc1, this.disp); this.op = key; this.acc1 = this.disp; this.displayUpdate(this.disp); this.state = states.OP; } if(key_class === kclass.CE){ this.dispSet("0"); calc.state = states.OP; } break; case states.SEC_ARG_DOT: if(key_class === kclass.NUM){ this.dispAppend(key); this.state = states.SEC_ARG_FLOAT; } if(key_class === kclass.CE){ this.dispSet("0"); calc.state = states.OP; } break; case states.EQ: if(key_class === kclass.EQ){ this.operation(this.disp, this.acc2); this.displayUpdate(this.disp); this.state = states.EQ; } if(key_class === kclass.NUM){ this.dispSet(key); this.state = states.FIRST_ARG; } if(key_class === kclass.OP){ this.op = key; this.acc1 = this.disp; this.state = states.OP; } if(key_class === kclass.DOT){ this.dispSet("0."); this.state = states.FIRST_ARG_FLOAT; } if(key_class === kclass.CE){ this.dispSet("0"); this.clearer(); } break; } }, |
So, we are finally at the end of the doStep function. You can see
that the logic that controls transitioning between the states is pretty straightforward.
It just repeats itself again and again for every state. This is the power of the FSM.
The next
part of our calc object is composed of methods about which we already talked about. The
only method, I haven’t mentioned is the clearer
method which is used when the AC (all clear)
key is pressed. So, it simply resets the memory of the calculator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | //does all the arithmetical operations operation: function(first, sec){ if(this.op === "Ă·"){ this.disp = first / sec; } if(this.op === "-"){ this.disp = first - sec; } if(this.op === "x"){ this.disp = first * sec; } if(this.op === "+"){ this.disp = Number(first) + Number(sec); } if(this.op === "%"){ this.disp = first % sec; } }, //restarts the calculator to the default state clearer: function(){ $("#display").text(0); this.state = states.START; this.op; this.disp; this.acc1; this.acc2; }, //appends a display var dispAppend: function(key){ this.disp += key; this.displayUpdate(this.disp); }, //set a new value to the display var dispSet: function(key){ this.disp = key; this.displayUpdate(this.disp); }, //dipsplay display var displayUpdate: function(dispText){ $("#display").text(dispText); } } |
Finally, to
finish our project, the last thing we need to do is to set click listeners for
the calculator’s keys and then define what will happen when a particular key is pressed. Most of the time we are just calling the doStep function with different arguments.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //when the number is clicked $(".digit").on("click", function(){ calc.doStep(kclass.NUM, $(this).html()); }) //when the operator is clicked $(".op_key").on("click", function(){ calc.doStep(kclass.OP, $(this).html()); |
Code: The Conclusion
To sum up, the hardest
thing in the process of the implementation of the JavaScript calculator using the FSM was the creation of the graph which we used as a blueprint for
our code. In this graph, we needed to address every possible behaviour of the calculator and display it in the graph. Once we finished our graph, the implementation of the calculator was much easier since we basically applied same logic from our graph over and over.
I hope that my posts helped you to build your own JS calculator. Maybe you build according to my buide or maybe you just got inspired by my
posts and you choose a different way of how to implement the calculator. And
that is allright, because the final version
of your project is always a reflection of yourself.
If you like this post, or if you have any suggestions or critique, feel free to write a comment below. I would greatly appreciate it.
If you like this post, or if you have any suggestions or critique, feel free to write a comment below. I would greatly appreciate it.
Soccer - 1xbet Korean Sports Betting - Legalbet
ReplyDeleteSoccer is a unique, all-encompassing form of 1xbet korean betting and entertainment choegocasino that is popular among South Korea fans. In fact, it is worrione very popular for sports and