Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions config/sim_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"stabilization_time": 200,
"trial_time": 300,
"beat_time": 20,
"beat_fade_time": 5,
"c_dim": [4, 4],
"beats_per_cycle": 3,
"node_coor_count": 4,
"y_clips": [-10000000, 0],
"ground_friction_coef": 25,
"gravity_acceleration_coef": 0.002,
"calming_friction_coef": 0.7,
"typical_friction_coef": 0.8,
"muscle_coef": 0.08,
"traits_per_box": 3,
"traits_extra": 1,
"mutation_rate": 0.07,
"big_mutation_rate": 0.025,
"units_per_meter": 0.05
}
15 changes: 15 additions & 0 deletions config/ui_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "Jelly Evolution Simulator",
"fps": 30,
"window_width": 1920,
"window_height": 1080,
"movie_single_dim": [650, 650],
"graph_coor": [850, 50, 900, 500],
"sac_coor": [850, 560, 900, 300],
"genealogy_coor": [20, 105, 530, 802, 42],
"column_margin": 330,
"mosaic_dim": [10, 24, 24, 30],
"menu_text_up": 180,
"cm_margin_1": 20,
"cm_margin_2": 1
}
13 changes: 13 additions & 0 deletions enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from enum import Enum


class Color(tuple, Enum):
BLACK = (0,0,0)
GRAY25 = (70,70,70)
GRAY50 = (128,128,128)
GRAYISH = (108, 118, 155)
WHITE = (255,255,255)
RED = (255,0,0)
GREEN = (0,255,0)
MOSAIC = (80, 80, 80)
SIGN = (150, 100, 50)
43 changes: 19 additions & 24 deletions jes.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,35 @@
from jes_sim import Sim
from jes_ui import UI
from utils import read_config

sim_config_file: str = 'config/sim_config.json'
ui_config_file: str = 'config/ui_config.json'

sim_config: dict = read_config(filename=sim_config_file)
ui_config: dict = read_config(filename=ui_config_file)

c_input = input("How many creatures do you want?\n100: Lightweight\n250: Standard (if you don't type anything, I'll go with this)\n500: Strenuous (this is what my carykh video used)\n")
if c_input == "":
c_input = "250"

# Simulation
# population size is 250 here, because that runs faster. You can increase it to 500 to replicate what was in my video, but do that at your own risk!

sim = Sim(_c_count=int(c_input), _stabilization_time=200, _trial_time=300,
_beat_time=20, _beat_fade_time=5, _c_dim=[4,4],
_beats_per_cycle=3, _node_coor_count=4, # x_position, y_position, x_velocity, y_velocity
_y_clips=[-10000000,0], _ground_friction_coef=25,
_gravity_acceleration_coef=0.002, _calming_friction_coef=0.7,
_typical_friction_coef=0.8, _muscle_coef=0.08,
_traits_per_box=3, # desired width, desired height, rigidity
_traits_extra=1, # heartbeat (time)
_mutation_rate=0.07, _big_mutation_rate=0.025,
_UNITS_PER_METER=0.05)
sim: Sim = Sim(creature_count=int(c_input), config=sim_config)

# Cosmetic UI variables
ui = UI(_W_W=1920, _W_H=1080, _MOVIE_SINGLE_DIM=(650,650),
_GRAPH_COOR=(850,50,900,500), _SAC_COOR=(850,560,900,300), _GENEALOGY_COOR=(20,105,530,802,42),
_COLUMN_MARGIN=330, _MOSAIC_DIM=[10,24,24,30], #_MOSAIC_DIM=[10,10,17,22],
_MENU_TEXT_UP=180, _CM_MARGIN1=20, _CM_MARGIN2=1)

ui: UI = UI(config=ui_config)

sim.ui = ui
ui.sim = sim
ui.addButtonsAndSliders()
ui.add_buttons_and_sliders()

sim.initializeUniverse()
sim.initialize_universe()

while ui.running:
sim.checkALAP()
ui.detectMouseMotion()
ui.detectEvents()
ui.detectSliders()
ui.doMovies()
ui.drawMenu()
sim.check_alap()
ui.detect_mouse_motion()
ui.detect_events()
ui.detect_sliders()
ui.do_movies()
ui.draw_menu()
ui.show()
33 changes: 17 additions & 16 deletions jes_button.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
from typing import Callable

