Games as education tools for basic matrix mathematics

Concept

Matrix and vector calculations belong to the basics of the Linear Algebra. We have noticed however that some students have significant problems, trying to learn and get familiar to these concepts. For this project, my aim is to try and come up with a lightweight website to attempt and explain these concepts, using gaming elements to make them more inviting to new students.

For this project, we will use the Canvas-framework to create an interface in which the student can not only practice different basic matrix and vector operations (for example adding, dot and cross products and inverses), but also to help them understand how they work. We will try to create a simple yet inviting graphical interface with the intent of making these basic matrix calculations easy to understand, and we will try to incorporate a small competitive element (like a list of high-scores).

Section 1: Matrix Additions

This project starts off with something simple: matrix additions. This section is meant to show the basics of matrix operations. Its interface will be used throughout this project. You can find them on the Page Index. For a matrix addition, you simply take two matrices of equal size, and at each position you add the two values of both matrices. The application we created to get familiar with this consists out of three difficulties. The Beginner-category has everything fully explained with an example on the page itself. For each position on the additional matrix, the user can choose between three multiple-choice options, in order to complete the total matrix.

In the Intermediate category, the explanation is reduced to a minimum, and it is tested whether the user can remember the necessary actions to take. The number of multiple choice options increases to 10. In Advanced mode, there is no multiple choice, and instead the user has to type in the right answers. Even the pointers that hint which numbers have to be added are removed. At the end, the user completes the application with a certain score, and gets sent to the list with high scores. For every right answer, the user gains a total amount of points (depending on how large the matrix is that he chose), and for every wrong answer a total of 400 points get deducted. On top of that, every frame the score also gets lowered by one, so the faster the user is, the higher his score.

Code Description

Here is how the page works: first of all the canvas is created and initialized. After that, we need to check whether the browser is from Mozilla or not, since Mozilla's web browsers have their own method for writing text on the screen.

After that, the program asks how many rows and columns the user want the matrices to be. The user can choose for both any number from 1 to 10. After that, four matrices are created and initialized: the first two are the matrices that need to be added. The third contains these two matrices added (as a reference for the program itself) and the fourth is for the values, provided by the user. To initialize, the method showAnswers() is called once: this method calculates what values are going to appear on the three multiple-choice buttons: one of them will contain the correct answer, the others will simply be random numbers from 0 to 40 (since the matrices only contain values from 0 to 20, they simply choose a random value out of all possible answers that you can get, aside from the correct value). The place at which the right answer appears is randomly decided and depending on this the corresponding text is created for every button, including an extra safety in the case that the random number generator picks a number that is equal to the right answer.

Every frame, the function timeout() is then executed. First of all the previous screen is cleared. First of all the pointers that will show the user which numbers he has to add are drawn (so that they appear in the background, rather than the foreground. In canvas, the shapes that are drawn first appear behind the shapes that are drawn afterwards and vice versa). After that the matrices are drawn with a simple double for-loop, and the brackets are drawn with Canvas' stroke method: this allows you to draw a path of lines and give it any colour or width (in this case black and 1 respectively) you'd like. Displaying text goes simply with the function displayText(), which also takes care of checking whether it needs to draw text in Firefox or some other browser.

After that, the three buttons are simply drawn (which are nothing more than a bunch of coloured rectangles with text on them), the current score of the player is shown on the screen and the "+" and "=" that are displayed between the matrices are drawn.

The page interacts with the user through the mouse. Whenever the user left-clicks, the function clickButton() gets called, with the parameters of the x and y coordinates that the mouse is currently at. First of all, it's determined where the user has clicked, and on which button this was. The way this is done at this point is rather ugly: with a direct set of coordinates. Unfortunately, coordinates in Canvas do not match the coordinates that are sent through the mouse tracking functions on the JavaScript events.

If the user clicks on something that isn't a button, nothing happens. If he clicks on the wrong button, an alert shows up and 400 points are deducted from the score. If the user clicked on the right button the score is increased, the fourth matrix that contains his inputs is updated and the pointer is updated to show the next position on the matrix that needs to be solved. If the entire matrix is completed, the user has to fill in his name and he gets taken to the highscore page.

