Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
Binary file modified .coverage
Binary file not shown.
450 changes: 277 additions & 173 deletions cov.xml

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions src/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,14 @@ def __init__(self):
self.earlyGameGracePeriod = 50 # Number of ticks of protection for player
self.playerDamageReduction = 0.4 # 40% damage reduction for player during grace period
# During grace period, other creatures have 85% chance to avoid attacking player

# Population control settings to prevent lag
self.maxEntities = 50 # Starting maximum number of entities (will adjust dynamically)
self.minEntities = 20 # Minimum entities to maintain for gameplay
self.maxEntitiesLimit = 200 # Hard upper limit for entities
self.entityCullThreshold = 0.9 # Cull entities when population reaches 90% of max
self.entityLogMaxSize = 50 # Maximum number of log entries per entity to prevent memory bloat

# Dynamic performance monitoring settings
self.lagThreshold = 0.05 # Tick time in seconds that indicates lag (50ms)
self.performanceWindow = 10 # Number of recent ticks to analyze for performance
33 changes: 20 additions & 13 deletions src/entity/livingEntity.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ def getNextAction(self, kreature):
return "befriend"

def reproduce(self, kreature):
self.log.append("%s made a baby with %s!" % (self.name, kreature.name))
kreature.log.append("%s made a baby with %s!" % (kreature.name, self.name))
self.addLogEntry("%s made a baby with %s!" % (self.name, kreature.name))
kreature.addLogEntry("%s made a baby with %s!" % (kreature.name, self.name))
self.stats.numOffspring += 1
kreature.stats.numOffspring += 1
# Return the parent entities so the child can be created with proper references
Expand All @@ -64,20 +64,20 @@ def fight(self, kreature):

kreature.health -= damage
if kreature.health <= 0:
self.log.append(
self.addLogEntry(
"%s fought and ate %s!" % (self.name, kreature.name)
)
kreature.log.append(
kreature.addLogEntry(
"%s was eaten by %s!" % (kreature.name, self.name)
)
self.stats.numCreaturesEaten += 1
break
else:
self.log.append(
self.addLogEntry(
"%s fought %s and dealt %d damage!"
% (self.name, kreature.name, damage)
)
kreature.log.append(
kreature.addLogEntry(
"%s took %d damage from %s! Health: %d"
% (kreature.name, damage, self.name, kreature.health)
)
Expand All @@ -92,25 +92,25 @@ def fight(self, kreature):

self.health -= damage
if self.health <= 0:
kreature.log.append(
kreature.addLogEntry(
"%s fought and ate %s!" % (kreature.name, self.name)
)
self.log.append("%s was eaten by %s!" % (self.name, kreature.name))
self.addLogEntry("%s was eaten by %s!" % (self.name, kreature.name))
kreature.stats.numCreaturesEaten += 1
break
else:
kreature.log.append(
kreature.addLogEntry(
"%s fought %s and dealt %d damage!"
% (kreature.name, self.name, damage)
)
self.log.append(
self.addLogEntry(
"%s took %d damage from %s! Health: %d"
% (self.name, damage, kreature.name, self.health)
)

def befriend(self, kreature):
self.log.append("%s made friends with %s!" % (self.name, kreature.name))
kreature.log.append("%s made friends with %s!" % (kreature.name, self.name))
self.addLogEntry("%s made friends with %s!" % (self.name, kreature.name))
kreature.addLogEntry("%s made friends with %s!" % (kreature.name, self.name))
self.friends.append(kreature)
kreature.friends.append(
self
Expand Down Expand Up @@ -159,7 +159,14 @@ def regenerateHealth(self):
self.health = min(self.health + regeneration, self.maxHealth)
# Only log significant regeneration events to avoid spam
if regeneration >= 2:
self.log.append(
self.addLogEntry(
"%s regenerated %d health! Health: %d/%d"
% (self.name, regeneration, self.health, self.maxHealth)
)

def addLogEntry(self, message, maxLogSize=50):
"""Add a log entry and maintain log size limit"""
self.log.append(message)
# Keep only the most recent entries to prevent memory bloat
if len(self.log) > maxLogSize:
self.log = self.log[-maxLogSize:]
95 changes: 88 additions & 7 deletions src/kreatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,26 @@ def __init__(self):
self.config = Config()
self.tick = 0

# Performance monitoring for dynamic entity limits
self.tickTimes = [] # Store recent tick times for lag detection

# Initialize player early-game protection
self.playerCreature.damageReduction = self.config.playerDamageReduction
self.playerCreature.log.append("%s has early-game protection!" % self.playerCreature.name)
self.playerCreature.addLogEntry("%s has early-game protection!" % self.playerCreature.name)

def initiateEntityActions(self):
entities_to_remove = [] # Track entities that die this turn

for entity in self.environment.getEntities():
target = self.environment.getRandomEntity()

if target == entity:
if target == entity or target is None:
continue

decision = entity.getNextAction(target)

if decision == "nothing":
entity.log.append(
entity.addLogEntry(
"%s had an argument with %s!" % (entity.name, target.name)
)
elif decision == "love":
Expand All @@ -70,7 +73,7 @@ def initiateEntityActions(self):
# During grace period, 85% chance to skip attacking the player
if (self.tick < self.config.earlyGameGracePeriod and
random.randint(1, 100) <= 85):
entity.log.append(
entity.addLogEntry(
"%s decided not to attack %s." % (entity.name, target.name)
)
continue
Expand All @@ -91,14 +94,35 @@ def initiateEntityActions(self):
# Remove all entities that died this turn
for entity in entities_to_remove:
self.environment.removeEntity(entity)

# Manage population to prevent lag
self.managePopulation()

def updatePlayerProtection(self):
"""Update player protection based on current tick"""
if self.tick >= self.config.earlyGameGracePeriod:
# Grace period has ended
if hasattr(self.playerCreature, 'damageReduction') and self.playerCreature.damageReduction > 0:
self.playerCreature.damageReduction = 0
self.playerCreature.log.append("%s's protection has worn off!" % self.playerCreature.name)
self.playerCreature.addLogEntry("%s's protection has worn off!" % self.playerCreature.name)

def managePopulation(self):
"""Manage entity population to prevent performance issues"""
current_count = self.environment.getNumEntities()

# Check if we need to cull entities
cull_threshold = int(self.config.maxEntities * self.config.entityCullThreshold)

if current_count > cull_threshold:
target_count = int(self.config.maxEntities * 0.7) # Reduce to 70% of max
removed_entities = self.environment.cullWeakestEntities(target_count, self.playerCreature)

if removed_entities:
print(f"Population management: Removed {len(removed_entities)} weak entities (Population: {current_count} -> {self.environment.getNumEntities()})")

def canCreateNewEntity(self):
"""Check if we can create a new entity without exceeding limits"""
return self.environment.getNumEntities() < self.config.maxEntities

def regenerateAllEntities(self):
"""Regenerate health for all living entities"""
Expand All @@ -107,11 +131,20 @@ def regenerateAllEntities(self):
entity.regenerateHealth()

def createEntity(self):
if not self.canCreateNewEntity():
return None
newEntity = LivingEntity(self.names[random.randint(0, len(self.names) - 1)])
self.environment.addEntity(newEntity)
return newEntity

def createChildEntity(self, parent1, parent2):
"""Create a child entity with proper parent-child relationships"""
if not self.canCreateNewEntity():
# Population limit reached, no new child can be created
parent1.addLogEntry(f"{parent1.name} and {parent2.name} tried to have a child, but the world is too crowded!")
parent2.addLogEntry(f"{parent1.name} and {parent2.name} tried to have a child, but the world is too crowded!")
return None

childName = self.names[random.randint(0, len(self.names) - 1)]
child = LivingEntity(childName)

Expand All @@ -130,8 +163,9 @@ def createChildEntity(self, parent1, parent2):
child.health = parentHealthAvg + random.randint(-10, 10) # Add some variation
child.maxHealth = child.health

child.log.append(
"%s is the child of %s and %s." % (childName, parent1.name, parent2.name)
child.addLogEntry(
"%s is the child of %s and %s." % (childName, parent1.name, parent2.name),
self.config.entityLogMaxSize
)

self.environment.addEntity(child)
Expand Down Expand Up @@ -192,6 +226,37 @@ def continueAsChild(self):

return False

def monitorPerformance(self, tick_duration):
"""Monitor tick performance and adjust max entities dynamically"""
# Track recent tick times
self.tickTimes.append(tick_duration)

# Keep only recent performance window
if len(self.tickTimes) > self.config.performanceWindow:
self.tickTimes = self.tickTimes[-self.config.performanceWindow:]

# Only adjust after we have some data
if len(self.tickTimes) >= 5:
avg_tick_time = sum(self.tickTimes) / len(self.tickTimes)
self.adjustMaxEntitiesBasedOnLag(avg_tick_time)

def adjustMaxEntitiesBasedOnLag(self, avg_tick_time):
"""Dynamically adjust max entities based on performance"""
current_max = self.config.maxEntities

if avg_tick_time > self.config.lagThreshold:
# Performance is poor, reduce max entities
new_max = max(self.config.minEntities, int(current_max * 0.8))
if new_max != current_max:
self.config.maxEntities = new_max
print(f"Performance lag detected (avg: {avg_tick_time:.3f}s). Reducing max entities to {new_max}")
elif avg_tick_time < self.config.lagThreshold * 0.5:
# Performance is good, cautiously increase max entities
new_max = min(self.config.maxEntitiesLimit, int(current_max * 1.1))
if new_max != current_max and self.environment.getNumEntities() > current_max * 0.8:
self.config.maxEntities = new_max
print(f"Good performance (avg: {avg_tick_time:.3f}s). Increasing max entities to {new_max}")

def printSummary(self):
print("=== Summary ===")
if self.playerCreature.chanceToFight > self.playerCreature.chanceToBefriend:
Expand Down Expand Up @@ -227,6 +292,12 @@ def printSummary(self):
print("%s died during the simulation." % self.playerCreature.name)
print("Kreatures still alive: %d" % self.environment.getNumEntities())
print("Simulation ran for %d ticks." % self.tick)

# Show performance and dynamic entity limit info
if self.tickTimes:
avg_tick_time = sum(self.tickTimes) / len(self.tickTimes)
print("Average tick time: %.4f seconds" % avg_tick_time)
print("Final max entities limit: %d (started at 50)" % self.config.maxEntities)

def printStats(self):
print("=== Stats ===")
Expand All @@ -252,9 +323,19 @@ def run(self):
except: # if list is empty, just keep going
pass

# Monitor performance and run simulation tick
tick_start_time = time.time()

self.initiateEntityActions()
self.updatePlayerProtection() # Update player protection status
self.regenerateAllEntities() # Regenerate health for all entities

tick_end_time = time.time()
tick_duration = tick_end_time - tick_start_time

# Track performance and adjust entity limits dynamically
self.monitorPerformance(tick_duration)

time.sleep(self.config.tickLength)
self.tick += 1
if self.tick >= self.config.maxTicks:
Expand Down
30 changes: 29 additions & 1 deletion src/world/world.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ def __init__(self):
self.Jasper = LivingEntity("Jasper")

self.starterEntities = [
"placeholder",
self.Alison,
self.Barry,
self.Conrad,
Expand Down Expand Up @@ -52,4 +51,33 @@ def getEntities(self):
return self.entities

def getRandomEntity(self):
if len(self.entities) == 0:
return None
return self.entities[random.randint(0, len(self.entities) - 1)]

def cullWeakestEntities(self, targetCount, protectedEntity=None):
"""Remove the weakest entities to reduce population to targetCount"""
if len(self.entities) <= targetCount:
return []

# Create list of entities that can be culled (excluding protected entity)
cullable_entities = [e for e in self.entities if e != protectedEntity and hasattr(e, 'health')]

if len(cullable_entities) == 0:
return []

# Sort by health (weakest first), then by number of children (fewer children first)
cullable_entities.sort(key=lambda x: (x.health, len(getattr(x, 'children', []))))

# Calculate how many to remove
entities_to_remove = len(self.entities) - targetCount
entities_to_remove = min(entities_to_remove, len(cullable_entities))

# Remove the weakest entities
removed_entities = []
for i in range(entities_to_remove):
entity = cullable_entities[i]
self.removeEntity(entity)
removed_entities.append(entity)

return removed_entities
Loading