✨feature: Split styles and JavaScript to separate files, rework layout
This commit is contained in:
108
app.js
Normal file
108
app.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
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 Left Today</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">Earned Today</span><span class="mono">${state.earned}</span></div>
|
||||||
|
<div class="kv"><span class="label">Total Earned</span><span class="mono">${state.earned_total}</span></div>
|
||||||
|
<div class="kv"><span class="label">Efficiency</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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', onLoad);
|
||||||
429
game.php
429
game.php
@@ -1,116 +1,197 @@
|
|||||||
<?php
|
<?php
|
||||||
|
/**
|
||||||
|
* Days In Fortuna Game Logic
|
||||||
|
*
|
||||||
|
* PHP version: 8.0+
|
||||||
|
*
|
||||||
|
* This file contains the main game logic for the Days In Fortuna simulation.
|
||||||
|
* Handles session state, game actions, and JSON responses.
|
||||||
|
*
|
||||||
|
* @category API
|
||||||
|
* @package DaysInFortuna
|
||||||
|
* @author Keith Solomon <keith@keithsolomon.net>
|
||||||
|
* @license MIT License
|
||||||
|
* @version GIT: <git_id>
|
||||||
|
* @link https://git.keithsolomon.net/keith/Days-In-Fortuna
|
||||||
|
*/
|
||||||
|
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
function init_state() {
|
/**
|
||||||
$_SESSION['game'] = [
|
* Initializes the game state.
|
||||||
'day' => 1,
|
*
|
||||||
'hours_left' => 26,
|
* This function sets up the initial state for the game, preparing any necessary
|
||||||
'meals' => 0,
|
* variables, data structures, or configurations required to start gameplay.
|
||||||
'earned' => 0,
|
*
|
||||||
'earned_total' => 0,
|
* @return void
|
||||||
'debt' => 127000,
|
*/
|
||||||
'mods' => 0,
|
function initState() {
|
||||||
'wage' => 800,
|
$_SESSION['game'] = [
|
||||||
'interest' => 0.20,
|
'day' => 1,
|
||||||
'eff' => 100.0,
|
'hours_left' => 26,
|
||||||
'game_over' => false,
|
'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 = [];
|
* Checks and returns the current status of the game.
|
||||||
$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. ";
|
* @param array $g Reference to the game state array.
|
||||||
$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 array Status messages describing the current game state.
|
||||||
return $s;
|
*/
|
||||||
|
function statusCheck(&$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'];
|
* Increases the debt value in the provided game state array.
|
||||||
$fltDebt = ($fltDebt + ($fltDebt * $g['interest']));
|
*
|
||||||
$g['debt'] = (int)$fltDebt;
|
* @param array $g Reference to the game state array.
|
||||||
return $g['debt'];
|
*
|
||||||
|
* @return int The updated debt value.
|
||||||
|
*/
|
||||||
|
function debtIncrease(&$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) {
|
* Checks if a new day has started in the game and performs necessary updates.
|
||||||
$g['day'] += 1;
|
*
|
||||||
$g['hours_left'] = 26 + $g['hours_left'];
|
* This function examines the game state and determines if a new day should begin.
|
||||||
$g['debt'] -= $g['earned'];
|
* If a new day is detected, it updates relevant game variables and appends messages
|
||||||
debt_increase($g);
|
* to the provided messages array.
|
||||||
$g['earned'] = 0;
|
*
|
||||||
$g['eff'] -= 50;
|
* @param array $g Reference to the game state array.
|
||||||
if ($g['eff'] < 0) $g['eff'] = 0;
|
* @param array $messages Reference to the array of messages to be updated.
|
||||||
$g['meals'] = 0;
|
*
|
||||||
$messages[] = 'A new day has begun.';
|
* @return void
|
||||||
}
|
*/
|
||||||
}
|
function newDayCheck(&$g, &$messages) {
|
||||||
|
if ($g['hours_left'] <= 0) {
|
||||||
|
$g['day'] += 1;
|
||||||
|
$g['hours_left'] = 26 + $g['hours_left'];
|
||||||
|
$g['debt'] -= $g['earned'];
|
||||||
|
|
||||||
function repo_check(&$g, &$messages) {
|
debtIncrease($g);
|
||||||
if ($g['debt'] >= 300000) {
|
|
||||||
if ($g['mods'] < 4) {
|
$g['earned'] = 0;
|
||||||
$messages[] = 'Your debt has grown too high. A Corpus Repossession Team has been waiting outside to arrest you.';
|
$g['eff'] -= 50;
|
||||||
$g['mods'] += 1;
|
|
||||||
$g['hours_left'] -= 6;
|
if ($g['eff'] < 0) {
|
||||||
switch ($g['mods']) {
|
$g['eff'] = 0;
|
||||||
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['meals'] = 0;
|
||||||
$g['wage'] = 1150;
|
$messages[] = 'A new day has begun.';
|
||||||
$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 [
|
* Checks the repository status and updates the game state and messages accordingly.
|
||||||
'day' => $g['day'],
|
*
|
||||||
'hours_left' => $g['hours_left'],
|
* @param array $g Reference to the game state array.
|
||||||
'meals' => $g['meals'],
|
* @param array $messages Reference to the array of messages to be updated.
|
||||||
'earned' => $g['earned'],
|
*
|
||||||
'earned_total' => $g['earned_total'],
|
* @return void
|
||||||
'debt' => $g['debt'],
|
*/
|
||||||
'mods' => $g['mods'],
|
function repoCheck(&$g, &$messages) {
|
||||||
'wage' => $g['wage'],
|
if ($g['debt'] >= 300000) {
|
||||||
'interest' => $g['interest'],
|
if ($g['mods'] < 4) {
|
||||||
'eff' => $g['eff'],
|
$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.\n\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.\n\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\nThe 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\nThe 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 = [];
|
||||||
|
|
||||||
|
newDayCheck($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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the current game state.
|
||||||
|
*
|
||||||
|
* @param mixed $g The game state object or array to be serialized.
|
||||||
|
*
|
||||||
|
* @return string The serialized representation of the game state.
|
||||||
|
*/
|
||||||
|
function serializeState($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) ?? [];
|
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||||
$action = $input['action'] ?? 'start';
|
$action = $input['action'] ?? 'start';
|
||||||
$value = $input['value'] ?? null;
|
$value = $input['value'] ?? null;
|
||||||
|
|
||||||
if (!isset($_SESSION['game'])) init_state();
|
if (!isset($_SESSION['game'])) {
|
||||||
|
initState();
|
||||||
|
}
|
||||||
|
|
||||||
$g =& $_SESSION['game'];
|
$g =& $_SESSION['game'];
|
||||||
|
|
||||||
$messages = [];
|
$messages = [];
|
||||||
@@ -118,131 +199,159 @@ $can_install = false;
|
|||||||
$final_summary = null;
|
$final_summary = null;
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case 'start':
|
case 'start':
|
||||||
// Re-init if game over or user explicitly starts
|
// Re-init if game over or user explicitly starts
|
||||||
if (!empty($input['reset']) || $g['day'] === 1 && $g['earned_total'] === 0) {
|
if (!empty($input['reset']) || $g['day'] === 1 && $g['earned_total'] === 0) {
|
||||||
init_state();
|
initState();
|
||||||
$g =& $_SESSION['game'];
|
$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";
|
$messages[] = "Days in Fortuna\n------------------------";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'reset':
|
case 'reset':
|
||||||
init_state();
|
initState();
|
||||||
$g =& $_SESSION['game'];
|
$g =& $_SESSION['game'];
|
||||||
$messages[] = 'Game reset.';
|
$messages[] = 'Game reset.';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'status':
|
case 'status':
|
||||||
foreach (status_check($g) as $m) $messages[] = $m;
|
foreach (statusCheck($g) as $m) {
|
||||||
|
$messages[] = $m;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'work':
|
case 'work':
|
||||||
$hours = (int)$value;
|
$hours = (int)$value;
|
||||||
|
|
||||||
if ($hours < 1) {
|
if ($hours < 1) {
|
||||||
$messages[] = 'Invalid entry.';
|
$messages[] = 'Invalid entry.';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hours > 26) {
|
if ($hours > 26) {
|
||||||
$messages[] = 'You cannot work more than one full day at a time.';
|
$messages[] = 'You cannot work more than one full day at a time.';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$g['earned'] += (int)round($hours * $g['wage'] * ($g['eff'] / 100.0));
|
$g['earned'] += (int)round($hours * $g['wage'] * ($g['eff'] / 100.0));
|
||||||
$g['earned_total'] += $g['earned'];
|
$g['earned_total'] += $g['earned'];
|
||||||
$g['hours_left'] -= $hours;
|
$g['hours_left'] -= $hours;
|
||||||
|
|
||||||
$messages[] = 'You earned ' . $g['earned'] . ' credits. Well done!';
|
$messages[] = 'You earned ' . $g['earned'] . ' credits. Well done!';
|
||||||
$messages[] = "\n------------";
|
$messages[] = "\n------------";
|
||||||
new_day_check($g, $messages);
|
|
||||||
repo_check($g, $messages);
|
newDayCheck($g, $messages);
|
||||||
|
|
||||||
|
repoCheck($g, $messages);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'sleep':
|
case 'sleep':
|
||||||
$hours = (int)$value;
|
$hours = (int)$value;
|
||||||
|
|
||||||
if ($hours < 1) {
|
if ($hours < 1) {
|
||||||
$messages[] = 'Invalid entry.';
|
$messages[] = 'Invalid entry.';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$g['hours_left'] -= $hours;
|
$g['hours_left'] -= $hours;
|
||||||
new_day_check($g, $messages);
|
|
||||||
|
newDayCheck($g, $messages);
|
||||||
|
|
||||||
if ($hours <= 7) {
|
if ($hours <= 7) {
|
||||||
$g['eff'] += ((50.0 * $hours) / 8.0);
|
$g['eff'] += ((50.0 * $hours) / 8.0);
|
||||||
} else {
|
} else {
|
||||||
$g['eff'] += 50.0;
|
$g['eff'] += 50.0;
|
||||||
}
|
}
|
||||||
if ($g['eff'] > 100.0) $g['eff'] = 100.0;
|
|
||||||
|
if ($g['eff'] > 100.0) {
|
||||||
|
$g['eff'] = 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
$messages[] = 'You wake up feeling refreshed. Your efficiency has been restored.';
|
$messages[] = 'You wake up feeling refreshed. Your efficiency has been restored.';
|
||||||
repo_check($g, $messages);
|
|
||||||
|
repoCheck($g, $messages);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'eat':
|
case 'eat':
|
||||||
if ($g['meals'] < 2) {
|
if ($g['meals'] < 2) {
|
||||||
$g['eff'] += 20.0;
|
$g['eff'] += 20.0;
|
||||||
if ($g['eff'] > 100.0) $g['eff'] = 100.0;
|
|
||||||
$messages[] = 'You feel satisfied. Your efficiency has been restored.';
|
if ($g['eff'] > 100.0) {
|
||||||
|
$g['eff'] = 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$messages[] = 'You feel satisfied. Your efficiency has been restored.';
|
||||||
} else {
|
} else {
|
||||||
$messages[] = "You finish eating, but don't feel any better.";
|
$messages[] = "You finish eating, but don't feel any better.";
|
||||||
}
|
}
|
||||||
|
|
||||||
$g['hours_left'] -= 1;
|
$g['hours_left'] -= 1;
|
||||||
$g['meals'] += 1;
|
$g['meals'] += 1;
|
||||||
new_day_check($g, $messages);
|
|
||||||
repo_check($g, $messages);
|
newDayCheck($g, $messages);
|
||||||
|
|
||||||
|
repoCheck($g, $messages);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'enhance':
|
case 'enhance':
|
||||||
if ($g['mods'] >= 4) {
|
if ($g['mods'] >= 4) {
|
||||||
$messages[] = "No available enhancements were found...\n";
|
$messages[] = "No available enhancements were found...\n";
|
||||||
} else {
|
} else {
|
||||||
$messages[] = "You found a shop with modifications and enhancements you can install. Would you like to install one?";
|
$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;
|
||||||
$can_install = true;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'install_enhancement':
|
case 'install_enhancement':
|
||||||
if ($g['mods'] < 4) {
|
if ($g['mods'] < 4) {
|
||||||
$g['mods'] += 1;
|
$g['mods'] += 1;
|
||||||
$g['hours_left'] -= 6;
|
$g['hours_left'] -= 6;
|
||||||
switch ($g['mods']) {
|
|
||||||
|
switch ($g['mods']) {
|
||||||
case 1:
|
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";
|
$messages[] = "You have replaced your legs with robotic enhancements. You can now stand longer and lift more weight, increasing your productivity.\n\nThe Corpus have been generous enough to offer this for no cost, apart from an increased interest rate.\n";
|
||||||
$g['interest'] = 0.26;
|
$g['interest'] = 0.26;
|
||||||
$g['wage'] = 1150;
|
$g['wage'] = 1150;
|
||||||
break;
|
break;
|
||||||
case 2:
|
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";
|
$messages[] = "You have replaced your arms with robotic prosthetics, allowing you to work harder and more accurately.\n\nThe Corpus have been generous enough to offer this for no cost, apart from an increased interest rate.\n";
|
||||||
$g['interest'] = 0.30;
|
$g['interest'] = 0.30;
|
||||||
$g['wage'] = 1400;
|
$g['wage'] = 1400;
|
||||||
break;
|
break;
|
||||||
case 3:
|
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";
|
$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\nThe Corpus have been generous enough to offer this for no cost, apart from an increased interest rate.\n";
|
||||||
$g['interest'] = 0.35;
|
$g['interest'] = 0.35;
|
||||||
$g['wage'] = 1600;
|
$g['wage'] = 1600;
|
||||||
break;
|
break;
|
||||||
case 4:
|
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";
|
$messages[] = "You have replaced your head with a robotic apparatus. You will no longer feel tired, or pain, or anything at all.\n\nThe Corpus have been generous enough to offer this for no cost, apart from an increased interest rate.\n";
|
||||||
$g['interest'] = 0.38;
|
$g['interest'] = 0.38;
|
||||||
$g['wage'] = 1800;
|
$g['wage'] = 1800;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
new_day_check($g, $messages);
|
|
||||||
repo_check($g, $messages);
|
newDayCheck($g, $messages);
|
||||||
|
|
||||||
|
repoCheck($g, $messages);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'quit':
|
case 'quit':
|
||||||
$g['game_over'] = true;
|
$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)) . '%.';
|
$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;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
$messages[] = 'Invalid input. Please try again';
|
$messages[] = 'Invalid input. Please try again';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode(
|
||||||
'messages' => $messages,
|
[
|
||||||
'state' => serialize_state($g),
|
'messages' => $messages,
|
||||||
'game_over' => $g['game_over'],
|
'state' => serializeState($g),
|
||||||
'final_summary' => $final_summary,
|
'game_over' => $g['game_over'],
|
||||||
'can_install' => $can_install,
|
'final_summary' => $final_summary,
|
||||||
]);
|
'can_install' => $can_install,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|||||||
156
index.php
156
index.php
@@ -3,138 +3,25 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Corpus Worker RPG (Web)</title>
|
<title>Days In Fortuna</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) {
|
<link rel="stylesheet" href="style.css" />
|
||||||
const log = document.getElementById('log');
|
</head>
|
||||||
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>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>Corpus Worker RPG</h1>
|
<h1>Days In Fortuna</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<button onclick="resetGame()">Reset Game</button>
|
<button onclick="resetGame()">Reset Game</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<h2>Status</h2>
|
<h2>Status</h2>
|
||||||
<div id="miniState" class="content"></div>
|
<div id="miniState" class="content"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<h2>Actions</h2>
|
<h2>Actions</h2>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -145,29 +32,38 @@
|
|||||||
<button data-action onclick="doAction('eat')">Eat</button>
|
<button data-action onclick="doAction('eat')">Eat</button>
|
||||||
<button data-action onclick="handleEnhance()">Search Enhancements</button>
|
<button data-action onclick="handleEnhance()">Search Enhancements</button>
|
||||||
<button data-action onclick="doAction('quit')">Quit</button>
|
<button data-action onclick="doAction('quit')">Quit</button>
|
||||||
|
<button data-action onclick="doAction('reset')">Reset</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="sep"></div>
|
<div class="sep"></div>
|
||||||
<div class="muted">Tip: Sleep 8h and eat twice daily to maintain efficiency.</div>
|
<div class="muted">
|
||||||
|
<p>Tip: Sleep 8h and eat twice daily to maintain efficiency.</p>
|
||||||
|
|
||||||
|
<p>This 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.</p>
|
||||||
|
|
||||||
|
<p>Click the button above associated with what you want to do in order to progress.</p>
|
||||||
|
|
||||||
|
<p>Enhancements can be rented from the Corpus, which will increase your possible wages.</p>
|
||||||
|
|
||||||
|
<p>It 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.</p>
|
||||||
|
|
||||||
|
<p>Interest 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.</p>
|
||||||
|
|
||||||
|
Good luck!
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel">
|
<section class="panel log">
|
||||||
<h2>Log</h2>
|
<h2>Log</h2>
|
||||||
<div id="log" class="content"></div>
|
<div id="log" class="content"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
async function resetGame() {
|
<section class="panel hidden">
|
||||||
// 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>
|
<h2>State</h2>
|
||||||
<div id="state" class="content"></div>
|
<div id="state" class="content"></div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<script src="app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
99
style.css
Normal file
99
style.css
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
body {
|
||||||
|
background: #0f1420;
|
||||||
|
color: #e9eef8;
|
||||||
|
font-family: system-ui, Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
align-items: center;
|
||||||
|
background: #121a2a;
|
||||||
|
border-bottom: 1px solid #27324a;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
grid-template-columns: 300px 1fr;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
background: #111827;
|
||||||
|
border: 1px solid #27324a;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #a3b5d9;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content { padding: 12px; }
|
||||||
|
|
||||||
|
&.log { grid-column: 1 / span 2; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
background: #0b1020;
|
||||||
|
color: #e9eef8;
|
||||||
|
max-height: 60vh;
|
||||||
|
min-height: 360px;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: #1f2a44;
|
||||||
|
border: 1px solid #2c3a5e;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #e9eef8;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 10px;
|
||||||
|
|
||||||
|
&:hover { background: #263356; }
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.muted { color: #a3b5d9; }
|
||||||
|
|
||||||
|
.sep {
|
||||||
|
background: #27324a;
|
||||||
|
height: 1px;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
||||||
|
|
||||||
|
.kv {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 2px 0;
|
||||||
|
|
||||||
|
.label { color: #a3b5d9; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user