V3.2 Microbial Life Simu Added

Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 9

import random

import pygame

#global variables for screen dimensions, to be called everytime in the future


screen_w=800
screen_h=600

pygame.init()

screen=pygame.display.set_mode((screen_w, screen_h)) #with pygame initialized we


can now set up our screen variable using the library
pygame.display.set_caption("Game of Life")

#color library
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN=(0,255,0)
RED=(255,0,0)
BLUE=(0,0,255)
GOLD=(255,215,0)

# Microbe types with their corresponding colors


MICROBE_TYPES = {"Producer": GREEN, "Consumer": BLUE, "Predator": RED}

# Holographic() function defined for generating random colors


def holographic():
return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)

class Grid: #grid class containing all the set-up & update functions
def __init__(self, rows, cols, cell_size):
self.rows=rows #assigning values to instance variables for grid mapping,
all the values here should be integers
self.cols=cols
self.cell_size=cell_size
self.manualgrid=self.create_empty_grid() #creates an instance variable for
an empty grid that we can call later on
self.randomgrid=self.create_random_grid()
self.currentgrid=self.randomgrid #instance variable to be called as the
grid placeholder, we choose the random grid as the default
self.microbe_grid = self.create_empty_grid() # Track microbe types

def set_random_grid(self): #function to set the random grid as the one we are
working with
self.currentgrid=self.randomgrid

def set_manual_grid(self): #function to set the manual grid as the one we are
working with
self.currentgrid=self.manualgrid

def create_empty_grid(self): #empty grid for manual placement mode, only


depends on # of rows & columns, calls initialized object
return [[0 for _ in range(self.cols)] for _ in range(self.rows)]

def create_random_grid(self): #random grid for random mode


grid = [[random.choice([0, 1]) for _ in range(self.cols)] for _ in
range(self.rows)]
self.microbe_grid = [[random.choice(list(MICROBE_TYPES.keys())) if cell ==
1 else None
for cell in row] for row in grid] # Assign random
microbe types
return grid

def count_neighbors(self, row, col): #neighbor positioning & detection system,


row & col corresponds to cell coordinate location
neighbors = [
(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1)
]
count = 0
for dr, dc in neighbors:
r, c = row + dr, col + dc
if 0 <= r < self.rows and 0 <= c < self.cols: #makes sure the computer
doesn't go beyond the limits of the window
count += self.currentgrid[r][c]
return count

#dynamic method that updates grid based on neighbor information


def update_grid(self):
new_grid = [[0 for _ in range(self.cols)] for _ in range(self.rows)]
new_microbe_grid = [[None for _ in range(self.cols)] for _ in
range(self.rows)] # Update microbe types
for row in range(self.rows):
for col in range(self.cols):
live_neighbors = self.count_neighbors(row, col)
if self.currentgrid[row][col] == 1: # possibilities for life
holding cells
if live_neighbors in [2, 3]:
new_grid[row][col] = 1 # cell has 2 or 3 neighbors,
preserves life value
new_microbe_grid[row][col] = self.microbe_grid[row][col] #
Preserve type
else: # possibilities for dead cell
if live_neighbors == 3:
new_grid[row][col] = 1 # dead cell has 3 neighbors, new
cell holding life value created
new_microbe_grid[row][col] =
random.choice(list(MICROBE_TYPES.keys())) # Assign new type
self.currentgrid = new_grid
self.microbe_grid = new_microbe_grid

#grid drawing
def draw_grid(self):
for row in range(self.rows):
for col in range(self.cols):
if self.currentgrid[row][col] == 1:
microbe_type = self.microbe_grid[row][col]
color = MICROBE_TYPES[microbe_type] if microbe_type else
holographic()
else:
color = BLACK
pygame.draw.rect(screen, color,(col * self.cell_size, row *
self.cell_size, self.cell_size, self.cell_size))

class Settings: #settings class designed to allow user to alter game parameters
def __init__(self):
self.fps = 10 #creation of a fps instance variable to replace our
time.sleep fps config function
self.resolution = (800, 600)
self.grid_size = (20, 20)

def update_settings(self):
running = True
options = [
"FPS",
"Resolution",
"Grid Size",
"Back"
]
selected = 0
font = pygame.font.SysFont("sans-serif", 36)

while running:
screen.fill(BLACK)

#display settings options


for i, option in enumerate(options):
# we want to use arrow keys to navigate the settings menu, have to
use colors to distinguish between selected option & rest
color = WHITE if i == selected else (100, 100, 100) #grey chosen
out of convention
#render and positioning of settings text, similar to what we did
for the menu
text = font.render(f"{option}: {self.get_setting_value(option)}",
True, color)
screen.blit(text, (screen_w // 2 - text.get_width() // 2, 150 + i *
50))

pygame.display.flip()

for event in pygame.event.get():


