Skip to content

xrayian/Life-Simulator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🧬 Life Simulator

A life simulation game written in Python. Create a character, watch them grow up, find love, build a career, buy a home, and pass their legacy on to the next generation — play in the terminal or through a modern web UI.


Table of Contents

  1. Getting Started
  2. Web Version
  3. How to Play
  4. Project Structure
  5. Module Reference
  6. API Reference
  7. Frontend Architecture
  8. Game Systems
  9. Bug Fixes & Improvements
  10. Dependencies

Getting Started

Prerequisites

Requirement Version Used By
Python 3.10+ Game engine
Node.js 18+ Web frontend
colorama any Terminal colors

Installation

# 1. Clone or download the project
cd Life_Sim

# 2. Create a virtual environment (recommended)
python -m venv .venv
.venv\Scripts\activate        # Windows
# source .venv/bin/activate   # macOS / Linux

# 3. Install Python dependencies
pip install colorama

# 4. Run the terminal game
python run.py

Web Version

The web version wraps the same game engine in a FastAPI backend and presents it through a Nuxt 3 frontend with a dark-themed, responsive dashboard.

Quick Start

# Install all dependencies and start both servers
python start_web.py

This launches:

  • API serverhttp://localhost:8000 (FastAPI + Uvicorn)
  • Frontend dev serverhttp://localhost:3000 (Nuxt 3)

Manual Setup

# Backend
pip install fastapi uvicorn[standard] colorama
# (or: pip install -r api/requirements.txt)

# Frontend
cd frontend
npm install
cd ..

# Start API (from project root)
uvicorn api.main:app --host 0.0.0.0 --port 8000

# Start frontend (in a second terminal)
cd frontend
npm run dev

Web Features

Feature Description
Dashboard layout 3-column grid: Event Log · Player Cards · NPC list
Mobile responsive Stacked layout with smart column ordering on small screens
Event timeline Scrollable, color-coded, year-grouped event log (max viewport)
Auto-scroll Event log scrolls to the latest year automatically
Clickable names Person names in events are links — click to open their card
Category icons Each event shows an emoji icon matching its category
Person modal Full detail view for any character with family navigation
Auto-advance Toggle to automatically progress years (1.5s interval)
Keyboard shortcuts Press Space or Enter to advance to the next year
Tombstones Deceased important characters displayed with memorial cards
Succession Seamless control transfer when the player character dies
Event bus Structured events with real names — no pronoun ambiguity

How to Play

The game is fully automatic — each year advances with a single press of Enter. In the web version you can also press Space to advance, or toggle Auto-advance for hands-free play. You observe your character's life unfold: school, university, career, relationships, home purchases, car ownership, and eventually death.

When the player character dies, control transfers to their eldest living child, preserving the family legacy. If no children survive, the oldest person in the simulation takes over.

Important deceased characters are displayed as tombstones (🪦) so you can track your family history.

Key Mechanics

Mechanic Description
Stats Health ❤️, Happiness 😀, Intelligence 🧠, Mental Health ☮️
Personality Goodness, Lust, Ambition (0–100 each)
Aspirations Family, Wealth, Knowledge, Romance, Career
Education University enrollment at 18 if smart ≥ 50; 4-year degree
Work Three job tiers with promotion paths
Housing Parents → Apartment → House
Cars Economy / Luxury / Supercar tiers
Legacy Inheritance splits among spouse + children on death

Project Structure

