Created
February 28, 2024 13:15
-
-
Save Arno0x/0d158ed2a629353924a1133f91c4a777 to your computer and use it in GitHub Desktop.
Game of life in Python3 - Command line - Full options
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf8 -*- | |
import sys | |
import random | |
import time | |
import re | |
import argparse | |
import os | |
# Import external modules | |
try: | |
import numpy as np | |
import pickle | |
import requests | |
except ModuleNotFoundError as e: | |
print("Could not import module [%s]. Not installed. Please install using 'pip3 install %s'" %(e.name, e.name)) | |
exit() | |
#-------------------------------------------------------------- | |
def initializeRandomMatrix(probability): | |
for l in range(L): | |
for c in range(C): | |
matrix[l, c] = 1 if random.random() < probability else 0 | |
#-------------------------------------------------------------- | |
def decodeRLE(rle): | |
decoded = '' | |
for line in rle.splitlines(): | |
# Get rid of comment lines starting with a '#' | |
if line.startswith('#'): continue | |
if line.startswith('x'): continue | |
nbcell=1 | |
for item in re.findall(r'(?:\d+|[b|o|$])',line): | |
# If item is not 'b' or 'o' or '$' then its a number | |
if item not in "bo$": | |
nbcell = int(item) | |
elif item == 'b': | |
decoded+='0'*nbcell | |
nbcell=1 | |
elif item == 'o': | |
decoded+='1'*nbcell | |
nbcell=1 | |
else: | |
decoded+='\n'*nbcell | |
nbcell=1 | |
return decoded | |
#-------------------------------------------------------------- | |
# Imports an object the matrix and center it into the matrix | |
def importObject(objectRef): | |
if objectRef.startswith("http"): | |
try: | |
x = requests.get(objectRef) | |
if objectRef.endswith(".rle"): | |
objectAsText = decodeRLE(x.text) | |
else: | |
objectAsText = x.text | |
except: | |
print("[Error] Could not download file from URL [%s]"% objectRef) | |
return -1 | |
else: | |
try: | |
f = open(objectRef, "r") | |
objectAsText = f.read() | |
f.close() | |
except FileNotFoundError: | |
print("[Error] Could not download file from file [%s]"% objectRef) | |
return -1 | |
# Remove any potential '\r' character | |
objectAsText = objectAsText.replace('\r','') | |
# Get the shape of the object = nb of lines, max nb of characters | |
lines = objectAsText.splitlines() | |
sizeL = len(lines) | |
sizeC = 0 | |
for line in lines: | |
sizeC = max(sizeC, len(line)) | |
# Calculate the starting position for the object (up-left corner) so that is centered in the matrix | |
startL = int(L/2 - sizeL/2) | |
startC = int(C/2 - sizeC/2) | |
l, c = 0, 0 | |
for char in objectAsText: | |
try: | |
if char == "\n": | |
l+=1 | |
c=0 | |
else: | |
matrix[(startL+l)%L , (startC+c)%C] = int(char) | |
c+=1 | |
except ValueError: | |
matrix[ligne+l , colonne+c] = 0 | |
return 0 | |
#-------------------------------------------------------------- | |
def printMatrix(): | |
print("\033[0;0H") # Move Cursor back to position 0,0 | |
print('\33[0m'+'-'*C) | |
for l in range(L): | |
for c in range(C): | |
value = matrix[l,c] | |
if value == 1 : sys.stdout.write('\33[38;5;39m◉') # Cell that was just born | |
elif value > 1: sys.stdout.write('\33[38;5;26m◉') # Cell that is alive for more than a generation | |
elif value > 0 and ShowAgingCells: sys.stdout.write('\33[38;5;%dm◉' % (232+(value*100))) # Cells that are slowly dying | |
else: sys.stdout.write(' ') | |
sys.stdout.write('\n') | |
print('\33[0m'+'-'*C) | |
#-------------------------------------------------------------- | |
def liveOrDie(l, c): | |
# Summing value of all neighbor cells, keeping integer part only | |
lBefore = (l-1)%L | |
lAfter = (l+1)%L | |
cBefore = (c-1)%C | |
cAfter = (c+1)%C | |
sum = int( matrix[lBefore, cBefore] + \ | |
matrix[lBefore, c] + \ | |
matrix[lBefore, cAfter] + \ | |
matrix[l, cBefore] + \ | |
matrix[l, cAfter] + \ | |
matrix[lAfter, cBefore] + \ | |
matrix[lAfter, c] + \ | |
matrix[lAfter, cAfter]) | |
# If we have a living cell, check [S]urvive condition | |
if matrix[l, c] >= 1: | |
if sum in Rule[1]: return 1.01 | |
else: return 0.09 | |
# If we don't have a living cell, check [B]orn condition | |
elif matrix[l, c] <= 1: | |
if sum in Rule[0]: return 1 | |
else: return max(0, matrix[l, c]-0.03) | |
#-------------------------------------------------------------- | |
def evolveMatrix(): | |
tempMatrix = np.empty_like(matrix) | |
for l in range(L): | |
for c in range(C): | |
tempMatrix[l, c] = liveOrDie(l,c) | |
return np.copy(tempMatrix) | |
#======================================================================================= | |
# MAIN | |
#======================================================================================= | |
# Parse arguments | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-m", "--matrixFile", help="Specify a matrix file to load", required=False) | |
parser.add_argument("-p", "--probability", help="Set probability of life in the matrix in %% (default 15%%)", type=int, default=15, required=False) | |
parser.add_argument("-l", "--lines", help="Set number of lines for the matrix (default 50)", dest="L", type=int, default=50, required=False) | |
parser.add_argument("-c", "--columns", help="Set number of columns for the matrix (default 100)", dest="C", type=int, default=100, required=False) | |
parser.add_argument("-a", "--aging", help="Switch display of aging cells to TRUE", action="store_true", required=False) | |
parser.add_argument("-d", "--delay", help="Set delay in seconds between each refresh (default 0.1s)", type=float, default=0.1, required=False) | |
parser.add_argument("-o", "--objectRef", help="Import an object in the matrix by providing either a local file or a URL", required=False) | |
parser.add_argument("-r", "--rule", help="Set the game rule in the form [BxSyz] where x,y and z are digits (B=Born, S=Survive). Eg: B3S23", required=False) | |
args = parser.parse_args() | |
#----------------------------------------------- | |
# Set Global Variables based on passed arguments | |
L = args.L | |
C = args.C | |
Probability = args.probability/100 | |
ShowAgingCells = args.aging | |
Delay = args.delay | |
#---------------------------------------------- | |
# Set the rule for the game | |
Rule = [[],[]] | |
if args.rule != None: | |
if re.search(r'(B)(\d+)(S)(\d+)', args.rule) != None: | |
r = 0 | |
for char in args.rule[1:]: | |
if char == "S": | |
r = 1 | |
continue | |
Rule[r].append(int(char)) | |
else: | |
print("[Error] Rules must match the pattern B{digits}S{digits}. Ex: B2S23, B12S34, etc.") | |
exit() | |
else: Rule = [[3], [2,3]] | |
#------------------------------------------------ | |
# Create matrix from scratch or load it from file | |
if args.matrixFile == None: | |
if Probability == 0: matrix = np.zeros((L, C)) | |
elif Probability == 1: matrix = np.ones((L, C)) | |
else: | |
matrix = np.empty((L, C)) | |
initializeRandomMatrix(Probability) | |
else: | |
try: | |
with open(args.matrixFile, "rb") as f: | |
matrix = pickle.load(f) | |
L, C = matrix.shape | |
except FileNotFoundError: | |
print("[Error] Matrix file [%s] not found" % args.matrixFile) | |
exit() | |
#---------------------------------------------- | |
# Add objects to the initial matrix | |
if args.objectRef != None: | |
if importObject(args.objectRef) == -1: exit() | |
nbLiveCellStart = np.count_nonzero(matrix == 1) | |
#---------------------------------------------- | |
# Make a backup of the matrix at startup | |
backupMatrix = matrix.copy() | |
#---------------------------------------------- | |
# Print starting state matrix | |
os.system('clear') | |
printMatrix() | |
print(">> Start density: %d/%d -> %2.2f%%" % (nbLiveCellStart,L*C,nbLiveCellStart/(L*C)*100)) | |
try: | |
input("Press <Enter> to start") | |
os.system('clear') | |
generation = 0 | |
while True: | |
matrix = evolveMatrix() | |
printMatrix() | |
generation+=1 | |
print("Generation: %d" % (generation)) | |
time.sleep(Delay) | |
except KeyboardInterrupt: | |
print() | |
nbLiveCellEnd = np.count_nonzero(matrix == 1) | |
print(">> Start density: %d/%d -> %2.2f%%" % (nbLiveCellStart,L*C,nbLiveCellStart/(L*C)*100)) | |
print(">> End density: %d/%d -> %2.2f%%" % (nbLiveCellEnd,L*C,nbLiveCellEnd/(L*C)*100)) | |
save = input("Save initial (starting) matrix (y/[n]) ? ").lower() | |
if save == 'y': | |
matrixName = input("Matrix name: ").lower() | |
with open(matrixName+'.bin', 'wb') as f: | |
pickle.dump(backupMatrix, f, pickle.HIGHEST_PROTOCOL) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment