The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
10KB

  1. /**
  2. @page example_tic_tac_toe Tic Tac Toe
  3. @section tic_tac_toe_introduction Introduction
  4. Implement the classic game of Tic Tac Toe on the Lightpad Block.
  5. Launch the BLOCKS CODE application and open the script called @s_file{TicTacToe.littlefoot}. You can find the script in the Littlefoot-Examples repository, which you can download from GitHub @littlefootgithub{here}. If you don't have the BLOCKS CODE IDE installed on your system, please refer to the section @ref getting_started_with_blocks_code for help.
  6. @section tic_tac_toe_drawing_grid Drawing the Grid
  7. Let's start by defining global variables to save the current state of the game. To define global variables in LittleFoot simply place your variables outside any function declaration and at the top of the file for convenience. We first have 9 variables for the state of each cell in the grid, a @s_projcode{currentTurn} variable for the current player's turn and a @s_projcode{winner} variable to determine which player is the winner.
  8. @code{.cpp}
  9. int tl, t, tr;
  10. int ml, m, mr;
  11. int bl, b, br;
  12. int currentTurn;
  13. int winner;
  14. @endcode
  15. The initialise() function in the littlefoot language is useful to initialise variables and the state of your script as the function is called once when the program is loaded onto the device. Here we set all the cells to 0, a number chosen to define the blank state of a cell, the current turn to player 1 and the winner to 0, again a number chosen to define the ongoing state of the game.
  16. @code{.cpp}
  17. void initialise()
  18. {
  19. tl = 0;
  20. t = 0;
  21. tr = 0;
  22. ml = 0;
  23. m = 0;
  24. mr = 0;
  25. bl = 0;
  26. b = 0;
  27. br = 0;
  28. currentTurn = 1;
  29. winner = 0;
  30. }
  31. @endcode
  32. In the repaint() function, we first clear the display as usual and check if a winner has been selected. If not, this means the game is still ongoing and we proceed to draw a grid and signs if there are any. Otherwise we draw the winner screen.
  33. @code{.cpp}
  34. void repaint()
  35. {
  36. clearDisplay();
  37. if (winner == 0)
  38. {
  39. drawGrid();
  40. drawSigns();
  41. }
  42. else
  43. {
  44. drawWinner();
  45. }
  46. }
  47. @endcode
  48. In order to draw the default grid, we have created a helper function called drawGrid() which is called in the previously defined repaint() function. We first define a variable to the colour white and proceed to draw a rectangle if the cell is still unoccupied.
  49. @code{.cpp}
  50. void drawGrid()
  51. {
  52. int white = makeARGB (255, 200, 200, 200);
  53. if (tl == 0) drawRect (white, 0, 0, 5, 5);
  54. if (t == 0) drawRect (white, 5, 0, 5, 5);
  55. if (tr == 0) drawRect (white, 10, 0, 5, 5);
  56. if (ml == 0) drawRect (white, 0, 5, 5, 5);
  57. if (m == 0) drawRect (white, 5, 5, 5, 5);
  58. if (mr == 0) drawRect (white, 10, 5, 5, 5);
  59. if (bl == 0) drawRect (white, 0, 10, 5, 5);
  60. if (b == 0) drawRect (white, 5, 10, 5, 5);
  61. if (br == 0) drawRect (white, 10, 10, 5, 5);
  62. }
  63. @endcode
  64. This is performed on each cell using the helper function drawRect() defined below which takes as argument a colour, the coordinates and the size of the rectangle.
  65. @code{.cpp}
  66. void drawRect (int colour, int x, int y, int w, int h)
  67. {
  68. fillRect (colour, x, y, 1, h);
  69. fillRect (colour, x, y, w, 1);
  70. fillRect (colour, x + w - 1, y, 1, h);
  71. fillRect (colour, x, y + h - 1, w, 1);
  72. }
  73. @endcode
  74. Notice here we use the built-in fillRect() function with either a width or height of 1 to draw lines for the four sides of the rectangle because we don't want the rectangle to be filled.
  75. @section tic_tac_toe_drawing_signs Drawing the Signs
  76. Now let's see how we can draw the player signs if any of the cells are occupied. The drawSigns() function called in the repaint() loop checks every cell using the evaluate() helper function defined after. It passes the cell variables along with the coordinates in case we need to draw the signs.
  77. @code{.cpp}
  78. void drawSigns()
  79. {
  80. evaluate (tl, 0, 0);
  81. evaluate (t , 5, 0);
  82. evaluate (tr, 10, 0);
  83. evaluate (ml, 0, 5);
  84. evaluate (m, 5, 5);
  85. evaluate (mr, 10, 5);
  86. evaluate (bl, 0, 10);
  87. evaluate (b, 5, 10);
  88. evaluate (br, 10, 10);
  89. }
  90. @endcode
  91. The evaluate() function below checks if the cell variable has been switched to a player index of 1 or 2 in which case we need to draw the corresponding player sign. If the cell variable is still set to 0 this means that the cell is still blank and we keep the white default square.
  92. @code{.cpp}
  93. void evaluate (int value, int x, int y)
  94. {
  95. if (value == 1)
  96. drawX (x, y);
  97. else if (value == 2)
  98. drawO (x, y);
  99. }
  100. @endcode
  101. To draw player 1's "X" sign, we first define the player colour and draw each point in the two diagonals as follows:
  102. @code{.cpp}
  103. void drawX (int x, int y)
  104. {
  105. int xColour = makeARGB (255, 0, 255, 0);
  106. fillRect (xColour, x, y, 1, 1);
  107. fillRect (xColour, x + 1, y + 1, 1, 1);
  108. fillRect (xColour, x + 2, y + 2, 1, 1);
  109. fillRect (xColour, x + 3, y + 3, 1, 1);
  110. fillRect (xColour, x + 4, y + 4, 1, 1);
  111. fillRect (xColour, x + 4, y, 1, 1);
  112. fillRect (xColour, x + 3, y + 1, 1, 1);
  113. fillRect (xColour, x + 2, y + 2, 1, 1);
  114. fillRect (xColour, x + 1, y + 3, 1, 1);
  115. fillRect (xColour, x, y + 4, 1, 1);
  116. }
  117. @endcode
  118. To draw player 2's "O" sign, we first define the player colour and draw the four sides like so:
  119. @code{.cpp}
  120. void drawO (int x, int y)
  121. {
  122. int oColour = makeARGB (255, 0, 0, 255);
  123. fillRect (oColour, x, y, 5, 1);
  124. fillRect (oColour, x, y + 4, 5, 1);
  125. fillRect (oColour, x, y, 1, 5);
  126. fillRect (oColour, x + 4, y, 1, 5);
  127. }
  128. @endcode
  129. @section tic_tac_toe_handling_touch_events Handling Touch Events
  130. In its current implementation state, our script won't receive any touch events and therefore the game will stay in the blank state with a white grid showing. Let's implement the touchStart() callback to receive player moves.
  131. @code{.cpp}
  132. void touchStart (int index, float x, float y, float z, float vz)
  133. {
  134. int xPos = int (x * 7);
  135. int yPos = int (y * 7);
  136. int index = getIndex (xPos, yPos);
  137. if (setValueForIndex (index, currentTurn))
  138. {
  139. currentTurn = currentTurn == 1 ? 2 : 1;
  140. }
  141. if (xHasWon()) winner = 1;
  142. else if (oHasWon()) winner = 2;
  143. }
  144. @endcode
  145. In the above function, we first convert the device coordinates into LED grid coordinates by multiplying both x and y variables by 7. Device coordinates are defined using the number of DNA connectors on the side of the device so for example in the case of a Lightpad %Block, the device has a size of 2x2 and therefore the device coordinates will range from 0.0 to 2.0 on each x and y dimensions. Multiplying this range by 7 gives us the LED grid coordinates ranging from 0 to 14 inclusive.
  146. Now using these grid coordinates, we use the @s_projcode{getIndex()} helper function defined below to retrieve the cell index ranging from 0 to 8 from our 3x3 game grid.
  147. @code{.cpp}
  148. int getIndex (int x, int y)
  149. {
  150. int xInd = x / 5;
  151. int yInd = y / 5;
  152. return yInd * 3 + xInd;
  153. }
  154. @endcode
  155. When the index is retrieved we attempt to set a cell variable value only if the touched cell is unoccupied. If a value has been successfully set for the current player, the function returns true and the @s_projcode{currentTurn} variable is switched to the other player.
  156. @code{.cpp}
  157. bool setValueForIndex (int index, int value)
  158. {
  159. if (index == 0) { tl = tl == 0 ? value : tl; return true; }
  160. if (index == 1) { t = t == 0 ? value : t ; return true; }
  161. if (index == 2) { tr = tr == 0 ? value : tr; return true; }
  162. if (index == 3) { ml = ml == 0 ? value : ml; return true; }
  163. if (index == 4) { m = m == 0 ? value : m ; return true; }
  164. if (index == 5) { mr = mr == 0 ? value : mr; return true; }
  165. if (index == 6) { bl = bl == 0 ? value : bl; return true; }
  166. if (index == 7) { b = b == 0 ? value : b ; return true; }
  167. if (index == 8) { br = br == 0 ? value : br; return true; }
  168. return false;
  169. }
  170. @endcode
  171. @section tic_tac_toe_determining_winner Determining a Winner
  172. We were able to implement the basic gameplay but we don't have a mechanism to select a winner yet. As a last step to the touchStart() callback, we check if a winner has been selected by calling respectively the @s_projcode{xHasWon()} and @s_projcode{oHasWon()} helper functions defined as follows:
  173. @code{.cpp}
  174. bool xHasWon() { return hasWon (1); }
  175. bool oHasWon() { return hasWon (2); }
  176. @endcode
  177. These in turn call the @s_projcode{hasWon()} function with the player number which checks all 8 possible combination of winning lines (3 horizontal, 3 vertical and 2 diagonal).
  178. @code{.cpp}
  179. bool hasWon (int player)
  180. {
  181. return (tl == player && t == player && tr == player)
  182. || (ml == player && m == player && mr == player)
  183. || (bl == player && b == player && br == player)
  184. || (tl == player && ml == player && bl == player)
  185. || (t == player && m == player && b == player)
  186. || (tr == player && mr == player && br == player)
  187. || (tl == player && m == player && br == player)
  188. || (tr == player && m == player && bl == player);
  189. }
  190. @endcode
  191. If a winner is selected the repaint() function will call the @s_projcode{drawWinner()} function defined below and fill the screen with the appropriate winner's colour:
  192. @code{.cpp}
  193. void drawWinner()
  194. {
  195. if (winner == 1)
  196. {
  197. int xColour = makeARGB (255, 0, 255, 0);
  198. fillRect (xColour, 0, 0, 15, 15);
  199. }
  200. else if (winner == 2)
  201. {
  202. int oColour = makeARGB (255, 0, 0, 255);
  203. fillRect (oColour, 0, 0, 15, 15);
  204. }
  205. }
  206. @endcode
  207. As a final step we can implement a simple way to reset the game back to the beginning by calling the initialise() function ourselves when the side button of the device is pressed. This is achieved by implementing the handleButtonDown() callback function like this:
  208. @code{.cpp}
  209. void handleButtonDown (int index)
  210. {
  211. initialise();
  212. }
  213. @endcode
  214. @section tic_tac_toe_summary Summary
  215. In this example, we learnt how to recreate the classic game of Tic Tac Toe on the Lightpad %Block.
  216. @section tic_tac_toe_see_also See also
  217. - @ref example_dynamic_parameters
  218. - @ref example_colour_pressure_map
  219. - @ref example_music_gen
  220. */