Life_Sim/
├── run.py                  # Terminal game entry point
├── start_web.py            # Launches API + frontend servers
├── README.md               # This file
├── .gitignore
│
├── lifesim/                # Core game engine package
│   ├── __init__.py
│   ├── constants.py         # Enumerations & static config
│   ├── utils.py             # Shared helpers & person factory
│   ├── assets.py            # Car / Manufacturer definitions
│   ├── systems.py           # Education, Housing, Work subsystems
│   ├── person.py            # Central Person class
│   ├── events.py            # Random events, death, inheritance
│   ├── event_bus.py         # Structured event emitter (replaces print)
│   ├── display.py           # Terminal rendering / UI output
│   └── sim.py               # Terminal simulation loop
│
├── api/                    # Web backend (FastAPI)
│   ├── main.py              # FastAPI app, routes, CORS
│   ├── engine.py            # Headless engine wrapper (event bus)
│   ├── requirements.txt     # Python dependencies for the API
│   └── test_engine.py       # Engine smoke tests
│
└── frontend/               # Web frontend (Nuxt 3)
    ├── nuxt.config.ts       # Nuxt configuration
    ├── tailwind.config.ts   # Tailwind CSS theme & colors
    ├── package.json
    ├── tsconfig.json
    ├── app.vue              # Root Vue component
    ├── pages/
    │   └── index.vue        # Main dashboard page
    ├── components/
    │   ├── PlayerCard.vue    # Player identity & portrait
    │   ├── StatsCard.vue     # Health, happiness, etc. bars
    │   ├── CareerCard.vue    # Work, education, housing info
    │   ├── FinanceCard.vue   # Bank balance, car, assets
    │   ├── FamilyCard.vue    # Partner & children list
    │   ├── EventLog.vue      # Scrollable event timeline
    │   ├── NPCList.vue       # Non-player character sidebar
    │   ├── TombstoneList.vue # Deceased character memorials
    │   ├── PersonModal.vue   # Full person detail overlay
    │   └── StatBar.vue       # Reusable progress bar
    ├── composables/
    │   └── useGame.ts       # Game state management & API calls
    └── types/
        └── game.ts          # TypeScript interfaces

Dependency Graph (Terminal)

run.py
 └── sim.py
      ├── constants.py
      ├── utils.py ──────► person.py  (lazy import)
      ├── event_bus.py
      ├── events.py
      │    └── constants.py
      ├── display.py
      │    └── constants.py
      └── person.py
           ├── constants.py
           ├── event_bus.py
           ├── systems.py
           │    ├── constants.py
           │    └── event_bus.py
           ├── assets.py
           ├── utils.py
           └── events.py  (lazy import in get_older)

Dependency Graph (Web)