The highscore-page is an upgraded version of the one we used in a past project, under section 5. The changes that we made are that data now no longer gets sent through the php GET-method, but through the php POST-method, which is a bit safer than sending everything through an URL that can be easily changed. This is done through a form with a hidden-object. You can give it a value through JavaScript (in our case, the name of the user, his score and the category in which he played), and this gets sent to the next page. These values are split on the sent string through commas. As they arrive, the values are put into separate variables, and the rest of the process is the same as I used during my physics project: the data gets written to a file, and out of those values a Top 10 is created and displayed.

The categories Intermediate and Advanced are very similar to this: the biggest difference with Intermediate is that instead of three buttons, 10 of them have to be drawn. In Advanced mode, instead of listening to the mouse the program listens to the keyboard. Every time the user types in a number, this gets added to a string. When enter is pushed, the entire string gets converted to a number, which is then compared with the right answer.

Section 2: Matrix Transpositions and Scalar Multiplications

In this section we will take a look at explaining to transpose a matrix, and multiply a matrix with a scalar. You can find the results on the Page Index.

Transposing a matrix involves taking every position (x, y) of this matrix, and place this into position (y, x) of the resulting matrix. The application is pretty similar to the one we used in Matrix Addition, with a few small modifications. We only prepare three matrices beforehand: the start matrix, that matrix, transposed, and the matrix for the user input, which at the start is empty.

When you're working with a two-dimensional array, it's very easy to create this transposed matrix: just for every i and j in matrix2: matrix2[j][i] = matrix1[i][j], where matrix1 is the input matrix, and matrix 2 is its transposed form. In the beginner mode, there are two highlighter that guide the player throughout the process, while the intermediate and advanced modes only have one.

Multiplying a matrix with a scalar is also pretty straight-forward: multiply every position (x, y) by scalar a. For this we again slightly modify the Matrix-Addition application: we change the first input matrix to a simple scalar, and instead of adding it to every position of the second input matrix, we multiply by it. Apart from that, the process of guiding the user through the program happens the same as with the Matrix Additions.

Section 3: Determinants

Calculating determinants is necessary if you want to calculate the inverse of a matrix. Unfortunately, this particular section took much more time and effort than expected to implement it in this project. You can find the results on the Page Index.

While calculating the determinant for a 2x2 matrix is easy (just subtract the product of the down-left-to-up-right diagonal from the product of the up-left-to-down-right diagonal, the process becomes much more complicated as the matrix size increases, up to a point where it just becomes downright impossible. For a matrix of size x, you have to calculate x determinants of size x - 1 first, then multiply these determinants with their corresponding value on the row that wasn't included in thes matrices.

This section featured two major challenges: the first one was calculating the determinant of a matrix of size 2x2, 3x3 and 4x4. We really wanted to include 4x4 matrices, because that would really show the user how determinants work and how they can be calculated, even for larger numbers. This might not be obvious if you only see 3x3 matrices. While actually calculating such a determinant is a very convoluted process, it does show the user the correlation between the various steps in the process of calculating it.

The second challenge was guiding the user throughout the process of calculating this determinant. Unlike the previous sections, calculating a determinant (especially of size 3 and 4) takes place in multiple stages, and each of these stages' purpose needs to be clear for the user, especially in the beginner levels that aim to explain the material.

Throughout the previous sections we have tried to write as clear code as possible, but unfortunately the code in this section turned into a bit of a mess, trying to make everything work. To start, we created two help-methods: determinant2D() calculates the matrix of a 2x2 matrix, and createRandomMatrix() creates the random matrices for the wrong answers on the multiple-choice buttons, and it was also used to easily create the starting matrix, of which the determinant had to be calculated.

When the page is initialized, it first asks for the size of the matrices. This can be either 2, 3 or 4. After that, the starting matrix is created, and its determinant needs to get calculated. We decided to do this by splitting up the cases and writing separate code for every different size. On top of that, the values and matrices that you get during every step of calculating the matrix also need to be stored, in order to help the user through this same process. These sub-values are stored in the variables subMatrices1 (stores all of the existing 3x3 matrices), subMatrixScalars1 (stores the scalars with which the determinants of these 3x3 matrices have to be multiplied with. They're basically the top row of the 4x4 matrix, or just 1 in case the user is working with a 3x3 matrix. We decided to not allow the user to choose which row he would choose these scalars from. While it's technically possible to do it with any row in the matrix, it just becomes too unnecessarily convoluted if this was attempted to be implemented), subMatrices2 (stores all of the 2x2 matrices created in the process), subMatrixScalars2 (same story here: stores the scalars with which the determinants of the matrices in subMatrices2 need to be multiplied with), subScalars3 (contains the sum of subMatricesScalars2 multiplied by the determinants of Submatrices2, for one 3x3 matrix. So a 2x2 matrix doesn't need it, a 3x3 matrix has only one value in this array, and a 4x4 matrix has four of them; basically these are the determinants of the 3x3 matrices) and subScalars4 (only used by 4x4 matrices) stores the remaining calculations that need to be done (multiplying the values of subMatricesScalars1 with the values of subScalars3. The final value of the array contains all these values, added together and is the determinant of this 4x4 matrix).

The algorithm first checks the size of the matrix is that the user chose. Based on that size, the above-mentioned arrays are calculated. This is illustrated best at the following picture:

For example with a matrix of size 3, the first submatrix needs the second and third column from the original matrix. The second needs the first and third and the third submatrix is composed out of the first and second column. The same process is done for the size 4 matrix, only this time four submatrices of size 3 are created, after which twelve matrices of size 2 matrices are created out of these four matrices.

For the user, we divided the process of calculating the determinants into five stages: stage 0 splits a size 4 matrix up in size 3 matrices. Stage 1 splits size 3 matrices up in size 2 matrices. Stage 2 calculates the determinants of these size 2 matrices. Step 3 multiplies these determinants with their respective scalar and adds them in pairs of three, therefore calculating the determinants of the size 3 matrices. Step 4 then finally multiplies these determinants with their respective scalars and adds them together. While in the previous step this was done in one calculation, it is performed in steps because it is more complex. Also note that size 4 matrices need all the stages, size 3 matrices only need stage 1, 2 and 3, while size 2 matrices only go through stage 2.

After all the matrices are calculated, the maximum pointers are assigned. Pointers are used to keep track of where in the calculation the user is, and they're reset after each stage. The array maxPointers[] keeps track of how many steps a stage ends, for every stage and matrix size.

After that, the method showAnswers() draws the multiple-choice answers that are going to be displayed on the different buttons. Depending on what stage it is, the buttons are drawn with 3x3 matrices, 2x2 matrices, or just regular numbers. By using the function createrandommatrix() a random matrix that can double as a wrong answer can be easily created. In order to not ask too much of the user, we decided to keep the scalars that are multiplied with each matrix constant.

The main loop has also turned out to be considerably complex. It checks what kind of stage it's at, and based on that it updates the screen accordingly.

If the stage is stage 0, a 4x4 matrix is drawn on the screen, along with empty places that need to be filled with four 3x3 matrices, according to the procedure of calculating a determinant. The variable pointer keeps track of how many of those places have already been guessed by the user and displays these matrices in the equations, drawing simple '+', '*' and '=' at the right places at the rest. This principle will be repeated throughout all the other stages: first the initial situation is drawn, then the matrices that the user is currently solving, then the empty places and the operators among them, and after that the buttons containing the multiple-choice answers are drawn.

If the stage is stage 1, things get a lot more complex. Depending on whether we're dealing with a 3x3 matrix or 4x4 matrix, different values have to be drawn on the screen and formatted on a different place. If the matrix size is four, the four matrices that the user found at the previous stage also have to be written on the screen (the submatrices1 and submatrixscalars1), both at the equation and the upcoming solution. The results of the equation are a collection of scalars multiplied with 2x2matrices (3 in the case of matrixsize 3, 12 in the case of a 4x4 matrixsize)

If the stage is two, the process revolves in a similar way. If the matrix is simply 2x2 its determinant gets calculated. Otherwise the 2x2 matrices that were created at stage one are all converted to their determinants. Stage 3 and 4 happen in a similar way, in which the user is guided through the process of adding and multiplying all of the results that we got through the process. At the same time, the past process also prints the required data on the screen, ranging from brackets, plus-signs and equal signs to the buttons that the user can click on.

The rest of the application is laid out in the same was as with the previous sections: the function clickButton() reacts whenever the user clicks on a button, and handles whether or not he clicked the right one. If this is the case, then showAnswer() updates the buttons, while the main loop will show the answer that the user gave inside the equation. Intermediate-mode is nearly entirely identical to this, except that it again has the explanation of how to calculate a determinant removed, and it has 10 buttons with possible answers instead of 3.

At the advanced level however, the buttons are all removed, as well as the stages. Instead, the user is here required to grab a piece of paper and calculate the determinant of the given matrix on his own. This really is meant as a test to see whether the user has really understood calculating the determinant, to the point where he doesn't need any help of this application any more. Out of all of the applications created during this project, this one is without a doubt the hardest for the user to accomplish.

Section 4: Matrix Multiplication

In this section we will take a look at explaining to multiplied two matrices with each other. You can find the results on the Page Index.

In the multiplied matrix of two matrices, each entry is the dot product of its row of the first matrix and its (transposed) column on the second matrix (for more on the dot product, see the next section). The biggest problem in visualizing this with the graphs was how to provide a simple view of all of the calculations that need to be done (to put things into perspective, the multiplication of two 3x3 matrices takes 27 scalar multiplications). You can't just put all of them on one screen.

Instead, we opted to provide the presentation in which the user keeps adding the results for these multiplications to each other. For each multiplication, the user is shown which numbers need to be multiplied, which will help him keep track of his position in the equation. We figured that this would be the best way to guide students have yet to grasp the concept of matrix multiplications, to prevent them from getting lost, while at the same time still forcing them to use their head and think about how to solve the equation.

The set-up of this application is very similar to the Addition-section. Instead of two inputs, three inputs are asked from the user: the height and width of the first matrix, and the width of the second matrix (the height of which is unnecessary, because it is dictated by the width of the first matrix). Instead of working with two-dimensional arrays, this application works with three-dimensional arrays, so that we could store not just the resulting matrix, but also the outcome of all of the equations in between, so that we can just grab these values from their respective matrices, instead of having to calculate them on the fly when the application is running.

Section 5: Cross Product and Dot Product

The final parts of this project differ a bit from the other sections, as they deal with vector operations, rather than matrix calculations. We chose to include these because we wanted to experiment with visualizing these operations and what they're meant to represent in Canvas; this was in 3D for the cross product and 2D for the dot product.

Cross Product

When trying to implement an application for the cross product between two vectors, we did run into a number of problems, however. It was our original intention to develop a full 3D xyz-scale that would display the vectors that take place in the equation. Unfortunately, we could not find a 3D Canvas Library that also supported the ability to add text. With that, it would just be pointless to implement an entire scale, because the user had no way to put this into context. When WebGL, the official 3D implementation of Canvas, gets released, we hope that they will also find out a way to easily display text.

Eventually we decided to just model one particular case of a cross product. By using colours, we hoped to explain to which vectors they refer in the application itself. The most advanced 3D environment that we could find was Peter Nederhof's Clay Engine, which however was also far from perfect. While it allows you to display actual 3D models and shapes and use lighting and depth correctly, it lacks any sort of support or documentation. Worst of all is that something went wrong with the source code, causing all of the end-of-lines to suddenly disappear, resulting in it becoming unreadable, so even looking up the simplest of functions becomes difficult.

Also, for as far as we could find, there was no way to draw lines in this environment. So, if you wanted to draw a line from A to B you had to cheat a bit by creating a cuboid with a very small height and width, and fiddle around with its rotation to get it at the right angle. The ability to draw lines is another feature we hope will be included in the official version of WebGL.

The application is again much of the same as the previous incarnation, since technically vectors are a sub-set of matrices. Since a lot of the process of calculating a cross product of two vectors is just relocating the elements of each vector at the right place in an equation, it was relatively easy to just store all of the elements of this equation into one big array. The code for aligning the circle pointers correctly, along with drawing the different matrices is a bit inefficient here, though. The functions that we've been using in the previous sections turned out to not be flexible enough to handle some of the different cases we wanted to display here. We figured that it wasn't worth the time and effort to fully optimize the code there, so instead there are two near-identical functions for both drawing matrices and drawing circles.

Because the process of calculating a cross product is fairly simple compared to other matrix calculations, combined with time constraints, we opted not to implement intermediate and advanced levels for cross products (and for dot products as well).

For the explanation of the algorithm to calculate a cross product, we decided to first show the formula that is easier to visualize (combined with the 3D Model). From thereon, we would explain how to get to a formula that can actually be used to calculate it. Immediately bringing up the actual formula in the explanation would not have given students the insight needed to understand what the cross product is meant to represent, as it's rather nondescript.

Dot Product

Visualizing the concept of a dot product on the screen was a lot easier than with the cross product since it was done in 2D, but here too we ran into a few difficulties. First of all, there was the realization that Javascript doesn't support Greek characters. Because the formula of the dot product between vectors a and b is generally defined as |a||b|cosθ, we unfortunately had to be a bit inconsistent in order to visualize the place of angle θ (which we ended up calling angle c) on the canvas screen.

In the end, we decided to go with a scale from one to 10 on both the x and y axis, and made both vectors consist out of random numbers from 0 to 10. In the graph, we visualized these two vectors, along with the length of |A|cos(c) (where A is the vector and c is the angle between the two vectors), in an attempt to use trigonometry in order to help students visualizing what the dot product represents.

The biggest problem with this approach that we found was that not all of the graphs are aligned right for its descriptions. Because the points are assigned randomly, it can cause the graph to become a bit unreadable. A few refreshes however will likely lead to a more readable graph.

Section 6: Conclusion

In this project, we attempted to create light-weight applications in order to aid students grasping the basic matrix operations. We brought in a simple gaming element, that would allow users to compete and submit high scores, bringing in a bit of competition that would motivate students to spent time into trying to grasp the basics of the algorithms required.

With our explanations we didn't try to be the most detailed, but instead tried to provide explanations that were as simple as possible, without simply stating a bunch of formulas. We found that often, when these theories are explained in mathematics text books, they can be quite overwhelming when you see them for the first time. Our explanation at the "Beginner"-section of each game aims to explain these from a different perspective as an alternative.

With these applications, we hope to introduce new students to matrix operations, and get them motivated to spend more time on the subject. We hope that these applications will provide the right stepping stones for them to get familiar with matrix operations, without just spoon-feeding them the right answers.

Personal Evaluation

Overall, this was quite an interesting project to work on, as it got me thinking about ways to teach and explain new material, using these serious games. The thing with teaching is that in your head things may make sense, but it is quite a challenge to explain this in a way that will be easily understood, especially when this is on a website where you only have a few seconds in order to grasp the user's attention.

There were a number of things that I had in mind when I started this project that I didn't manage to realize. For example, when visualizing the concepts of the cross product between two vectors, I would have liked to have found a 3D Canvas library that had more functionalities to really properly show the thing that you're trying to calculate. I also originally planned to include matrix inversion into this project, but after seeing how ridiculously complicated the determinant section ended up to be, I decided that trying to break down the process of matrix inversion, which takes up even more different steps, is just too much for this project.

In technical terms, when working on these learning games, I of course learned a lot more about matrices this way, and especially how to model them in a programming or scripting language. Especially the determinant section required a lot of effort to keep track of everything and every value that I wanted to display to the user, and I gained some experience in keeping track of large amounts of variables, even though the code itself may have ended up in a bit of a mess.

Peter Gels, 1536478