Source: sketch.js

  1. /*******************************************************************************
  2. *
  3. * @file sketch.js
  4. * @brief Game of Life simulation
  5. *
  6. * @author <a href='mailto:omareq08@gmail.com'> Omar Essilfie-Quaye </a>
  7. * @version 1.0
  8. * @date 06-Jan-2019
  9. *
  10. *******************************************************************************/
  11. /**
  12. * Variable to hold the shape of a glider, an oscilating pattern which
  13. * translates across the page.
  14. *
  15. * @type {Array<Array<number> >}
  16. */
  17. let glider = [[0, 0, 0, 0, 0],
  18. [0, 0, 0, 1, 0],
  19. [0, 1, 0, 1, 0],
  20. [0, 0, 1, 1, 0],
  21. [0, 0, 0, 0, 0]];
  22. /**
  23. * Variable to hold the shape of a light weight space ship, an oscilating
  24. * pattern which translates across the page.
  25. *
  26. * @type {Array<Array<number> >}
  27. */
  28. let lightSpaceShip = [[0, 0, 0, 0, 0, 0, 0],
  29. [0, 0, 1, 1, 1, 1, 0],
  30. [0, 1, 0, 0, 0, 1, 0],
  31. [0, 0, 0, 0, 0, 1, 0],
  32. [0, 1, 0, 0, 1, 0, 0],
  33. [0, 0, 0, 0, 0, 0, 0]];
  34. /**
  35. * Variable to hold the shape of a Gosper Glider Gun. This is interesting as
  36. * it proves that a finite number of cells can replicate indefinietly.
  37. *
  38. * @type {Array<Array<number> >}
  39. */
  40. let gliderGun = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  41. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  42. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  43. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
  44. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
  45. [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  46. [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  47. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  48. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  49. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  50. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
  51. /**
  52. * Used to hold the state of all cell sites in the simulation.
  53. *
  54. * @type {Array<Array<number> >}
  55. */
  56. let grid1 = [];
  57. /**
  58. * Used to hold the state of all cell sites in the simulation.
  59. *
  60. * @type {Array<Array<number> >}
  61. */
  62. let grid2 = [];
  63. /**
  64. * Stores the width of the simulation grid
  65. *
  66. * @type {number}
  67. */
  68. let gridX = 75;
  69. /**
  70. * Stores the height of the simulation grid
  71. *
  72. * @type {number}
  73. */
  74. let gridY = 75;
  75. /**
  76. * Stores the size of one grid cell
  77. *
  78. * @type {number}
  79. */
  80. let size;
  81. /**
  82. * Stores which grid is currently being displayed. This allows for the rules
  83. * to be applied on the sites of the alternate grid without changing the sites
  84. * that are being read.
  85. *
  86. * @type {boolean}
  87. */
  88. let usingGrid1 = true;
  89. /**
  90. * Flag to see if the simulation has been paused.
  91. *
  92. * @type {boolean}
  93. */
  94. let paused = false;
  95. /**
  96. * The interval number of frames between simulation updates.
  97. *
  98. * @type {number}
  99. */
  100. let updateRate = 2;
  101. /**
  102. * What type of object will be inserted when the mouse is clicked.
  103. *
  104. * @type {string}
  105. */
  106. let selectedInsertion = "cell";
  107. /**
  108. * Function to get the number of live neighbours around a certain cell. If
  109. * the cell is at the edge this function will automatically wrap around to
  110. * the other side of the grid.
  111. *
  112. * @param {Array<Array<number> >} grid - The array on which the neighbours
  113. * shall be counted.
  114. * @param {number} x - The coloumn for the cell that will be checked
  115. * @param {number} y - The row for the cell that will be checked
  116. *
  117. * @return {number} - How many cells are alive around the given cell.
  118. */
  119. function neighbours(grid, x, y) {
  120. let numNeighbours = 0;
  121. for(let col = -1; col < 2; ++col) {
  122. for(let row = -1; row < 2; ++row) {
  123. if(row == 0 && col == 0) {
  124. continue;
  125. }
  126. let xIndex = (x + col);// % grid.length;
  127. if(xIndex < 0) {
  128. xIndex += grid.length;
  129. } else if(xIndex >= grid.length) {
  130. xIndex -= grid.length;
  131. }
  132. let yIndex = (y + row);// % grid[xIndex].length;
  133. if(yIndex < 0) {
  134. yIndex += grid[xIndex].length;
  135. } else if(yIndex >= grid[xIndex].length) {
  136. yIndex -= grid[xIndex].length;
  137. }
  138. if(grid[xIndex][yIndex]) {
  139. numNeighbours++;
  140. }
  141. }
  142. }
  143. return numNeighbours;
  144. }
  145. /**
  146. * Handles key presses. Will either switch the global selectedInsertion
  147. * variable, pause the simulation of clear the screen.
  148. */
  149. function keyPressed() {
  150. if(key.toLowerCase() == "d") {
  151. clearGrid();
  152. } else if(key.toLowerCase() == "r") {
  153. randomiseGrid();
  154. } else if(key.toLowerCase() == "p") {
  155. paused = !paused;
  156. } else if(key.toLowerCase() == "g") {
  157. selectedInsertion = "glider";
  158. } else if(key.toLowerCase() == "c") {
  159. selectedInsertion = "cell";
  160. } else if(key.toLowerCase() == "l") {
  161. selectedInsertion = "lightSpaceShip";
  162. } else if(key.toLowerCase() == "h") {
  163. selectedInsertion = "gliderGun";
  164. }
  165. }
  166. /**
  167. * Will insert the selected object at the mouse location. Will wrap cells
  168. * that go over the edge to the other side of the screen.
  169. */
  170. function mousePressed() {
  171. if(mouseX < 0 || mouseX > width || mouseY < 0 || mouseY > height) {
  172. return;
  173. }
  174. let x = floor(mouseX / size);
  175. let y = floor(mouseY / size);
  176. if(selectedInsertion == "cell") {
  177. if(x >= 0 && y >= 0 && x < gridX && y < gridY) {
  178. grid1[x][y] = (grid2[x][y] + 1) % 2;
  179. grid2[x][y] = grid1[x][y];
  180. }
  181. } else if(selectedInsertion == "glider") {
  182. for(let i = 0; i < glider.length; ++i) {
  183. for(let j = 0; j < glider[i].length; ++j) {
  184. let xIndex = (x + j) % gridX;
  185. let yIndex = (y + i) % gridY;
  186. grid1[xIndex][yIndex] = glider[j][i];
  187. grid2[xIndex][yIndex] = glider[j][i];
  188. }
  189. }
  190. } else if(selectedInsertion == "lightSpaceShip") {
  191. for(let i = 0; i < lightSpaceShip.length; ++i) {
  192. for(let j = 0; j < lightSpaceShip[i].length; ++j) {
  193. let xIndex = (x + i) % gridX;
  194. let yIndex = (y + j) % gridY;
  195. grid1[xIndex][yIndex] = lightSpaceShip[i][j];
  196. grid2[xIndex][yIndex] = lightSpaceShip[i][j];
  197. }
  198. }
  199. } else if(selectedInsertion == "gliderGun") {
  200. for(let i = 0; i < gliderGun.length; ++i) {
  201. for(let j = 0; j < gliderGun[i].length; ++j) {
  202. let xIndex = (x + i) % gridX;
  203. let yIndex = (y + j) % gridY;
  204. grid1[xIndex][yIndex] = gliderGun[i][j];
  205. grid2[xIndex][yIndex] = gliderGun[i][j];
  206. }
  207. }
  208. }
  209. }
  210. /**
  211. * Removes all live cells from the grid.
  212. */
  213. function clearGrid() {
  214. grid1 = [];
  215. grid2 = [];
  216. for(let x = 0; x < gridX; ++x) {
  217. let emptyCol1 = [];
  218. let emptyCol2 = [];
  219. for(let y = 0; y < gridY; ++y) {
  220. emptyCol2.push(0);
  221. emptyCol1.push(0);
  222. }
  223. grid1.push(emptyCol1);
  224. grid2.push(emptyCol2);
  225. }
  226. usingGrid1 = true;
  227. }
  228. /**
  229. * Randomly kills or inserts cells at every point on the grid. There is a 50
  230. * percent chance of either occurence.
  231. */
  232. function randomiseGrid() {
  233. grid1 = [];
  234. grid2 = [];
  235. for(let x = 0; x < gridX; ++x) {
  236. let randCol1 = [];
  237. let randCol2 = [];
  238. for(let y = 0; y < gridY; ++y) {
  239. randCol1.push(round(random()));
  240. randCol2.push(round(random()));
  241. }
  242. grid1.push(randCol1);
  243. grid2.push(randCol2);
  244. }
  245. usingGrid1 = true;
  246. }
  247. /**
  248. * Draws the menu with the keyboard operations on the side of the canvas.
  249. */
  250. function drawMenu() {
  251. textAlign(LEFT, TOP);
  252. textSize(0.035 * height);
  253. fill(155);
  254. if(paused) {
  255. fill(255);
  256. }
  257. text("Click P to Pause", 1.02 * height, 0.05 * height);
  258. fill(155);
  259. text("Click D to Delete All", 1.02 * height, 0.05 * 3 * height);
  260. text("Click R to Randmoise", 1.02 * height, 0.05 * 5 * height);
  261. fill(155);
  262. if(selectedInsertion == "cell") {
  263. fill(255);
  264. }
  265. text("Click C to add Cell", 1.02 * height, 0.05 * 7 * height);
  266. fill(155);
  267. if(selectedInsertion == "glider") {
  268. fill(255);
  269. }
  270. text("Click G to add Glider", 1.02 * height, 0.05 * 9 * height);
  271. fill(155);
  272. if(selectedInsertion == "lightSpaceShip") {
  273. fill(255);
  274. }
  275. text("Click L to add Light Ship", 1.02 * height, 0.05 * 11 * height);
  276. fill(155);
  277. if(selectedInsertion == "gliderGun") {
  278. fill(255);
  279. }
  280. text("Click H to add Glider Gun", 1.02 * height, 0.05 * 13 * height);
  281. }
  282. /**
  283. * p5.js setup function, creates canvas and randomises grid1 and grid2.
  284. */
  285. function setup() {
  286. let cnvSize;
  287. if(windowWidth > windowHeight) {
  288. cnvSize = 0.95 * windowHeight;
  289. } else {
  290. cnvSize = 0.6 * windowWidth;
  291. }
  292. let cnv = createCanvas(cnvSize, 0.7 * cnvSize);
  293. cnv.parent("sketch");
  294. size = height / gridY;
  295. randomiseGrid();
  296. }
  297. /**
  298. * p5.js draw function, draws cells on the canvas and updates the simulation
  299. * based on updateRate.
  300. */
  301. function draw() {
  302. background(0);
  303. stroke(155);
  304. strokeWeight(1);
  305. line(height, 0, height, height);
  306. drawMenu();
  307. noStroke();
  308. for(x = 0; x < gridX; ++x) {
  309. for(y = 0; y < gridY; ++y) {
  310. if(usingGrid1) {
  311. if(!paused && frameCount % updateRate == 0) {
  312. let numNeighbours = neighbours(grid1, x, y);
  313. grid2[x][y] = grid1[x][y];
  314. if((grid1[x][y] == 1) && (numNeighbours <= 1 || numNeighbours >= 4)) {
  315. grid2[x][y] = 0;
  316. }
  317. if(grid1[x][y] == 1 && numNeighbours < 4 && numNeighbours > 1) {
  318. grid2[x][y] = 1;
  319. }
  320. if(grid1[x][y] == 0 && numNeighbours == 3) {
  321. grid2[x][y] = 1;
  322. }
  323. }
  324. if(grid2[x][y] == 1) {
  325. fill(0, 255, 0);
  326. rect(x * size, y * size, size, size);
  327. }
  328. } else {
  329. if(!paused && frameCount % updateRate == 0) {
  330. let numNeighbours = neighbours(grid2, x, y);
  331. grid1[x][y] = grid2[x][y];
  332. if((grid2[x][y] == 1) && (numNeighbours < 2 || numNeighbours > 3)) {
  333. grid1[x][y] = 0;
  334. }
  335. if(grid2[x][y] == 1 && numNeighbours < 4 && numNeighbours > 1) {
  336. grid1[x][y] = 1;
  337. }
  338. if(grid2[x][y] == 0 && numNeighbours == 3) {
  339. grid1[x][y] = 1;
  340. }
  341. }
  342. if(grid1[x][y] == 1) {
  343. fill(0, 255, 0);
  344. rect(x * size, y * size, size, size);
  345. }
  346. }
  347. }
  348. }
  349. if(!paused && frameCount % updateRate == 0) {
  350. usingGrid1 = !usingGrid1;
  351. }
  352. }

Documentation generated by JSDoc 3.6.3 on Sun Jun 05 2022 20:13:18 GMT+0100 (BST)