<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chronicles of Aethelgard - Text Adventure</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #2c3e50; /* Dark background */
color: #ecf0f1; /* Light text */
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
line-height: 1.6;
}
#game-container {
background-color: #34495e; /* Slightly lighter container */
border: 2px solid #7f8c8d;
border-radius: 8px;
padding: 25px;
width: 90%;
max-width: 700px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
}
h1 {
color: #e67e22; /* Orange title */
text-align: center;
border-bottom: 1px solid #e67e22;
padding-bottom: 10px;
}
#character-info {
background-color: #2c3e50;
padding: 10px;
margin-bottom: 15px;
border-radius: 4px;
border-left: 4px solid #e67e22;
}
#story-text {
margin-bottom: 20px;
min-height: 100px; /* Ensure space for text */
background-color: #4a617a; /* Different background for text area */
padding: 15px;
border-radius: 4px;
}
#choices {
display: flex;
flex-direction: column;
gap: 10px;
}
.choice-button {
background-color: #e67e22; /* Orange buttons */
color: #2c3e50; /* Dark text on buttons */
border: none;
padding: 12px 15px;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
font-weight: bold;
text-align: left;
transition: background-color 0.2s ease, transform 0.1s ease;
}
.choice-button:hover {
background-color: #d35400; /* Darker orange on hover */
transform: translateY(-2px);
}
.choice-button:disabled {
background-color: #7f8c8d; /* Greyed out disabled */
cursor: not-allowed;
transform: none;
}
.ending {
font-weight: bold;
color: #f1c40f; /* Yellow for endings */
text-align: center;
font-size: 1.2em;
padding: 15px;
border: 1px dashed #f1c40f;
margin-top: 20px;
}
</style>
</head>
<body>
<div id="game-container">
<h1>Chronicles of Aethelgard</h1>
<div id="character-info">
Name: <span id="player-name"></span> | HP: <span id="player-hp"></span>
</div>
<div id="story-text">
Loading your adventure...
</div>
<div id="choices">
<!-- Choices will be populated here -->
</div>
</div>
<script>
// --- Game State ---
const player = {
name: "Hero",
hp: 20,
maxHp: 20,
inventory: {}, // Simple flags for items, e.g., { hasSword: true }
flags: {} // Flags for choices made, e.g., { helpedGuard: true, angeredMerchant: false }
};
let currentSceneKey = 'start';
// --- DOM Elements ---
const playerNameDisplay = document.getElementById('player-name');
const playerHpDisplay = document.getElementById('player-hp');
const storyTextElement = document.getElementById('story-text');
const choicesElement = document.getElementById('choices');
// --- Scene Definitions ---
// Each scene has text and an array of choices.
// Each choice has text, potentially a condition function, an action function (optional), and the next scene key.
const scenes = {
'start': {
text: "The wind howls around you. You stand at a crossroads outside the village of Oakhaven. Smoke rises from the village - trouble is brewing. To the east lies the whispering Darkwood forest. What do you do?",
choices: [
{ text: "Enter Oakhaven village", nextScene: 'oakhaven_entrance' },
{ text: "Head east into the Darkwood", nextScene: 'darkwood_entrance' }
]
},
'oakhaven_entrance': {
text: "Guards block the gate, looking panicked. 'Halt! Goblins attacked! The merchant's guild is blaming the woodcutters, and things are tense. What's your business?'",
choices: [
{ text: "Offer to help investigate the goblin attack.", nextScene: 'offer_help_guard', action: () => player.flags.offeredHelp = true },
{ text: "'Just passing through.' Try to bluff your way in.", nextScene: 'bluff_guard' },
{ text: "Mention you know something about the Darkwood.", condition: () => player.flags.knowsDarkwoodSecret, nextScene: 'mention_secret_guard' } // Example condition
]
},
'offer_help_guard': {
text: "The guard captain looks relieved. 'Thank the Divines! We suspect they came from the Darkwood. Speak to Elder Rowan in the village square. He knows more.' The guards let you pass.",
choices: [
{ text: "Go to the village square", nextScene: 'village_square' }
]
},
'bluff_guard': {
text: () => { // Use function for dynamic text based on state
const bluffSuccess = Math.random() > 0.4; // 60% chance to succeed
if (bluffSuccess) {
player.flags.bluffedGuard = true;
return "You spin a convincing tale about urgent business. The guards, distracted and stressed, wave you through impatiently.";
} else {
player.flags.angeredGuard = true;
return "'We don't have time for games!' The guards roughly push you back. 'Come back when you have real business, or not at all!' You cannot enter Oakhaven this way now.";
}
},
choices: [
{ text: "Go to the village square (if successful)", condition: () => player.flags.bluffedGuard, nextScene: 'village_square' },
{ text: "Turn back to the crossroads (if failed)", condition: () => player.flags.angeredGuard, nextScene: 'start' },
{ text: "Head east into the Darkwood", nextScene: 'darkwood_entrance' } // Always an option to leave
]
},
'village_square': {
text: "The square is tense. Merchants glare at rough-looking woodcutters. Elder Rowan, a calm figure amidst the chaos, spots you. 'A new face? We need aid. Goblins stole the Sunstone Pendant.'",
choices: [
{ text: "'I'll retrieve the pendant.'", nextScene: 'accept_quest_pendant' },
{ text: "'What's the reward?'", nextScene: 'ask_reward' },
{ text: "Observe the merchants and woodcutters", nextScene: 'observe_square' },
{ text: "Leave the village", nextScene: 'start'}
]
},
'accept_quest_pendant': {
text: "'Bless you,' Rowan says. 'We believe the goblins lair is deep within the Darkwood. Be careful, adventurer.' He gives you a simple map.",
action: () => { player.inventory.hasMap = true; player.flags.acceptedQuest = true; },
choices: [
{ text: "Head to the Darkwood", nextScene: 'darkwood_entrance' },
{ text: "Ask Rowan more about the factions", nextScene: 'ask_factions' }
]
},
'ask_reward': {
text: "'The gratitude of Oakhaven! And... perhaps a share of the merchant guild's recovery fund, if they're feeling generous,' Rowan admits wryly.",
choices: [
{ text: "'Fine, I'll do it.'", nextScene: 'accept_quest_pendant' },
{ text: "'Not worth my time.'", nextScene: 'village_square' } // Go back to square options
]
},
'darkwood_entrance': {
text: "The edge of the Darkwood is unnerving. Twisted trees block the sun, and strange noises echo. You see faint goblin tracks leading deeper in.",
choices: [
{ text: "Follow the goblin tracks", nextScene: 'darkwood_path' },
{ text: "Search the edge of the forest for herbs", nextScene: 'search_herbs' },
{ text: "Turn back to the crossroads", nextScene: 'start' }
]
},
'darkwood_path': {
text: "You follow the tracks. Suddenly, a snarling Goblin Jumper leaps from the bushes!",
choices: [
{ text: "Fight the Goblin!", nextScene: 'combat_goblin' },
{ text: "Try to sneak past", nextScene: 'sneak_goblin' }
]
},
'combat_goblin': {
text: () => {
const playerWins = Math.random() > 0.3; // 70% chance player wins simple fight
if (playerWins) {
player.hp -= 3; // Take some damage
if (player.hp <= 0) return "You fought bravely, but the goblin was too quick. Your adventure ends here..."; // Go to game over later
return `You defeat the goblin after a brief struggle, taking a few scratches (HP: ${player.hp}). The path ahead is clearer.`;
} else {
player.hp -= 10; // Take more damage
if (player.hp <= 0) return "The goblin overwhelms you with surprising ferocity. Your vision fades to black..."; // Go to game over later
return `The goblin lands a nasty blow (HP: ${player.hp})! You manage to scare it off, but you're wounded.`;
}
},
choices: [
{ text: "Continue deeper into the wood (if HP > 0)", condition: () => player.hp > 0, nextScene: 'darkwood_deep' },
{ text: "Game Over (Return to start)", condition: () => player.hp <= 0, nextScene: 'game_over_death' }
]
},
'sneak_goblin': {
text: () => {
const sneakSuccess = Math.random() > 0.6; // 40% chance to succeed
if (sneakSuccess) {
player.flags.snuckPastGoblin = true;
return "Holding your breath, you slip past the distracted goblin unnoticed. Good job.";
} else {
return "You snap a twig! The goblin screeches and lunges! Prepare to fight!";
}
},
choices: [
{ text: "Continue deeper into the wood (if successful)", condition: () => player.flags.snuckPastGoblin, nextScene: 'darkwood_deep' },
{ text: "Fight the Goblin! (if failed)", condition: () => !player.flags.snuckPastGoblin, nextScene: 'combat_goblin' }
]
},
'darkwood_deep': {
text: "Deeper in the woods, you find a crude goblin camp. In the center, resting on a pile of stolen goods, is the Sunstone Pendant!",
choices: [
{ text: "Grab the pendant and run!", nextScene: 'grab_and_run' },
{ text: "Try to stealthily take the pendant", nextScene: 'stealth_pendant' },
{ text: "Charge in and fight the remaining goblins!", nextScene: 'fight_camp' } // More complex combat needed here
]
},
'grab_and_run': {
text: "You snatch the pendant! Goblins shout and give chase! You sprint back through the woods, branches whipping your face.",
action: () => player.inventory.hasPendant = true,
choices: [
{ text: "Keep running!", nextScene: 'escape_woods' }
]
},
'escape_woods': {
text: () => {
const escapeSuccess = Math.random() > 0.2; // 80% chance
if (escapeSuccess) {
return "You burst out of the Darkwood, heart pounding, pendant clutched tight! You made it!";
} else {
player.hp -= 8;
if (player.hp <= 0) return "A well-aimed rock strikes your head. You stumble and fall as the goblins swarm...";
return `You trip and fall, taking a beating (HP: ${player.hp}) before scrambling away. You escaped, but barely.`;
}
},
choices: [
{ text: "Return to Oakhaven (if HP > 0)", condition: () => player.hp > 0 && player.inventory.hasPendant, nextScene: 'return_oakhaven_success' },
{ text: "Game Over (Return to start)", condition: () => player.hp <= 0, nextScene: 'game_over_death' }
]
},
// --- Endings ---
'return_oakhaven_success': {
text: "You present the Sunstone Pendant to Elder Rowan. Relief washes over the village square. Rowan thanks you profusely.",
choices: [
{ text: "Accept the village's gratitude (Good Ending)", nextScene: 'ending_hero' },
{ text: "Demand payment from the Merchant Guild (Neutral Ending)", nextScene: 'ending_mercenary', condition: () => !player.flags.angeredGuard } // Can't demand if you angered them
]
},
'ending_hero': {
text: "<div class='ending'>You are hailed as the Hero of Oakhaven. Though the reward is modest, the people's gratitude is genuine. You brought peace back to the village, proving that cooperation, not blame, is the way forward. Your legend begins here.<br>THE END</div>",
isEnding: true
},
'ending_mercenary': {
text: "<div class='ending'>You leverage the pendant's return for a hefty pouch of gold from the grudging merchants. You're richer, but the tensions in Oakhaven remain unresolved. Some call you shrewd, others greedy. Your path is paved with coin, not camaraderie.<br>THE END</div>",
isEnding: true
},
'game_over_death': {
text: "<div class='ending'>Your journey ends prematurely. Aethelgard is a dangerous world, and this time, it claimed you. Perhaps another hero will succeed where you failed.<br>GAME OVER</div>",
isEnding: true,
action: () => player.hp = 0 // Ensure HP is 0 for display
}
// --- Add many more scenes! ---
// 'search_herbs', 'ask_factions', 'observe_square', 'stealth_pendant', 'fight_camp', 'mention_secret_guard' etc.
};
// --- Game Logic Functions ---
function updateDisplay() {
playerNameDisplay.textContent = player.name;
playerHpDisplay.textContent = `${player.hp} / ${player.maxHp}`;
}
function showScene(sceneKey) {
const scene = scenes[sceneKey];
if (!scene) {
console.error(`Scene not found: ${sceneKey}`);
storyTextElement.textContent = "Error: Scene not found. Your adventure is lost in the void.";
choicesElement.innerHTML = '';
return;
}
// Update current key
currentSceneKey = sceneKey;
// Execute scene action BEFORE showing text/choices (e.g., for game over)
if (scene.action) {
scene.action();
}
// Display text - Allow text to be a function for dynamic content
let displayText = (typeof scene.text === 'function') ? scene.text() : scene.text;
storyTextElement.innerHTML = displayText; // Use innerHTML to allow ending divs
// Clear old choices
choicesElement.innerHTML = '';
// Check if it's an ending scene
if (scene.isEnding) {
// Optionally add a restart button
const restartButton = document.createElement('button');
restartButton.textContent = "Restart Adventure?";
restartButton.classList.add('choice-button');
restartButton.onclick = () => window.location.reload(); // Simple restart
choicesElement.appendChild(restartButton);
} else if (scene.choices) {
// Display choices
scene.choices.forEach(choice => {
// Check if the choice condition is met (if it exists)
if (choice.condition === undefined || choice.condition()) {
const button = document.createElement('button');
button.textContent = choice.text;
button.classList.add('choice-button');
button.onclick = () => makeChoice(choice);
choicesElement.appendChild(button);
}
});
}
// Update player stats display
updateDisplay();
}
function makeChoice(choice) {
// Optional: Execute choice-specific action (e.g., modify flags BEFORE moving scene)
if (choice.action) {
choice.action();
}
// Move to the next scene
showScene(choice.nextScene);
}
function promptForName() {
const name = prompt("Enter your hero's name:", "Hero");
if (name) {
player.name = name;
}
}
// --- Start Game ---
document.addEventListener('DOMContentLoaded', () => {
promptForName();
showScene('start');
});
</script>
</body>
</html>
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.