if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_DOWN:
selected = (selected + 1) % len(options)
#we add %len(options) to have the computer return to the first
option once the end of the index is reached, inverse is true below
if event.key == pygame.K_UP:
selected = (selected - 1) % len(options)
if event.key == pygame.K_RETURN:
if options[selected] == "Back":
running = False #breaks settings loop and returns user
to menu
else:
self.adjust_setting(options[selected])

def get_setting_value(self, option): #method that displays the current game


parameters to the user
if option == "FPS":
return self.fps
elif option == "Resolution":
return f"{self.resolution[0]}x{self.resolution[1]}"
elif option == "Grid Size":
return f"{self.grid_size[0]}x{self.grid_size[1]}"
return ""
def adjust_setting(self, option): #method that allows user to input his own
values to modify config settings
if option == "FPS":
self.fps = self.number_input("Enter FPS:", self.fps)
elif option == "Resolution":
self.resolution = self.resolution_input("Enter Resolution (WIDTH x
HEIGHT):", self.resolution)
elif option == "Grid Size":
self.grid_size = self.grid_size_input("Enter Grid Size (ROWS x COLS):",
self.grid_size)

def number_input(self, prompt, current_value):


return self.input_prompt(prompt, str(current_value), int)

def resolution_input(self, prompt, current_value):


def parse_resolution(input_str):
width, height = map(int, input_str.lower().split('x'))
return width, height
#parse_resolution method takes the string of the user input and converts it
into width & height (implied integer values)
return self.input_prompt(prompt, f"{current_value[0]}x{current_value[1]}",
parse_resolution)

def grid_size_input(self, prompt, current_value):


def parse_grid_size(input_str):
rows, cols = map(int, input_str.lower().split('x'))
return rows, cols
return self.input_prompt(prompt, f"{current_value[0]}x{current_value[1]}",
parse_grid_size)

def input_prompt(self, prompt, default_value, parser):


input_active = True
user_input = str(default_value)
font = pygame.font.SysFont("sans-serif", 36)
# we use input_prompt as a static method to ask the user for a new integer
value all the while displaying the current one

while input_active:
#loop to support the method's functionality through handling user input
values, updating game and preserving game consistency in case of parsing failure
screen.fill(BLACK)
prompt_text = font.render(prompt, True, WHITE)
input_text = font.render(user_input, True, GREEN)
screen.blit(prompt_text, (screen_w // 2 - prompt_text.get_width() // 2,
150))
screen.blit(input_text, (screen_w // 2 - input_text.get_width() // 2,
250))
pygame.display.flip()

for event in pygame.event.get():


if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
try:
return parser(user_input)
except ValueError:
user_input = str(default_value)
elif event.key == pygame.K_BACKSPACE:
user_input = user_input[:-1]
elif event.unicode.isprintable():
user_input += event.unicode

#the below methods prompt the user for values, updates them and essentially
communicate the results of the loop above
def change_fps(self):
new_fps = input("Enter new FPS (current: {}): ".format(self.fps))
try:
self.fps = int(new_fps)
except ValueError:
print("Invalid input! FPS remains unchanged.")

