feature: Initial commit

This commit is contained in:
Keith Solomon
2025-09-01 10:17:36 -05:00
commit ba6818cfff
5 changed files with 433 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
notes/

8
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"workbench.colorCustomizations": {
"tree.indentGuidesStroke": "#3d92ec",
"activityBar.background": "#482405",
"titleBar.activeBackground": "#653308",
"titleBar.activeForeground": "#FEFAF6"
}
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# Days In Fortuna
Text-based RPG based on Warframe. You play as a Corpus worker, trying to work your way out of debt. Managing efficiency versus time worked, and trying to get sleep and meals in are important features. The option to replace body parts to raise your wages is available, and repo squads will come after you if your debt gets too high.

248
game.php Normal file
View File

@@ -0,0 +1,248 @@
<?php
session_start();
header('Content-Type: application/json');
function init_state() {
$_SESSION['game'] = [
'day' => 1,
'hours_left' => 26,
'meals' => 0,
'earned' => 0,
'earned_total' => 0,
'debt' => 127000,
'mods' => 0,
'wage' => 800,
'interest' => 0.20,
'eff' => 100.0,
'game_over' => false,
];
}
function status_check(&$g) {
$s = [];
$hours_str = ($g['hours_left'] == 1) ? 'is 1 hour' : ('are ' . $g['hours_left'] . ' hours');
$s[] = "------------\nIt is day {$g['day']}. There {$hours_str} left today. ";
$s[] = 'You have earned ' . $g['earned'] . ' credits today and ' . $g['earned_total'] . ' total. ' .
'This brings your total debt to ' . $g['debt'] . ' credits, with an interest rate of ' . ((int)($g['interest']*100)) . '%.\n------------';
return $s;
}
function debt_increase(&$g) {
$fltDebt = (float)$g['debt'];
$fltDebt = ($fltDebt + ($fltDebt * $g['interest']));
$g['debt'] = (int)$fltDebt;
return $g['debt'];
}
function new_day_check(&$g, &$messages) {
if ($g['hours_left'] <= 0) {
$g['day'] += 1;
$g['hours_left'] = 26 + $g['hours_left'];
$g['debt'] -= $g['earned'];
debt_increase($g);
$g['earned'] = 0;
$g['eff'] -= 50;
if ($g['eff'] < 0) $g['eff'] = 0;
$g['meals'] = 0;
$messages[] = 'A new day has begun.';
}
}
function repo_check(&$g, &$messages) {
if ($g['debt'] >= 300000) {
if ($g['mods'] < 4) {
$messages[] = 'Your debt has grown too high. A Corpus Repossession Team has been waiting outside to arrest you.';
$g['mods'] += 1;
$g['hours_left'] -= 6;
switch ($g['mods']) {
case 1:
$messages[] = "Your legs have been replaced with robotic enhancements. You can now stand longer and lift more weight, increasing your productivity.\nThe Corpus have been generous enough to reduce your debt as a token of thanks. Your legs were worth enough credits to significantly reduce your debt. To ensure you remain motivated to work hard, your interest rate has been raised.\n------------";
$g['interest'] = 0.23;
$g['wage'] = 1150;
$g['debt'] = 170000;
break;
case 2:
$messages[] = "Your arms have been replaced with robotic prosthetics, allowing you to work harder and more accurately.\nThe Corpus have been generous enough to reduce your debt as a token of thanks. Your arms were worth enough credits to significantly reduce your debt. To ensure you remain motivated to work hard, your interest rate has been raised.\n------------";
$g['interest'] = 0.28;
$g['wage'] = 1300;
$g['debt'] = 160000;
break;
case 3:
$messages[] = "Your internal organs, apart from your head, have been replaced with enhanced prosthetics. You are much more durable, and need no breaks during work hours.\n The Corpus have been generous enough to reduce your debt as a token of thanks. Your organs were worth enough credits to significantly reduce your debt. To ensure you remain motivated to work hard, your interest rate has been raised.\n------------";
$g['interest'] = 0.30;
$g['wage'] = 1600;
$g['debt'] = 150000;
break;
case 4:
$messages[] = "Your head has been replaced with a robotic apparatus. You will no longer feel tired, or pain, or anything at all.\n The Corpus have been generous enough to reduce your debt as a token of thanks. Your head was worth enough credits to significantly reduce your debt. To ensure you remain motivated to work hard, your interest rate has been raised.\n------------";
$g['interest'] = 0.34;
$g['wage'] = 1800;
$g['debt'] = 140000;
break;
}
$tmp = [];
new_day_check($g, $tmp); // may add day rollover message
foreach ($tmp as $m) $messages[] = $m;
} else {
$messages[] = 'You are entirely comprised of robotic parts owned by the Corpus. As none of you is human anymore, and you cannot repay what you owe, you will be regarded as any other robot.\nYou are now the property of the Corpus, and will work until you break and then be melted down for scrap. Your debt will be passed to any heirs you may have.\n------------';
$g['game_over'] = true;
}
}
}
function serialize_state($g) {
return [
'day' => $g['day'],
'hours_left' => $g['hours_left'],
'meals' => $g['meals'],
'earned' => $g['earned'],
'earned_total' => $g['earned_total'],
'debt' => $g['debt'],
'mods' => $g['mods'],
'wage' => $g['wage'],
'interest' => $g['interest'],
'eff' => $g['eff'],
];
}
$input = json_decode(file_get_contents('php://input'), true) ?? [];
$action = $input['action'] ?? 'start';
$value = $input['value'] ?? null;
if (!isset($_SESSION['game'])) init_state();
$g =& $_SESSION['game'];
$messages = [];
$can_install = false;
$final_summary = null;
switch ($action) {
case 'start':
// Re-init if game over or user explicitly starts
if (!empty($input['reset']) || $g['day'] === 1 && $g['earned_total'] === 0) {
init_state();
$g =& $_SESSION['game'];
}
$messages[] = "English 1101\nSection: 205\nTerm: Fall 2022\nInstructor: Dr. Ferguson\nName: Asher Graham\n\n\tRhetorical Analysis Multimodal Project\n";
$messages[] = "\n\n\tThis program is a simulation of the life of a Corpus worker on Venus. You are trying to work off your debt, which has been passed down through your family for generations. As the last of your family, the burden is entirely on your shoulders. \n\tType the number associated with what you want to do in order to progress.\n\tEnhancements can be rented from the Corpus, which will increase your possible wages.\n\tIt is recommended that you sleep at least 8 hours a day and eat twice a day, which takes 1 hour per meal, to keep your efficiency up.\n\tInterest is applied daily. Should your debt grow too high, a team of Corpus members will arrest you and replace parts of you with robotic ones to increase the profit you create. This will lower your debt however, as body parts are worth a lot of credits.\n\tGood luck!\n------------------------\n";
break;
case 'reset':
init_state();
$g =& $_SESSION['game'];
$messages[] = 'Game reset.';
break;
case 'status':
foreach (status_check($g) as $m) $messages[] = $m;
break;
case 'work':
$hours = (int)$value;
if ($hours < 1) {
$messages[] = 'Invalid entry.';
break;
}
if ($hours > 26) {
$messages[] = 'You cannot work more than one full day at a time.';
break;
}
$g['earned'] += (int)round($hours * $g['wage'] * ($g['eff'] / 100.0));
$g['earned_total'] += $g['earned'];
$g['hours_left'] -= $hours;
$messages[] = 'You earned ' . $g['earned'] . ' credits. Well done!';
$messages[] = "\n------------";
new_day_check($g, $messages);
repo_check($g, $messages);
break;
case 'sleep':
$hours = (int)$value;
if ($hours < 1) {
$messages[] = 'Invalid entry.';
break;
}
$g['hours_left'] -= $hours;
new_day_check($g, $messages);
if ($hours <= 7) {
$g['eff'] += ((50.0 * $hours) / 8.0);
} else {
$g['eff'] += 50.0;
}
if ($g['eff'] > 100.0) $g['eff'] = 100.0;
$messages[] = 'You wake up feeling refreshed. Your efficiency has been restored.';
repo_check($g, $messages);
break;
case 'eat':
if ($g['meals'] < 2) {
$g['eff'] += 20.0;
if ($g['eff'] > 100.0) $g['eff'] = 100.0;
$messages[] = 'You feel satisfied. Your efficiency has been restored.';
} else {
$messages[] = "You finish eating, but don't feel any better.";
}
$g['hours_left'] -= 1;
$g['meals'] += 1;
new_day_check($g, $messages);
repo_check($g, $messages);
break;
case 'enhance':
if ($g['mods'] >= 4) {
$messages[] = "No available enhancements were found...\n";
} else {
$messages[] = "You found a shop with modifications and enhancements you can install. Would you like to install one?";
$messages[] = '1) Yes 2) No';
$can_install = true;
}
break;
case 'install_enhancement':
if ($g['mods'] < 4) {
$g['mods'] += 1;
$g['hours_left'] -= 6;
switch ($g['mods']) {
case 1:
$messages[] = "You have replaced your legs with robotic enhancements. You can now stand longer and lift more weight, increasing your productivity.\nThe Corpus have been generous enough to offer this for no cost, apart from an increased interest rate.\n";
$g['interest'] = 0.26;
$g['wage'] = 1150;
break;
case 2:
$messages[] = "You have replaced your arms with robotic prosthetics, allowing you to work harder and more accurately.\nThe Corpus have been generous enough to offer this for no cost, apart from an increased interest rate.\n";
$g['interest'] = 0.30;
$g['wage'] = 1400;
break;
case 3:
$messages[] = "You have replaced your internal organs, apart from your head, with enhanced prosthetics. You are much more durable, and need no breaks during work hours.\n The Corpus have been generous enough to offer this for no cost, apart from an increased interest rate.\n";
$g['interest'] = 0.35;
$g['wage'] = 1600;
break;
case 4:
$messages[] = "You have replaced your head with a robotic apparatus. You will no longer feel tired, or pain, or anything at all.\n The Corpus have been generous enough to offer this for no cost, apart from an increased interest rate.\n";
$g['interest'] = 0.38;
$g['wage'] = 1800;
break;
}
new_day_check($g, $messages);
repo_check($g, $messages);
}
break;
case 'quit':
$g['game_over'] = true;
$final_summary = 'You made it to day ' . $g['day'] . ', and earned ' . $g['earned_total'] . ' credits total. Your final debt was ' . $g['debt'] . ' credits, with an interest rate of ' . ((int)($g['interest']*100)) . '%.';
break;
default:
$messages[] = 'Invalid input. Please try again';
}
echo json_encode([
'messages' => $messages,
'state' => serialize_state($g),
'game_over' => $g['game_over'],
'final_summary' => $final_summary,
'can_install' => $can_install,
]);