start_web.py
 ├── api/main.py
 │    └── api/engine.py
 │         ├── lifesim/* (same engine, event bus in silent mode)
 │         └── event_bus.collect() → structured event dicts
 └── frontend/
      └── pages/index.vue
           ├── composables/useGame.ts  →  HTTP  →  api/main.py
           └── components/*.vue

Circular-import strategy: construct_person() lives in utils.py and uses a lazy import of Person from person.py inside the function body, breaking the import cycle.


Module Reference

constants.py

Static configuration — no runtime state.

Symbol Type Description
Gender.MALE str 'M'
Gender.FEMALE str 'F'
Gender.label(code) static Returns "boy" / "girl" for display
RelationshipStatus class SINGLE, IN_RELATIONSHIP, MARRIED, DIVORCED, WIDOWED
SpecialRole class PLAYER, FATHER, MOTHER, BROTHER, SISTER, SON, DAUGHTER, PARTNER, GIRLFRIEND, BOYFRIEND, WIFE, HUSBAND
Aspiration class FAMILY, WEALTH, KNOWLEDGE, ROMANCE, CAREER + ALL list

utils.py

Shared helpers and the person factory.

Symbol Signature Description
clamp() clamp(value, lo=0, hi=100) → int Clamp a numeric value into a bounded range
construct_person() construct_person(min_age, max_age, gender, special=None, family_name=None) → Person Safe factory with bounds guard
MALE_NAMES list[str] 26 male first names
FEMALE_NAMES list[str] 24 female first names
LAST_NAMES list[str] 24 surnames

assets.py

Vehicle definitions and the car-selection factory.

Symbol Description
Manufacturer Data holder: name, _cars, _prices, random_car()
Car Owned vehicle: manufacturer, model, price, years_owned
choose_car() choose_car(price_point) → (name, model, price) — picks from economy / luxury / sports catalogues

Catalogues (module-level, pre-built):

Catalogue Brands Price Range
_ECONOMY Toyota, Nissan, Honda, Ford, Chevrolet, Hyundai $16k – $40k
_LUXURY BMW, Mercedes, Audi, Lexus, Tesla $40k – $140k
_SPORTS McLaren, Lamborghini, Ferrari, Bugatti, Porsche $120k – $3M

systems.py

Subsystem classes — each holds a back-reference to its owning Person.

Education

Method Trigger Effect
university_check() Age == 18, smart ≥ 50 Enrolls in university, takes $40k loan
progress() Each year Increments study years; graduates at 4
pay_loan() Each year Deducts $3k/yr until loan is $0

Housing

Method Trigger Effect
check_housing() Each year Parents → Apartment (age 22+) → House
pay_costs() Each year Rent ($12k/yr) or mortgage ($15k/yr)

Work

Tier Example Roles Salary Range
1 CEO, CTO, Surgeon, Judge $150k – $500k
2 Developer, Engineer, Nurse, Teacher $45k – $85k
3 Intern, Janitor, Cashier, Barista $15k – $30k
Method Description
payment() Adds yearly salary to bank balance
check_promotion() Evaluates tenure + stats + aspiration for tier climb

Promotion from Tier 3→2 requires 4–10 years; Tier 2→1 requires 10–30 years, influenced by smart, ambition, degree, and the Career aspiration.


person.py

The central Person class.

Key Attributes

Attribute Type Description
alive bool Living/dead flag
is_controlled bool Currently the player character
was_player bool Was ever player-controlled
important bool Preserved in display after death
parents list[Person] Father + Mother references
children list[Person] All biological children
partner Person | None Current romantic partner
status str RelationshipStatus value
aspiration str Lifetime goal (influences behaviour)

Key Methods

Method Called By Purpose
get_older(people_list) Simulation Full yearly lifecycle tick
clamp_stats() Internal Keeps all stats within 0–100
death(death_year) death_check() Marks person dead, widows partner
_handle_relationships() get_older() Partner finding, breakups, marriage, kids
_try_buy_car() get_older() Attempt vehicle purchase

events.py

World events that act on a Person from the outside.

Function Signature Description
random_event(person) person: Person → None 1-in-50 chance of a lottery/mugging/etc.
death_check(person, yr) person: Person, current_year: int → bool Age + health mortality roll; returns True if died
distribute_inheritance(deceased, people) deceased: Person, people: list → None Splits estate among spouse + children

event_bus.py

Lightweight event bus that replaces raw print() calls throughout the game code. All gameplay events (promotions, deaths, relationships, etc.) are emitted as structured dicts instead of being printed to stdout.

Symbol Signature Description
emit() emit(category, message, person=None) Record one event with category + person ref
collect() collect() → list[dict] Return all pending events and clear the buffer
set_mode() set_mode("console" | "silent") Console mode also prints; silent collects only
set_context() set_context(person) Auto-attach person to subsequent emit() calls
clear() clear() Discard all pending events

Modes:

Mode Behaviour Used By
console Print with emoji + colour and collect Terminal game
silent Collect only (no stdout) Web API engine

Categories: death, inheritance, love, marriage, breakup, baby, family, career, education, housing, finance, car, event, mental, info.


display.py

All terminal output formatting.

Function Purpose
render_player_card() Full HUD with stats, job, housing, car, bank
render_npc_card() Compact one-liner for non-player characters
render_tombstone() Memorial line for deceased important characters
render_year_header() Year banner with alive population count

sim.py

The game engine — owns year and people, orchestrates the loop.

Method Description
start() Creates family, enters infinite year loop
_create_family() Generates father, mother, player, optional siblings
_next_year() Ages everyone, checks death, renders, awaits input
_handle_succession() Transfers control to heir after player death

API Reference

The FastAPI backend (api/main.py) exposes a JSON API that the frontend consumes. Game state is held in-memory per session.

Endpoints

Method Path Description
POST /api/game/new Create a new game — returns initial state
POST /api/game/{id}/advance Advance one year — returns updated state
GET /api/game/{id}/state Fetch current state without advancing
GET /api/game/{id}/person/{pid} Fetch full details for a single person

Event Model

Events are emitted by the game engine via the event bus (lifesim/event_bus.py). Each event is a structured dict with the person's actual name baked into the message — no pronoun rewriting or stdout capture required.

{
  "category": "career",
  "message": "Promotion! Alice got promoted to a Developer!",
  "person_id": "a1b2c3d4",
  "person_name": "Alice Smith"
}

Categories: death, inheritance, love, marriage, breakup, baby, family, career, education, housing, finance, car, event, mental, info.

Every event includes the character's full name directly in the message text — no ambiguous "He" / "She" / "I" pronouns.

engine.py — Headless Engine Wrapper

Class / Function Purpose
GameEngine Wraps the lifesim engine for API use
_bus_events_to_dicts() Converts event bus entries → JSON-safe dicts with IDs
_person_id() Returns a stable UUID-based ID for each person
serialise_person() Converts a Person instance to a JSON-safe dict

Frontend Architecture

Built with Nuxt 3, Vue 3, Tailwind CSS, and TypeScript.

Components

Component Purpose
PlayerCard.vue Hero card showing player name, age, gender, aspiration
StatsCard.vue Four stat bars (health, happiness, intelligence, mental)
CareerCard.vue Work role, education status, housing type
FinanceCard.vue Bank balance, car info, financial summary
FamilyCard.vue Partner and children with clickable links
EventLog.vue Scrollable, year-grouped, color-coded event timeline
NPCList.vue Sidebar listing all non-player characters
TombstoneList.vue Memorial cards for deceased important characters
PersonModal.vue Full-detail overlay with family tree navigation
StatBar.vue Reusable animated progress bar with color theming

Composable: useGame.ts

Manages all game state and API communication:

Export Type Description
state Ref<GameState> Reactive game state
loading Ref<boolean> Request in-flight flag
autoAdvance Ref<boolean> Auto-year toggle
newGame() () → Promise Start a fresh game
advanceYear() () → Promise Progress one year
fetchPerson(id) (id) → Promise<Person> Fetch full person details
player ComputedRef Current player character
npcs ComputedRef Non-player characters
tombstones ComputedRef Deceased important characters
allEvents ComputedRef Full event history
latestEvents ComputedRef Events from the current year

Responsive Layout

Breakpoint Layout
Mobile Single column — Player cards → This Year/NPCs → Event Log
sm (640px) Two-column stat/career/finance grids
lg (1024px) Full 3-column dashboard — viewport-height locked, all columns independently scrollable

Game Systems

Aspiration Influence Matrix

Aspiration Stat Nudge Gameplay Effect
Family Goodness +10 Higher child/sibling chance, earlier marriage
Wealth Ambition +20 Buys cars more often, lower car thresholds
Knowledge Smart +10 3× university enrollment chance
Romance Lust +20 Finds partners faster, more relationship events
Career Ambition +20 Faster promotions, 2× enrollment chance

Death Probability Table

Condition Added Death Chance (per 1000)
Age > 40 10
Age > 65 50
Age > 80 100
Age > 100 500
Age ≥ 115 Guaranteed
Health < 20 +200
Health < 10 +300 (cumulative)
Health ≤ 0 +1000 (guaranteed)
Freak accident (16+) 1-in-500

Inheritance Distribution

  1. Estate = bank balance + 40% car value + (house value − mortgage)
  2. If married spouse is alive → included in heir list
  3. All living children → included in heir list
  4. Estate split equally among all heirs

Bug Fixes & Improvements

The modular rewrite addressed 11 bugs found in the original monolithic codebase:

# Bug Fix
1 Person never initialised self.parents Added self.parents = [] in __init__
2 death_check() ran on already-dead people Early if not person.alive: return False guard
3 Breaking up removed important partners forever Guard: skip removal if ex.important or ex.was_player
4 construct_person crashed on negative age range max_age = max(max_age, min_age) + max(16, …) partner guard
5 Marriage proposal fired repeatedly each year self.status != MARRIED check before proposing
6 relationship_status property was unused Replaced with self.status, set on partnering + breakup
7 buy_car() was defined but never called Integrated as _try_buy_car() in get_older() cycle
8 Population count included dead important people Uses alive_count = sum(… if p.alive) for header
9 smart stat overflowed above 100 clamp_stats() applied after every mutation
10 Unused imports scattered across files Clean imports in every new module
11 Sibling birth printed raw gender code M/F Replaced with Gender.label()"boy" / "girl"
+ Partner left as IN_RELATIONSHIP after death Now set to WIDOWED in Person.death()
+ Events showed "He/She/I" instead of names Event bus emits person's real name in every message
+ Web events had --CATEGORY prefixes Replaced stdout capture with structured event bus
+ person_id missing on many web events Event bus attaches person ref; engine maps to stable ID
+ Event log expanded past viewport on desktop Dashboard locked to viewport height with overflow scroll
+ Random events only fired for the player random_event() now fires for all alive characters

Dependencies

Python (Game Engine + API)

Package Purpose Install
colorama Cross-platform terminal colors pip install colorama
fastapi Web API framework pip install fastapi
uvicorn[standard] ASGI server pip install uvicorn[standard]
pip install -r api/requirements.txt

Node.js (Frontend)

Package Purpose
nuxt ^3.14 Vue 3 meta-framework
vue ^3.5 Reactive UI library
@nuxtjs/tailwindcss Utility-first CSS
@nuxtjs/google-fonts Font loading
typescript ^5.6 Type safety
cd frontend && npm install

Built with ❤️ in Python + Vue.

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors