Skip to content

Instantly share code, notes, and snippets.

@Arno0x
Created February 28, 2024 13:15
Show Gist options
  • Save Arno0x/0d158ed2a629353924a1133f91c4a777 to your computer and use it in GitHub Desktop.
Save Arno0x/0d158ed2a629353924a1133f91c4a777 to your computer and use it in GitHub Desktop.
Game of life in Python3 - Command line - Full options
#!/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