├── .gitignore ├── LICENSE.md ├── README.md └── script └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2017] [Maurice Harris] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Conway's Game of Life 2 | 3 | The Game of Life is a cellular automaton created by John H. Conway in 1970. The game is a zero-player game in which an initially configured 2D grid of cells evolves according to the Game of Life [ruleset](#Ruleset). 4 | 5 | Built using Python 3.5, this implementation of Conway's Game of Life allows the user to easily run the Game of Life using a 2D grid of choosen number of rows and columns in either a Linux or Windows terminal/console. 6 | 7 | This project is licensed under the terms of the MIT license. 8 | 9 | ## Ruleset 10 | 11 | Using the following ruleset the 2D grid of cells will evolve from generation to generation until it reaches a static state of either all dead cells or a mix of still, oscillating, or moving (spaceship) cells. 12 | 13 | 1. _**Underpopulation**_ - If a live cell has is surrounded by less than two surrounding neighbours it dies and does not make it to the next generation. 14 | 2. _**Equilibrium**_ - If a live cell is surrounded by two or three living neighbors the cell stays alive and makes it to the next generation. 15 | 3. _**Overpopulation**_ - If a live cell is surrounded by more than three living neighbors the cell dies and does not make it to the next generation. 16 | 4. _**Reproduction**_ - If a dead cell is surrounded by three living neighbors the cell stays alive and makes it to the next generation. 17 | 18 | ## How to Run 19 | 20 | This project is built using Python 3.5 and requires that the user has at least Python 3 installed in order to run the program. Python 3+ can be installed [here](https://www.python.org/downloads/). 21 | 22 | In order to run this project the user must open a terminal/console and navigate to the project folder and then into the script directory. Once inside of the script directory simply run **`python main.py`** in order to begin the program. 23 | 24 | Once the program has been started the user will be prompted to input the number of **rows** and **columns** to make the Game of Life grid. 25 | 26 | -------------------------------------------------------------------------------- /script/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time 4 | import os 5 | import random 6 | import sys 7 | 8 | 9 | def clear_console(): 10 | """ 11 | Clears the console using a system command based on the user's operating system. 12 | 13 | """ 14 | 15 | if sys.platform.startswith('win'): 16 | os.system("cls") 17 | elif sys.platform.startswith('linux'): 18 | os.system("clear") 19 | elif sys.platform.startswith('darwin'): 20 | os.system("clear") 21 | else: 22 | print("Unable to clear terminal. Your operating system is not supported.\n\r") 23 | 24 | 25 | def resize_console(rows, cols): 26 | """ 27 | Re-sizes the console to the size of rows x columns 28 | 29 | :param rows: Int - The number of rows for the console to re-size to 30 | :param cols: Int - The number of columns for the console to re-size to 31 | """ 32 | 33 | if cols < 32: 34 | cols = 32 35 | 36 | if sys.platform.startswith('win'): 37 | command = "mode con: cols={0} lines={1}".format(cols + cols, rows + 5) 38 | os.system(command) 39 | elif sys.platform.startswith('linux'): 40 | command = "\x1b[8;{rows};{cols}t".format(rows=rows + 3, cols=cols + cols) 41 | sys.stdout.write(command) 42 | elif sys.platform.startswith('darwin'): 43 | command = "\x1b[8;{rows};{cols}t".format(rows=rows + 3, cols=cols + cols) 44 | sys.stdout.write(command) 45 | else: 46 | print("Unable to resize terminal. Your operating system is not supported.\n\r") 47 | 48 | 49 | def create_initial_grid(rows, cols): 50 | """ 51 | Creates a random list of lists that contains 1s and 0s to represent the cells in Conway's Game of Life. 52 | 53 | :param rows: Int - The number of rows that the Game of Life grid will have 54 | :param cols: Int - The number of columns that the Game of Life grid will have 55 | :return: Int[][] - A list of lists containing 1s for live cells and 0s for dead cells 56 | """ 57 | 58 | grid = [] 59 | for row in range(rows): 60 | grid_rows = [] 61 | for col in range(cols): 62 | # Generate a random number and based on that decide whether to add a live or dead cell to the grid 63 | if random.randint(0, 7) == 0: 64 | grid_rows += [1] 65 | else: 66 | grid_rows += [0] 67 | grid += [grid_rows] 68 | return grid 69 | 70 | 71 | def print_grid(rows, cols, grid, generation): 72 | """ 73 | Prints to console the Game of Life grid 74 | 75 | :param rows: Int - The number of rows that the Game of Life grid has 76 | :param cols: Int - The number of columns that the Game of Life grid has 77 | :param grid: Int[][] - The list of lists that will be used to represent the Game of Life grid 78 | :param generation: Int - The current generation of the Game of Life grid 79 | """ 80 | 81 | clear_console() 82 | 83 | # A single output string is used to help reduce the flickering caused by printing multiple lines 84 | output_str = "" 85 | 86 | # Compile the output string together and then print it to console 87 | output_str += "Generation {0} - To exit the program press \n\r".format(generation) 88 | for row in range(rows): 89 | for col in range(cols): 90 | if grid[row][col] == 0: 91 | output_str += ". " 92 | else: 93 | output_str += "@ " 94 | output_str += "\n\r" 95 | print(output_str, end=" ") 96 | 97 | 98 | def create_next_grid(rows, cols, grid, next_grid): 99 | """ 100 | Analyzes the current generation of the Game of Life grid and determines what cells live and die in the next 101 | generation of the Game of Life grid. 102 | 103 | :param rows: Int - The number of rows that the Game of Life grid has 104 | :param cols: Int - The number of columns that the Game of Life grid has 105 | :param grid: Int[][] - The list of lists that will be used to represent the current generation Game of Life grid 106 | :param next_grid: Int[][] - The list of lists that will be used to represent the next generation of the Game of Life 107 | grid 108 | """ 109 | 110 | for row in range(rows): 111 | for col in range(cols): 112 | # Get the number of live cells adjacent to the cell at grid[row][col] 113 | live_neighbors = get_live_neighbors(row, col, rows, cols, grid) 114 | 115 | # If the number of surrounding live cells is < 2 or > 3 then we make the cell at grid[row][col] a dead cell 116 | if live_neighbors < 2 or live_neighbors > 3: 117 | next_grid[row][col] = 0 118 | # If the number of surrounding live cells is 3 and the cell at grid[row][col] was previously dead then make 119 | # the cell into a live cell 120 | elif live_neighbors == 3 and grid[row][col] == 0: 121 | next_grid[row][col] = 1 122 | # If the number of surrounding live cells is 3 and the cell at grid[row][col] is alive keep it alive 123 | else: 124 | next_grid[row][col] = grid[row][col] 125 | 126 | 127 | def get_live_neighbors(row, col, rows, cols, grid): 128 | """ 129 | Counts the number of live cells surrounding a center cell at grid[row][cell]. 130 | 131 | :param row: Int - The row of the center cell 132 | :param col: Int - The column of the center cell 133 | :param rows: Int - The number of rows that the Game of Life grid has 134 | :param cols: Int - The number of columns that the Game of Life grid has 135 | :param grid: Int[][] - The list of lists that will be used to represent the Game of Life grid 136 | :return: Int - The number of live cells surrounding the cell at grid[row][cell] 137 | """ 138 | 139 | life_sum = 0 140 | for i in range(-1, 2): 141 | for j in range(-1, 2): 142 | # Make sure to count the center cell located at grid[row][col] 143 | if not (i == 0 and j == 0): 144 | # Using the modulo operator (%) the grid wraps around 145 | life_sum += grid[((row + i) % rows)][((col + j) % cols)] 146 | return life_sum 147 | 148 | 149 | def grid_changing(rows, cols, grid, next_grid): 150 | """ 151 | Checks to see if the current generation Game of Life grid is the same as the next generation Game of Life grid. 152 | 153 | :param rows: Int - The number of rows that the Game of Life grid has 154 | :param cols: Int - The number of columns that the Game of Life grid has 155 | :param grid: Int[][] - The list of lists that will be used to represent the current generation Game of Life grid 156 | :param next_grid: Int[][] - The list of lists that will be used to represent the next generation of the Game of Life 157 | grid 158 | :return: Boolean - Whether the current generation grid is the same as the next generation grid 159 | """ 160 | 161 | for row in range(rows): 162 | for col in range(cols): 163 | # If the cell at grid[row][col] is not equal to next_grid[row][col] 164 | if not grid[row][col] == next_grid[row][col]: 165 | return True 166 | return False 167 | 168 | 169 | def get_integer_value(prompt, low, high): 170 | """ 171 | Asks the user for integer input and between given bounds low and high. 172 | 173 | :param prompt: String - The string to prompt the user for input with 174 | :param low: Int - The low bound that the user must stay within 175 | :param high: Int - The high bound that the user must stay within 176 | :return: The valid input value that the user entered 177 | """ 178 | 179 | while True: 180 | try: 181 | value = int(input(prompt)) 182 | except ValueError: 183 | print("Input was not a valid integer value.") 184 | continue 185 | if value < low or value > high: 186 | print("Input was not inside the bounds (value <= {0} or value >= {1}).".format(low, high)) 187 | else: 188 | break 189 | return value 190 | 191 | 192 | def run_game(): 193 | """ 194 | Asks the user for input to setup the Game of Life to run for a given number of generations. 195 | 196 | """ 197 | 198 | clear_console() 199 | 200 | # Get the number of rows and columns for the Game of Life grid 201 | rows = get_integer_value("Enter the number of rows (10-60): ", 10, 60) 202 | clear_console() 203 | cols = get_integer_value("Enter the number of cols (10-118): ", 10, 118) 204 | 205 | # Get the number of generations that the Game of Life should run for 206 | generations = 5000 207 | resize_console(rows, cols) 208 | 209 | # Create the initial random Game of Life grids 210 | current_generation = create_initial_grid(rows, cols) 211 | next_generation = create_initial_grid(rows, cols) 212 | 213 | # Run Game of Life sequence 214 | gen = 1 215 | for gen in range(1, generations + 1): 216 | if not grid_changing(rows, cols, current_generation, next_generation): 217 | break 218 | print_grid(rows, cols, current_generation, gen) 219 | create_next_grid(rows, cols, current_generation, next_generation) 220 | time.sleep(1 / 5.0) 221 | current_generation, next_generation = next_generation, current_generation 222 | 223 | print_grid(rows, cols, current_generation, gen) 224 | return input(" to exit or r to run again: ") 225 | 226 | 227 | # Start the Game of Life 228 | run = "r" 229 | while run == "r": 230 | out = run_game() 231 | run = out 232 | 233 | --------------------------------------------------------------------------------