/processing_gameoflife/processing_gameoflife.pde
Processing | 244 lines | 151 code | 32 blank | 61 comment | 87 complexity | cf3bdf20b599dcec46cbd6b951739462 MD5 | raw file
- //Lokidottir
- //Game of life simulation, working edition
- int boardSize = 150; //The number of rows/coloums in the grid
- int boxSize = 5; //The size that the boxes fo the grid will be displayed in pixels
- int frameDelay = 50; //The delay in milliseconds between each update. E.G. 50ms = maximum 20 updates per second
- int tempDelay = 0; //The value which frameDelay is swapped with when the delay is removed
- int updateCount = 0; //The number of generations that have passed
- int lastTime = millis(); //The time of the last update, used for the delay
- int opsPerFrame = 1; //The number of generations per update
- boolean paused = true; //If set to true, then the program will not process any updates until it has been set to false
- boolean drawText = false; //If set to true, then text will be drawn at the location of each cell showing how many neighors it has
- boolean showUpdateC = true; //If set to true, then the program will show how many generations have been processed since the beginning of the program
- boolean autopause = true;
- 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
- boolean[][] previous;
- boolean[][] twoBefore;
- void setup() {
- size(boardSize * boxSize,boardSize * boxSize);
- noStroke();
- //Initialising each element in the board as false
- for (int y = 0; y < gameMap.length; y++) {
- for (int x = 0; x < gameMap[y].length; x++) {
- gameMap[y][x] = false;
- }
- }
- }
- void draw() {
- float currenttime = millis();
-
- if (keyPressed) {
-
- //Changing the configuration of the program through key presses
-
- if (key == ENTER) {
- //Switches the paused variable, pausing or unpausing the program
- paused = !paused;
- int delay = millis() + 200;
- while (millis() < delay); //This delay is to prevent keypresses being registered multiple times, it's an awful hack.
- }
-
- else if (key == 't') {
- //Switches the drawText variable, showing or hiding the number of neighbors a cell has
- drawText = !drawText;
- int delay = millis() + 200;
- while (millis() < delay);
- }
-
- else if (key == ' ' && paused) {
- //If the program is paused, the board will be processed once. This is to allow showing a step-by-step of Game of Life
- gameMap = processBoard(gameMap);
- int delay = millis() + 200;
- while (millis() < delay);
- }
-
- else if (key == 'c') {
- //Switches the showUpdateC variable, showing or hiding the number of generations that have passed since the beginning of the program
- showUpdateC = !showUpdateC;
- int delay = millis() + 200;
- while (millis() < delay);
- }
-
- else if (key == 'l') {
- //Removes, or imposes, the limit on the number of updates per second. The new limit is how fast the program renders (60hz, usually).
- int temp = frameDelay;
- frameDelay = tempDelay;
- tempDelay = temp;
- int delay = millis() + 200;
- while (millis() < delay);
- }
- else if (key == 'a') {
- autopause = !autopause;
- int delay = millis() + 200;
- while (millis() < delay);
- }
-
- }
-
- if (mousePressed) {
- //Mouse handling.
- //First make sure the mouse's coordinates are within the window to prevent out of index errors
- if ((mouseX >= 0 && mouseX < width) && (mouseY >= 0 && mouseY < height)) {
- //If the left mouse button is pressed, then the cell the mouse is over is set to true (alive)
- //If the right mouse button is pressed, then the cell is instead set to false (dead)
- if (mouseButton == LEFT) gameMap[mouseY/boxSize][mouseX/boxSize] = true;
- else if (mouseButton == RIGHT) gameMap[mouseY/boxSize % gameMap.length][mouseX/boxSize % gameMap[0].length] = false;
- }
-
- }
-
- //End of user input
-
- //If the program is unpaused and the time between the last update delay has passed, the program processes the board
- if (!paused && (millis() > lastTime + frameDelay)) {
-
- //The number of times the program processes the board is determined by the opsPerFrame variable
- for (int i = 0; i < opsPerFrame; i++) {
- twoBefore = previous;
- previous = gameMap;
- gameMap = processBoard(gameMap);
- if (boardsEqual(twoBefore, gameMap) && autopause) paused = true;
-
- }
- lastTime = millis(); //The lastTime variable is set to the current time
- }
-
- //The grid is drawn
- drawGrid(gameMap, drawText);
-
- //If the showUpdateC variable is true, then the count for the number of generations is shown
- if (showUpdateC) {
- textSize(20);
- fill(0,0,255);
- textSize(18);
- text(updateCount,50,50);
- }
- }
- boolean[][] processBoard(boolean[][] board) {
- //The function that updates the board
- /*
- Or rather, creates a new 2d array of booleans which will be populated with the next generation and returned.
- This is because of how java's memory management works. Orginially this worked with a buffer, but when assigning
- the buffer to the original, it didn't copy the buffer's values but instead make both variables reference the same
- 2d array.
-
- Creating a new 2d array bypasses this problem, but also effects how fast the program can run due to memory management
- and allocation that is outside my control. However, the program in it's current state is designed to let a user see
- the Game of Life in progress, so speed is not part of the design, observation is.
- */
- boolean[][] toReturn = new boolean[board.length][board[0].length];
- for (int y = 0; y < board.length; y++) {
- for (int x = 0; x < board[y].length; x++) {
- //The rules() function is called on each element in the 2d array
- toReturn[y][x] = rules(board, x, y);
- }
- }
- updateCount++;
- return toReturn;
- }
- int nbors(boolean[][] board, int x, int y) {
- //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
- //The function takes the 2d array and the x coordinate and the y coordinate of the cell being tested as parameters
- int nbor_count = 0;
- /*
- A nested for loop that covers the values -1 to 1 will be able to cover the relative coordinates:
- (-1,-1) (+0,-1) (+1,-1)
- (-1,+0) (+0,+0) (+1,+0)
- (-1,+1) (+0,+1) (+1,+1)
- 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
- */
- for (int y2 = -1; y2 <= 1; y2++) {
- //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
- int newy = (y + y2 < 0) ? (board.length - (abs(y + y2) % board.length)) % board.length : ((y + y2) % board.length);
- for (int x2 = -1; x2 <= 1; x2++) {
- int newx = (x + x2 < 0) ? (board[newy].length - (abs(x + x2) % board[newy].length)) % board[newy].length : ((x + x2) % board[newy].length);
- if (board[newy][newx]) {
- //If an ajacent cell (neighbor) is true (alive) then the neighbor count is incremented
- nbor_count++;
- }
- }
- }
- /*
- The condition for the center:
- The center (+0,+0) is the cell that the number of neighbors is being calculated for, so if the cell is alive
- and has no neighbors, then 1 would be returned. To prevent this, we check if the cell at the coordinates given
- as the parameters of the function is true (alive), if so then we decrease the neighbor count by 1.
- */
- if (board[y][x]) nbor_count--;
- return nbor_count;
- }
- boolean rules(boolean[][] board, int x, int y) {
- //The function that determines if a cell will be true (alive) or false (dead) in the next generation
- //The function takes the 2d array and the x coordinate and the y coordinate of the cell being tested as parameters
-
- int nbor_count = nbors(board, x, y); //The number of live neighbors for the cell is retrived
-
- //The rules for the game of life
- //1. Any cell with less than 2 live neighbors dies of underpopulation/loneliness (false is returned)
- if (nbor_count < 2 && board[y % board.length][x % board[y % board.length].length]) {
- return false;
- }
- //2. Any cell with 2 or 3 live neighbors lives on to the next generation (true is returned)
- else if ((nbor_count == 2 || nbor_count == 3) && board[y % board.length][x % board[y % board.length].length]) {
- return true;
- }
- //3. Any cell with more than 3 live neighbors dies of overpopulation (false is returned)
- else if (nbor_count > 3 && board[y % board.length][x % board[y % board.length].length]) {
- return false;
- }
- //4. Any dead cell with exactly 3 live neighbors becomes a live cell, as if by repopulation (true is returned)
- else if (nbor_count == 3 && !board[y % board.length][x % board[y % board.length].length]) {
- return true;
- }
- //any exceptions to the rules are caught by this else statement
- else {
- return false;
- }
- }
- void drawGrid(boolean[][] board, boolean drawText) {
- //The function that draws the 2d array
- textSize(boxSize);
- //For optimised rendering, only the alive cells are drawn. The background is drawn as black for the dead cells.
- background(0);
- for (int y = 0; y < board.length; y++) {
- for (int x = 0; x < board[y].length; x++) {
- //If the cell is alive, then a white box is drawn
- if (board[y][x]) {
- fill(255);
- rect(x * boxSize, y * boxSize, boxSize, boxSize);
- }
- //If the drawText flag is set, then the number of neighbors is shown on each cell.
- //For optimising perposes, only cells with a neighbor count greater than 0 have their
- //count drawn
- if ((drawText && boxSize > 6) && (nbors(board, x, y) > 0 || board[y][x])) {
- if (board[y][x]) fill(0);
- else fill(255);
- text(nbors(board, x, y), x * boxSize + boxSize/4, y * boxSize + boxSize);
- }
- }
- }
- }
- boolean boardsEqual(boolean[][] board1, boolean[][] board2) {
- if (board1 == null || board2 == null || board1.length != board2.length || board1[0].length != board2[0].length) return false;
- boolean result = true;
- for (int y = 0; y < board1.length && result; y++) {
- for (int x = 0; x < board1[0].length && result; x++) {
- result = (board1[y][x] == board2[y][x]);
- }
- }
- return result;
- }