338 lines
11 KiB
Python
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
|
|
|