173
index.php Normal file
View File

@@ -0,0 +1,173 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Corpus Worker RPG (Web)</title>
<style>
body { font-family: system-ui, Arial, sans-serif; margin: 0; background:#0f1420; color:#e9eef8; }
header { padding: 12px 16px; background: #121a2a; border-bottom: 1px solid #27324a; display:flex; align-items:center; justify-content:space-between; }
h1 { margin: 0; font-size: 18px; }
main { display: grid; grid-template-columns: 300px 1fr; gap: 16px; padding: 16px; }
.panel { background:#111827; border:1px solid #27324a; border-radius:8px; }
.panel h2 { font-size:14px; margin: 0; padding: 12px 12px 0; color:#a3b5d9; }
.panel .content { padding: 12px; }
#log { background:#0b1020; color:#e9eef8; min-height: 360px; max-height: 60vh; overflow: auto; white-space: pre-wrap; }
.row { display:flex; gap:8px; flex-wrap:wrap; }
button { background:#1f2a44; color:#e9eef8; border:1px solid #2c3a5e; border-radius:6px; padding:8px 10px; cursor:pointer; }
button:hover { background:#263356; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
.muted { color:#a3b5d9; }
.sep { height:1px; background:#27324a; margin: 8px 0; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.kv { display:flex; justify-content:space-between; gap:8px; margin:2px 0; }
.kv .label { color:#a3b5d9; }
</style>
<script>
async function api(action, value) {
const res = await fetch('game.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, value })
});
if (!res.ok) throw new Error('Network error: ' + res.status);
return res.json();
}
function appendLog(lines) {
const log = document.getElementById('log');
const atBottom = Math.abs(log.scrollHeight - log.scrollTop - log.clientHeight) < 4;
for (const line of lines) {
const div = document.createElement('div');
div.textContent = line;
log.appendChild(div);
}
if (atBottom) log.scrollTop = log.scrollHeight;
}
function setState(state) {
if (!state) return;
const s = document.getElementById('state');
s.innerHTML = ''+`
<div>Day: <span class="mono">${state.day}</span></div>
<div>Hours Left Today: <span class="mono">${state.hours_left}</span></div>
<div>Meals Today: <span class="mono">${state.meals}</span></div>
<div>Earned Today: <span class="mono">${state.earned}</span></div>
<div>Total Earned: <span class="mono">${state.earned_total}</span></div>
<div>Debt: <span class="mono">${state.debt}</span></div>
<div>Interest: <span class="mono">${(state.interest * 100).toFixed(0)}%</span></div>
<div>Wage: <span class="mono">${state.wage}</span></div>
<div>Efficiency: <span class="mono">${state.eff}</span></div>
<div>Enhancements: <span class="mono">${state.mods}</span></div>
`;
const m = document.getElementById('miniState');
if (m) {
m.innerHTML = ''+`
<div class="kv"><span class="label">Day</span><span class="mono">${state.day}</span></div>
<div class="kv"><span class="label">Hours</span><span class="mono">${state.hours_left}</span></div>
<div class="kv"><span class="label">Debt</span><span class="mono">${state.debt}</span></div>
<div class="kv"><span class="label">Interest</span><span class="mono">${(state.interest*100).toFixed(0)}%</span></div>
<div class="kv"><span class="label">Wage</span><span class="mono">${state.wage}</span></div>
<div class="kv"><span class="label">Eff</span><span class="mono">${state.eff}</span></div>
<div class="kv"><span class="label">Mods</span><span class="mono">${state.mods}</span></div>
`;
}
}
function setGameOver(summary) {
if (!summary) return;
appendLog(['', 'GAME OVER', summary]);
for (const btn of document.querySelectorAll('button[data-action]')) btn.disabled = true;
}
async function doAction(action, value) {
try {
const data = await api(action, value);
if (data.messages) appendLog(data.messages);
if (data.state) setState(data.state);
if (data.game_over) setGameOver(data.final_summary || '');
return data;
} catch (e) {
appendLog(['Error: ' + e.message]);
}
}
async function onLoad() {
await doAction('start');
}
async function handleWork() {
const s = prompt('How many hours would you like to work?');
if (s == null) return;
const hours = parseInt(s, 10);
if (!Number.isFinite(hours)) return appendLog(['Invalid entry.']);
await doAction('work', hours);
}
async function handleSleep() {
const s = prompt('How many hours would you like to sleep?');
if (s == null) return;
const hours = parseInt(s, 10);
if (!Number.isFinite(hours)) return appendLog(['Invalid entry.']);
await doAction('sleep', hours);
}
async function handleEnhance() {
const resp = await doAction('enhance');
if (resp && resp.can_install) {
const ok = confirm('Install an enhancement?');
if (ok) await doAction('install_enhancement');
}
}
window.addEventListener('DOMContentLoaded', onLoad);
</script>
</head>
<body>
<header>
<h1>Corpus Worker RPG</h1>
<div class="row">
<button onclick="resetGame()">Reset Game</button>
</div>
</header>
<main>
<section class="panel">
<h2>Status</h2>
<div id="miniState" class="content"></div>
</section>
<section class="panel">
<h2>Actions</h2>
<div class="content">
<div class="row">
<button data-action onclick="doAction('status')">Check Status</button>
<button data-action onclick="handleWork()">Go to Work</button>
<button data-action onclick="handleSleep()">Sleep</button>
<button data-action onclick="doAction('eat')">Eat</button>
<button data-action onclick="handleEnhance()">Search Enhancements</button>
<button data-action onclick="doAction('quit')">Quit</button>
</div>
<div class="sep"></div>
<div class="muted">Tip: Sleep 8h and eat twice daily to maintain efficiency.</div>
</div>
</section>
<section class="panel">
<h2>Log</h2>
<div id="log" class="content"></div>
</section>
async function resetGame() {
// Clear log and re-enable action buttons
const log = document.getElementById('log');
log.innerHTML = '';
for (const btn of document.querySelectorAll('button[data-action]')) btn.disabled = false;
await doAction('reset');
await doAction('start');
}
<section class="panel" style="grid-column: 1 / span 2;">
<h2>State</h2>
<div id="state" class="content"></div>
</section>
</main>
</body>
</html>