import pygame
from jes_shapes import centerText
from jes_shapes import center_text
import time

class Button:
def __init__(self,ui,pdim,pnames,pfunc):
def __init__(self, pdim, pnames, callback_func: Callable) -> None:
self.dim = pdim # Dim is a list of 4 parameters: x, y, width, height
self.names = pnames
self.setting = 0
self.timeOfLastClick = 0
self.func = pfunc
ui.buttonList.append(self)

def drawButton(self, screen, font):
self.time_of_last_click = 0
self._func = callback_func

def draw_button(self, screen, font) -> None:
x, y, w, h = self.dim
name = self.names[self.setting]

sliderSurface = pygame.Surface((w,h), pygame.SRCALPHA, 32)
sliderSurface.fill((30,150,230))
slider_surface = pygame.Surface((w,h), pygame.SRCALPHA, 32)
slider_surface.fill((30,150,230))
if name == "Turn off ALAP" or name[:4] == "Stop" or name[:4] == "Hide":
sliderSurface.fill((128,255,255))
centerText(sliderSurface, name, w/2, h/2, (0,0,0), font)
slider_surface.fill((128,255,255))
center_text(slider_surface, name, w / 2, h / 2, (0, 0, 0), font)

screen.blit(sliderSurface,(x,y))
screen.blit(slider_surface,(x,y))

def click(self):
self.setting = (self.setting+1)%len(self.names)
self.timeOfLastClick = time.time()
self.func(self)
def click(self) -> None:
self.setting = (self.setting + 1)%len(self.names)
self.time_of_last_click = time.time()
self._func(self)
143 changes: 74 additions & 69 deletions jes_creature.py
Original file line number Diff line number Diff line change
@@ -1,109 +1,111 @@
import pygame
from utils import arrayLerp, dist_to_text, speciesToColor, listLerp, lerp
from jes_shapes import drawRect, drawTextRect, centerText, drawClock
from pygame import Surface

from enums import Color
from utils import array_lerp, dist_to_text, species_to_color, list_lerp, lerp
from jes_shapes import draw_rect, draw_text_rect, center_text, draw_clock
import numpy as np
import math
from jes_species_info import SpeciesInfo
import random

class Creature:
def __init__(self,d,pIDNumber,parent_species,_sim,_ui):
def __init__(self, d, p_id_number, parent_species, _sim, _ui) -> None:
self.dna = d
self.calmState = None
self.icons = [None]*2
self.iconCoor = None
self.IDNumber = pIDNumber
self.icon_coor = None
self.id_number = p_id_number
self.fitness = None
self.rank = None
self.living = True
self.species = self.getSpecies(parent_species)
self.species = self.get_species(parent_species)
self.sim = _sim
self.ui = _ui
self.codonWithChange = None
self.codon_with_change = None

def getSpecies(self, parent_species):
def get_species(self, parent_species):
if parent_species == -1:
return self.IDNumber
return self.id_number
else:
return parent_species

def drawCell(self,surface,nodeState,frame,transform,x,y):
tx,ty,s = transform
color = self.traitsToColor(self.dna,x,y,frame)
points = [None]*4
def draw_cell(self, surface, node_state, frame, transform, x, y) -> None:
tx, ty, s = transform
color = self.traits_to_color(self.dna, x, y, frame)
points = [None] * 4
for p in range(4):
px = x
if p == 1 or p == 2:
px += 1
py = y+p//2
points[p] = [tx+nodeState[px,py,0]*s,ty+nodeState[px,py,1]*s]
pygame.draw.polygon(surface,color,points)
points[p] = [tx + node_state[px,py,0] * s, ty + node_state[px,py,1] * s]

pygame.draw.polygon(surface, color, points)

def drawEnvironment(self,surface,nodeState,frame,transform):
BLACK = (0,0,0)
WHITE = (255,255,255)
SIGN_COLOR = (150,100,50)
def draw_environment(self, surface, transform) -> None:
#sky
drawRect(surface,transform,None,BLACK)
draw_rect(surface, transform, None, Color.BLACK)

#signs
font = self.ui.bigFont if transform[2] >= 50 else self.ui.smallFont
font = self.ui.big_font if transform[2] >= 50 else self.ui.small_font
for meters in range(0,3000,100):
u = meters*self.sim.UNITS_PER_METER
drawRect(surface,transform,[u-0.2,-6,u+0.2,0],SIGN_COLOR)
drawTextRect(surface,transform,[u-1.5,-6.8,u+1.5,-5.4],SIGN_COLOR,WHITE,f"{meters}cm",font)
u = meters*self.sim.units_per_meter
draw_rect(surface, transform, [u - 0.2, -6, u + 0.2, 0], Color.SIGN)
draw_text_rect(surface, transform, [u - 1.5, -6.8, u + 1.5, -5.4], Color.SIGN, Color.WHITE, f"{meters}cm", font)

#ground
drawRect(surface,transform,[None,0,None,None],WHITE)
draw_rect(surface, transform, [None, 0, None, None], Color.WHITE)

def drawCreature(self, surface, nodeState, frame, transform, drawLabels, shouldDrawClock):
if drawLabels:
self.drawEnvironment(surface,nodeState,frame,transform)
def draw_creature(self, surface, node_state, frame, transform, draw_labels:bool, should_draw_clock: bool):
if draw_labels:
self.draw_environment(surface, transform)

cellSurface = pygame.Surface(surface.get_size(), pygame.SRCALPHA, 32)
cell_surface = pygame.Surface(surface.get_size(), pygame.SRCALPHA, 32)
for x in range(self.sim.CW):
for y in range(self.sim.CH):
self.drawCell(cellSurface,nodeState,frame,transform,x,y)
surface.blit(cellSurface,(0,0))
self.draw_cell(cell_surface, node_state, frame, transform, x, y)
surface.blit(cell_surface,(0,0))

if drawLabels:
if draw_labels:
tx,ty,s = transform
avg_x = np.mean(nodeState[:,:,0],axis=(0,1))
avg_x = np.mean(node_state[:, :, 0], axis=(0, 1))
lx = tx+avg_x*s
ly = 20
lw = 100
lh = 36
ar = 15
pygame.draw.rect(surface, (255,0,0),(lx-lw/2,ly,lw,lh))
pygame.draw.polygon(surface,(255,0,0),((lx,ly+lh+ar),(lx-ar,ly+lh),(lx+ar,ly+lh)))
centerText(surface, f"{dist_to_text(avg_x,True,self.sim.UNITS_PER_METER)}", lx, ly+18, self.ui.WHITE, self.ui.smallFont)
center_text(surface, f"{dist_to_text(avg_x, True, self.sim.units_per_meter)}", lx, ly + 18, Color.WHITE, self.ui.small_font)

ratio = 1-frame/self.sim.trial_time
if shouldDrawClock:
drawClock(surface,[40,40,32],ratio,str(math.ceil(ratio*self.sim.trial_time/self.ui.FPS)),self.ui.smallFont)

if should_draw_clock:
draw_clock(surface, [40, 40, 32], ratio, str(math.ceil(ratio * self.sim.trial_time / self.ui.fps)), self.ui.small_font)


def drawIcon(self, ICON_DIM, BG_COLOR, BEAT_FADE_TIME):
icon = pygame.Surface(ICON_DIM, pygame.SRCALPHA, 32)
icon.fill(BG_COLOR)
transform = [ICON_DIM[0]/2,ICON_DIM[0]/(self.sim.CW+2),ICON_DIM[0]/(self.sim.CH+2.85)]
self.drawCreature(icon,self.calmState,BEAT_FADE_TIME,transform,False,False)
R = ICON_DIM[0]*0.09
R2 = ICON_DIM[0]*0.12
pygame.draw.circle(icon,speciesToColor(self.species, self.ui),(ICON_DIM[0]-R2,R2),R)
def draw_icon(self, icon_dim, bg_color, beat_fade_time: int) -> Surface:
icon: Surface = pygame.Surface(icon_dim, pygame.SRCALPHA, 32)
icon.fill(bg_color)
transform = [icon_dim[0] / 2, icon_dim[0] / (self.sim.CW + 2), icon_dim[0] / (self.sim.CH + 2.85)]
self.draw_creature(icon, self.calmState, beat_fade_time, transform, False, False)
r = icon_dim[0] * 0.09
r2 = icon_dim[0] * 0.12
pygame.draw.circle(icon, species_to_color(self.species, self.ui), (icon_dim[0] - r2, r2), r)

return icon

def saveCalmState(self, arr):
def save_calm_state(self, arr):
self.calmState = arr

def getMutatedDNA(self, sim):
def get_mutated_dna(self, sim):
mutation = np.clip(np.random.normal(0.0, 1.0, self.dna.shape[0]),-99,99)
result = self.dna + sim.mutation_rate*mutation
newSpecies = self.species
new_species = self.species

big_mut_loc = 0
if random.uniform(0,1) < self.sim.big_mutation_rate: # do a big mutation
newSpecies = sim.species_count
new_species = sim.species_count
sim.species_count += 1
cell_x = random.randint(0,self.sim.CW-1)
cell_y = random.randint(0,self.sim.CH-1)
Expand All @@ -120,33 +122,36 @@ def getMutatedDNA(self, sim):
if i == 2 and result[big_mut_loc+i] < 0.5:
result[big_mut_loc+i] = 0.5

return result, newSpecies, big_mut_loc
return result, new_species, big_mut_loc

def traitsToColor(self, dna, x, y, frame):
beat = self.sim.frameToBeat(frame)
beat_prev = (beat+self.sim.beats_per_cycle-1)%self.sim.beats_per_cycle
prog = self.sim.frameToBeatFade(frame)
def traits_to_color(self, dna, x, y, frame):
beat = self.sim.frame_to_beat(frame)
beat_prev = (beat+ self.sim.beats_per_cycle - 1) % self.sim.beats_per_cycle
prog = self.sim.frame_to_beat_fade(frame)

locationIndex = x*self.sim.CH+y
DNAIndex = (locationIndex*self.sim.beats_per_cycle+beat)*self.sim.traits_per_box
DNAIndex_prev = (locationIndex*self.sim.beats_per_cycle+beat_prev)*self.sim.traits_per_box
traits = dna[DNAIndex:DNAIndex+self.sim.traits_per_box]
traits_prev = dna[DNAIndex_prev:DNAIndex_prev+self.sim.traits_per_box]
traits = arrayLerp(traits_prev,traits,prog)
location_index = x * self.sim.CH + y
dna_index = (location_index*self.sim.beats_per_cycle+beat)*self.sim.traits_per_box
dna_index_prev = (location_index*self.sim.beats_per_cycle+beat_prev)*self.sim.traits_per_box

traits = dna[dna_index:dna_index+self.sim.traits_per_box]
traits_prev = dna[dna_index_prev:dna_index_prev+self.sim.traits_per_box]
traits = array_lerp(traits_prev, traits, prog)

red = min(max(int(128+traits[0]*128),0),255)
green = min(max(int(128+traits[1]*128),0),255)
alpha = min(max(int(155+traits[2]*100),64),255) #alpha can't go below 25%
color_result = (red,green,255,alpha)

if self.codonWithChange is not None:
nextGreen = 0
if self.codonWithChange >= DNAIndex and self.codonWithChange < DNAIndex+self.sim.traits_per_box:
nextGreen = 1
prevGreen = 0
if self.codonWithChange >= DNAIndex_prev and self.codonWithChange < DNAIndex_prev+self.sim.traits_per_box:
prevGreen = 1
green_ness = lerp(prevGreen,nextGreen,prog)
color_result = listLerp(color_result,(0,255,0,255),green_ness)
if self.codon_with_change is not None:
next_green = 0
if dna_index <= self.codon_with_change < dna_index+self.sim.traits_per_box:
next_green = 1

prev_green = 0
if dna_index_prev <= self.codon_with_change < dna_index_prev+self.sim.traits_per_box:
prev_green = 1

green_ness = lerp(prev_green,next_green,prog)
color_result = list_lerp(color_result, (0, 255, 0, 255), green_ness)

return color_result
Loading