def change_resolution(self):
new_res = input("Enter new resolution as WIDTH x HEIGHT (current: {}):
".format(self.resolution))
try:
width, height = map(int, new_res.lower().split('x'))
self.resolution = (width, height)
except ValueError:
print("Invalid input! Resolution remains unchanged.")

def change_grid_size(self):
new_size = input("Enter new grid size as ROWSxCOLS (current: {}):
".format(self.grid_size))
try:
rows, cols = map(int, new_size.lower().split('x'))
self.grid_size = (rows, cols)
except ValueError:
print("Invalid input! Grid size remains unchanged.")

def exit_settings(self):
print("Exiting settings menu.")

class Menu:
def __init__(self, title, font):
self.title=title #menu title, data type=string
self.screen=screen #global screen config variable called to be applied to
menu
self.font=font
self.options={"1": "Random Game", "2": "Manual Game", "3": "Add Nutrients",
"ESC": "Quit"} #menu options index, defined as dict

def render_text(self, text, y):


rendered_text = self.font.render(text, True, WHITE)
x= screen_w // 2 - rendered_text.get_width() // 2 #to center our text we
take the middle line where the screen would be split in half and then shift each
half of our text in both screen halves
self.screen.blit(rendered_text, (x, y)) #we use the pygame surface
rendering function to put our text at coordinate (x,y), with x being fixed

def display(self, start_y=175):


while True:
self.screen.fill(BLACK)
self.render_text(self.title, 50)
for i, (key, description) in enumerate(self.options.items()): #iterates
through items of our index
y = start_y + i * 100
self.render_text(f"{key} - {description}", y) #renders str value of
dictionary alongside key int

pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
key_pressed = pygame.key.name(event.key).upper() #.upper() used
to avoid having to capitalize numerical inputs to access game
if key_pressed in self.options:
return key_pressed

class Game:
def __init__(self, settings):
self.settings = settings
self.rows, self.cols = self.settings.grid_size
self.cell_size = self.settings.resolution[0] // self.cols #we get cell_size
to depend on resolution & columns due to newly added dynamism of game
self.fps = self.settings.fps
self.grid = Grid(self.rows, self.cols, self.cell_size)
self.menu = Menu(
title="Game of Life",
font=pygame.font.SysFont("sans-serif", 48)
)
self.running = True
self.actions = {
"1": self.start_random_game,
"2": self.start_manual_game,
"3": self.add_nutrients, # Add nutrients
"ESC": self.quit_game
}

def show_settings(self):
self.settings.update_settings()
#update game config after settings change
self.rows, self.cols = self.settings.grid_size
self.cell_size = self.settings.resolution[0] // self.cols
self.fps = self.settings.fps
self.grid = Grid(self.rows, self.cols, self.cell_size)

def start_random_game(self):
print("Starting Random Game...") #debugging, was having issues with this
part can remove
self.grid.set_random_grid()
self.run_game_loop()

def start_manual_game(self):
print("Starting Manual Game...") #debugging
self.grid.set_manual_grid()
self.manual_placement()
self.run_game_loop()

def add_nutrients(self):
print("Adding nutrients...")
for _ in range(random.randint(5, 10)):
row, col = random.randint(0, self.rows - 1), random.randint(0,
self.cols - 1)
self.grid.currentgrid[row][col] = 1
self.grid.microbe_grid[row][col] = "Producer" # Add Producer

def quit_game(self):
print("Quitting the game...") #debugging
pygame.quit()
exit()

def manual_placement(self):
placing = True
while placing:
screen.fill(BLACK)
self.grid.draw_grid() #draw the grid based on the current state in
manual mode
pygame.display.flip()

for event in pygame.event.get():


if event.type == pygame.QUIT:
pygame.quit()
exit()
if pygame.mouse.get_pressed()[0]: #left click to toggle cell on
pos = pygame.mouse.get_pos()
col = pos[0] // self.cell_size
row = pos[1] // self.cell_size
self.grid.manualgrid[row][col] = 1 #cell is activated
self.grid.microbe_grid[row][col] =
random.choice(list(MICROBE_TYPES.keys()))
if pygame.mouse.get_pressed()[2]: #right click to toggle cell off
pos = pygame.mouse.get_pos()
col = pos[0] // self.cell_size
row = pos[1] // self.cell_size
self.grid.manualgrid[row][col] = 0 #cell is deactivated
self.grid.microbe_grid[row][col] = None
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
placing = False #quit manual mode and launch simulation
if event.key == pygame.K_m:
return "menu" #go back to menu if 'M' is pressed

def run_game_loop(self):
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_m:
return

screen.fill(BLACK)
self.grid.draw_grid()
self.grid.update_grid()
pygame.display.flip()
clock.tick(self.fps) #use our newly defined dynamic FPS value
def run(self):
while self.running:
choice = self.menu.display()
if choice in self.actions:
self.actions[choice]() #dynamically call the associated action

if __name__ == "__main__":
settings = Settings() #instance of the settings class, brings forth contents &
methods defined in it as an object
game = Game(settings) #instance of the game class running our settings object
as a parameter, makes sure the game runs based on the defined configs
game.run()

class MicrobialGame:
def __init__(self):
self.settings = Settings()
self.grid = Grid(self.settings.grid_size[0], self.settings.grid_size[1],
self.settings.resolution[0] // self.settings.grid_size[1])
self.running = True

def mutate_microbes(self):
"""
Introduces random mutations to existing microbes.
"""
for row in range(self.grid.rows):
for col in range(self.grid.cols):
if self.grid.currentgrid[row][col] == 1: # Only mutate live cells
if random.random() < 0.05: # 5% chance of mutation
self.grid.microbe_grid[row][col] =
random.choice(list(MICROBE_TYPES.keys()))

def add_random_events(self):
"""
Introduces random events like toxins or resource booms.
"""
if random.random() < 0.1: # 10% chance per cycle
event_type = random.choice(["toxins", "resources"])
for _ in range(random.randint(3, 7)): # Affect 3-7 cells
row = random.randint(0, self.grid.rows - 1)
col = random.randint(0, self.grid.cols - 1)
if event_type == "toxins":
self.grid.currentgrid[row][col] = 0 # Kill the cell
self.grid.microbe_grid[row][col] = None
elif event_type == "resources":
self.grid.currentgrid[row][col] = 1 # Add a Producer
self.grid.microbe_grid[row][col] = "Producer"

def run(self):
clock = pygame.time.Clock()
while self.running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False

self.grid.update_grid()
self.mutate_microbes()
self.add_random_events()
screen.fill(BLACK)
self.grid.draw_grid()
pygame.display.flip()
clock.tick(self.settings.fps)

if __name__ == "__main__":
pygame.init()
microbial_game = MicrobialGame()
microbial_game.run()
pygame.quit()

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy