Skip to content

Instantly share code, notes, and snippets.

@mode-mercury
Created October 19, 2025 04:21
Show Gist options
  • Select an option

  • Save mode-mercury/40920181f544708606832f4af8e09fd7 to your computer and use it in GitHub Desktop.

Select an option

Save mode-mercury/40920181f544708606832f4af8e09fd7 to your computer and use it in GitHub Desktop.
Untitled
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Occult Masters: Retro Edition</title>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
body { background-color: #000; color: #fff; font-family: 'Press Start 2P', monospace; text-align: center; margin: 0; padding: 0; height: 100vh; display: flex; justify-content: center; align-items: center; overflow: hidden; touch-action: manipulation; }
#game-container { width: 100vw; height: 100vh; max-width: 800px; max-height: 600px; border: 8px solid #333; box-shadow: 0 0 0 4px #888, inset 0 0 10px rgba(0,0,0,0.8); background-color: #222; position: relative; overflow: hidden; }
.screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 20px; background-color: #111; z-index: 10; }
.hidden { display: none !important; z-index: 0; }
#transition-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: #000; z-index: 999; opacity: 0; pointer-events: none; transition: opacity 0.3s; }
.title { font-size: clamp(16px, 4vw, 32px); color: #ff0; text-shadow: 2px 2px #f00; margin-bottom: 5px; line-height: 1.2; }
.subtitle { font-size: clamp(8px, 2vw, 16px); color: #0f0; margin-bottom: 20px; line-height: 1.3; }
.btn { background-color: #f00; color: #fff; border: 4px solid #fff; padding: 10px 20px; margin: 8px; cursor: pointer; font-family: 'Press Start 2P', monospace; font-size: clamp(8px, 2vw, 12px); box-shadow: 4px 4px 0 #800; transition: all 0.1s; }
.btn:hover, .btn:active { background-color: #ff8800; box-shadow: 4px 4px 0 #aa0; transform: translate(2px, 2px); }
.message-log { background-color: #3e3831; border: 4px solid #6c6358; padding: 10px; overflow-y: auto; text-align: left; margin-top: 10px; font-size: clamp(6px, 1.5vw, 10px); color: #d0d0d0; line-height: 1.4; }
.color-red { color: #f00; } .color-blue { color: #00f; } .color-yellow { color: #ff0; } .color-cyan { color: #0ff; } .color-magenta { color: #f0f; } .color-white { color: #fff; } .color-green { color: #0f0; }
.pixel-art { width: clamp(32px, 8vw, 64px); height: clamp(32px, 8vw, 64px); margin: 5px; border: 2px solid #fff; box-shadow: 0 0 0 2px #000; background-color: transparent; image-rendering: pixelated; }
.character-card { cursor: pointer; margin: 5px; display: flex; flex-direction: column; align-items: center; padding: 10px; border: 2px solid transparent; transition: all 0.2s; }
.character-card:hover { border-color: #ff0; background-color: rgba(255,255,0,0.1); }
.character-card.selected { border-color: #f00; background-color: rgba(255,0,0,0.1); }
.character-name { font-size: clamp(6px, 1.5vw, 10px); margin-top: 5px; text-align: center; }
#dragon-follower { width: clamp(16px, 4vw, 32px); height: clamp(16px, 4vw, 32px); border: 2px solid #fff; margin-right: 10px; }
#game-screen { display: grid; grid-template-columns: 2fr 1fr; grid-template-rows: 1fr auto auto; gap: 5px; padding: 5px; height: 100%; width: 100%; }
#game-canvas { grid-column: 1 / 2; grid-row: 1 / 3; background-color: #000; border: 2px solid #6c6358; width: 100%; height: 100%; position: relative; }
#game-screen .message-log { grid-column: 1 / 3; grid-row: 3 / 4; height: 80px; margin-top: 0; }
#character-select { overflow: hidden; padding: 10px; justify-content: flex-start; }
.character-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-height: 70%; overflow-y: auto; padding: 5px; margin: 10px 0; }
#world-map-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: none; background-color: #000; z-index: 20; padding: 10px; }
#world-map { display: grid; grid-template-columns: repeat(8, 1fr); grid-template-rows: repeat(8, 1fr); width: 100%; height: 100%; gap: 1px; }
.map-tile { border: 1px solid rgba(255,255,255,0.3); position: relative; display: flex; justify-content: center; align-items: center; font-size: 8px; }
.map-tile.tundra { background-color: #aaf; color: #000; } .map-tile.water { background-color: #44f; color: #fff; } .map-tile.mountain { background-color: #888; color: #fff; } .map-tile.forest { background-color: #0a0; color: #fff; } .map-tile.plain { background-color: #8a8; color: #000; } .map-tile.city { background-color: #ff0; color: #000; } .map-tile.desert { background-color: #fc8; color: #000; } .map-tile.dungeon { background-color: #800; color: #fff; } .map-tile.home { background-color: #f0f; color: #000; }
.player-marker { position: absolute; width: 60%; height: 60%; background-color: #f00; border-radius: 50%; z-index: 10; border: 2px solid #fff; }
.stats-container { background-color: rgba(0,0,0,0.7); padding: 10px; border: 2px solid #6c6358; font-size: clamp(6px, 1.5vw, 10px); }
.stat { font-size: clamp(6px, 1.5vw, 10px); margin-bottom: 5px; }
.health-bar, .magic-bar { width: 100%; height: 10px; background-color: #333; margin-bottom: 5px; border: 1px solid #fff; position: relative; }
.health-fill { height: 100%; background-color: #f00; transition: width 0.3s; } .magic-fill { height: 100%; background-color: #00f; transition: width 0.3s; }
.companion-stats { display: flex; align-items: center; margin-top: 10px; font-size: clamp(6px, 1.5vw, 8px); }
.combatants { display: flex; justify-content: space-around; width: 90%; margin-bottom: 20px; }
.combatant { display: flex; flex-direction: column; align-items: center; }
.combat-sprite { width: clamp(40px, 10vw, 80px); height: clamp(40px, 10vw, 80px); }
.controls-container { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); gap: 5px; width: 120px; height: 120px; margin-top: 10px; }
.control-btn { background-color: #444; color: #fff; border: 2px solid #fff; display: flex; justify-content: center; align-items: center; font-size: 20px; cursor: pointer; user-select: none; }
.control-btn:active { background-color: #666; transform: translate(1px, 1px); }
#up-btn { grid-column: 2; grid-row: 1; } #left-btn { grid-column: 1; grid-row: 2; } #right-btn { grid-column: 3; grid-row: 2; } #down-btn { grid-column: 2; grid-row: 3; } #action-btn { grid-column: 2; grid-row: 2; background-color: #f00; font-size: 14px; }
.action-buttons { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; margin-top: 10px; }
.action-btn { font-size: clamp(6px, 1.5vw, 10px); padding: 8px 5px; }
#home-screen { display: flex; flex-direction: column; justify-content: space-between; padding: 20px; }
.home-options { display: flex; flex-direction: column; width: 80%; gap: 10px; }
.home-btn { width: 100%; padding: 15px; }
.locomotion-card { cursor: pointer; margin: 10px; display: flex; flex-direction: column; align-items: center; padding: 10px; border: 2px solid transparent; transition: all 0.2s; }
.locomotion-card:hover, .locomotion-card.selected { border-color: #ff0; background-color: rgba(255,255,0,0.1); }
.npc-dialogue { background-color: rgba(0,0,0,0.8); border: 2px solid #6c6358; padding: 15px; margin: 10px 0; font-size: clamp(6px, 1.5vw, 10px); line-height: 1.4; }
.quest-item { color: #ff0; font-weight: bold; }
@media (max-height: 500px) { .title { font-size: 16px; } .subtitle { font-size: 10px; } .btn { padding: 5px 10px; } .character-grid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 500px) { .character-grid { grid-template-columns: repeat(3, 1fr); } }
</style>
</head>
<body>
<div id="transition-overlay"></div>
<div id="game-container">
<div id="loading-screen" class="screen">
<h1 class="title">OCCULT MASTERS</h1>
<p class="subtitle">TAP ANYWHERE TO BEGIN</p>
</div>
<div id="title-screen" class="screen hidden">
<h1 class="title">OCCULT MASTERS</h1>
<p class="subtitle">THE HERMETIC QUEST</p>
<button id="start-btn" class="btn">START GAME</button>
<button id="settings-btn" class="btn">SETTINGS</button>
</div>
<div id="intro-screen" class="screen hidden">
<div class="message-log" id="intro-dialogue-box" style="height: 70%; width: 90%;"></div>
<button id="intro-continue" class="btn">NEXT</button>
</div>
<div id="character-select" class="screen hidden">
<h2 class="title">CHOOSE YOUR MASTER</h2>
<p class="subtitle">WHICH DISCIPLE OF THE HERMETIC ARTS WILL YOU BE?</p>
<div class="character-grid"></div>
<button id="next-to-locomotion" class="btn hidden">NEXT</button>
</div>
<div id="locomotion-choice" class="screen hidden">
<h2 class="title">CHOOSE YOUR MODE OF TRAVEL</h2>
<p class="subtitle">WILL YOU CHOOSE THE MUNDANE, THE MYSTICAL, OR THE MADNESS?</p>
<div id="locomotion-select" style="display: flex; flex-wrap: wrap; justify-content: center;"></div>
<button id="confirm-locomotion" class="btn">BEGIN QUEST</button>
</div>
<div id="home-screen" class="screen hidden">
<h2 class="title">HERMETIC SANCTUARY</h2>
<p class="subtitle">YOUR PERSONAL REALM OF ESOTERIC STUDY</p>
<div class="home-options">
<button id="home-to-map" class="btn home-btn">ENTER WORLD MAP</button>
<button id="home-rest" class="btn home-btn">REST & RECOVER</button>
<button id="home-grimoire" class="btn home-btn">STUDY GRIMOIRE</button>
<button id="home-alchemy" class="btn home-btn">ALCHEMY LAB</button>
</div>
<div class="message-log" id="home-log" style="height: 30%; width: 90%;">WELCOME TO YOUR SANCTUARY. THE AIR HUM WITH ANCIENT POWER.</div>
</div>
<div id="game-screen" class="screen hidden">
<canvas id="game-canvas"></canvas>
<div id="game-ui">
<div class="stats-container">
<div class="stat" id="player-stats">LVL: 1 | HP: 100/100 | MP: 50/50</div>
<div class="health-bar"><div class="health-fill" id="player-health-bar" style="width: 100%;"></div></div>
<div class="magic-bar"><div class="magic-fill" id="player-magic-bar" style="width: 100%;"></div></div>
<div class="stat" id="location">HERMETIC SANCTUARY</div>
<div class="companion-stats">
<div id="dragon-follower" class="pixel-art"></div>
<span>DRAGON: FLAMEL | HP: 50 | ATK: 5</span>
</div>
</div>
<div class="controls-container">
<div id="up-btn" class="control-btn">↑</div>
<div id="left-btn" class="control-btn">←</div>
<div id="action-btn" class="control-btn">A</div>
<div id="right-btn" class="control-btn">→</div>
<div id="down-btn" class="control-btn">↓</div>
</div>
<div class="action-buttons" id="map-action-buttons">
<button class="btn action-btn" data-action="talk">TALK</button>
<button class="btn action-btn" data-action="magic">MAGIC</button>
<button class="btn action-btn" data-action="item">ITEM</button>
<button class="btn action-btn" data-action="map">MAP</button>
</div>
<div class="message-log" id="game-log">OCCULT MASTER IS READY. DRAGON IS ASLEEP.</div>
</div>
</div>
<div id="world-map-container" class="hidden">
<div id="world-map"></div>
<button id="close-map" class="btn" style="position: absolute; bottom: 10px; right: 10px;">CLOSE MAP</button>
<button id="return-home" class="btn" style="position: absolute; bottom: 10px; left: 10px;">RETURN HOME</button>
</div>
<div id="combat-screen" class="screen hidden">
<h2 class="title">OCCULT COMBAT!</h2>
<div class="combatants">
<div class="combatant">
<div class="player-sprite pixel-art combat-sprite" id="player-combat-sprite"></div>
<div class="character-name" id="player-name">MASTER</div>
<div class="health-bar" style="width: 80px;"><div class="health-fill" id="player-health-combat" style="width: 100%;"></div></div>
<div class="stat">MP: <span id="player-mp-combat">50/50</span></div>
</div>
<div class="combatant">
<div class="enemy-sprite pixel-art combat-sprite" id="enemy-combat-sprite"></div>
<div class="character-name" id="enemy-name">ENEMY</div>
<div class="health-bar" style="width: 80px;"><div class="health-fill" id="enemy-health" style="width: 100%;"></div></div>
</div>
</div>
<div class="action-buttons" id="combat-action-buttons">
<button class="btn action-btn" data-action="attack">ATTACK</button>
<button class="btn action-btn" data-action="magic">MAGIC</button>
<button class="btn action-btn" data-action="item">ITEM</button>
<button class="btn action-btn" data-action="flee">FLEE</button>
</div>
<div class="message-log" id="combat-log">A GOETIC SPIRIT HAS CHALLENGED YOU!</div>
</div>
<div id="npc-screen" class="screen hidden">
<div class="npc-dialogue" id="npc-dialogue-box"></div>
<button id="npc-continue" class="btn">CONTINUE</button>
</div>
</div>
<script>
// Game State
let currentGameScreen = '#loading-screen';
let selectedCharacter = null;
let selectedLocomotion = null;
let currentDialogueStep = 0;
let playerPosition = { x: 3, y: 3 };
let gameState = {
level: 1,
health: 100,
maxHealth: 100,
magic: 50,
maxMagic: 50,
location: 'Hermetic Sanctuary',
quests: {
findScrollCase: false,
findEmeraldTablet: false,
awakenDragon: false,
defeatTechnocracy: false
},
inventory: ['Dusty Robe', 'Beginner\'s Wand']
};
// Enhanced dialogue with rich occult humor
const dialogueLines = [
"<p><span class='color-white'>MASTER INITIATOR:</span> Welcome, Neophyte. The path to the Great Work is fraught with danger, bureaucracy, and endless appendices.</p>",
"<p>Your first task is simple: find the <span class='color-yellow'>Philosopher's Stone</span>... but first, you need to find your scroll case. I think I left it near the <span class='quest-item'>777 correspondence tables</span>.</p>",
"<p><span class='color-red'>A WIZENED GOETIAN:</span> \"I swear, the filing system for the <span class='color-red'>GOETIC DEMONS</span> hasn't been updated since the 17th century. What's the point of the <span class='color-red'>777</span> tables if the library hours are only 9-to-5? Also, don't summon <span class='color-red'>Buer</span> on a Friday - he's grumpy about weekend work.\"</p>",
"<p><span class='color-cyan'>ENOCHIAN WATCHER:</span> A dusty voice whispers: \"If you don't know the proper <span class='color-cyan'>ENOCHIAN CALL</span> for a lost sock, you'll never achieve <span class='color-magenta'>BINAH</span>. Also, the <span class='color-cyan'>Archangel Gabriel</span> keeps complaining about the Wi-Fi in the celestial spheres.\"</p>",
"<p><span class='color-blue'>TAROT READER:</span> Beware the Hermetic Order of the Broken Coffee Maker. Their Grand Master is terrible, but his <span class='color-blue'>TAROT</span> reading about your next date was spot on. <span class='color-red'>The Hanged Man</span> is not a good sign for a first kiss, trust me.</p>",
"<p><span class='color-yellow'>QABALLIST:</span> \"You must balance the Pillars of the <span class='color-yellow'>QABALLAH</span>! But mostly, you need to remember the order of the Spheres, or you'll accidentally summon a librarian instead of an Archangel. <span class='color-yellow'>Malkuth</span> is NOT where you store your lunch.\"</p>",
"<p><span class='color-white'>DRAGON WHISPERER:</span> \"And don't forget your loyal companion, Flamel. He's a baby dragon. Mostly he just breathes light smoke, but he keeps the mice out of your <span class='color-white'>Grimoire</span>. Just don't let him near the <span class='color-red'>Goetia</span> section - he thinks the demons are chew toys.\"</p>",
"<p><span class='color-magenta'>HERMETIC BUREAUCRAT:</span> \"Before you can access the Akashic Records, you'll need to fill out form 7B in triplicate. And don't forget to initial the section about non-disclosure of cosmic secrets. The <span class='color-magenta'>Technocracy</span> has been auditing us lately.\"</p>",
"<p><span class='color-cyan'>ALCHEMIST:</span> \"Yes, turning lead into gold is possible, but have you tried turning coffee into coherent thoughts at 3 AM? That's the real transmutation. Also, the <span class='quest-item'>Emerald Tablet</span> makes a great cutting board if you're in a pinch.\"</p>",
"<p><span class='color-green'>THELEMA INITIATE:</span> \"Remember: <span class='color-green'>Do what thou wilt shall be the whole of the Law</span>. Unless what thou wilt involves rearranging the library's <span class='color-red'>777 tables</span> - that requires a committee meeting.\"</p>"
];
// Real occult figures with specialties
const characters = [
{ id: 'crowley', name: 'CROWLEY', specialty: 'Thelema & Magick' },
{ id: 'blavatsky', name: 'BLAVATSKY', specialty: 'Theosophy' },
{ id: 'levi', name: 'ELIPHAS LEVI', specialty: 'Ritual Magic' },
{ id: 'dee', name: 'JOHN DEE', specialty: 'Enochian Magic' },
{ id: 'paracelsus', name: 'PARACELSUS', specialty: 'Alchemy' },
{ id: 'merlin', name: 'MERLIN', specialty: 'Druidic Arts' },
{ id: 'nostradamus', name: 'NOSTRADAMUS', specialty: 'Divination' },
{ id: 'bardon', name: 'FRANZ BARDON', specialty: 'Hermetics' },
{ id: 'regardie', name: 'ISRAEL REGARDIE', specialty: 'Golden Dawn' }
];
const locomotionTypes = [
{ id: 'skateboard', name: 'MAGICAL SKATEBOARD', desc: 'FAST (HIGH ENCOUNTER RATE)', speed: 'fast' },
{ id: 'broom', name: 'FLYING BROOMSTICK', desc: 'BALANCED (MEDIUM ENCOUNTER RATE)', speed: 'medium' },
{ id: 'carpet', name: 'MAGIC CARPET', desc: 'SLOW (LOW ENCOUNTER RATE)', speed: 'slow' }
];
const worldMap = [
['tundra', 'tundra', 'water', 'water', 'water', 'water', 'mountain', 'mountain'],
['mountain', 'forest', 'forest', 'forest', 'plain', 'plain', 'plain', 'mountain'],
['plain', 'plain', 'home', 'forest', 'desert', 'desert', 'desert', 'water'],
['forest', 'forest', 'plain', 'plain', 'desert', 'desert', 'plain', 'water'],
['desert', 'desert', 'desert', 'plain', 'mountain', 'dungeon', 'mountain', 'water'],
['water', 'water', 'plain', 'plain', 'forest', 'forest', 'forest', 'water'],
['water', 'water', 'water', 'water', 'tundra', 'tundra', 'tundra', 'water'],
['water', 'water', 'water', 'water', 'tundra', 'tundra', 'tundra', 'water']
];
const enemies = [
{ name: 'Goetic Spirit', health: 30, attack: 8, magic: 5, quote: "I was doing fine until you disturbed my 500-year nap!" },
{ name: 'Bureaucratic Demon', health: 40, attack: 6, magic: 8, quote: "Your form 7B is incomplete! Prepare for paperwork punishment!" },
{ name: 'Misplaced Librarian', health: 25, attack: 10, magic: 3, quote: "The Dewey Decimal system is the TRUE path to enlightenment!" },
{ name: 'Paperwork Golem', health: 50, attack: 5, magic: 2, quote: "*rustles menacingly* Your permits have expired!" },
{ name: 'Coffee Elemental', health: 35, attack: 9, magic: 6, quote: "You dare approach without the proper caffeine offering?" },
{ name: 'Technocracy Agent', health: 45, attack: 7, magic: 9, quote: "Your paradigm is statistically insignificant!" }
];
const npcDialogues = {
thoth: [
"Greetings, mortal. I am Thoth, god of wisdom. Have you seen my pen? I was in the middle of recording the true nature of reality.",
"The <span class='quest-item'>Emerald Tablet</span> you seek? It's currently being used as a coaster in the break room. Typical.",
"Remember: as above, so below. But also, as within, so without. And as beside, so... well, you get the idea."
],
hermes: [
"Hermes Trismegistus at your service! I invented alchemy, you know. Also, the first pyramid scheme.",
"The secret to the Philosopher's Stone? It's mostly about proper filing. And believing in yourself! 10% belief, 90% organization.",
"My three parts are: knowing, being, and doing. My three problems are: taxes, laundry, and finding my car keys in the astral plane."
],
merlin: [
"By the oak and the holly! Another seeker of arcane knowledge. Have you tried turning it off and on again?",
"I could teach you magic, but first you must fetch me a shrubbery! One that looks nice, and not too expensive.",
"The dragon? Oh, he's mostly decorative. Though he does keep the squirrels out of my crystal collection."
]
};
let currentEnemy = null;
let combatTurn = 'player';
let currentNPC = null;
let currentNPCDialogue = [];
// Initialize Game Elements
function initializeCharacterSelection() {
const characterGrid = document.querySelector('.character-grid');
characterGrid.innerHTML = '';
characters.forEach(character => {
const card = document.createElement('div');
card.className = 'character-card';
card.setAttribute('data-character', character.id);
const sprite = document.createElement('div');
sprite.className = 'character-sprite pixel-art';
sprite.id = `sprite-${character.id}`;
const name = document.createElement('div');
name.className = 'character-name';
name.textContent = character.name;
const specialty = document.createElement('div');
specialty.className = 'character-specialty';
specialty.textContent = character.specialty;
specialty.style.fontSize = 'clamp(4px, 1vw, 8px)';
specialty.style.color = '#aaa';
specialty.style.marginTop = '2px';
card.appendChild(sprite);
card.appendChild(name);
card.appendChild(specialty);
characterGrid.appendChild(card);
generateSprite(character.id, 'character', sprite);
});
}
function initializeLocomotionSelection() {
const locomotionSelect = document.getElementById('locomotion-select');
locomotionSelect.innerHTML = '';
locomotionTypes.forEach(loco => {
const card = document.createElement('div');
card.className = 'locomotion-card';
card.setAttribute('data-loco', loco.id);
const sprite = document.createElement('div');
sprite.className = 'loco-sprite pixel-art';
sprite.id = `loco-sprite-${loco.id}`;
const name = document.createElement('div');
name.className = 'loco-name';
name.textContent = loco.name;
name.style.marginTop = '5px';
name.style.fontSize = 'clamp(6px, 1.5vw, 10px)';
const desc = document.createElement('div');
desc.className = 'loco-desc';
desc.textContent = loco.desc;
desc.style.fontSize = 'clamp(5px, 1.2vw, 8px)';
card.appendChild(sprite);
card.appendChild(name);
card.appendChild(desc);
locomotionSelect.appendChild(card);
generateSprite(loco.id, 'locomotion', sprite);
});
}
function initializeWorldMap() {
const worldMapElement = document.getElementById('world-map');
worldMapElement.innerHTML = '';
const locationNames = {
'tundra': 'FROZEN WASTES',
'water': 'MYSTIC OCEAN',
'mountain': 'DRAGON PEAK',
'forest': 'ELDER WOODS',
'plain': 'SERPENT PLAINS',
'city': 'OCCULT CITY',
'desert': 'SUNSCORCH DUNES',
'dungeon': 'FORGOTTEN TOMB',
'home': 'YOUR SANCTUARY'
};
for (let y = 0; y < 8; y++) {
for (let x = 0; x < 8; x++) {
const tile = document.createElement('div');
const tileType = worldMap[y][x];
tile.className = `map-tile ${tileType}`;
tile.textContent = locationNames[tileType];
if (x === playerPosition.x && y === playerPosition.y) {
const marker = document.createElement('div');
marker.className = 'player-marker';
tile.appendChild(marker);
}
worldMapElement.appendChild(tile);
}
}
}
function generateSprite(seed, type, element) {
const width = element.offsetWidth;
const height = element.offsetHeight;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
function hash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash);
}
const hue = hash(seed) % 360;
const saturation = 70 + (hash(seed + 's') % 30);
const lightness = 30 + (hash(seed + 'l') % 40);
const baseColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
ctx.fillStyle = baseColor;
ctx.fillRect(0, 0, width, height);
if (type === 'character') {
// Character sprite with more detail
ctx.fillStyle = `hsl(${hue}, ${saturation}%, ${lightness + 20}%)`;
ctx.fillRect(width/4, height/4, width/2, height/3);
ctx.fillStyle = '#000';
ctx.fillRect(width/3, height/3, width/12, height/12);
ctx.fillRect(width*2/3 - width/12, height/3, width/12, height/12);
ctx.fillStyle = baseColor;
ctx.fillRect(width/3, height/2, width/3, height/3);
const detailType = hash(seed + 'detail') % 4;
if (detailType === 0) {
// Pointy hat
ctx.fillStyle = `hsl(${(hue + 180) % 360}, ${saturation}%, ${lightness}%)`;
ctx.beginPath();
ctx.moveTo(width/4, height/6);
ctx.lineTo(width*3/4, height/6);
ctx.lineTo(width/2, height/8);
ctx.closePath();
ctx.fill();
} else if (detailType === 1) {
// Robe pattern
ctx.fillStyle = `hsl(${(hue + 120) % 360}, ${saturation}%, ${lightness}%)`;
for (let i = 0; i < 3; i++) {
ctx.fillRect(width/3 + i*width/9, height/2, width/12, height/3);
}
} else if (detailType === 2) {
// Staff
ctx.fillStyle = '#8B4513';
ctx.fillRect(width*3/4, height/4, width/16, height/2);
ctx.fillStyle = '#FFD700';
ctx.fillRect(width*3/4, height/4, width/16, width/16);
} else {
// Book
ctx.fillStyle = '#8B0000';
ctx.fillRect(width/4, height*2/3, width/6, height/6);
ctx.fillStyle = '#FFD700';
ctx.fillRect(width/4 + 2, height*2/3 + 2, width/6 - 4, 2);
}
} else if (type === 'locomotion') {
if (seed.includes('skateboard')) {
ctx.fillStyle = '#8B4513';
ctx.fillRect(width/4, height*2/3, width/2, height/6);
ctx.fillStyle = '#000';
ctx.fillRect(width/3, height*5/6, width/8, height/8);
ctx.fillRect(width*5/8, height*5/6, width/8, height/8);
// Magical glow
ctx.fillStyle = '#00ff00';
ctx.globalAlpha = 0.3;
ctx.fillRect(width/4, height*2/3, width/2, 2);
ctx.globalAlpha = 1.0;
} else if (seed.includes('broom')) {
ctx.fillStyle = '#8B4513';
ctx.fillRect(width/2 - width/16, height/4, width/8, height/2);
ctx.fillStyle = '#654321';
ctx.fillRect(width/4, height*3/4, width/2, height/8);
// Bristles
for (let i = 0; i < 5; i++) {
ctx.fillRect(width/4 + i*width/10, height*3/4, 2, height/12);
}
} else if (seed.includes('carpet')) {
ctx.fillStyle = '#FFD700';
ctx.fillRect(width/4, height/3, width/2, height/3);
ctx.fillStyle = '#FF4500';
for (let i = 0; i < 3; i++) {
ctx.fillRect(width/4 + i*width/6, height/3, width/12, height/3);
}
// Tassels
ctx.fillStyle = '#8B0000';
for (let i = 0; i < 4; i++) {
ctx.fillRect(width/4 + i*width/8, height*2/3, 2, height/8);
}
}
} else if (type === 'dragon') {
ctx.fillStyle = '#FF4500';
// Body
ctx.fillRect(width/3, height/4, width/3, height/2);
// Head
ctx.fillRect(width/4, height/3, width/8, height/8);
// Tail
ctx.fillRect(width*5/8, height/3, width/8, height/8);
ctx.fillStyle = '#FFD700';
// Wings
ctx.fillRect(width/3, height/4, width/12, height/12);
ctx.fillRect(width*2/3, height/4, width/12, height/12);
// Spikes
for (let i = 0; i < 3; i++) {
ctx.fillRect(width/3 + i*width/9, height/4, 2, height/16);
}
} else if (type === 'enemy') {
ctx.fillStyle = '#800080';
ctx.fillRect(width/4, height/4, width/2, height/2);
ctx.fillStyle = '#000';
ctx.fillRect(width/3, height/3, width/12, height/12);
ctx.fillRect(width*2/3 - width/12, height/3, width/12, height/12);
ctx.fillStyle = '#FF0000';
ctx.fillRect(width/2 - width/8, height*2/3, width/4, height/8);
// Evil aura
ctx.strokeStyle = '#f00';
ctx.lineWidth = 2;
ctx.strokeRect(width/4-2, height/4-2, width/2+4, height/2+4);
}
element.style.backgroundImage = `url(${canvas.toDataURL()})`;
}
function generateDragonSprite() {
const dragonElement = document.getElementById('dragon-follower');
generateSprite('dragon', 'dragon', dragonElement);
}
function generateEnemySprite(enemyType) {
const enemyElement = document.getElementById('enemy-combat-sprite');
generateSprite(enemyType, 'enemy', enemyElement);
}
function transitionTo(nextScreen, callback = null) {
const overlay = document.getElementById('transition-overlay');
overlay.style.pointerEvents = 'auto';
overlay.style.opacity = '1';
setTimeout(() => {
document.querySelector(currentGameScreen).classList.add('hidden');
document.querySelector(nextScreen).classList.remove('hidden');
currentGameScreen = nextScreen;
if (callback) callback();
setTimeout(() => {
overlay.style.opacity = '0';
setTimeout(() => {
overlay.style.pointerEvents = 'none';
}, 300);
}, 100);
}, 300);
}
function initializeGame() {
initializeCharacterSelection();
initializeLocomotionSelection();
generateDragonSprite();
initializeWorldMap();
setupEventListeners();
// Draw initial game canvas
drawGameCanvas();
}
function drawGameCanvas() {
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
// Set canvas size
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
// Clear canvas
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw a simple sanctuary scene
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw bookshelves
ctx.fillStyle = '#8B4513';
ctx.fillRect(50, 50, canvas.width - 100, 30);
ctx.fillRect(50, 120, canvas.width - 100, 30);
// Draw books
ctx.fillStyle = '#8B0000';
for (let i = 0; i < 8; i++) {
ctx.fillRect(60 + i * 40, 55, 20, 20);
}
ctx.fillStyle = '#000080';
for (let i = 0; i < 8; i++) {
ctx.fillRect(60 + i * 40, 125, 20, 20);
}
// Draw altar
ctx.fillStyle = '#333';
ctx.fillRect(canvas.width/2 - 40, canvas.height/2 + 20, 80, 20);
// Draw crystal ball
ctx.fillStyle = '#0ff';
ctx.beginPath();
ctx.arc(canvas.width/2, canvas.height/2 - 20, 20, 0, Math.PI * 2);
ctx.fill();
// Draw player if selected
if (selectedCharacter) {
const playerX = canvas.width/2;
const playerY = canvas.height/2 + 10;
// Simple player representation
ctx.fillStyle = '#f00';
ctx.fillRect(playerX - 10, playerY - 30, 20, 30);
ctx.fillStyle = '#ff0';
ctx.fillRect(playerX - 5, playerY - 25, 10, 10);
}
}
function setupEventListeners() {
// Loading screen - tap anywhere to start
document.getElementById('loading-screen').addEventListener('click', function() {
transitionTo('#title-screen');
});
// Title screen
document.getElementById('start-btn').addEventListener('click', function() {
const introBox = document.getElementById('intro-dialogue-box');
introBox.innerHTML = dialogueLines[0];
currentDialogueStep = 0;
transitionTo('#intro-screen');
});
document.getElementById('settings-btn').addEventListener('click', function() {
document.getElementById('game-log').textContent = "SETTINGS: MAXIMUM OCCULT POWER ACTIVATED!";
transitionTo('#title-screen');
});
// Intro screen
document.getElementById('intro-continue').addEventListener('click', function() {
currentDialogueStep++;
const introBox = document.getElementById('intro-dialogue-box');
if (currentDialogueStep < dialogueLines.length) {
introBox.innerHTML = dialogueLines[currentDialogueStep];
} else {
transitionTo('#character-select');
}
});
// Intro screen - tap anywhere to continue
document.getElementById('intro-screen').addEventListener('click', function(e) {
// Only proceed if the click wasn't on the next button (to avoid double-triggering)
if (!e.target.closest('#intro-continue')) {
currentDialogueStep++;
const introBox = document.getElementById('intro-dialogue-box');
if (currentDialogueStep < dialogueLines.length) {
introBox.innerHTML = dialogueLines[currentDialogueStep];
} else {
transitionTo('#character-select');
}
}
});
// Character selection
document.addEventListener('click', function(e) {
if (e.target.closest('.character-card')) {
const card = e.target.closest('.character-card');
document.querySelectorAll('.character-card').forEach(c => c.classList.remove('selected'));
card.classList.add('selected');
selectedCharacter = card.getAttribute('data-character');
document.getElementById('next-to-locomotion').classList.remove('hidden');
// Update game log with character info
const character = characters.find(c => c.id === selectedCharacter);
document.getElementById('game-log').textContent = `SELECTED: ${character.name} - ${character.specialty}`;
}
});
document.getElementById('next-to-locomotion').addEventListener('click', function() {
if (selectedCharacter) {
const playerCombatSprite = document.getElementById('player-combat-sprite');
const characterSprite = document.getElementById(`sprite-${selectedCharacter}`);
playerCombatSprite.style.backgroundImage = characterSprite.style.backgroundImage;
document.getElementById('player-name').textContent = selectedCharacter.toUpperCase();
transitionTo('#locomotion-choice');
}
});
// Locomotion selection
document.addEventListener('click', function(e) {
if (e.target.closest('.locomotion-card')) {
const card = e.target.closest('.locomotion-card');
document.querySelectorAll('.locomotion-card').forEach(c => c.classList.remove('selected'));
card.classList.add('selected');
selectedLocomotion = card.getAttribute('data-loco');
}
});
document.getElementById('confirm-locomotion').addEventListener('click', function() {
if (selectedLocomotion) {
const loco = locomotionTypes.find(l => l.id === selectedLocomotion);
document.getElementById('game-log').textContent = `TRAVEL MODE: ${loco.name} - READY FOR ADVENTURE!`;
transitionTo('#home-screen');
} else {
document.getElementById('game-log').textContent = "SELECT A MODE OF TRAVEL FIRST!";
}
});
// Home screen
document.getElementById('home-to-map').addEventListener('click', function() {
document.getElementById('world-map-container').classList.remove('hidden');
});
document.getElementById('home-rest').addEventListener('click', function() {
gameState.health = gameState.maxHealth;
gameState.magic = gameState.maxMagic;
updatePlayerStats();
document.getElementById('home-log').textContent =
"YOU REST IN YOUR SANCTUARY. HP AND MP RESTORED! THE DRAGON PURRS CONTENTEDLY. YOUR 777 TABLES ARE NEATLY ORGANIZED.";
});
document.getElementById('home-grimoire').addEventListener('click', function() {
const grimoireTexts = [
"YOU STUDY THE GOETIA: REMEMBER, DEMON #32 IS ALLERGIC TO CATS.",
"THE EMERALD TABLET REVEALS: 'AS ABOVE, SO BELOW. ALSO, WASH YOUR ROBES REGULARLY.'",
"YOUR MAGUS TRAINING: TRUE WILL IS DOING WHAT YOU WANT, EXCEPT ON TUESDAYS.",
"THE 777 CORRESPONDENCES: FIRE = WANDS = SOUTH = MARS = TUESDAY = RED = ANGRY DEMONS."
];
const randomText = grimoireTexts[Math.floor(Math.random() * grimoireTexts.length)];
document.getElementById('home-log').textContent = randomText;
});
document.getElementById('home-alchemy').addEventListener('click', function() {
const alchemyTexts = [
"YOU ATTEMPT TO TRANSFORM LEAD INTO GOLD. IT TURNS INTO A DISAPPOINTING PAPERWEIGHT.",
"THE PHILOSOPHER'S STONE REQUIRES: SULFUR, MERCURY, SALT, AND A VALID LIBRARY CARD.",
"YOUR ELIXIR OF LIFE SMELLS SUSPICIOUSLY LIKE COFFEE. YOU DRINK IT ANYWAY.",
"ALCHEMICAL NOTE: TURNING COFFEE INTO AWAKENESS IS THE TRUE GREAT WORK."
];
const randomText = alchemyTexts[Math.floor(Math.random() * alchemyTexts.length)];
document.getElementById('home-log').textContent = randomText;
});
// World map
document.getElementById('close-map').addEventListener('click', function() {
document.getElementById('world-map-container').classList.add('hidden');
});
document.getElementById('return-home').addEventListener('click', function() {
document.getElementById('world-map-container').classList.add('hidden');
transitionTo('#home-screen');
});
// Movement controls
document.getElementById('up-btn').addEventListener('click', movePlayer.bind(null, 0, -1));
document.getElementById('down-btn').addEventListener('click', movePlayer.bind(null, 0, 1));
document.getElementById('left-btn').addEventListener('click', movePlayer.bind(null, -1, 0));
document.getElementById('right-btn').addEventListener('click', movePlayer.bind(null, 1, 0));
// Action button
document.getElementById('action-btn').addEventListener('click', handleAction);
// Map action buttons
document.querySelectorAll('#map-action-buttons .action-btn').forEach(btn => {
btn.addEventListener('click', function() {
const action = this.getAttribute('data-action');
handleMapAction(action);
});
});
// Combat action buttons
document.querySelectorAll('#combat-action-buttons .action-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (combatTurn === 'player') {
const action = this.getAttribute('data-action');
handleCombatAction(action);
}
});
});
// NPC dialogue
document.getElementById('npc-continue').addEventListener('click', function() {
currentDialogueStep++;
const npcBox = document.getElementById('npc-dialogue-box');
if (currentDialogueStep < currentNPCDialogue.length) {
npcBox.innerHTML = currentNPCDialogue[currentDialogueStep];
} else {
transitionTo('#game-screen');
// Give player something after NPC interaction
if (currentNPC === 'thoth' && !gameState.quests.findEmeraldTablet) {
gameState.quests.findEmeraldTablet = true;
gameState.inventory.push('Emerald Tablet Fragment');
document.getElementById('game-log').textContent = "THOTH GAVE YOU AN EMERALD TABLET FRAGMENT! THE GREAT WORK CONTINUES!";
}
}
});
}
// Gameplay Functions
function movePlayer(dx, dy) {
const newX = playerPosition.x + dx;
const newY = playerPosition.y + dy;
if (newX >= 0 && newX < 8 && newY >= 0 && newY < 8) {
playerPosition.x = newX;
playerPosition.y = newY;
initializeWorldMap();
// Update location display
const locationTypes = {
'tundra': 'FROZEN WASTES',
'water': 'MYSTIC OCEAN',
'mountain': 'DRAGON PEAK',
'forest': 'ELDER WOODS',
'plain': 'SERPENT PLAINS',
'city': 'OCCULT CITY',
'desert': 'SUNSCORCH DUNES',
'dungeon': 'FORGOTTEN TOMB',
'home': 'YOUR SANCTUARY'
};
const currentLocation = worldMap[playerPosition.y][playerPosition.x];
document.getElementById('location').textContent = locationTypes[currentLocation];
// Random events based on location
const encounterChance = Math.random();
if (encounterChance < 0.3) { // 30% chance of combat
startRandomEncounter();
} else if (encounterChance < 0.4) { // 10% chance of NPC
meetRandomNPC();
} else if (encounterChance < 0.5) { // 10% chance of finding item
findRandomItem();
}
}
}
function handleAction() {
const currentLocation = worldMap[playerPosition.y][playerPosition.x];
if (currentLocation === 'home') {
transitionTo('#home-screen');
} else {
const locationMessages = {
'forest': "YOU SEARCH THE ANCIENT WOODS. THE TREES WHISPER FORGOTTEN SPELLS FROM THE 777 TABLES.",
'desert': "THE BLAZING SANDS HIDE ANCIENT PYRAMIDS AND FORGOTTEN TOMES OF GOETIA.",
'mountain': "YOU CLIMB THE PEAK. THE THIN AIR IS FILLED WITH MYSTICAL ENERGY AND TECHNOCRACY SURVEILLANCE.",
'dungeon': "A DANK, DARK DUNGEON. PERFECT FOR FINDING LOST ARTIFACTS AND ANNOYING DEMONS.",
'water': "THE MYSTICAL WATERS GLOW WITH AN UNEARTHLY LIGHT. PROBABLY RADIATION. OR MAGIC. OR BOTH.",
'tundra': "THE FROZEN WASTES HOLD SECRETS OF ANCIENT CIVILIZATIONS AND FROZEN DEMONS.",
'plain': "THE ROLLING PLAINS STRETCH ENDLESSLY. A PEACEFUL, IF BORING, PLACE TO PRACTICE YOUR MAGUS SKILLS.",
'city': "THE BUSTLING OCCULT CITY. FULL OF MYSTIC SHOPS, ARCANE LIBRARIES, AND DEMONS ON THEIR LUNCH BREAK."
};
const message = locationMessages[currentLocation] || "YOU EXAMINE THE AREA BUT FIND NOTHING OF INTEREST. EXCEPT DUST. LOTS OF DUST.";
document.getElementById('game-log').textContent = message;
}
}
function handleMapAction(action) {
const actionMessages = {
'talk': "YOU MUMBLE ABOUT THE KABBALAH TO A NEARBY TREE. IT DOESN'T RESPOND, BUT A SQUIRREL LOOKS INTERESTED.",
'magic': "YOU CAST 'FIND LOST KEYS'. YOUR WAND GLOWS BRIEFLY, THEN RETURNS TO NORMAL. THE KEYS REMAIN LOST.",
'item': "YOU SEARCH YOUR BAG: DUSTY SCROLLS, A HALF-EATEN SANDWICH, AND A SUSPICIOUSLY GLOWING ROCK.",
'map': showWorldMap
};
if (typeof actionMessages[action] === 'function') {
actionMessages[action]();
} else {
document.getElementById('game-log').textContent = actionMessages[action];
}
}
function showWorldMap() {
document.getElementById('world-map-container').classList.remove('hidden');
}
function startRandomEncounter() {
const randomEnemy = enemies[Math.floor(Math.random() * enemies.length)];
currentEnemy = {
...randomEnemy,
currentHealth: randomEnemy.health
};
generateEnemySprite(randomEnemy.name);
document.getElementById('enemy-name').textContent = randomEnemy.name.toUpperCase();
document.getElementById('enemy-health').style.width = '100%';
const combatMessages = [
`A ${randomEnemy.name.toUpperCase()} CHALLENGES YOU TO A DUEL OF ESOTERIC KNOWLEDGE! "${randomEnemy.quote}"`,
`THE ${randomEnemy.name.toUpperCase()} LOOKS UNIMPRESSED BY YOUR OCCULT CREDENTIALS! "${randomEnemy.quote}"`,
`YOUR DRAGON AWAKENS WITH A STARTLED SNORT. THE ${randomEnemy.name.toUpperCase()} IS HERE! "${randomEnemy.quote}"`,
`THE ${randomEnemy.name.toUpperCase()} DEMANDS TO SEE YOUR HERMETIC LICENSE! "${randomEnemy.quote}"`,
`A ${randomEnemy.name.toUpperCase()} BLOCKS YOUR PATH! IT SEEMS TO BE HOLDING A CLIPBOARD. "${randomEnemy.quote}"`
];
document.getElementById('combat-log').textContent =
combatMessages[Math.floor(Math.random() * combatMessages.length)];
combatTurn = 'player';
transitionTo('#combat-screen');
}
function meetRandomNPC() {
const npcs = ['thoth', 'hermes', 'merlin'];
currentNPC = npcs[Math.floor(Math.random() * npcs.length)];
currentNPCDialogue = npcDialogues[currentNPC];
currentDialogueStep = 0;
document.getElementById('npc-dialogue-box').innerHTML = currentNPCDialogue[0];
transitionTo('#npc-screen');
}
function findRandomItem() {
const items = [
"MYSTICAL HERB",
"GLOWING CRYSTAL",
"ANCIENT COIN",
"DUSTY TOME",
"SUSPICIOUS POTION"
];
const foundItem = items[Math.floor(Math.random() * items.length)];
gameState.inventory.push(foundItem);
const findMessages = [
`YOU FIND A ${foundItem}! YOUR DRAGON SNIFFS IT SUSPICIOUSLY.`,
`A ${foundItem} CATCHES YOUR EYE! PROBABLY CURSED, BUT STILL USEFUL.`,
`YOUR DRAGON DIGS UP A ${foundItem}! GOOD BOY!`,
`BEHIND A ROCK, YOU DISCOVER A ${foundItem}. THE UNIVERSE REWARDS YOUR CURIOSITY.`
];
document.getElementById('game-log').textContent =
findMessages[Math.floor(Math.random() * findMessages.length)];
}
function handleCombatAction(action) {
if (combatTurn !== 'player') return;
// Disable buttons during enemy turn
document.querySelectorAll('#combat-action-buttons .action-btn').forEach(btn => {
btn.disabled = true;
});
setTimeout(() => {
document.querySelectorAll('#combat-action-buttons .action-btn').forEach(btn => {
btn.disabled = false;
});
}, 1500);
switch(action) {
case 'attack':
playerAttack();
break;
case 'magic':
playerMagic();
break;
case 'item':
useItem();
break;
case 'flee':
attemptFlee();
break;
}
}
function playerAttack() {
const damage = 10 + Math.floor(Math.random() * 10);
currentEnemy.currentHealth -= damage;
const healthPercent = (currentEnemy.currentHealth / currentEnemy.health) * 100;
document.getElementById('enemy-health').style.width = `${healthPercent}%`;
const attackMessages = [
`YOU WAVE YOUR WAND MENACINGLY! DEALT ${damage} DAMAGE! "BY THE POWER OF THE 777 TABLES!"`,
`YOUR DRAGON BURPS A SMALL FLAME! DEALT ${damage} DAMAGE! THE ENEMY SMELLS LIKE TOAST.`,
`YOU QUOTE THELEMA AT THE ENEMY! DEALT ${damage} DAMAGE! "DO WHAT THOU WILT... TO SURRENDER!"`,
`YOU THROW A DUSTY TOME! DEALT ${damage} DAMAGE! KNOWLEDGE IS POWER, LITERALLY.`,
`YOUR ESOTERIC GESTURE CAUSES MILD PERPLEXITY! DEALT ${damage} DAMAGE! THE ENEMY SCRATCHES ITS HEAD.`
];
document.getElementById('combat-log').textContent =
attackMessages[Math.floor(Math.random() * attackMessages.length)];
if (currentEnemy.currentHealth <= 0) {
endCombat(true);
} else {
combatTurn = 'enemy';
setTimeout(enemyTurn, 1500);
}
}
function playerMagic() {
if (gameState.magic < 10) {
document.getElementById('combat-log').textContent =
"NOT ENOUGH MAGIC! YOUR SPELL FIZZLES PATHETICALLY. EVEN YOUR DRAGON LOOKS EMBARRASSED.";
return;
}
const damage = 15 + Math.floor(Math.random() * 15);
currentEnemy.currentHealth -= damage;
gameState.magic -= 10;
const healthPercent = (currentEnemy.currentHealth / currentEnemy.health) * 100;
document.getElementById('enemy-health').style.width = `${healthPercent}%`;
updatePlayerStats();
const magicMessages = [
`YOU CAST ARCANE BOLT! DEALT ${damage} DAMAGE! "THE GOETIA COMMANDS YOU!"`,
`YOUR DRAGON BREATHES MYSTICAL FIRE! DEALT ${damage} DAMAGE! SMELLS LIKE SULFUR AND VICTORY.`,
`YOU INVOKE THE ENOCHIAN CALL! DEALT ${damage} DAMAGE! THE ANGELS ARE IMPRESSED.`,
`A MYSTIC RUNE GLOWS BRIGHTLY! DEALT ${damage} DAMAGE! "BY THE POWER OF THE MAGUS!"`,
`YOUR TAROT CARDS FLY FORWARD! DEALT ${damage} DAMAGE! THE FOOL CARD HITS PARTICULARLY HARD.`
];
document.getElementById('combat-log').textContent =
magicMessages[Math.floor(Math.random() * magicMessages.length)];
if (currentEnemy.currentHealth <= 0) {
endCombat(true);
} else {
combatTurn = 'enemy';
setTimeout(enemyTurn, 1500);
}
}
function useItem() {
if (gameState.inventory.length === 0) {
document.getElementById('combat-log').textContent =
"NO ITEMS LEFT! YOUR BAG IS EMPTY EXCEPT FOR DUST AND REGRET.";
return;
}
const item = gameState.inventory.pop();
const itemMessages = [
`YOU USE ${item}! HP RESTORED! YOUR DRAGON NODS APPROVINGLY.`,
`YOU DRINK ${item}! MP RESTORED! IT TASTES LIKE VICTORY AND ELDERBERRIES.`,
`YOU WAVE ${item} MENACINGLY! THE ENEMY LOOKS CONFUSED BUT UNIMPRESSED.`,
`${item} GLOWS MYSTERIOUSLY! NOTHING HAPPENS, BUT IT LOOKED COOL.`
];
document.getElementById('combat-log').textContent =
itemMessages[Math.floor(Math.random() * itemMessages.length)];
// Item effects
gameState.health = Math.min(gameState.health + 20, gameState.maxHealth);
gameState.magic = Math.min(gameState.magic + 15, gameState.maxMagic);
updatePlayerStats();
combatTurn = 'enemy';
setTimeout(enemyTurn, 1500);
}
function attemptFlee() {
const fleeChance = 0.6;
if (Math.random() < fleeChance) {
const fleeMessages = [
"YOU MAKE A TACTICAL RETREAT, TRIPPING OVER YOUR ROBE! 'AN IT HARM NONE, DO WHAT THOU WILT... TO RUN AWAY!'",
"YOUR DRAGON CREATES A SMOKESCREEN! FLEEING SUCCESSFUL! THE 777 TABLES GUIDE YOUR ESCAPE.",
"YOU QUOTE AN OBSCURE BYLAW FROM THE GOETIA AND THE ENEMY IS TEMPORARILY CONFUSED!",
"YOU REMEMBER AN URGENT APPOINTMENT WITH YOUR ALCHEMY TUTOR! CONVENIENT TIMING!"
];
document.getElementById('combat-log').textContent =
fleeMessages[Math.floor(Math.random() * fleeMessages.length)];
setTimeout(() => {
endCombat(false);
}, 1500);
} else {
document.getElementById('combat-log').textContent =
"THE ENEMY BLOCKS YOUR ESCAPE! FLEE FAILED! YOUR DRAGON SHRUGS APOLOGETICALLY.";
combatTurn = 'enemy';
setTimeout(enemyTurn, 1500);
}
}
function enemyTurn() {
if (combatTurn !== 'enemy') return;
const damage = currentEnemy.attack + Math.floor(Math.random() * 5);
gameState.health -= damage;
const healthPercent = (gameState.health / gameState.maxHealth) * 100;
document.getElementById('player-health-combat').style.width = `${healthPercent}%`;
updatePlayerStats();
const enemyMessages = [
`${currentEnemy.name.toUpperCase()} ATTACKS! YOU TAKE ${damage} DAMAGE! "YOUR PARADIGM IS WEAK!"`,
`${currentEnemy.name.toUpperCase()} CASTS A DARK SPELL! YOU TAKE ${damage} DAMAGE! "THE TECHNOCRACY WILL PREVAIL!"`,
`${currentEnemy.name.toUpperCase()} SUMMONS MINOR DEMONS! YOU TAKE ${damage} DAMAGE! "THE GOETIA OBEYS ME!"`,
`${currentEnemy.name.toUpperCase()} USES BUREAUCRATIC NIGHTMARE! YOU TAKE ${damage} DAMAGE! "YOUR FORM 7B IS INCOMPLETE!"`
];
document.getElementById('combat-log').textContent =
enemyMessages[Math.floor(Math.random() * enemyMessages.length)];
if (gameState.health <= 0) {
gameState.health = 0;
endCombat(false);
} else {
combatTurn = 'player';
}
}
function endCombat(victory) {
if (victory) {
gameState.level += 1;
gameState.maxHealth += 10;
gameState.maxMagic += 5;
gameState.health = gameState.maxHealth;
gameState.magic = gameState.maxMagic;
// Add random item reward
const rewardItems = [
"MYSTICAL DUST",
"GLOWING GEM",
"ANCIENT SCROLL",
"DEMONIC CONTRACT",
"CRYSTAL BALL"
];
const reward = rewardItems[Math.floor(Math.random() * rewardItems.length)];
gameState.inventory.push(reward);
const victoryMessages = [
`VICTORY! YOU GAIN A LEVEL AND FIND ${reward}! THE GREAT WORK CONTINUES!`,
"THE FOE VANQUISHED! YOUR DRAGON LOOKS IMPRESSED AND BURPS APPROVINGLY!",
"COMBAT ENDS! YOUR ROBE IS RUFFLED BUT YOUR DIGNITY IS (MOSTLY) INTACT!",
`SUCCESS! YOU FIND ${reward} AMONG THE DEBRIS! THE 777 TABLES FAVOR YOU!`
];
document.getElementById('combat-log').textContent =
victoryMessages[Math.floor(Math.random() * victoryMessages.length)];
} else {
gameState.health = Math.floor(gameState.maxHealth / 2);
const defeatMessages = [
"DEFEAT! YOU WAKE UP BACK AT YOUR SANCTUARY... YOUR DRAGON LOOKS DISAPPOINTED.",
"THE ENEMY PROVES TOO POWERFUL! YOU RETREAT TO RECOVER AND REORGANIZE YOUR 777 TABLES.",
"YOUR OCCULT POWERS FALTER! TIME TO REGROUP AND CONSULT THE GOETIA FOR BETTER TACTICS.",
"THE BATTLE IS LOST! YOUR DRAGON DRAGS YOU TO SAFETY, MUMBLING ABOUT BETTER TRAINING."
];
document.getElementById('combat-log').textContent =
defeatMessages[Math.floor(Math.random() * defeatMessages.length)];
}
updatePlayerStats();
setTimeout(() => {
transitionTo('#home-screen');
}, 2000);
}
function updatePlayerStats() {
document.getElementById('player-stats').textContent =
`LVL: ${gameState.level} | HP: ${gameState.health}/${gameState.maxHealth} | MP: ${gameState.magic}/${gameState.maxMagic}`;
const healthPercent = (gameState.health / gameState.maxHealth) * 100;
const magicPercent = (gameState.magic / gameState.maxMagic) * 100;
document.getElementById('player-health-bar').style.width = `${healthPercent}%`;
document.getElementById('player-magic-bar').style.width = `${magicPercent}%`;
document.getElementById('player-health-combat').style.width = `${healthPercent}%`;
document.getElementById('player-mp-combat').textContent = `${gameState.magic}/${gameState.maxMagic}`;
}
// Initialize the game when the page loads
document.addEventListener('DOMContentLoaded', function() {
console.log('Game initializing...');
initializeGame();
console.log('Game initialized!');
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment