villsim/backend/domain/religion.py

338 lines
11 KiB
Python

"""Religion system for the Village Simulation.
Creates diverse religious beliefs that affect agent behavior:
- Each agent has a religion (or atheist)
- Faith level affects actions and decisions
- Same-religion agents cooperate better
- Different religions can create conflict
- High faith agents become zealots
"""
import random
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class ReligionType(Enum):
"""Types of religions in the simulation.
These represent different belief systems with unique characteristics.
"""
ATHEIST = "atheist" # No religion - neutral
SOLARIS = "solaris" # Sun worshippers - value energy and activity
AQUARIUS = "aquarius" # Water worshippers - value water and peace
TERRANUS = "terranus" # Earth worshippers - value resources and hoarding
IGNIS = "ignis" # Fire worshippers - value heat and trade
NATURIS = "naturis" # Nature worshippers - value gathering and sustainability
# Religion characteristics
RELIGION_TRAITS = {
ReligionType.ATHEIST: {
"description": "No religious belief",
"bonus_action": None,
"preferred_resource": None,
"aggression": 0.0,
"conversion_resistance": 0.3,
},
ReligionType.SOLARIS: {
"description": "Worshippers of the Sun",
"bonus_action": "hunt", # Sun gives strength to hunt
"preferred_resource": "meat",
"aggression": 0.4, # Moderate aggression
"conversion_resistance": 0.6,
},
ReligionType.AQUARIUS: {
"description": "Worshippers of Water",
"bonus_action": "get_water",
"preferred_resource": "water",
"aggression": 0.1, # Peaceful religion
"conversion_resistance": 0.7,
},
ReligionType.TERRANUS: {
"description": "Worshippers of the Earth",
"bonus_action": "gather",
"preferred_resource": "berries",
"aggression": 0.2,
"conversion_resistance": 0.8,
},
ReligionType.IGNIS: {
"description": "Worshippers of Fire",
"bonus_action": "trade", # Fire of commerce
"preferred_resource": "wood",
"aggression": 0.5, # Hot-tempered
"conversion_resistance": 0.5,
},
ReligionType.NATURIS: {
"description": "Worshippers of Nature",
"bonus_action": "gather",
"preferred_resource": "berries",
"aggression": 0.15, # Peaceful
"conversion_resistance": 0.75,
},
}
@dataclass
class ReligiousBeliefs:
"""An agent's religious beliefs and faith state."""
religion: ReligionType = ReligionType.ATHEIST
faith: int = 50 # 0-100, loaded from config
# Historical conversion tracking
times_converted: int = 0
last_prayer_turn: int = -1
# Zealot state
is_zealot: bool = False
# Religious influence
converts_made: int = 0
sermons_given: int = 0
def __post_init__(self):
self._update_zealot_status()
def _update_zealot_status(self) -> None:
"""Update zealot status based on faith level."""
from backend.config import get_config
config = get_config()
threshold = int(config.religion.zealot_threshold * 100)
self.is_zealot = self.faith >= threshold
@property
def traits(self) -> dict:
"""Get traits for current religion."""
return RELIGION_TRAITS.get(self.religion, RELIGION_TRAITS[ReligionType.ATHEIST])
@property
def description(self) -> str:
"""Get religion description."""
return self.traits["description"]
@property
def is_religious(self) -> bool:
"""Check if agent has a religion."""
return self.religion != ReligionType.ATHEIST
@property
def conversion_resistance(self) -> float:
"""Get resistance to conversion."""
base = self.traits.get("conversion_resistance", 0.5)
# Higher faith = harder to convert
faith_modifier = self.faith / 100 * 0.3
return min(0.95, base + faith_modifier)
def gain_faith(self, amount: int) -> None:
"""Increase faith level."""
self.faith = min(100, self.faith + amount)
self._update_zealot_status()
def lose_faith(self, amount: int) -> None:
"""Decrease faith level."""
self.faith = max(0, self.faith - amount)
self._update_zealot_status()
def apply_decay(self) -> None:
"""Apply faith decay per turn (if not recently prayed)."""
from backend.config import get_config
decay = get_config().agent_stats.faith_decay
self.faith = max(0, self.faith - decay)
self._update_zealot_status()
def convert_to(self, new_religion: ReligionType, faith_level: int = 30) -> bool:
"""Attempt to convert to a new religion."""
if new_religion == self.religion:
return False
# Check conversion resistance
if random.random() < self.conversion_resistance:
return False
self.religion = new_religion
self.faith = faith_level
self.times_converted += 1
self._update_zealot_status()
return True
def record_prayer(self, turn: int) -> None:
"""Record that prayer was performed."""
self.last_prayer_turn = turn
def record_conversion(self) -> None:
"""Record a successful conversion made."""
self.converts_made += 1
def record_sermon(self) -> None:
"""Record a sermon given."""
self.sermons_given += 1
def get_trade_modifier(self, other: "ReligiousBeliefs") -> float:
"""Get trade modifier when dealing with another agent's religion."""
from backend.config import get_config
config = get_config()
if self.religion == ReligionType.ATHEIST or other.religion == ReligionType.ATHEIST:
return 1.0 # No modifier for atheists
if self.religion == other.religion:
# Same religion bonus
bonus = config.religion.same_religion_bonus
# Zealots get extra bonus
if self.is_zealot and other.is_zealot:
bonus *= 1.5
return 1.0 + bonus
else:
# Different religion penalty
penalty = config.religion.different_religion_penalty
# Zealots are more hostile to other religions
if self.is_zealot:
penalty *= 1.5
return 1.0 - penalty
def should_convert_other(self, other: "ReligiousBeliefs") -> bool:
"""Check if agent should try to convert another agent."""
if not self.is_religious:
return False
if self.religion == other.religion:
return False
# Zealots always want to convert
if self.is_zealot:
return True
# High faith agents sometimes want to convert
return random.random() < (self.faith / 100) * 0.5
def is_hostile_to(self, other: "ReligiousBeliefs") -> bool:
"""Check if religiously hostile to another agent."""
if not self.is_religious or not other.is_religious:
return False
if self.religion == other.religion:
return False
from backend.config import get_config
config = get_config()
# Only zealots are hostile
if not self.is_zealot:
return False
# Check if faith is high enough for holy war
if self.faith >= config.religion.holy_war_threshold * 100:
return True
return random.random() < self.traits.get("aggression", 0.0)
def to_dict(self) -> dict:
"""Convert to dictionary for serialization."""
return {
"religion": self.religion.value,
"faith": self.faith,
"is_zealot": self.is_zealot,
"times_converted": self.times_converted,
"converts_made": self.converts_made,
"description": self.description,
}
def generate_random_religion(archetype: Optional[str] = None) -> ReligiousBeliefs:
"""Generate random religious beliefs for an agent.
Args:
archetype: Optional personality archetype that influences religion
"""
from backend.config import get_config
config = get_config()
# Get available religions
religions = list(ReligionType)
# Weight by archetype
weights = [1.0] * len(religions)
if archetype == "hunter":
# Hunters favor Solaris (sun/strength)
weights[religions.index(ReligionType.SOLARIS)] = 3.0
weights[religions.index(ReligionType.IGNIS)] = 2.0
elif archetype == "gatherer":
# Gatherers favor Naturis/Terranus
weights[religions.index(ReligionType.NATURIS)] = 3.0
weights[religions.index(ReligionType.TERRANUS)] = 2.0
elif archetype == "trader":
# Traders favor Ignis (commerce)
weights[religions.index(ReligionType.IGNIS)] = 3.0
weights[religions.index(ReligionType.AQUARIUS)] = 2.0 # Water trade routes
elif archetype == "woodcutter":
weights[religions.index(ReligionType.TERRANUS)] = 2.0
weights[religions.index(ReligionType.NATURIS)] = 1.5
# Atheists are uncommon - lower base weight
weights[religions.index(ReligionType.ATHEIST)] = 0.2
# Weighted random selection
total = sum(weights)
r = random.random() * total
cumulative = 0
chosen_religion = ReligionType.ATHEIST
for i, (religion, weight) in enumerate(zip(religions, weights)):
cumulative += weight
if r <= cumulative:
chosen_religion = religion
break
# Starting faith varies
if chosen_religion == ReligionType.ATHEIST:
starting_faith = random.randint(0, 20)
else:
starting_faith = random.randint(30, 70)
return ReligiousBeliefs(
religion=chosen_religion,
faith=starting_faith,
)
def get_religion_compatibility(religion1: ReligionType, religion2: ReligionType) -> float:
"""Get compatibility score between two religions (0-1)."""
if religion1 == religion2:
return 1.0
if religion1 == ReligionType.ATHEIST or religion2 == ReligionType.ATHEIST:
return 0.7 # Atheists are neutral
# Compatible pairs
compatible_pairs = [
(ReligionType.NATURIS, ReligionType.AQUARIUS), # Nature and water
(ReligionType.TERRANUS, ReligionType.NATURIS), # Earth and nature
(ReligionType.SOLARIS, ReligionType.IGNIS), # Sun and fire
]
# Hostile pairs
hostile_pairs = [
(ReligionType.AQUARIUS, ReligionType.IGNIS), # Water vs fire
(ReligionType.SOLARIS, ReligionType.AQUARIUS), # Sun vs water
]
pair = (religion1, religion2)
reverse_pair = (religion2, religion1)
if pair in compatible_pairs or reverse_pair in compatible_pairs:
return 0.8
if pair in hostile_pairs or reverse_pair in hostile_pairs:
return 0.3
return 0.5 # Neutral
def get_religion_action_bonus(religion: ReligionType, action_type: str) -> float:
"""Get action bonus/penalty for a religion performing an action."""
traits = RELIGION_TRAITS.get(religion, {})
bonus_action = traits.get("bonus_action")
if bonus_action == action_type:
return 1.15 # 15% bonus for favored action
return 1.0