PageRenderTime 50ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/processing_gameoflife/processing_gameoflife.pde

https://github.com/Lokidottir/processing-gameoflife
Processing | 244 lines | 151 code | 32 blank | 61 comment | 87 complexity | cf3bdf20b599dcec46cbd6b951739462 MD5 | raw file
  1. //Lokidottir
  2. //Game of life simulation, working edition
  3. int boardSize = 150; //The number of rows/coloums in the grid
  4. int boxSize = 5; //The size that the boxes fo the grid will be displayed in pixels
  5. int frameDelay = 50; //The delay in milliseconds between each update. E.G. 50ms = maximum 20 updates per second
  6. int tempDelay = 0; //The value which frameDelay is swapped with when the delay is removed
  7. int updateCount = 0; //The number of generations that have passed
  8. int lastTime = millis(); //The time of the last update, used for the delay
  9. int opsPerFrame = 1; //The number of generations per update
  10. boolean paused = true; //If set to true, then the program will not process any updates until it has been set to false
  11. boolean drawText = false; //If set to true, then text will be drawn at the location of each cell showing how many neighors it has
  12. boolean showUpdateC = true; //If set to true, then the program will show how many generations have been processed since the beginning of the program
  13. boolean autopause = true;
  14. boolean[][] gameMap = new boolean[boardSize][boardSize]; //The 2D array of booleans that acts as the board of cells, with true and false being dead and alive states respectively
  15. boolean[][] previous;
  16. boolean[][] twoBefore;
  17. void setup() {
  18. size(boardSize * boxSize,boardSize * boxSize);
  19. noStroke();
  20. //Initialising each element in the board as false
  21. for (int y = 0; y < gameMap.length; y++) {
  22. for (int x = 0; x < gameMap[y].length; x++) {
  23. gameMap[y][x] = false;
  24. }
  25. }
  26. }
  27. void draw() {
  28. float currenttime = millis();
  29. if (keyPressed) {
  30. //Changing the configuration of the program through key presses
  31. if (key == ENTER) {
  32. //Switches the paused variable, pausing or unpausing the program
  33. paused = !paused;
  34. int delay = millis() + 200;
  35. while (millis() < delay); //This delay is to prevent keypresses being registered multiple times, it's an awful hack.
  36. }
  37. else if (key == 't') {
  38. //Switches the drawText variable, showing or hiding the number of neighbors a cell has
  39. drawText = !drawText;
  40. int delay = millis() + 200;
  41. while (millis() < delay);
  42. }
  43. else if (key == ' ' && paused) {
  44. //If the program is paused, the board will be processed once. This is to allow showing a step-by-step of Game of Life
  45. gameMap = processBoard(gameMap);
  46. int delay = millis() + 200;
  47. while (millis() < delay);
  48. }
  49. else if (key == 'c') {
  50. //Switches the showUpdateC variable, showing or hiding the number of generations that have passed since the beginning of the program
  51. showUpdateC = !showUpdateC;
  52. int delay = millis() + 200;
  53. while (millis() < delay);
  54. }
  55. else if (key == 'l') {
  56. //Removes, or imposes, the limit on the number of updates per second. The new limit is how fast the program renders (60hz, usually).
  57. int temp = frameDelay;
  58. frameDelay = tempDelay;
  59. tempDelay = temp;
  60. int delay = millis() + 200;
  61. while (millis() < delay);
  62. }
  63. else if (key == 'a') {
  64. autopause = !autopause;
  65. int delay = millis() + 200;
  66. while (millis() < delay);
  67. }
  68. }
  69. if (mousePressed) {
  70. //Mouse handling.
  71. //First make sure the mouse's coordinates are within the window to prevent out of index errors
  72. if ((mouseX >= 0 && mouseX < width) && (mouseY >= 0 && mouseY < height)) {
  73. //If the left mouse button is pressed, then the cell the mouse is over is set to true (alive)
  74. //If the right mouse button is pressed, then the cell is instead set to false (dead)
  75. if (mouseButton == LEFT) gameMap[mouseY/boxSize][mouseX/boxSize] = true;
  76. else if (mouseButton == RIGHT) gameMap[mouseY/boxSize % gameMap.length][mouseX/boxSize % gameMap[0].length] = false;
  77. }
  78. }
  79. //End of user input
  80. //If the program is unpaused and the time between the last update delay has passed, the program processes the board
  81. if (!paused && (millis() > lastTime + frameDelay)) {
  82. //The number of times the program processes the board is determined by the opsPerFrame variable
  83. for (int i = 0; i < opsPerFrame; i++) {
  84. twoBefore = previous;
  85. previous = gameMap;
  86. gameMap = processBoard(gameMap);
  87. if (boardsEqual(twoBefore, gameMap) && autopause) paused = true;
  88. }
  89. lastTime = millis(); //The lastTime variable is set to the current time
  90. }
  91. //The grid is drawn
  92. drawGrid(gameMap, drawText);
  93. //If the showUpdateC variable is true, then the count for the number of generations is shown
  94. if (showUpdateC) {
  95. textSize(20);
  96. fill(0,0,255);
  97. textSize(18);
  98. text(updateCount,50,50);
  99. }
  100. }
  101. boolean[][] processBoard(boolean[][] board) {
  102. //The function that updates the board
  103. /*
  104. Or rather, creates a new 2d array of booleans which will be populated with the next generation and returned.
  105. This is because of how java's memory management works. Orginially this worked with a buffer, but when assigning
  106. the buffer to the original, it didn't copy the buffer's values but instead make both variables reference the same
  107. 2d array.
  108. Creating a new 2d array bypasses this problem, but also effects how fast the program can run due to memory management
  109. and allocation that is outside my control. However, the program in it's current state is designed to let a user see
  110. the Game of Life in progress, so speed is not part of the design, observation is.
  111. */
  112. boolean[][] toReturn = new boolean[board.length][board[0].length];
  113. for (int y = 0; y < board.length; y++) {
  114. for (int x = 0; x < board[y].length; x++) {
  115. //The rules() function is called on each element in the 2d array
  116. toReturn[y][x] = rules(board, x, y);
  117. }
  118. }
  119. updateCount++;
  120. return toReturn;
  121. }
  122. int nbors(boolean[][] board, int x, int y) {
  123. //A function that returns the number of neighbors a cell has, this is used for deciding if the cell will be alive or dead in the next generation
  124. //The function takes the 2d array and the x coordinate and the y coordinate of the cell being tested as parameters
  125. int nbor_count = 0;
  126. /*
  127. A nested for loop that covers the values -1 to 1 will be able to cover the relative coordinates:
  128. (-1,-1) (+0,-1) (+1,-1)
  129. (-1,+0) (+0,+0) (+1,+0)
  130. (-1,+1) (+0,+1) (+1,+1)
  131. This allows finding the number of neighbors as defined by the rules of Game of Life, with the exception of the center (+0,+0) which is explained further down
  132. */
  133. for (int y2 = -1; y2 <= 1; y2++) {
  134. //Ternary statements are used to allow the game of life to "loop around" itself, which avoids outOfBounds errors and allows the game to operate as if it were tiled
  135. int newy = (y + y2 < 0) ? (board.length - (abs(y + y2) % board.length)) % board.length : ((y + y2) % board.length);
  136. for (int x2 = -1; x2 <= 1; x2++) {
  137. int newx = (x + x2 < 0) ? (board[newy].length - (abs(x + x2) % board[newy].length)) % board[newy].length : ((x + x2) % board[newy].length);
  138. if (board[newy][newx]) {
  139. //If an ajacent cell (neighbor) is true (alive) then the neighbor count is incremented
  140. nbor_count++;
  141. }
  142. }
  143. }
  144. /*
  145. The condition for the center:
  146. The center (+0,+0) is the cell that the number of neighbors is being calculated for, so if the cell is alive
  147. and has no neighbors, then 1 would be returned. To prevent this, we check if the cell at the coordinates given
  148. as the parameters of the function is true (alive), if so then we decrease the neighbor count by 1.
  149. */
  150. if (board[y][x]) nbor_count--;
  151. return nbor_count;
  152. }
  153. boolean rules(boolean[][] board, int x, int y) {
  154. //The function that determines if a cell will be true (alive) or false (dead) in the next generation
  155. //The function takes the 2d array and the x coordinate and the y coordinate of the cell being tested as parameters
  156. int nbor_count = nbors(board, x, y); //The number of live neighbors for the cell is retrived
  157. //The rules for the game of life
  158. //1. Any cell with less than 2 live neighbors dies of underpopulation/loneliness (false is returned)
  159. if (nbor_count < 2 && board[y % board.length][x % board[y % board.length].length]) {
  160. return false;
  161. }
  162. //2. Any cell with 2 or 3 live neighbors lives on to the next generation (true is returned)
  163. else if ((nbor_count == 2 || nbor_count == 3) && board[y % board.length][x % board[y % board.length].length]) {
  164. return true;
  165. }
  166. //3. Any cell with more than 3 live neighbors dies of overpopulation (false is returned)
  167. else if (nbor_count > 3 && board[y % board.length][x % board[y % board.length].length]) {
  168. return false;
  169. }
  170. //4. Any dead cell with exactly 3 live neighbors becomes a live cell, as if by repopulation (true is returned)
  171. else if (nbor_count == 3 && !board[y % board.length][x % board[y % board.length].length]) {
  172. return true;
  173. }
  174. //any exceptions to the rules are caught by this else statement
  175. else {
  176. return false;
  177. }
  178. }
  179. void drawGrid(boolean[][] board, boolean drawText) {
  180. //The function that draws the 2d array
  181. textSize(boxSize);
  182. //For optimised rendering, only the alive cells are drawn. The background is drawn as black for the dead cells.
  183. background(0);
  184. for (int y = 0; y < board.length; y++) {
  185. for (int x = 0; x < board[y].length; x++) {
  186. //If the cell is alive, then a white box is drawn
  187. if (board[y][x]) {
  188. fill(255);
  189. rect(x * boxSize, y * boxSize, boxSize, boxSize);
  190. }
  191. //If the drawText flag is set, then the number of neighbors is shown on each cell.
  192. //For optimising perposes, only cells with a neighbor count greater than 0 have their
  193. //count drawn
  194. if ((drawText && boxSize > 6) && (nbors(board, x, y) > 0 || board[y][x])) {
  195. if (board[y][x]) fill(0);
  196. else fill(255);
  197. text(nbors(board, x, y), x * boxSize + boxSize/4, y * boxSize + boxSize);
  198. }
  199. }
  200. }
  201. }
  202. boolean boardsEqual(boolean[][] board1, boolean[][] board2) {
  203. if (board1 == null || board2 == null || board1.length != board2.length || board1[0].length != board2[0].length) return false;
  204. boolean result = true;
  205. for (int y = 0; y < board1.length && result; y++) {
  206. for (int x = 0; x < board1[0].length && result; x++) {
  207. result = (board1[y][x] == board2[y][x]);
  208. }
  209. }
  210. return result;
  211. }