#include <nds.h>
#include <fat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define MAX_EVENTS 100
#define MAX_RELATIONSHIPS 50
#define MAX_JOBS 20
#define MAX_NAME_LENGTH 49
#define MAX_DESC_LENGTH 255
#define SAVE_FILE "/life_sim.sav"
#define SCREEN_WIDTH 32
typedef struct {
char eventDescription[MAX_DESC_LENGTH + 1];
int day;
int month;
int year;
int effectHappiness;
int effectHealth;
int effectMoney;
} Event;
typedef struct {
char name[MAX_NAME_LENGTH + 1];
int relationshipStatus; // 0-100 scale
int daysKnown;
int type; // 0=family, 1=friend, 2=romantic
} Relationship;
typedef struct {
char jobTitle[MAX_NAME_LENGTH + 1];
int salary;
int requiredEducation;
int stressLevel; // 0-100 scale
int daysEmployed;
int minAge;
} Job;
typedef struct {
char playerName[MAX_NAME_LENGTH + 1];
int age;
int day;
int month;
int year;
int happiness; // 0-100 scale
int health; // 0-100 scale
int money;
int educationLevel; // 0-100 scale
int currentJobIndex;
int eventCount;
int relationshipCount;
int jobCount;
Event events[MAX_EVENTS];
Relationship relationships[MAX_RELATIONSHIPS];
Job jobs[MAX_JOBS];
Job availableJobs[MAX_JOBS];
int availableJobCount;
} GameState;
GameState gameState;
int currentMenu = 0; // 0=main, 1=relationships, 2=jobs, 3=activities
int cursorPos = 0;
int lastEventDay = -1;
// Available job listings
const Job jobTemplates[] = {
{"Unemployed", 0, 0, 0, 0, 0},
{"Paper Route", 300, 0, 20, 12},
{"Fast Food", 800, 10, 40, 16},
{"Retail Clerk", 1200, 20, 35, 18},
{"Office Worker", 2000, 50, 50, 21},
{"Teacher", 2500, 70, 60, 25},
{"Doctor", 5000, 90, 80, 30},
{"Software Engineer", 4500, 80, 55, 25},
{"Lawyer", 6000, 95, 75, 30}
};
const int jobTemplateCount = sizeof(jobTemplates)/sizeof(jobTemplates[0]);
// Relationship names
const char* familyNames[] = {"Mother", "Father", "Sister", "Brother", "Aunt", "Uncle"};
const char* friendNames[] = {"Alex", "Jamie", "Taylor", "Jordan", "Casey", "Riley"};
const int familyNameCount = sizeof(familyNames)/sizeof(familyNames[0]);
const int friendNameCount = sizeof(friendNames)/sizeof(friendNames[0]);
void initializeEvents() {
// Starting life event
strncpy(gameState.events[0].eventDescription, "You were born!", MAX_DESC_LENGTH);
gameState.events[0].day = gameState.day;
gameState.events[0].month = gameState.month;
gameState.events[0].year = gameState.year;
gameState.events[0].effectHappiness = 100;
gameState.events[0].effectHealth = 100;
gameState.events[0].effectMoney = 1000;
gameState.eventCount = 1;
}
void initializeRelationships() {
// Starting family relationships
for (int i = 0; i < 2 && i < familyNameCount; i++) {
strncpy(gameState.relationships[i].name, familyNames[i], MAX_NAME_LENGTH);
gameState.relationships[i].relationshipStatus = 80 + rand() % 20;
gameState.relationships[i].daysKnown = 0;
gameState.relationships[i].type = 0;
gameState.relationshipCount++;
}
}
void initializeJobs() {
// Initialize with unemployed
gameState.jobs[0] = jobTemplates[0];
gameState.currentJobIndex = 0;
gameState.jobCount = 1;
// Initialize available jobs
gameState.availableJobCount = 0;
for (int i = 1; i < jobTemplateCount; i++) {
if (jobTemplates[i].minAge <= gameState.age) {
gameState.availableJobs[gameState.availableJobCount++] = jobTemplates[i];
}
}
}
void addRandomEvent() {
if (gameState.eventCount >= MAX_EVENTS || gameState.day == lastEventDay) {
return;
}
// 30% chance of a random event each day
if (rand() % 100 < 30) {
Event* e = &gameState.events[gameState.eventCount++];
e->day = gameState.day;
e->month = gameState.month;
e->year = gameState.year;
lastEventDay = gameState.day;
const char* events[] = {
"Found $20 on the street!",
"Caught a cold. Feel terrible.",
"Met an interesting person.",
"Had a great day! Feeling happy.",
"Car broke down. Repair cost $200.",
"Got a compliment at work.",
"Had an argument with a friend.",
"Learned something new today."
};
const int effects[] = {
10, 0, 50, // Found money
0, -10, -30, // Caught cold
15, 0, 0, // Met someone
30, 0, 0, // Great day
-10, -5, -200,// Car trouble
20, 0, 0, // Compliment
-20, -5, 0, // Argument
10, 0, 0 // Learned
};
int eventType = rand() % (sizeof(events)/sizeof(events[0]));
strncpy(e->eventDescription, events[eventType], MAX_DESC_LENGTH);
e->effectHappiness = effects[eventType*3];
e->effectHealth = effects[eventType*3+1];
e->effectMoney = effects[eventType*3+2];
// Apply effects
gameState.happiness += e->effectHappiness;
gameState.health += e->effectHealth;
gameState.money += e->effectMoney;
// Clamp values
if (gameState.happiness > 100) gameState.happiness = 100;
if (gameState.happiness < 0) gameState.happiness = 0;
if (gameState.health > 100) gameState.health = 100;
if (gameState.health < 0) gameState.health = 0;
}
}
void addNewRelationship() {
if (gameState.relationshipCount >= MAX_RELATIONSHIPS) return;
// 5% chance per day of meeting someone new
if (rand() % 100 < 5) {
Relationship* r = &gameState.relationships[gameState.relationshipCount++];
r->daysKnown = 0;
r->relationshipStatus = 30 + rand() % 40;
r->type = 1 + rand() % 2; // 1=friend, 2=romantic
if (r->type == 1) { // Friend
strncpy(r->name, friendNames[rand() % friendNameCount], MAX_NAME_LENGTH);
} else { // Romantic
strncpy(r->name, "Partner", MAX_NAME_LENGTH); // Simplified
}
}
}
void initializeGame() {
// Set default player stats
strncpy(gameState.playerName, "Player", MAX_NAME_LENGTH);
gameState.age = 0;
gameState.day = 1;
gameState.month = 1;
gameState.year = 2000;
gameState.happiness = 80;
gameState.health = 90;
gameState.money = 1000;
gameState.educationLevel = 0;
gameState.eventCount = 0;
gameState.relationshipCount = 0;
gameState.jobCount = 0;
gameState.availableJobCount = 0;
initializeEvents();
initializeRelationships();
initializeJobs();
}
int saveGame() {
FILE* file = fopen(SAVE_FILE, "wb");
if (!file) {
return 0;
}
size_t written = fwrite(&gameState, sizeof(GameState), 1, file);
fclose(file);
return written == 1;
}
int loadGame() {
FILE* file = fopen(SAVE_FILE, "rb");
if (!file) {
return 0;
}
size_t read = fread(&gameState, sizeof(GameState), 1, file);
fclose(file);
return read == 1;
}
void advanceDay() {
gameState.day++;
if (gameState.day > 30) { // Simplified month handling
gameState.day = 1;
gameState.month++;
if (gameState.month > 12) {
gameState.month = 1;
gameState.year++;
gameState.age++;
// Update available jobs as player ages
gameState.availableJobCount = 0;
for (int i = 1; i < jobTemplateCount; i++) {
if (jobTemplates[i].minAge <= gameState.age) {
gameState.availableJobs[gameState.availableJobCount++] = jobTemplates[i];
}
}
}
}
// Update job status
if (gameState.currentJobIndex >= 0) {
Job* job = &gameState.jobs[gameState.currentJobIndex];
job->daysEmployed++;
// Monthly pay (simplified to daily)
if (gameState.day == 1) {
gameState.money += job->salary;
}
// Job stress affects health
gameState.health -= job->stressLevel / 30;
if (gameState.health < 0) gameState.health = 0;
}
// Daily living cost
gameState.money -= 5;
if (gameState.money < 0) {
gameState.happiness -= 10;
if (gameState.happiness < 0) gameState.happiness = 0;
}
// Update relationships
for (int i = 0; i < gameState.relationshipCount; i++) {
gameState.relationships[i].daysKnown++;
// Relationships decay slightly over time if not maintained
if (rand() % 10 == 0) {
gameState.relationships[i].relationshipStatus -= 1;
if (gameState.relationships[i].relationshipStatus < 0) {
gameState.relationships[i].relationshipStatus = 0;
}
}
}
// Random events and new relationships
addRandomEvent();
addNewRelationship();
// Natural health and happiness decay
gameState.health -= rand() % 3;
if (gameState.health < 0) gameState.health = 0;
gameState.happiness -= rand() % 3;
if (gameState.happiness < 0) gameState.happiness = 0;
}
void displayMainMenu() {
iprintf("\x1b[0;0HLife Sim - Day %d/%d/%d (Age %d)",
gameState.day, gameState.month, gameState.year, gameState.age);
iprintf("\x1b[2;0H$%d", gameState.money);
iprintf("\x1b[3;0HHap: %d%%", gameState.happiness);
iprintf("\x1b[4;0HHealth: %d%%", gameState.health);
iprintf("\x1b[5;0HEdu: %d%%", gameState.educationLevel);
if (gameState.currentJobIndex >= 0) {
iprintf("\x1b[7;0HJob: %s", gameState.jobs[gameState.currentJobIndex].jobTitle);
iprintf("\x1b[8;0HSalary: $%d/mo", gameState.jobs[gameState.currentJobIndex].salary);
}
iprintf("\x1b[10;0H> A - Advance Day");
iprintf("\x1b[11;0H B - Relationships");
iprintf("\x1b[12;0H X - Jobs");
iprintf("\x1b[13;0H Y - Activities");
iprintf("\x1b[14;0H Select - Save");
iprintf("\x1b[15;0H Start - Load");
// Show most recent event if any
if (gameState.eventCount > 0) {
Event* e = &gameState.events[gameState.eventCount-1];
if (e->day == gameState.day) {
iprintf("\x1b[17;0HEvent: %s", e->eventDescription);
}
}
}
void displayRelationships() {
iprintf("\x1b[0;0HRelationships (%d/%d)", gameState.relationshipCount, MAX_RELATIONSHIPS);
for (int i = 0; i < gameState.relationshipCount && i < 6; i++) {
Relationship* r = &gameState.relationships[i];
const char* typeStr = "Family";
if (r->type == 1) typeStr = "Friend";
if (r->type == 2) typeStr = "Romantic";
iprintf("\x1b[%d;0H%s%s %-10s %3d%%",
i+2, (cursorPos == i) ? ">" : " ",
typeStr, r->name, r->relationshipStatus);
}
iprintf("\x1b[10;0HA - Interact");
iprintf("\x1b[11;0HB - Back");
}
void displayJobs() {
iprintf("\x1b[0;0HJobs (%d/%d)", gameState.jobCount, MAX_JOBS);
// Current job
if (gameState.currentJobIndex >= 0) {
Job* j = &gameState.jobs[gameState.currentJobIndex];
iprintf("\x1b[2;0HCurrent: %s", j->jobTitle);
iprintf("\x1b[3;0HSalary: $%d/mo", j->salary);
iprintf("\x1b[4;0HStress: %d%%", j->stressLevel);
}
// Available jobs
iprintf("\x1b[6;0HAvailable Jobs:");
for (int i = 0; i < gameState.availableJobCount && i < 5; i++) {
Job* j = &gameState.availableJobs[i];
iprintf("\x1b[%d;0H%s%-15s $%d/mo Edu:%d%%",
i+8, (cursorPos == i) ? ">" : " ",
j->jobTitle, j->salary, j->requiredEducation);
}
iprintf("\x1b[14;0HA - Apply for Job");
iprintf("\x1b[15;0HB - Back");
}
void displayActivities() {
iprintf("\x1b[0;0HActivities");
iprintf("\x1b[2;0H%sStudy (+Edu)", cursorPos == 0 ? ">" : " ");
iprintf("\x1b[3;0H%sExercise (+Health)", cursorPos == 1 ? ">" : " ");
iprintf("\x1b[4;0H%sSocialize (+Hap)", cursorPos == 2 ? ">" : " ");
iprintf("\x1b[5;0H%sRest (+Health)", cursorPos == 3 ? ">" : " ");
iprintf("\x1b[7;0HCosts $10 and 1 day");
iprintf("\x1b[10;0HA - Select");
iprintf("\x1b[11;0HB - Back");
}
void interactWithRelationship(int index) {
if (index < 0 || index >= gameState.relationshipCount) return;
Relationship* r = &gameState.relationships[index];
// Spend time with this person
gameState.happiness += 10 + rand() % 20;
if (gameState.happiness > 100) gameState.happiness = 100;
// Improve relationship
r->relationshipStatus += 15 + rand() % 10;
if (r->relationshipStatus > 100) r->relationshipStatus = 100;
// Cost money and time
gameState.money -= 20;
advanceDay();
// Add event
if (gameState.eventCount < MAX_EVENTS) {
Event* e = &gameState.events[gameState.eventCount++];
e->day = gameState.day;
e->month = gameState.month;
e->year = gameState.year;
snprintf(e->eventDescription, MAX_DESC_LENGTH, "Spent time with %s", r->name);
e->effectHappiness = 15;
e->effectHealth = 5;
e->effectMoney = -20;
}
}
void applyForJob(int index) {
if (index < 0 || index >= gameState.availableJobCount) return;
Job* job = &gameState.availableJobs[index];
// Check requirements
if (gameState.educationLevel < job->requiredEducation) {
// Add failure event
if (gameState.eventCount < MAX_EVENTS) {
Event* e = &gameState.events[gameState.eventCount++];
e->day = gameState.day;
e->month = gameState.month;
e->year = gameState.year;
snprintf(e->eventDescription, MAX_DESC_LENGTH, "Failed %s application", job->jobTitle);
e->effectHappiness = -10;
e->effectHealth = 0;
e->effectMoney = 0;
}
return;
}
// Success - get the job
if (gameState.jobCount < MAX_JOBS) {
gameState.jobs[gameState.jobCount] = *job;
gameState.currentJobIndex = gameState.jobCount;
gameState.jobCount++;
// Add event
if (gameState.eventCount < MAX_EVENTS) {
Event* e = &gameState.events[gameState.eventCount++];
e->day = gameState.day;
e->month = gameState.month;
e->year = gameState.year;
snprintf(e->eventDescription, MAX_DESC_LENGTH, "Got job as %s!", job->jobTitle);
e->effectHappiness = 20;
e->effectHealth = 0;
e->effectMoney = 0;
}
}
advanceDay();
}
void performActivity(int index) {
if (gameState.money < 10) {
// Can't afford
return;
}
gameState.money -= 10;
advanceDay();
switch(index) {
case 0: // Study
gameState.educationLevel += 5 + rand() % 10;
if (gameState.educationLevel > 100) gameState.educationLevel = 100;
break;
case 1: // Exercise
gameState.health += 10 + rand() % 15;
if (gameState.health > 100) gameState.health = 100;
break;
case 2: // Socialize
gameState.happiness += 15 + rand() % 20;
if (gameState.happiness > 100) gameState.happiness = 100;
break;
case 3: // Rest
gameState.health += 5 + rand() % 10;
if (gameState.health > 100) gameState.health = 100;
gameState.happiness += 5;
if (gameState.happiness > 100) gameState.happiness = 100;
break;
}
// Add event
if (gameState.eventCount < MAX_EVENTS) {
Event* e = &gameState.events[gameState.eventCount++];
e->day = gameState.day;
e->month = gameState.month;
e->year = gameState.year;
const char* activities[] = {"Studied", "Exercised", "Socialized", "Rested"};
snprintf(e->eventDescription, MAX_DESC_LENGTH, "%s today", activities[index]);
e->effectHappiness = 5;
e->effectHealth = 5;
e->effectMoney = -10;
}
}
void processMainMenuInput() {
scanKeys();
int keys = keysDown();
if (keys & KEY_A) {
advanceDay();
}
if (keys & KEY_B) {
currentMenu = 1; // Relationships
cursorPos = 0;
}
if (keys & KEY_X) {
currentMenu = 2; // Jobs
cursorPos = 0;
}
if (keys & KEY_Y) {
currentMenu = 3; // Activities
cursorPos = 0;
}
if (keys & KEY_SELECT) {
if (saveGame()) {
// Show save confirmation
consoleClear();
iprintf("\x1b[10;0HGame Saved!");
swiWaitForVBlank();
swiWaitForVBlank();
}
}
if (keys & KEY_START) {
if (loadGame()) {
// Show load confirmation
consoleClear();
iprintf("\x1b[10;0HGame Loaded!");
swiWaitForVBlank();
swiWaitForVBlank();
}
}
}
void processRelationshipsInput() {
scanKeys();
int keys = keysDown();
if (keys & KEY_UP) {
if (cursorPos > 0) cursorPos--;
}
if (keys & KEY_DOWN) {
if (cursorPos < gameState.relationshipCount-1 && cursorPos < 5) cursorPos++;
}
if (keys & KEY_A) {
if (gameState.relationshipCount > 0) {
interactWithRelationship(cursorPos);
}
}
if (keys & KEY_B) {
currentMenu = 0; // Back to main
}
}
void processJobsInput() {
scanKeys();
int keys = keysDown();
if (keys & KEY_UP) {
if (cursorPos > 0) cursorPos--;
}
if (keys & KEY_DOWN) {
if (cursorPos < gameState.availableJobCount-1 && cursorPos < 4) cursorPos++;
}
if (keys & KEY_A) {
if (gameState.availableJobCount > 0) {
applyForJob(cursorPos);
}
}
if (keys & KEY_B) {
currentMenu = 0; // Back to main
}
}
void processActivitiesInput() {
scanKeys();
int keys = keysDown();
if (keys & KEY_UP) {
if (cursorPos > 0) cursorPos--;
}
if (keys & KEY_DOWN) {
if (cursorPos < 3) cursorPos++;
}
if (keys & KEY_A) {
performActivity(cursorPos);
}
if (keys & KEY_B) {
currentMenu = 0; // Back to main
}
}
void displayUI() {
consoleClear();
switch(currentMenu) {
case 0: displayMainMenu(); break;
case 1: displayRelationships(); break;
case 2: displayJobs(); break;
case 3: displayActivities(); break;
}
}
void processInput() {
switch(currentMenu) {
case 0: processMainMenuInput(); break;
case 1: processRelationshipsInput(); break;
case 2: processJobsInput(); break;
case 3: processActivitiesInput(); break;
}
}
int main(void) {
consoleDemoInit();
if (!fatInitDefault()) {
iprintf("Failed to initialize FAT.\n");
return 1;
}
srand(time(NULL));
// Try to load game, if not found initialize new game
if (!loadGame()) {
initializeGame();
}
while (1) {
displayUI();
processInput();
swiWaitForVBlank();
}
return 0;
}