TicTacToe: Difference between revisions
No edit summary |
|||
(28 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
In this tutorial, we walk you through making a Tic Tac Toe game from scratch. You will find this tutorial useful if you are new to programming and new to AppStudio. This tutorial uses JavaScript. You can download the source file for the completed app here [[File: | In this tutorial, we walk you through making a Tic Tac Toe game from scratch. You will find this tutorial useful if you are new to programming and new to AppStudio. This tutorial uses JavaScript. You can download the source file for the completed app here [[File:TicTacToe.zip]] | ||
This tutorial can be used as a self study course or with a teacher. | |||
== Installing AppStudio == | == Installing AppStudio == | ||
If you haven't already installed AppStudio, download it from here [https://www.nsbasic.com/app/downloads/] and follow the installation instructions here [https://www.nsbasic.com/app/downloads/]. | If you haven't already installed AppStudio, download it from here [https://www.nsbasic.com/app/downloads/] and follow the installation instructions here [https://www.nsbasic.com/app/downloads/]. The free demo will do everything you need for this tutorial. | ||
== Creating a New Project == | == Creating a New Project == | ||
Line 13: | Line 15: | ||
Like most games, we want to have a welcome page or a main menu page that takes us to the game. To do that, we can have multiple forms. A form is a container for controls such as buttons and drop down menus. We can use one form for our start page. Read more about forms [[Form|here]]. | Like most games, we want to have a welcome page or a main menu page that takes us to the game. To do that, we can have multiple forms. A form is a container for controls such as buttons and drop down menus. We can use one form for our start page. Read more about forms [[Form|here]]. | ||
Start AppStudio. Go to the File menu then choose New Project. From now on, we will adopt the notation File>New Project to denote choosing the option New Project from the File Menu. Choose the language (JavaScript), and choose the form size. We chose the 320x460 size, so it will fit on a phone. When done, click "Create". You'll see something like this: | |||
Start AppStudio. Go to the File menu then choose New Project. From now on, we will adopt the notation File > New Project to denote choosing the option New Project from the File Menu. Choose the language (JavaScript), and choose the form size. We chose the 320x460 size, so it will fit on a phone. When done, click "Create". You'll see something like this: | |||
[[File:TT01.01.png]]<br> | [[File:TT01.01.png]]<br> | ||
In the Design Screen (5), you will see an empty form, called Form1. In the Project Explorer (6) on the right, you will find Form1 under "Project properties and Global Code". Click on Form1. Under the Properties Grid (7), you will find all the properties of the form listed. Note that the designHeight and designWidth properties are set to 460 and 320. Scroll down the properties list and find the "id" property under "Common". Change the form id to "frmStart". Note that the name of the form changed in the Project Explorer and the form tab. | In the Design Screen (5), you will see an empty form, called Form1. In the Project Explorer (6) on the right, you will find Form1 under "Project properties and Global Code". Click on Form1. Under the Properties Grid (7), you will find all the properties of the form listed. Note that the designHeight and designWidth properties are set to 460 and 320. Scroll down the properties list and find the "id" property under "Common". Change the form id to "frmStart". Note that the name of the form changed in the Project Explorer and the form tab. | ||
== Designing the Start Page == | == Designing the Start Page == | ||
Line 28: | Line 27: | ||
Next, let us add a Play button. From the Toolbox (4), find Button under jQuery Mobile. Drag and drop the button to below the Tic Tac Toe label. Set its id property to btnPlay and its value to Play. Under the Project Explorer (6), you will find lblStartMenu and btnPlay under frmStart. | Next, let us add a Play button. From the Toolbox (4), find Button under jQuery Mobile. Drag and drop the button to below the Tic Tac Toe label. Set its id property to btnPlay and its value to Play. Under the Project Explorer (6), you will find lblStartMenu and btnPlay under frmStart. | ||
[[File:pic00.png]] | [[File:pic00.png]]<br> | ||
After setting up the Start form, we can now move to designing the Game form. Find the "Plus" sign next to the Start form tab. Click on it. This will create a new form, Form2. From the Properties Grid (7), change its id to frmPlay. In the Project Explorer (6), there are now two forms; frmStart and frmPlay. | After setting up the Start form, we can now move to designing the Game form. Find the "Plus" sign next to the Start form tab. Click on it. This will create a new form, Form2. From the Properties Grid (7), change its id to frmPlay. In the Project Explorer (6), there are now two forms; frmStart and frmPlay. | ||
Line 35: | Line 34: | ||
== Designing the Game Form == | == Designing the Game Form == | ||
Let's now design the Tic Tac Toe board. From the toolbox, drag and drop another button. Set its id to btn11 to denote the button in the first row, first column. Set its height and width to 70 to make it a square. Erase its value to remove any text. Set its corners to "Round" instead of Square. | Let's now design the Tic Tac Toe board. From the toolbox, drag and drop another button. Set its id to btn11 to denote the button in the first row, first column. Set its height and width to 70 to make it a square. Erase its value to remove any text. Set its corners to "Round" instead of Square. Set the fontSize to 24. | ||
[[File:pic01.png]] | [[File:pic01.png]]<br> | ||
Now right-click on the button from the design menu, and choose Copy. Right-click on the form and click Paste. In the Project Explorer pane, you will find two buttons under the form frmPlay. One with the name btn11 and the other with the name btn11Copy. Change its name to btn12 and place it to the right of btn11. To use the arrow keys to move the controls on the form, press alt + arrowKey. | Now right-click on the button from the design menu, and choose Copy. Right-click on the form and click Paste. In the Project Explorer pane, you will find two buttons under the form frmPlay. One with the name btn11 and the other with the name btn11Copy. Change its name to btn12 and place it to the right of btn11. To use the arrow keys to move the controls on the form, press alt + arrowKey. | ||
Line 43: | Line 42: | ||
Make more copies of the button to form a 3x3 board like so: | Make more copies of the button to form a 3x3 board like so: | ||
[[File:pic1.png]] | [[File:pic1.png]]<br> | ||
Remember to change the names of the buttons. When you are done, you should have something like this: | Remember to change the names of the buttons. When you are done, you should have something like this: | ||
[[File:pic2.png]] | [[File:pic2.png]]<br> | ||
Optionally, you can put the buttons into groups to give them the shape you see in the image above. To do so, set the groupBegin property of buttons btn11, btn12, btn13 to "Start vertical". And set the groupEnd property of buttons btn31, btn32, btn33 to "Yes". | Optionally, you can put the buttons into groups to give them the shape you see in the image above. To do so, set the groupBegin property of buttons btn11, btn12, btn13 to "Start vertical". And set the groupEnd property of buttons btn31, btn32, btn33 to "Yes". | ||
Line 57: | Line 56: | ||
We used the leftmost button to get to the next turn. We use the middle button to reset the game to start from the beginning. And finally, the rightmost button to go back to the main menu. We set their ids to btnNext, btnReset and btnMenu, respectively. We choose the Icons as in the image below. | We used the leftmost button to get to the next turn. We use the middle button to reset the game to start from the beginning. And finally, the rightmost button to go back to the main menu. We set their ids to btnNext, btnReset and btnMenu, respectively. We choose the Icons as in the image below. | ||
[[File:pic3.png]] | [[File:pic3.png]]<br> | ||
== Programming the Controls == | == Programming the Controls == | ||
Line 66: | Line 64: | ||
Go to Run>Start in Desktop Browser to try out the app. Alternatively, you can click on the Run button shown below. | Go to Run>Start in Desktop Browser to try out the app. Alternatively, you can click on the Run button shown below. | ||
[[File:pic4.png]] | [[File:pic4.png]]<br> | ||
Your default web browser will open. You will see the Start Form. You can try clicking the Play button, but of course, clicking on the Play button won't take you to the game as we have not programmed any of the controls yet! | Your default web browser will open. You will see the Start Form. You can try clicking the Play button, but of course, clicking on the Play button won't take you to the game as we have not programmed any of the controls yet! | ||
Line 75: | Line 73: | ||
Go back to AppStudio. Right click on the Play button btnPlay and click on Go To Event>onclick. This will take you to the Code Window of frmStart. You will also see the onclick function header. A function is a piece of code that executes when called or triggered. In this case, the event of clicking on a the Play button triggers this code. When clicking on this button, we want the app to take us to the Play form. We can simply write, ChangeForm(frmPlay). ChangeForm() is a function that AppStudio provides that allows us to switch from one form to another in Run time. You can tell that a code word is a function when it is followed by round brackets like in ChangeForm(). We use those brackets to pass in values that the function requires to execute. We call these values function parameters. For example, ChangeForm() requires the name of the form to change to. Thus, it takes in one parameter, a function name. In JavaScript, we follow the ChangeForm() line by a semicolon as shown in the picture below. A semicolon simply tells the computer "end of the command". | Go back to AppStudio. Right click on the Play button btnPlay and click on Go To Event>onclick. This will take you to the Code Window of frmStart. You will also see the onclick function header. A function is a piece of code that executes when called or triggered. In this case, the event of clicking on a the Play button triggers this code. When clicking on this button, we want the app to take us to the Play form. We can simply write, ChangeForm(frmPlay). ChangeForm() is a function that AppStudio provides that allows us to switch from one form to another in Run time. You can tell that a code word is a function when it is followed by round brackets like in ChangeForm(). We use those brackets to pass in values that the function requires to execute. We call these values function parameters. For example, ChangeForm() requires the name of the form to change to. Thus, it takes in one parameter, a function name. In JavaScript, we follow the ChangeForm() line by a semicolon as shown in the picture below. A semicolon simply tells the computer "end of the command". | ||
[[File:pic5.png]] | [[File:pic5.png]]<br> | ||
Deploy the app again. Now try clicking on the Play button. You should see that the app changes to the Play form! | Deploy the app again. Now try clicking on the Play button. You should see that the app changes to the Play form! | ||
Line 82: | Line 80: | ||
Go back to AppStudio. Go to the Play form. Let us program the "Go to Main Menu" button. You should know how to do this! | Go back to AppStudio. Go to the Play form. Let us program the "Go to Main Menu" button. You should know how to do this! | ||
Simply, right-click on btnMenu, choose Go to Event>onclick. In the function btnMenu.onclick write ChangeForm(frmStart); | Simply, right-click on btnMenu, choose Go to Event>onclick. In the function btnMenu.onclick write | ||
<pre>ChangeForm(frmStart);</pre> | |||
Deploy the app, test the Play and the Return to Main Menu buttons. You should be able to go back and forth between the two forms now. | Deploy the app, test the Play and the Return to Main Menu buttons. You should be able to go back and forth between the two forms now. | ||
Line 90: | Line 90: | ||
==== Global and Local Variables ==== | ==== Global and Local Variables ==== | ||
We now want to program the buttons of the Tic Tac Toe board. Upon clicking any empty button, we want to write an X or an O on it, depending on whose turn is it to play. We need a way to keep track of | We now want to program the buttons of the Tic Tac Toe board. Upon clicking any empty button, we want to write an X or an O on it, depending on whose turn is it to play. We need a way to keep track of players turns. | ||
For that, we create a global variable. At the top of the Coding tab of frmPlay, write the following line: | For that, we create a global variable. At the top of the Coding tab of frmPlay, write the following line: | ||
<pre>var player;</pre> | <pre>var player;</pre> | ||
Let's understand what that means. A variable is a place holder. Upon declaring a variable (example: the var statement declaring the variable "player" above), the computer reserves a space in memory and calls it "player". Every time you modify the value of the variable player, the computer | Let's understand what that means. A variable is a place holder. Upon declaring a variable (example: the var statement declaring the variable "player" above), the computer reserves a space in memory and calls it "player". Every time you modify the value of the variable player, the computer accesses this place in memory, and modifies its value. For example, if you were to write: | ||
<pre>player = 1;</pre> | <pre>player = 1;</pre> | ||
Line 101: | Line 101: | ||
the computer accesses the place it had reserved for player in memory, and writes a 1 there. This place in memory will always have the value 1 until you modify it or close your program. | the computer accesses the place it had reserved for player in memory, and writes a 1 there. This place in memory will always have the value 1 until you modify it or close your program. | ||
As for the | As for the keyword "global", a variable is global if it is declared outside of any function with a var statement. JavaScript also considers a variable global if it is not declared at all. When a variable is global, it means that any function is able to use this variable. That is, any function in our code is able to access this place in memory. If you declare a variable inside a function, the variable is only "visible" to that function; only that function has access to this variable. Such variable would be called a local variable. | ||
==== Resetting the Board before a New Game ==== | ==== Resetting the Board before a New Game ==== | ||
Line 119: | Line 119: | ||
Click on any of the Tic Tac Toe board's buttons, say btn11. Look at the properties window. You can see that the property Height has a numerical value: 70. Scroll down the properties list and find the Value property. The Height property is expecting a numerical value, but the Value property is expecting a string value. Set the Value property to the letter X. You see that the letter X appears on the button in your Design Screen. | Click on any of the Tic Tac Toe board's buttons, say btn11. Look at the properties window. You can see that the property Height has a numerical value: 70. Scroll down the properties list and find the Value property. The Height property is expecting a numerical value, but the Value property is expecting a string value. Set the Value property to the letter X. You see that the letter X appears on the button in your Design Screen. | ||
But what if we only want the X to appear when the button is clicked? We can do that by editing the Value property of btn11 through code. Go back to the properties | But what if we only want the X to appear when the button is clicked? We can do that by editing the Value property of btn11 through code. Go back to the properties panel of btn11 and delete the letter X from the Value property field. | ||
Line 126: | Line 126: | ||
<pre>btn11.value = "app";</pre> | <pre>btn11.value = "app";</pre> | ||
As you might have guessed, this line tells the computer to change the value of the Value property to the string "app". Generally, if we want to edit an object's properties through code, we must first tell the computer which object we want edit. Recall that we have set the buttons ids to btn11, btn12, ...., btn33. Here, we want to edit btn11's Value property, so we type btn11, followed by a ".". When you type a dot following an object's name, AppStudio understands that you are trying to edit one of the object's properties and it automatically shows a list of properties the object has. As you start typing the property name, only functions and properties that match what you've typed appear on the list. If you're not sure about the name of a property, you can simply go through that menu to find it. If you're not sure what a property or function does, you can refer to AppStudio's Wiki pages [http://wiki.nsbasic.com/AppStudio_Documentation | As you might have guessed, this line tells the computer to change the value of the Value property to the string "app". Generally, if we want to edit an object's properties through code, we must first tell the computer which object we want edit. Recall that we have set the buttons ids to btn11, btn12, ...., btn33. Here, we want to edit btn11's Value property, so we type btn11, followed by a ".". When you type a dot following an object's name, AppStudio understands that you are trying to edit one of the object's properties and it automatically shows a list of properties the object has. As you start typing the property name, only functions and properties that match what you've typed appear on the list. If you're not sure about the name of a property, you can simply go through that menu to find it. If you're not sure what a property or function does, you can refer to AppStudio's Wiki pages [http://wiki.nsbasic.com/AppStudio_Documentation]. | ||
After you've typed the line above, try running the app by clicking on the Run button. In your web browser, click on btn11. The word "app" should appear on the button when clicked. | After you've typed the line above, try running the app by clicking on the Run button. In your web browser, click on btn11. The word "app" should appear on the button when clicked. | ||
Line 162: | Line 162: | ||
</pre> | </pre> | ||
Write the | Write the lines above and test your code. You should see btn11's height increases from 70 to 100 when clicked. | ||
When done, delete the code lines under the function btn11.onclick(). We only wrote these lines to understand strings and object properties | |||
When done testing, delete the code lines under the function btn11.onclick(). We only wrote these lines to understand strings and object properties! | |||
<pre> | <pre> | ||
btn11.onclick=function(){ | btn11.onclick=function(){ | ||
Line 176: | Line 177: | ||
As evident by its name, this function executes when frmPlay appears. For example, in our app, when we click on the Play button from frmStart, frmPlay appears and the function frmPlay.onshow() will be executed automatically. When the frmPlay appears, it is the start of a new game and we want it to be Player 1's turn. So we set the variable player to 1. We also want to make sure that the Tic Tac Toe board is completely empty. That is, if there are any X's and O's on the board, we want to replace them with empty strings. So we set each button's value property to an empty string like so: | As evident by its name, this function executes when frmPlay appears. For example, in our app, when we click on the Play button from frmStart, frmPlay appears and the function frmPlay.onshow() will be executed automatically. When the frmPlay appears, it is the start of a new game and we want it to be Player 1's turn. So we set the variable player to 1. We also want to make sure that the Tic Tac Toe board is completely empty. That is, if there are any X's and O's on the board, we want to replace them with empty strings. So we set each button's value property to an empty string like so: | ||
[[ | <pre> | ||
function init_board() { | |||
btn11.value = ""; | |||
btn12.value = ""; | |||
btn13.value = ""; | |||
btn21.value = ""; | |||
btn22.value = ""; | |||
btn23.value = ""; | |||
btn31.value = ""; | |||
btn32.value = ""; | |||
btn33.value = ""; | |||
player = 1; | |||
moves_count = 0; | |||
last_state = [ | |||
["", "", ""], | |||
["", "", ""], | |||
["", "", ""] | |||
]; | |||
lblTurn.textContent = "Player " + player + "'s turn"; | |||
} | |||
frmPlay.onshow = function() { | |||
init_board(); | |||
} | |||
</pre> | |||
As you can see in the picture, the frmPlay.onshow function calls another function called init_board(). To understand the code in init_board(), first, note that the computer (or phone processor!) executes code sequentially. Meaning that it executes the first line in the function init_board() before moving on to the second line, then third, etc. In init board, we first set each of the buttons values to an empty text string. | As you can see in the picture, the frmPlay.onshow function calls another function called init_board(). To understand the code in init_board(), first, note that the computer (or phone processor!) executes code sequentially. Meaning that it executes the first line in the function init_board() before moving on to the second line, then third, etc. In init board, we first set each of the buttons values to an empty text string. | ||
Line 217: | Line 245: | ||
[[File:pic9.png]] | [[File:pic9.png]] | ||
==== Scopes ==== | |||
Now is a good time to discuss scopes. In JavaScript, scopes of functions, loops, and if statements are defined using curly brackets. Look at the function changeState() in the picture below. We see that after the function header, there's an open curly bracket '{' and at the end, there's a closed curly bracket '}'. This tells AppStudio that the code lines between the two curly brackets should be executed when the function changeState() is called. For readability, we indent the code inside the function. In the image below, the scope of the function is in blue. Note that the code under the if statement if(last_state[i][j] == "") is indented twice. This is to indicate that this code is within the scope of this if statement and will be executed if the if condition was found to be true. The scope of this if statement is in red. And so on. | |||
[[File:pic15.png]] | |||
Note that we can have nested if statements. That is, we can have if statements inside each other. But in JavaScript, we can't have nested functions. That is, we can't have a function definition inside another! But of course, we can call one function from another. | |||
For more about indenting and code formatting, read the section Formatting Your Code. | |||
=== Next Player's turn === | === Next Player's turn === | ||
Line 224: | Line 262: | ||
A move is invalid if the player makes more than one move at once or doesn't make any moves at all! | A move is invalid if the player makes more than one move at once or doesn't make any moves at all! | ||
Here's the code for and a flow chart for btnNext.onclick: | |||
[[File:pic14.png]] | |||
We will come back to this diagram later. But before we get into the details of programming btnNext, we first need to discuss two ideas; addition, and return values of functions. | |||
==== Addition ==== | ==== Addition ==== | ||
Line 307: | Line 349: | ||
Right click on btnNext, click on Go To Event> onclick. Upon clicking on that button, we first want to find where the player placed his move. We create a global variable, newMove to store the value of the new move. We create a function find_new_move(). The purpose of this funcion is to where the new move was placed and to count how many new moves were placed. To make sure that only one move was made. | Right click on btnNext, click on Go To Event> onclick. Upon clicking on that button, we first want to find where the player placed his move. We create a global variable, newMove to store the value of the new move. We create a function find_new_move(). The purpose of this funcion is to where the new move was placed and to count how many new moves were placed. To make sure that only one move was made. | ||
[[ | <pre> | ||
var newMove; | |||
function check_move(i, j, btn) { | |||
if (last_state[i][j] != btn.value) { | |||
newMove = btn.value; | |||
last_move_i = i; | |||
last_move_j = j; | |||
return 1; | |||
} | |||
return 0; | |||
} | |||
function find_new_move() { | |||
var count = 0; | |||
newMove = ""; | |||
count += check_move(0, 0, btn11); | |||
count += check_move(0, 1, btn12); | |||
count += check_move(0, 2, btn13); | |||
count += check_move(1, 0, btn21); | |||
count += check_move(1, 1, btn22); | |||
count += check_move(1, 2, btn23); | |||
count += check_move(2, 0, btn31); | |||
count += check_move(2, 1, btn32); | |||
count += check_move(2, 2, btn33); | |||
return count; | |||
} | |||
</pre> | |||
In find_new_move(), we write the following code. Initially, we create a local variable called count, which we initialize to 0. We then call another function, check_move() 9 times, for each of the buttons, to check if a move was made in that block. The function check_move returns a 1 if a new move was made in that block, and a 0 otherwise. We add the return value from check_move() to count. Recall how equations are evaluated in programming: first the right hand side expression is evaluated, then the result is saved in the left hand side of the equation. In this case, the function check_move() is executed, and its return value is saved in the variable count on the left hand side of the equation. After all 9 calls are executed, count would have the total number of moves made in total. find_new_move() can now return the value of count to its caller function btnNext.onclick. For btnNext.onclick to capture the return value of find_new_move(), we create a variable count. It is okay to call that variable count as well since it is local to the function btnNext.onclick and the other variable named count is local to the function find_new_move(). | In find_new_move(), we write the following code. Initially, we create a local variable called count, which we initialize to 0. We then call another function, check_move() 9 times, for each of the buttons, to check if a move was made in that block. The function check_move returns a 1 if a new move was made in that block, and a 0 otherwise. We add the return value from check_move() to count. Recall how equations are evaluated in programming: first the right hand side expression is evaluated, then the result is saved in the left hand side of the equation. In this case, the function check_move() is executed, and its return value is saved in the variable count on the left hand side of the equation. After all 9 calls are executed, count would have the total number of moves made in total. find_new_move() can now return the value of count to its caller function btnNext.onclick. For btnNext.onclick to capture the return value of find_new_move(), we create a variable count. It is okay to call that variable count as well since it is local to the function btnNext.onclick and the other variable named count is local to the function find_new_move(). | ||
Line 313: | Line 382: | ||
=== Handling invalid moves === | === Handling invalid moves === | ||
In this game, we allow Players to change their minds about the move they made before pressing the button "Next" for the next player's turn. A player can place an 'X' on an empty block, repress on that same block to undo the move, then press on another empty block to place a move there instead. When decided, the player presses "Next". However, this means that players might place more than one move during their turn. In this case, when they press "Next" we cannot accept it! | |||
We have the number of new moves made on the board during the last turn stored in the variable count. Now we need to take action. If the player made more than one move, we cannot accept that! We must inform the player that he'd made too many moves and remove the moves he made in his last turn! | |||
If the player made no moves, we must inform him that he should make a move before getting its the next player's turn. If he made exactly one move, we record that move in our last_state array, we check if the player has won, if not, we give the other player the turn. See the function btnNext.onclick below | If the player made no moves, we must inform him that he should make a move before getting its the next player's turn. If he made exactly one move, we record that move in our last_state array, we check if the player has won, if not, we give the other player the turn. See the function btnNext.onclick below | ||
[[File: | Take a look at the diagram below. The numbers indicate the sequence of functions the code will execute in case of an invalid move. | ||
[[File:pic14.png]] | |||
Let's understand this code. After the line 137, the function find_new_moves() has executed and the variable count has the number of moves a player made in the last turn. Next, we use an if statement to check if the player has made more than one move! If he did, We inform him that he's made too many moves: | |||
<pre>NSB.print("Too many moves! Resetting back");</pre> | |||
Then we reset the board. We could have reset the board in this function here. But for clarity, we chose to create a separate function reset_board(). In reset_board, we simply want to reset the values of buttons to the last state they were in before the player made these invalid moves. Recall that we store these moves in the array last_state[3][3]! So we can simply do the following: | |||
<pre> | |||
function reset_move(i, j, btn){ | |||
btn.value = last_state[i][j]; | |||
} | |||
function reset_board(){ | |||
reset_move(0,0,btn11); | |||
reset_move(0,1,btn12); | |||
reset_move(0,2,btn13); | |||
reset_move(1,0,btn21); | |||
reset_move(1,1,btn22); | |||
reset_move(1,2,btn23); | |||
reset_move(2,0,btn31); | |||
reset_move(2,1,btn32); | |||
reset_move(2,2,btn33); | |||
} | |||
</pre> | |||
=== Has anyone won the game yet? === | === Has anyone won the game yet? === | ||
In player_won() we check if any player has won. That is, they placed three moves in the same row, column on diagonal. If so, we return true. If not, we return false | In player_won() we check if any player has won. That is, they placed three moves in the same row, column on diagonal. If so, we return true. If not, we return false. | ||
[[ | <pre> | ||
function player_won() { | |||
if ((last_state[0][0] != "" && last_state[0][0] == last_state[0][1] && last_state[0][0] == last_state[0][2]) || | |||
(last_state[0][0] != "" && last_state[0][0] == last_state[1][1] && last_state[0][0] == last_state[2][2]) || | |||
(last_state[0][0] != "" && last_state[0][0] == last_state[1][0] && last_state[0][0] == last_state[2][0]) || | |||
(last_state[0][2] != "" && last_state[0][2] == last_state[1][1] && last_state[0][2] == last_state[2][0]) || | |||
(last_state[0][1] != "" && last_state[0][1] == last_state[1][1] && last_state[0][1] == last_state[2][1]) || | |||
(last_state[0][2] != "" && last_state[0][2] == last_state[1][2] && last_state[0][2] == last_state[2][2]) || | |||
(last_state[1][0] != "" && last_state[1][0] == last_state[1][1] && last_state[1][0] == last_state[1][2]) || | |||
(last_state[2][0] != "" && last_state[2][0] == last_state[2][1] && last_state[2][0] == last_state[2][2])) | |||
return true; | |||
return false; | |||
} | |||
</pre> | |||
Note the use of the "||" and "&&" operands. These are "OR" and "AND" operands used in if statements. For example, the first statement reads: | Note the use of the "||" and "&&" operands. These are "OR" and "AND" operands used in if statements. For example, the first statement reads: | ||
Line 332: | Line 441: | ||
Let's look at the if statement in btnNext.onclick. The if condition is evaluated by first evaluating each of the expressions on the right and left hand sides of the operand "==" then checking if the equality holds. In this case, play_won() is executed and the return value of the function is compared to "false". If the return value is false, the code under the if statement is executed. Starting with next_turn(). | Let's look at the if statement in btnNext.onclick. The if condition is evaluated by first evaluating each of the expressions on the right and left hand sides of the operand "==" then checking if the equality holds. In this case, play_won() is executed and the return value of the function is compared to "false". If the return value is false, the code under the if statement is executed. Starting with next_turn(). | ||
As you might expect, next_turn() just updates the variable player and the label that says whose turn it is | As you might expect, next_turn() just updates the variable player and the label that says whose turn it is. | ||
<pre> | |||
function next_turn() { | |||
if (player == 1) | |||
player = 2; | |||
else if (player == 2) | |||
player = 1; | |||
lblTurn.textContent = "Player " + player + "'s turn"; | |||
} | |||
</pre> | |||
=== What if it's a tie? === | === What if it's a tie? === | ||
Take a look at the code of the btnNext.onclick function (code snapshot under Handing Invalid Moves). After the next_turn() function returns, we want to check if the all the boxes on the board are full. If all the boxes on the board are full at that point, we know it is a tie since we already know that nobody won so far! (How do we know? Hint: at this point in the code, did the if statement condition player_won()==false evaluate to true or false?) | |||
To find out if there's a tie, we can count the number of full blocks, and if the number if 9, we let the players know that there's a tie. But can we find a faster way to do this? Instead of counting the number of full blocks every time a player makes a valid move, we can simply make a variable that will serve as a moves counter. Every time any of the players makes a valid move, we increment the counter. Then, we check if the counter value has reached 9, then we know it is a tie! | |||
To do that we declare a global variable at the top of our file. We called it moves_count. In our init_board function, initialize moves_count to 0, since every time a new game starts, we want to reset moves counter to indicate that there are 0 full blocks on the board. In the function btnNext.onclick, if the player has not won (that is, if player_won() returns false), we add one to moves_count. Note that following lines of code are equivalent: | |||
<pre>moves_count++;</pre> | <pre>moves_count++;</pre> | ||
Line 344: | Line 465: | ||
<pre>moves_count = moves_count + 1;</pre> | <pre>moves_count = moves_count + 1;</pre> | ||
Finally we check if moves_count is 9. If so, and we know nobody has won, then it must be a tie! We inform the players that there's a tie, reset the board, and change to the Start form! | |||
If however, player_won() returns true, the code under the if statement if(player_won() == false) will not execute, instead, the code under the else would execute: We inform the players which player won, we reset the board, and return to the start Form. | |||
Return to btnMenu.onclick function, before we return to the Start form, we probably want to initialize the board, so we simply make a call to init_board() before the line ChangeForm(frmStart). | Return to btnMenu.onclick function, before we return to the Start form, we probably want to initialize the board, so we simply make a call to init_board() before the line ChangeForm(frmStart). | ||
Now, we're just left with programming the middle button, btnReset. Right-click on btnReset Go to Event> onclick. Simply add one line. A call to init_board(). | Now, we're just left with programming the middle button, btnReset. Right-click on btnReset Go to Event> onclick. Simply add one line. A call to init_board(). | ||
== Debugging Tips == | |||
Now you're done writing your code. But it is likely that you have a few bugs and mistakes in your code. Some bugs are easy to find and fix. Other bugs won't be as easy to find. So here are a few tips that can help you debug your code. | |||
=== Formatting Your Code === | |||
Indenting and using spaces consistently in your code can make your code a lot more readable, which in turn can make debugging a lot easier! Always indent code that exists within the scope of a bigger function or statement. Let's take the function changeState() as an example. All code under inside the function scope (blue) should be indented once so it is easy for a reader to see that this code belongs inside a function. The code in red is indented twice (or once with respect to the if statement above it) because it is inside the scope of the if statement if(last_state[i][j]==""). The code in green is inside the scope of the if statement if(btn.value=="") and the code in purple is inside the scope of the else. | |||
[[File:pic15.png]] | |||
It is good practice to indent your code as you write it. But you can also make AppStudio format your code for you by going to '''Edit > Format Code'''. If you're debugging, try formatting your code and checking if the code is indented the way you expect it to be indented. If it isn't, this can mean that you missed a bracket or other syntax errors that caused AppStudio to misinterpret your code. It is recommended that you format your code occasionally as you write it so you can catch such syntax errors as they come up. | |||
=== Debugging Tools === | |||
You can also use Chrome's debugging tool to find bugs in your program. Go to '''Run > How''' To Use Debuggers to learn how to use Chrome's debugger. | |||
== Test your app! == | == Test your app! == | ||
You've now programmed, tested and debugged your app. It is time to deploy it and use it on your phone! | |||
Click on Run > Deploy. | |||
Click on Run>Deploy. | |||
AppStudio will give you a link. Open your phone browser and enter the link you were given. Use the app on your phone! | AppStudio will give you a link. Open your phone browser and enter the link you were given. Use the app on your phone! | ||
For future apps, you can read about app publishing options here [http://wiki.nsbasic.com/Distributing_your_App|here]. | For future apps, you can read about app publishing options here [http://wiki.nsbasic.com/Distributing_your_App|here]. |
Latest revision as of 23:02, 8 June 2016
In this tutorial, we walk you through making a Tic Tac Toe game from scratch. You will find this tutorial useful if you are new to programming and new to AppStudio. This tutorial uses JavaScript. You can download the source file for the completed app here File:TicTacToe.zip
This tutorial can be used as a self study course or with a teacher.
Installing AppStudio
If you haven't already installed AppStudio, download it from here [1] and follow the installation instructions here [2]. The free demo will do everything you need for this tutorial.
Creating a New Project
Let's start!
Like most games, we want to have a welcome page or a main menu page that takes us to the game. To do that, we can have multiple forms. A form is a container for controls such as buttons and drop down menus. We can use one form for our start page. Read more about forms here.
Start AppStudio. Go to the File menu then choose New Project. From now on, we will adopt the notation File>New Project to denote choosing the option New Project from the File Menu. Choose the language (JavaScript), and choose the form size. We chose the 320x460 size, so it will fit on a phone. When done, click "Create". You'll see something like this:
In the Design Screen (5), you will see an empty form, called Form1. In the Project Explorer (6) on the right, you will find Form1 under "Project properties and Global Code". Click on Form1. Under the Properties Grid (7), you will find all the properties of the form listed. Note that the designHeight and designWidth properties are set to 460 and 320. Scroll down the properties list and find the "id" property under "Common". Change the form id to "frmStart". Note that the name of the form changed in the Project Explorer and the form tab.
Designing the Start Page
Now let's start adding controls to the Start form. From the Toolbox (4), drag and drop a Label in the middle of the Design Screen (5). In the Properties Grid (7), change the id property to lblStartMenu and set its TextContent to "Tic Tac Toe". Set the width, height and fontSize properties to suitable values.
Next, let us add a Play button. From the Toolbox (4), find Button under jQuery Mobile. Drag and drop the button to below the Tic Tac Toe label. Set its id property to btnPlay and its value to Play. Under the Project Explorer (6), you will find lblStartMenu and btnPlay under frmStart.
After setting up the Start form, we can now move to designing the Game form. Find the "Plus" sign next to the Start form tab. Click on it. This will create a new form, Form2. From the Properties Grid (7), change its id to frmPlay. In the Project Explorer (6), there are now two forms; frmStart and frmPlay.
Designing the Game Form
Let's now design the Tic Tac Toe board. From the toolbox, drag and drop another button. Set its id to btn11 to denote the button in the first row, first column. Set its height and width to 70 to make it a square. Erase its value to remove any text. Set its corners to "Round" instead of Square. Set the fontSize to 24.
Now right-click on the button from the design menu, and choose Copy. Right-click on the form and click Paste. In the Project Explorer pane, you will find two buttons under the form frmPlay. One with the name btn11 and the other with the name btn11Copy. Change its name to btn12 and place it to the right of btn11. To use the arrow keys to move the controls on the form, press alt + arrowKey.
Make more copies of the button to form a 3x3 board like so:
Remember to change the names of the buttons. When you are done, you should have something like this:
Optionally, you can put the buttons into groups to give them the shape you see in the image above. To do so, set the groupBegin property of buttons btn11, btn12, btn13 to "Start vertical". And set the groupEnd property of buttons btn31, btn32, btn33 to "Yes".
Next, let's add a label to indicate which player's turn it is. Add a label to the Play form. Change its name to lblTurn. Set its initial textContent to "Player 1's turn".
Next, let's add some control buttons. Drag and drop three jQuery Mobile buttons below the Tic Tac Toe board. We chose to make them square buttons and to use icons instead of text to denote their usage. To choose an Icon, click on the Icon property from the Properties Grid (7) and choose a suitable icon. If you don't want any text, set the IconPos property to "none". We used the leftmost button to get to the next turn. We use the middle button to reset the game to start from the beginning. And finally, the rightmost button to go back to the main menu. We set their ids to btnNext, btnReset and btnMenu, respectively. We choose the Icons as in the image below.
Programming the Controls
With that, we are done designing our app. But we have yet to program the controls.
Go to Run>Start in Desktop Browser to try out the app. Alternatively, you can click on the Run button shown below.
Your default web browser will open. You will see the Start Form. You can try clicking the Play button, but of course, clicking on the Play button won't take you to the game as we have not programmed any of the controls yet!
Programming the buttons on the Start Page
Go back to AppStudio. Right click on the Play button btnPlay and click on Go To Event>onclick. This will take you to the Code Window of frmStart. You will also see the onclick function header. A function is a piece of code that executes when called or triggered. In this case, the event of clicking on a the Play button triggers this code. When clicking on this button, we want the app to take us to the Play form. We can simply write, ChangeForm(frmPlay). ChangeForm() is a function that AppStudio provides that allows us to switch from one form to another in Run time. You can tell that a code word is a function when it is followed by round brackets like in ChangeForm(). We use those brackets to pass in values that the function requires to execute. We call these values function parameters. For example, ChangeForm() requires the name of the form to change to. Thus, it takes in one parameter, a function name. In JavaScript, we follow the ChangeForm() line by a semicolon as shown in the picture below. A semicolon simply tells the computer "end of the command".
Deploy the app again. Now try clicking on the Play button. You should see that the app changes to the Play form!
Programming the buttons on the Play form
Go back to AppStudio. Go to the Play form. Let us program the "Go to Main Menu" button. You should know how to do this! Simply, right-click on btnMenu, choose Go to Event>onclick. In the function btnMenu.onclick write
ChangeForm(frmStart);
Deploy the app, test the Play and the Return to Main Menu buttons. You should be able to go back and forth between the two forms now.
Programming the Tic Tac Toe board buttons
Global and Local Variables
We now want to program the buttons of the Tic Tac Toe board. Upon clicking any empty button, we want to write an X or an O on it, depending on whose turn is it to play. We need a way to keep track of players turns. For that, we create a global variable. At the top of the Coding tab of frmPlay, write the following line:
var player;
Let's understand what that means. A variable is a place holder. Upon declaring a variable (example: the var statement declaring the variable "player" above), the computer reserves a space in memory and calls it "player". Every time you modify the value of the variable player, the computer accesses this place in memory, and modifies its value. For example, if you were to write:
player = 1;
the computer accesses the place it had reserved for player in memory, and writes a 1 there. This place in memory will always have the value 1 until you modify it or close your program.
As for the keyword "global", a variable is global if it is declared outside of any function with a var statement. JavaScript also considers a variable global if it is not declared at all. When a variable is global, it means that any function is able to use this variable. That is, any function in our code is able to access this place in memory. If you declare a variable inside a function, the variable is only "visible" to that function; only that function has access to this variable. Such variable would be called a local variable.
Resetting the Board before a New Game
We can now use the player variable to keep track of whose turn it is to play. At the start of a new game, we want to reset our board and game variables. For example, we want to clear any moves that might be there from the last game and reset the player variable to 1.
Characters and Strings
But before we do that let's talk about characters and strings. A character is any symbol, such as a letter, a number, punctuation marks, or other characters on the ASCII chart (see the full chart here [3]). A string is a sequence of characters. In code, we tell the computer that a certain sequence of characters is a string (as opposed to a variable name) by putting double quotation marks around it. For example the variable str has a string value "app":
var str = "app";
Go back to the Design Screen (5) on frmPlay by clicking on the Design Screen icon as shown below.
Click on any of the Tic Tac Toe board's buttons, say btn11. Look at the properties window. You can see that the property Height has a numerical value: 70. Scroll down the properties list and find the Value property. The Height property is expecting a numerical value, but the Value property is expecting a string value. Set the Value property to the letter X. You see that the letter X appears on the button in your Design Screen.
But what if we only want the X to appear when the button is clicked? We can do that by editing the Value property of btn11 through code. Go back to the properties panel of btn11 and delete the letter X from the Value property field.
Right click on btn11 Go To Event>onclick.
Type the following line:
btn11.value = "app";
As you might have guessed, this line tells the computer to change the value of the Value property to the string "app". Generally, if we want to edit an object's properties through code, we must first tell the computer which object we want edit. Recall that we have set the buttons ids to btn11, btn12, ...., btn33. Here, we want to edit btn11's Value property, so we type btn11, followed by a ".". When you type a dot following an object's name, AppStudio understands that you are trying to edit one of the object's properties and it automatically shows a list of properties the object has. As you start typing the property name, only functions and properties that match what you've typed appear on the list. If you're not sure about the name of a property, you can simply go through that menu to find it. If you're not sure what a property or function does, you can refer to AppStudio's Wiki pages [4].
After you've typed the line above, try running the app by clicking on the Run button. In your web browser, click on btn11. The word "app" should appear on the button when clicked.
Go back to the code, and remove the quotation marks around the word app:
btn11.value = app;
Try running the app again and clicking on btn11. You probably got an error message: Uncaught ReferenceError: app is not defined. This is because the computer no longer understands that the word app is a string. It thinks it is a variable name because there are no quotation marks around the word app. But we never gave a variable with the name app any value! So the computer doesn't know what to set btn11's Value property to.
Go back to the code, and now add the following line:
var app = "app"; btn11.value = app;
What do you expect to happen? Now we have a variable with the name app. This variable has a string value "app". On the next line, we set btn11's Value property to the value of the variable app: that is "app". Try running the code. Upon clicking on btn11, the string app should appear on the button! Of course, we could have set the value of the variable app to any string we want!
var app = "hello, there! 1234"; btn11.value = app;
Re-run the app to see the results.
If we want to change a property like the Height property, we won't use quotes since Height expects a numerical value. We can type:
btn11.Height = 100;
Equivalently we can type:
var heightvalue = 100; btn11.Height = heightvalue;
Write the lines above and test your code. You should see btn11's height increases from 70 to 100 when clicked.
When done testing, delete the code lines under the function btn11.onclick(). We only wrote these lines to understand strings and object properties!
btn11.onclick=function(){ }
The function init_board()
Now that we understand what strings are, let's go back to resetting the Tic Tac Toe board before the game starts.
Switch to the Design Screen (5). Right click on any empty space on the form and click on Go to Event>onshow. As evident by its name, this function executes when frmPlay appears. For example, in our app, when we click on the Play button from frmStart, frmPlay appears and the function frmPlay.onshow() will be executed automatically. When the frmPlay appears, it is the start of a new game and we want it to be Player 1's turn. So we set the variable player to 1. We also want to make sure that the Tic Tac Toe board is completely empty. That is, if there are any X's and O's on the board, we want to replace them with empty strings. So we set each button's value property to an empty string like so:
function init_board() { btn11.value = ""; btn12.value = ""; btn13.value = ""; btn21.value = ""; btn22.value = ""; btn23.value = ""; btn31.value = ""; btn32.value = ""; btn33.value = ""; player = 1; moves_count = 0; last_state = [ ["", "", ""], ["", "", ""], ["", "", ""] ]; lblTurn.textContent = "Player " + player + "'s turn"; } frmPlay.onshow = function() { init_board(); }
As you can see in the picture, the frmPlay.onshow function calls another function called init_board(). To understand the code in init_board(), first, note that the computer (or phone processor!) executes code sequentially. Meaning that it executes the first line in the function init_board() before moving on to the second line, then third, etc. In init board, we first set each of the buttons values to an empty text string.
Next, we set the global variable player to 1. Ignore the next three lines for now. We will get to them later.
To test this piece of code, go to the Design Screen (5). Click on btn11 and change it's property Value to "xxx" through the Properties pane. Run the app, click on btnStart. You might be able to catch the value of btn11 changing from "xxx" to no text. You now know when the function frmPlay.onshow() executes and you know that it resets our board. Go back to the design screen, and delete the "xxx" from btn11's Value property.
Now that we have a way to know which player's turn it is, and we have reset our board, we now want to program the board's blocks.
Keeping track of moves on the board
Think about what we want to do when a player clicks on one of the buttons. When it is Player 1's turn, we want to allow him to choose where to make a move. He may click on a button to place an X there, change their mind and want to place it somewhere else before its Player 2's turn. Thus we want to allow the player change his mind, by re-clicking on the button he wishes to remove a move from. Of course, once the player clicks on the nextTurn button, he cannot change his mind! Neither can he change his mind on a move he's made on a previous turn!
For that reason, we want to create a set of variables that allow us to keep track of the state of the board, so we know which blocks already have moves, and which blocks are empty. At the top of the coding window, declare the variable, last_state. Unlike the player variable, we want last_state to hold the place of 9 variables, one for each block on the board.
In the function init_board, we initialize the variable last_state to an empty 3x3 array of variables like so:
last_state = [["","",""],["","",""],["","",""]];
This means that last state is a two dimensional array of variables: a 3x3 two-dimensional array. The line of code above places an empty string in each of the 9 positions, as initially, all the positions are empty. Each of the array variables can be addressed as shown in the image below. For example, the top left block is last_state[0][0]. The center block is last_state[1][1].
Note that we could have created 9 individual variables instead of one 3x3 array of variables, but this would have been tedious! Now, lets start programming the board blocks.
X's and O's
Right-click on btn11. Click on Go To Event> onclick. Write the following code, as shown in the picture below.
As you can see, the function btn11.onclick calls the function changeState(0,0,btn11). That is, it passes three parameters to it. If you look at the definition of the function changeState() you see that it takes three parameters: i, j, and btn. This means that when we call changeState from btn11.onclick, we set i = 0, j = 0, and btn = btn11.
Before we get to the code in changeState(), let's talk about if statements. If statements allow the computer/phone to make decision whether to execute a certain piece of code, given a condition. Each if statement comes with a condition. If the condition evalues to true, it executes the piece of code inside its brackets. If it is false, it executes the piece of code under else, if any exists. In the case of changeState()'s if statement, the condition is whether last_state[0][0] (that is the variable in the zero-th row, zero-th column in the 2d array last_state) is an empty string. Note the use of two equal signs, instead of one. In JavaScript, two equal signs are used to check equality, whereas one equal sign is used to change a variable's value.
In changeState() we check the last_state array at position 0,0 to make sure that it didn't already contain a move, as players cannot place moves on top of ech others moves! If the position is empty, we want to allow the player to toggle between placing a move at that position or removing a move from it! Of course, we will only allow the player to make at most one move at a time. But until they decide, we want to allow them to change their mind! If the button had an empty string, we place an X or an O, depending on which player is currently playing. And if it had an X or an O, we set its value to an empty string.
Make sure to call the function changeState in each of the 9 onclick functions of each of the buttons. Make sure to call changeState with the suitable parameters for each button. Recall that arrays are 0-based, meaning that the block in the first row, in the first column is in position last_state[0][0]. The block to its right is at last_state[0][1], as shown in the picture below.
Scopes
Now is a good time to discuss scopes. In JavaScript, scopes of functions, loops, and if statements are defined using curly brackets. Look at the function changeState() in the picture below. We see that after the function header, there's an open curly bracket '{' and at the end, there's a closed curly bracket '}'. This tells AppStudio that the code lines between the two curly brackets should be executed when the function changeState() is called. For readability, we indent the code inside the function. In the image below, the scope of the function is in blue. Note that the code under the if statement if(last_state[i][j] == "") is indented twice. This is to indicate that this code is within the scope of this if statement and will be executed if the if condition was found to be true. The scope of this if statement is in red. And so on.
Note that we can have nested if statements. That is, we can have if statements inside each other. But in JavaScript, we can't have nested functions. That is, we can't have a function definition inside another! But of course, we can call one function from another.
For more about indenting and code formatting, read the section Formatting Your Code.
Next Player's turn
After a player is happy with the move they made, they click on btnNext. This button should first check if the move that the player made was valid. If it was, it gives the turn to the next player. If not, it restores the board to the last valid state and asks the player to try again.
A move is invalid if the player makes more than one move at once or doesn't make any moves at all!
Here's the code for and a flow chart for btnNext.onclick:
We will come back to this diagram later. But before we get into the details of programming btnNext, we first need to discuss two ideas; addition, and return values of functions.
Addition
Suppose that in the function btnNext.onclick we have the following statements:
var count = 1; var g = 2;
which declare two local variables, count and g and initializes them to 1 and 2, respectively. Then, supposose we write the following statement:
var add = count + g;
What value do you think will be stored in the variable add? That's right, 3! Let's another example:
count = count + 1;
In math, this statement would be meaningless, as you'd subtract count from both sides and be left with 0 = value! However, programming doesn't work like that. Equations in programming always go from right to left. That is, the expression on the right hand side is first evaluated and the result is stored in the variable on the left hand side. Thus, this statement simply adds one to the value that was previously stored in the variable count. In other words, we can rewrite the equation in pseudo code as follows:
new value of count = old value of count + 1
If count was initialized to 1, then the final value of count after executing this statement would be 2.
For that reason, as you might have already guessed, writing this statement as follows would be incorrect:
count + 1 = count;
Return Values of Functions
Now that we understand how addition works, let's discuss the second concept: return values of functions.
As we have seen, functions are triggered by certain events or called by other functions. We already used code to call functions in frmPlay.show and in btnxx.onclick. Let's go into more detail about how that works.
Recall that computers (or phone processors) execute code sequentially. That means that upon executing a function, the computer executes the first line in that function first before executing the next line. So what happens when a function calls another function?
Suppose function A calls function B, that each of these functions contain 10 lines of code, and that function A calls function B on line 2. Then the computer will start executing line 1 of function A, then line 2. In line 2, function B is called, so the computer goes to function B and executes lines 1-10 of function B before it returns to function A. Upon returning, it executes lines 3-10 of function A.
Sometimes functions have return values, which means that they return a value that they might have computed (or that they otherwise have access to) to the function that called them. In the above example, if function B has a return value, it returns it to function A. Function A can capture that value in a variable by calling function B as follows:
function A(){ //line 1 var result = B(); // lines 2-8.... }
In JavaScript, you can give a function a return value by using the keyword "return". For example, function B can look something like this
function B(){ return 1; }
From this code, we know that after the execution of line 2 of function A, the variable result will have the value 1. Return values are useful, for example, when the called function calculates a value that the caller function wants to use. For example, consider the following piece of code:
function A(){ var value = 5; var result; result = isBig(5); if(result == 1) NSB.Print("the value is bigger than 100!"); else NSB.Print("the value is smaller than 100!"); } function isBig(number){ if(number < 100) return 0; else return 1; }
Now that we understand how return values of functions work, we can return to programming our "Next" button.
Right click on btnNext, click on Go To Event> onclick. Upon clicking on that button, we first want to find where the player placed his move. We create a global variable, newMove to store the value of the new move. We create a function find_new_move(). The purpose of this funcion is to where the new move was placed and to count how many new moves were placed. To make sure that only one move was made.
var newMove; function check_move(i, j, btn) { if (last_state[i][j] != btn.value) { newMove = btn.value; last_move_i = i; last_move_j = j; return 1; } return 0; } function find_new_move() { var count = 0; newMove = ""; count += check_move(0, 0, btn11); count += check_move(0, 1, btn12); count += check_move(0, 2, btn13); count += check_move(1, 0, btn21); count += check_move(1, 1, btn22); count += check_move(1, 2, btn23); count += check_move(2, 0, btn31); count += check_move(2, 1, btn32); count += check_move(2, 2, btn33); return count; }
In find_new_move(), we write the following code. Initially, we create a local variable called count, which we initialize to 0. We then call another function, check_move() 9 times, for each of the buttons, to check if a move was made in that block. The function check_move returns a 1 if a new move was made in that block, and a 0 otherwise. We add the return value from check_move() to count. Recall how equations are evaluated in programming: first the right hand side expression is evaluated, then the result is saved in the left hand side of the equation. In this case, the function check_move() is executed, and its return value is saved in the variable count on the left hand side of the equation. After all 9 calls are executed, count would have the total number of moves made in total. find_new_move() can now return the value of count to its caller function btnNext.onclick. For btnNext.onclick to capture the return value of find_new_move(), we create a variable count. It is okay to call that variable count as well since it is local to the function btnNext.onclick and the other variable named count is local to the function find_new_move().
Handling invalid moves
In this game, we allow Players to change their minds about the move they made before pressing the button "Next" for the next player's turn. A player can place an 'X' on an empty block, repress on that same block to undo the move, then press on another empty block to place a move there instead. When decided, the player presses "Next". However, this means that players might place more than one move during their turn. In this case, when they press "Next" we cannot accept it!
We have the number of new moves made on the board during the last turn stored in the variable count. Now we need to take action. If the player made more than one move, we cannot accept that! We must inform the player that he'd made too many moves and remove the moves he made in his last turn!
If the player made no moves, we must inform him that he should make a move before getting its the next player's turn. If he made exactly one move, we record that move in our last_state array, we check if the player has won, if not, we give the other player the turn. See the function btnNext.onclick below
Take a look at the diagram below. The numbers indicate the sequence of functions the code will execute in case of an invalid move.
Let's understand this code. After the line 137, the function find_new_moves() has executed and the variable count has the number of moves a player made in the last turn. Next, we use an if statement to check if the player has made more than one move! If he did, We inform him that he's made too many moves:
NSB.print("Too many moves! Resetting back");
Then we reset the board. We could have reset the board in this function here. But for clarity, we chose to create a separate function reset_board(). In reset_board, we simply want to reset the values of buttons to the last state they were in before the player made these invalid moves. Recall that we store these moves in the array last_state[3][3]! So we can simply do the following:
function reset_move(i, j, btn){ btn.value = last_state[i][j]; } function reset_board(){ reset_move(0,0,btn11); reset_move(0,1,btn12); reset_move(0,2,btn13); reset_move(1,0,btn21); reset_move(1,1,btn22); reset_move(1,2,btn23); reset_move(2,0,btn31); reset_move(2,1,btn32); reset_move(2,2,btn33); }
Has anyone won the game yet?
In player_won() we check if any player has won. That is, they placed three moves in the same row, column on diagonal. If so, we return true. If not, we return false.
function player_won() { if ((last_state[0][0] != "" && last_state[0][0] == last_state[0][1] && last_state[0][0] == last_state[0][2]) || (last_state[0][0] != "" && last_state[0][0] == last_state[1][1] && last_state[0][0] == last_state[2][2]) || (last_state[0][0] != "" && last_state[0][0] == last_state[1][0] && last_state[0][0] == last_state[2][0]) || (last_state[0][2] != "" && last_state[0][2] == last_state[1][1] && last_state[0][2] == last_state[2][0]) || (last_state[0][1] != "" && last_state[0][1] == last_state[1][1] && last_state[0][1] == last_state[2][1]) || (last_state[0][2] != "" && last_state[0][2] == last_state[1][2] && last_state[0][2] == last_state[2][2]) || (last_state[1][0] != "" && last_state[1][0] == last_state[1][1] && last_state[1][0] == last_state[1][2]) || (last_state[2][0] != "" && last_state[2][0] == last_state[2][1] && last_state[2][0] == last_state[2][2])) return true; return false; }
Note the use of the "||" and "&&" operands. These are "OR" and "AND" operands used in if statements. For example, the first statement reads:
if last_state[0][0] (row 0, column 0) is not an empty string AND last_state[0][0] is equal to last_staet[0][1] (row 0, column 1) AND last_state[0][0] is equal to last_state[0][2], that means that the player has placed moves on all three blocks on the top row. In that case, the player wins, and we return 1 to indicate that the player has won. Because there are other ways to win, we list all of them, encapsulate each one by round brackets, and separate them by ORs.
Let's look at the if statement in btnNext.onclick. The if condition is evaluated by first evaluating each of the expressions on the right and left hand sides of the operand "==" then checking if the equality holds. In this case, play_won() is executed and the return value of the function is compared to "false". If the return value is false, the code under the if statement is executed. Starting with next_turn().
As you might expect, next_turn() just updates the variable player and the label that says whose turn it is.
function next_turn() { if (player == 1) player = 2; else if (player == 2) player = 1; lblTurn.textContent = "Player " + player + "'s turn"; }
What if it's a tie?
Take a look at the code of the btnNext.onclick function (code snapshot under Handing Invalid Moves). After the next_turn() function returns, we want to check if the all the boxes on the board are full. If all the boxes on the board are full at that point, we know it is a tie since we already know that nobody won so far! (How do we know? Hint: at this point in the code, did the if statement condition player_won()==false evaluate to true or false?)
To find out if there's a tie, we can count the number of full blocks, and if the number if 9, we let the players know that there's a tie. But can we find a faster way to do this? Instead of counting the number of full blocks every time a player makes a valid move, we can simply make a variable that will serve as a moves counter. Every time any of the players makes a valid move, we increment the counter. Then, we check if the counter value has reached 9, then we know it is a tie!
To do that we declare a global variable at the top of our file. We called it moves_count. In our init_board function, initialize moves_count to 0, since every time a new game starts, we want to reset moves counter to indicate that there are 0 full blocks on the board. In the function btnNext.onclick, if the player has not won (that is, if player_won() returns false), we add one to moves_count. Note that following lines of code are equivalent:
moves_count++;
moves_count += 1;
moves_count = moves_count + 1;
Finally we check if moves_count is 9. If so, and we know nobody has won, then it must be a tie! We inform the players that there's a tie, reset the board, and change to the Start form!
If however, player_won() returns true, the code under the if statement if(player_won() == false) will not execute, instead, the code under the else would execute: We inform the players which player won, we reset the board, and return to the start Form.
Return to btnMenu.onclick function, before we return to the Start form, we probably want to initialize the board, so we simply make a call to init_board() before the line ChangeForm(frmStart).
Now, we're just left with programming the middle button, btnReset. Right-click on btnReset Go to Event> onclick. Simply add one line. A call to init_board().
Debugging Tips
Now you're done writing your code. But it is likely that you have a few bugs and mistakes in your code. Some bugs are easy to find and fix. Other bugs won't be as easy to find. So here are a few tips that can help you debug your code.
Formatting Your Code
Indenting and using spaces consistently in your code can make your code a lot more readable, which in turn can make debugging a lot easier! Always indent code that exists within the scope of a bigger function or statement. Let's take the function changeState() as an example. All code under inside the function scope (blue) should be indented once so it is easy for a reader to see that this code belongs inside a function. The code in red is indented twice (or once with respect to the if statement above it) because it is inside the scope of the if statement if(last_state[i][j]==""). The code in green is inside the scope of the if statement if(btn.value=="") and the code in purple is inside the scope of the else.
It is good practice to indent your code as you write it. But you can also make AppStudio format your code for you by going to Edit > Format Code. If you're debugging, try formatting your code and checking if the code is indented the way you expect it to be indented. If it isn't, this can mean that you missed a bracket or other syntax errors that caused AppStudio to misinterpret your code. It is recommended that you format your code occasionally as you write it so you can catch such syntax errors as they come up.
Debugging Tools
You can also use Chrome's debugging tool to find bugs in your program. Go to Run > How To Use Debuggers to learn how to use Chrome's debugger.
Test your app!
You've now programmed, tested and debugged your app. It is time to deploy it and use it on your phone!
Click on Run > Deploy.
AppStudio will give you a link. Open your phone browser and enter the link you were given. Use the app on your phone!
For future apps, you can read about app publishing options here [5].