|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Washington's Crossing Simulator</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.river { |
|
|
background: linear-gradient(to bottom, #1a237e, #0d47a1); |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
.wave { |
|
|
position: absolute; |
|
|
width: 100%; |
|
|
height: 20px; |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
animation: wave 3s infinite linear; |
|
|
} |
|
|
.wave:nth-child(2) { |
|
|
top: 20px; |
|
|
animation-delay: 0.5s; |
|
|
} |
|
|
.wave:nth-child(3) { |
|
|
top: 40px; |
|
|
animation-delay: 1s; |
|
|
} |
|
|
@keyframes wave { |
|
|
0% { transform: translateX(-100%); } |
|
|
100% { transform: translateX(100%); } |
|
|
} |
|
|
.boat { |
|
|
transition: all 0.5s ease; |
|
|
} |
|
|
.fade-in { |
|
|
animation: fadeIn 1s; |
|
|
} |
|
|
@keyframes fadeIn { |
|
|
from { opacity: 0; } |
|
|
to { opacity: 1; } |
|
|
} |
|
|
.snowflake { |
|
|
position: absolute; |
|
|
background-color: white; |
|
|
border-radius: 50%; |
|
|
pointer-events: none; |
|
|
animation: fall linear infinite; |
|
|
} |
|
|
@keyframes fall { |
|
|
to { transform: translateY(100vh); } |
|
|
} |
|
|
.ice-floe { |
|
|
position: absolute; |
|
|
background-color: rgba(255, 255, 255, 0.7); |
|
|
border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%; |
|
|
pointer-events: none; |
|
|
} |
|
|
.patrol { |
|
|
position: absolute; |
|
|
width: 40px; |
|
|
height: 20px; |
|
|
background-color: #8B4513; |
|
|
border-radius: 5px; |
|
|
pointer-events: none; |
|
|
} |
|
|
.achievement-badge { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.achievement-badge:hover { |
|
|
transform: scale(1.1); |
|
|
} |
|
|
.secret-found { |
|
|
animation: pulse 1s infinite; |
|
|
} |
|
|
@keyframes pulse { |
|
|
0% { transform: scale(1); } |
|
|
50% { transform: scale(1.1); } |
|
|
100% { transform: scale(1); } |
|
|
} |
|
|
.timer-bar { |
|
|
transition: width 1s linear; |
|
|
} |
|
|
.glow { |
|
|
animation: glow 2s infinite alternate; |
|
|
} |
|
|
@keyframes glow { |
|
|
from { box-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #e60073, 0 0 20px #e60073; } |
|
|
to { box-shadow: 0 0 10px #fff, 0 0 20px #ff4da6, 0 0 30px #ff4da6, 0 0 40px #ff4da6; } |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4"> |
|
|
<div id="game-container" class="w-full max-w-4xl bg-gray-800 rounded-xl shadow-2xl overflow-hidden fade-in"> |
|
|
|
|
|
<div id="title-screen" class="p-8 text-center"> |
|
|
<h1 class="text-4xl md:text-6xl font-bold mb-6 text-yellow-400">WASHINGTON'S CROSSING</h1> |
|
|
<div class="river h-32 my-8 rounded-lg relative"> |
|
|
<div class="wave"></div> |
|
|
<div class="wave"></div> |
|
|
<div class="wave"></div> |
|
|
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Gilbert_Stuart_Williamstown_Portrait_of_George_Washington.jpg/800px-Gilbert_Stuart_Williamstown_Portrait_of_George_Washington.jpg" |
|
|
class="h-24 absolute left-1/2 -translate-x-1/2 -top-4 rounded-full border-4 border-yellow-500 shadow-lg"> |
|
|
</div> |
|
|
<p class="text-xl mb-8">December 25-26, 1776. Lead your troops across the Delaware River under cover of darkness to surprise the Hessians at Trenton.</p> |
|
|
<div class="flex flex-col space-y-4 max-w-xs mx-auto"> |
|
|
<button onclick="startGame('easy')" class="bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-6 rounded-lg transition transform hover:scale-105"> |
|
|
Easy Crossing |
|
|
</button> |
|
|
<button onclick="startGame('medium')" class="bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-3 px-6 rounded-lg transition transform hover:scale-105"> |
|
|
Standard Challenge |
|
|
</button> |
|
|
<button onclick="startGame('hard')" class="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-lg transition transform hover:scale-105"> |
|
|
Hardcore Mode |
|
|
</button> |
|
|
<button onclick="showInstructions()" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition transform hover:scale-105"> |
|
|
Instructions |
|
|
</button> |
|
|
<button onclick="showAchievements()" class="bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-6 rounded-lg transition transform hover:scale-105"> |
|
|
Achievements |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="instructions-screen" class="hidden p-8"> |
|
|
<h2 class="text-3xl font-bold mb-6 text-yellow-400">How to Cross Like Washington</h2> |
|
|
<div class="space-y-4 mb-8"> |
|
|
<div class="flex items-start"> |
|
|
<div class="bg-yellow-500 text-yellow-900 font-bold rounded-full w-8 h-8 flex items-center justify-center mr-4 mt-1 flex-shrink-0">1</div> |
|
|
<p>Choose your difficulty level - easier modes have better weather and fewer patrols.</p> |
|
|
</div> |
|
|
<div class="flex items-start"> |
|
|
<div class="bg-yellow-500 text-yellow-900 font-bold rounded-full w-8 h-8 flex items-center justify-center mr-4 mt-1 flex-shrink-0">2</div> |
|
|
<p>Select your crossing strategy - more boats are faster but more visible, night crossings are stealthier.</p> |
|
|
</div> |
|
|
<div class="flex items-start"> |
|
|
<div class="bg-yellow-500 text-yellow-900 font-bold rounded-full w-8 h-8 flex items-center justify-center mr-4 mt-1 flex-shrink-0">3</div> |
|
|
<p>Navigate the river - watch for ice floes and British patrols. You must respond to events quickly!</p> |
|
|
</div> |
|
|
<div class="flex items-start"> |
|
|
<div class="bg-yellow-500 text-yellow-900 font-bold rounded-full w-8 h-8 flex items-center justify-center mr-4 mt-1 flex-shrink-0">4</div> |
|
|
<p>Make tactical decisions during the crossing to avoid detection. Some choices may unlock secret achievements!</p> |
|
|
</div> |
|
|
<div class="flex items-start"> |
|
|
<div class="bg-yellow-500 text-yellow-900 font-bold rounded-full w-8 h-8 flex items-center justify-center mr-4 mt-1 flex-shrink-0">5</div> |
|
|
<p>Watch the timer during events - if it runs out, you'll automatically fail the event!</p> |
|
|
</div> |
|
|
</div> |
|
|
<button onclick="hideInstructions()" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg"> |
|
|
Back to Title |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="achievements-screen" class="hidden p-8"> |
|
|
<h2 class="text-3xl font-bold mb-6 text-yellow-400">Your Achievements</h2> |
|
|
<div id="achievements-container" class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8"> |
|
|
|
|
|
</div> |
|
|
<div class="mt-6 p-4 bg-gray-700 rounded-lg"> |
|
|
<h3 class="text-xl font-semibold mb-2">Secret Achievements</h3> |
|
|
<p class="text-gray-400">Some achievements are hidden! Discover them through special actions during your crossings.</p> |
|
|
<div id="secret-achievements-container" class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
<button onclick="hideAchievements()" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg mt-6"> |
|
|
Back to Title |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="strategy-screen" class="hidden p-8"> |
|
|
<h2 class="text-3xl font-bold mb-6 text-yellow-400">Plan Your Crossing</h2> |
|
|
<div id="weather-display" class="mb-6 p-4 rounded-lg bg-gray-700"> |
|
|
<h3 class="text-xl font-semibold mb-2">Weather Conditions:</h3> |
|
|
<p id="weather-text"></p> |
|
|
</div> |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8"> |
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<h3 class="text-xl font-semibold mb-3">Crossing Time</h3> |
|
|
<div class="space-y-3"> |
|
|
<label class="flex items-center space-x-3"> |
|
|
<input type="radio" name="time" value="dusk" class="form-radio text-yellow-500" checked> |
|
|
<span>Dusk (Moderate visibility, some patrols)</span> |
|
|
</label> |
|
|
<label class="flex items-center space-x-3"> |
|
|
<input type="radio" name="time" value="midnight" class="form-radio text-blue-500"> |
|
|
<span>Midnight (Best stealth, but colder)</span> |
|
|
</label> |
|
|
<label class="flex items-center space-x-3"> |
|
|
<input type="radio" name="time" value="dawn" class="form-radio text-red-500"> |
|
|
<span>Dawn (Worst stealth, but warmer)</span> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<h3 class="text-xl font-semibold mb-3">Boat Strategy</h3> |
|
|
<div class="space-y-3"> |
|
|
<label class="flex items-center space-x-3"> |
|
|
<input type="radio" name="boats" value="few" class="form-radio text-green-500" checked> |
|
|
<span>Few boats (Slow but stealthy)</span> |
|
|
</label> |
|
|
<label class="flex items-center space-x-3"> |
|
|
<input type="radio" name="boats" value="moderate" class="form-radio text-yellow-500"> |
|
|
<span>Moderate boats (Balanced)</span> |
|
|
</label> |
|
|
<label class="flex items-center space-x-3"> |
|
|
<input type="radio" name="boats" value="many" class="form-radio text-red-500"> |
|
|
<span>Many boats (Fast but visible)</span> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-gray-700 p-4 rounded-lg mb-8"> |
|
|
<h3 class="text-xl font-semibold mb-3">Special Options</h3> |
|
|
<div class="space-y-3"> |
|
|
<label class="flex items-center space-x-3"> |
|
|
<input type="checkbox" id="secret-route" class="form-checkbox text-purple-500"> |
|
|
<span>Attempt secret route (Risky but potentially rewarding)</span> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
<button onclick="beginCrossing()" class="bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-8 rounded-lg text-xl"> |
|
|
Begin Crossing |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="crossing-screen" class="hidden relative"> |
|
|
<div id="river-container" class="river h-64 w-full relative overflow-hidden"> |
|
|
|
|
|
<div id="boat" class="boat absolute bottom-4 left-8 w-16 h-16 bg-gray-300 rounded-lg"> |
|
|
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Gilbert_Stuart_Williamstown_Portrait_of_George_Washington.jpg/800px-Gilbert_Stuart_Williamstown_Portrait_of_George_Washington.jpg" |
|
|
class="h-10 absolute -top-4 left-1/2 -translate-x-1/2 rounded-full border-2 border-yellow-500"> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
<div class="bg-gray-800 p-4 flex justify-between items-center"> |
|
|
<div id="progress-container" class="w-full bg-gray-700 rounded-full h-4"> |
|
|
<div id="progress-bar" class="bg-yellow-500 h-4 rounded-full" style="width: 0%"></div> |
|
|
</div> |
|
|
<div id="distance-text" class="ml-4 font-bold">0%</div> |
|
|
</div> |
|
|
<div id="event-display" class="bg-gray-700 p-4 hidden"> |
|
|
<div class="flex justify-between items-center mb-2"> |
|
|
<h3 class="font-bold text-lg" id="event-title">Event Occurred!</h3> |
|
|
<div class="flex items-center"> |
|
|
<div class="w-24 h-2 bg-gray-600 rounded-full mr-2"> |
|
|
<div id="event-timer" class="timer-bar h-2 bg-red-500 rounded-full" style="width: 100%"></div> |
|
|
</div> |
|
|
<span id="event-time-remaining" class="text-sm">5s</span> |
|
|
</div> |
|
|
</div> |
|
|
<p id="event-description"></p> |
|
|
<div id="event-choices" class="mt-4 space-y-2"></div> |
|
|
</div> |
|
|
<div id="status-display" class="bg-gray-900 p-4 grid grid-cols-4 gap-4 text-center"> |
|
|
<div> |
|
|
<div class="text-sm text-gray-400">Visibility</div> |
|
|
<div id="visibility-value" class="font-bold text-lg">Medium</div> |
|
|
</div> |
|
|
<div> |
|
|
<div class="text-sm text-gray-400">Alert Level</div> |
|
|
<div id="alert-value" class="font-bold text-lg">Low</div> |
|
|
</div> |
|
|
<div> |
|
|
<div class="text-sm text-gray-400">Troop Morale</div> |
|
|
<div id="morale-value" class="font-bold text-lg">High</div> |
|
|
</div> |
|
|
<div> |
|
|
<div class="text-sm text-gray-400">Troops</div> |
|
|
<div id="troops-value" class="font-bold text-lg">2400</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="results-screen" class="hidden p-8 text-center"> |
|
|
<h2 id="result-title" class="text-4xl font-bold mb-6 text-yellow-400">Crossing Complete!</h2> |
|
|
<div id="result-icon" class="text-8xl mb-6">🎖️</div> |
|
|
<p id="result-description" class="text-xl mb-8">You successfully crossed the Delaware River!</p> |
|
|
|
|
|
<div id="new-achievements" class="mb-8 hidden"> |
|
|
<h3 class="text-2xl font-semibold mb-4 text-yellow-400">New Achievements Unlocked!</h3> |
|
|
<div id="achievements-earned" class="flex flex-wrap justify-center gap-4"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="stats-display" class="bg-gray-700 p-6 rounded-lg mb-8 text-left max-w-md mx-auto"> |
|
|
<h3 class="text-2xl font-semibold mb-4">Crossing Statistics</h3> |
|
|
<div class="grid grid-cols-2 gap-4"> |
|
|
<div>Time Taken:</div> |
|
|
<div id="stat-time" class="font-bold">12 hours</div> |
|
|
<div>Troops Lost:</div> |
|
|
<div id="stat-troops" class="font-bold">2</div> |
|
|
<div>Alert Level:</div> |
|
|
<div id="stat-alert" class="font-bold">Medium</div> |
|
|
<div>Difficulty:</div> |
|
|
<div id="stat-difficulty" class="font-bold">Standard</div> |
|
|
<div>Morale:</div> |
|
|
<div id="stat-morale" class="font-bold">High</div> |
|
|
<div>Score:</div> |
|
|
<div id="stat-score" class="font-bold">850</div> |
|
|
</div> |
|
|
</div> |
|
|
<button onclick="returnToTitle()" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-lg text-xl"> |
|
|
Try Again |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="achievement-toast" class="fixed bottom-4 right-4 bg-purple-700 text-white p-4 rounded-lg shadow-lg hidden max-w-xs transform translate-x-full transition-transform duration-300"> |
|
|
<div class="flex items-center"> |
|
|
<div class="mr-3 text-2xl"> |
|
|
<i class="fas fa-trophy"></i> |
|
|
</div> |
|
|
<div> |
|
|
<div class="font-bold" id="toast-title">Achievement Unlocked!</div> |
|
|
<div id="toast-description">Description here</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
let gameState = { |
|
|
difficulty: 'medium', |
|
|
crossingTime: 'dusk', |
|
|
boatStrategy: 'few', |
|
|
secretRoute: false, |
|
|
progress: 0, |
|
|
visibility: 50, |
|
|
alertLevel: 20, |
|
|
morale: 80, |
|
|
troops: 2400, |
|
|
events: [], |
|
|
iceFloes: [], |
|
|
patrols: [], |
|
|
startTime: null, |
|
|
endTime: null, |
|
|
eventCooldown: 0, |
|
|
eventTimer: null, |
|
|
eventTimeLeft: 5, |
|
|
achievements: [], |
|
|
secretsFound: [], |
|
|
score: 0 |
|
|
}; |
|
|
|
|
|
|
|
|
const achievements = [ |
|
|
{ |
|
|
id: 'first_crossing', |
|
|
title: 'First Crossing', |
|
|
description: 'Complete your first crossing of the Delaware', |
|
|
icon: 'fa-flag', |
|
|
color: 'bg-blue-500', |
|
|
earned: false, |
|
|
secret: false |
|
|
}, |
|
|
{ |
|
|
id: 'perfect_crossing', |
|
|
title: 'Perfect Crossing', |
|
|
description: 'Cross without losing any troops', |
|
|
icon: 'fa-star', |
|
|
color: 'bg-yellow-500', |
|
|
earned: false, |
|
|
secret: false |
|
|
}, |
|
|
{ |
|
|
id: 'stealth_master', |
|
|
title: 'Stealth Master', |
|
|
description: 'Complete a crossing with alert level below 30', |
|
|
icon: 'fa-user-secret', |
|
|
color: 'bg-green-500', |
|
|
earned: false, |
|
|
secret: false |
|
|
}, |
|
|
{ |
|
|
id: 'hardcore_victory', |
|
|
title: 'Hardcore Victory', |
|
|
description: 'Complete a crossing on Hardcore difficulty', |
|
|
icon: 'fa-skull', |
|
|
color: 'bg-red-500', |
|
|
earned: false, |
|
|
secret: false |
|
|
}, |
|
|
{ |
|
|
id: 'midnight_crossing', |
|
|
title: 'Midnight Crossing', |
|
|
description: 'Complete a crossing starting at midnight', |
|
|
icon: 'fa-moon', |
|
|
color: 'bg-indigo-500', |
|
|
earned: false, |
|
|
secret: false |
|
|
}, |
|
|
{ |
|
|
id: 'frozen_fingers', |
|
|
title: 'Frozen Fingers', |
|
|
description: 'Complete a crossing with morale below 30', |
|
|
icon: 'fa-snowflake', |
|
|
color: 'bg-blue-300', |
|
|
earned: false, |
|
|
secret: false |
|
|
}, |
|
|
{ |
|
|
id: 'speed_demon', |
|
|
title: 'Speed Demon', |
|
|
description: 'Complete a crossing in under 30 seconds', |
|
|
icon: 'fa-bolt', |
|
|
color: 'bg-yellow-400', |
|
|
earned: false, |
|
|
secret: false |
|
|
}, |
|
|
{ |
|
|
id: 'washington_reborn', |
|
|
title: 'Washington Reborn', |
|
|
description: 'Complete all standard achievements', |
|
|
icon: 'fa-crown', |
|
|
color: 'bg-purple-500', |
|
|
earned: false, |
|
|
secret: false |
|
|
}, |
|
|
|
|
|
{ |
|
|
id: 'secret_route', |
|
|
title: 'Secret Pathfinder', |
|
|
description: 'Discover and use the secret route', |
|
|
icon: 'fa-map', |
|
|
color: 'bg-purple-600', |
|
|
earned: false, |
|
|
secret: true |
|
|
}, |
|
|
{ |
|
|
id: 'full_alert', |
|
|
title: 'Wanted: Dead or Alive', |
|
|
description: 'Get caught with maximum alert level', |
|
|
icon: 'fa-bullhorn', |
|
|
color: 'bg-red-600', |
|
|
earned: false, |
|
|
secret: true |
|
|
}, |
|
|
{ |
|
|
id: 'ice_breaker', |
|
|
title: 'Ice Breaker', |
|
|
description: 'Push through 5 ice floes successfully', |
|
|
icon: 'fa-icicles', |
|
|
color: 'bg-blue-400', |
|
|
earned: false, |
|
|
secret: true |
|
|
}, |
|
|
{ |
|
|
id: 'patrol_baiter', |
|
|
title: 'Patrol Baiter', |
|
|
description: 'Get spotted by 3 patrols but still complete the crossing', |
|
|
icon: 'fa-binoculars', |
|
|
color: 'bg-yellow-600', |
|
|
earned: false, |
|
|
secret: true |
|
|
}, |
|
|
{ |
|
|
id: 'last_minute', |
|
|
title: 'Last Minute Hero', |
|
|
description: 'Win an event with less than 1 second remaining', |
|
|
icon: 'fa-clock', |
|
|
color: 'bg-red-500', |
|
|
earned: false, |
|
|
secret: true |
|
|
} |
|
|
]; |
|
|
|
|
|
|
|
|
const weatherConditions = { |
|
|
easy: { |
|
|
description: "Cold but clear night with minimal wind. Good visibility but also easier for patrols to spot you.", |
|
|
wind: 10, |
|
|
temperature: 25, |
|
|
ice: 3 |
|
|
}, |
|
|
medium: { |
|
|
description: "Snowing with moderate wind. Reduced visibility helps stealth but makes navigation harder.", |
|
|
wind: 20, |
|
|
temperature: 15, |
|
|
ice: 6 |
|
|
}, |
|
|
hard: { |
|
|
description: "Blizzard conditions with high winds. Very difficult navigation but excellent cover.", |
|
|
wind: 35, |
|
|
temperature: 5, |
|
|
ice: 10 |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const events = [ |
|
|
{ |
|
|
name: "Ice Floe Ahead", |
|
|
description: "Your scouts report a large ice floe directly in your path. Do you want to go around it or try to push through?", |
|
|
conditions: (state) => state.progress > 10 && state.progress < 90 && Math.random() < 0.3, |
|
|
choices: [ |
|
|
{ |
|
|
text: "Go around (slower but safer)", |
|
|
effect: (state) => { |
|
|
state.progress -= 5; |
|
|
state.morale -= 5; |
|
|
return "You successfully navigated around the ice floe, but it cost you time and chilled your troops."; |
|
|
} |
|
|
}, |
|
|
{ |
|
|
text: "Push through (faster but risky)", |
|
|
effect: (state) => { |
|
|
const success = Math.random() > 0.3; |
|
|
if (success) { |
|
|
state.progress += 10; |
|
|
checkAchievementProgress('ice_breaker', state); |
|
|
return "Your men pushed through the ice with minimal damage. The crossing continues!"; |
|
|
} else { |
|
|
state.troops -= Math.floor(Math.random() * 50) + 10; |
|
|
state.morale -= 15; |
|
|
return "The ice damaged several boats and some men fell into the freezing water. You lost troops in the crossing."; |
|
|
} |
|
|
} |
|
|
} |
|
|
] |
|
|
}, |
|
|
{ |
|
|
name: "British Patrol Spotted", |
|
|
description: "A British patrol boat has been spotted in the distance. They haven't seen you yet. What do you do?", |
|
|
conditions: (state) => state.progress > 30 && state.progress < 70 && Math.random() < 0.4, |
|
|
choices: [ |
|
|
{ |
|
|
text: "Stop all movement and hide", |
|
|
effect: (state) => { |
|
|
state.alertLevel += 5; |
|
|
const detected = Math.random() < 0.2; |
|
|
if (detected) { |
|
|
state.alertLevel = 100; |
|
|
checkAchievementProgress('full_alert', state); |
|
|
checkAchievementProgress('patrol_baiter', state); |
|
|
return "The patrol spotted you despite your efforts! The alarm has been raised!"; |
|
|
} else { |
|
|
return "You remained perfectly still and the patrol passed by without noticing you."; |
|
|
} |
|
|
} |
|
|
}, |
|
|
{ |
|
|
text: "Continue quietly at reduced speed", |
|
|
effect: (state) => { |
|
|
state.alertLevel += 15; |
|
|
state.progress += 2; |
|
|
return "You continued moving slowly. The patrol seemed to notice some movement but couldn't identify you."; |
|
|
} |
|
|
}, |
|
|
{ |
|
|
text: "Full speed ahead!", |
|
|
effect: (state) => { |
|
|
state.alertLevel += 40; |
|
|
state.progress += 10; |
|
|
const detected = Math.random() < 0.7; |
|
|
if (detected) { |
|
|
state.alertLevel = 100; |
|
|
checkAchievementProgress('full_alert', state); |
|
|
checkAchievementProgress('patrol_baiter', state); |
|
|
return "The patrol definitely saw you! They're raising the alarm!"; |
|
|
} else { |
|
|
return "Against all odds, you sped past the patrol without being detected!"; |
|
|
} |
|
|
} |
|
|
} |
|
|
] |
|
|
}, |
|
|
{ |
|
|
name: "Troop Morale Crisis", |
|
|
description: "Your men are freezing and exhausted. Some are talking about turning back.", |
|
|
conditions: (state) => state.morale < 50 && Math.random() < 0.5, |
|
|
choices: [ |
|
|
{ |
|
|
text: "Give an inspiring speech", |
|
|
effect: (state) => { |
|
|
const success = Math.random() > 0.3; |
|
|
if (success) { |
|
|
state.morale += 30; |
|
|
return '"These are the times that try men\'s souls..." Your speech rallied the troops!'; |
|
|
} else { |
|
|
state.morale -= 10; |
|
|
return "Your words fell flat. The men remain discouraged."; |
|
|
} |
|
|
} |
|
|
}, |
|
|
{ |
|
|
text: "Promise extra rations", |
|
|
effect: (state) => { |
|
|
state.morale += 15; |
|
|
return "The promise of extra food lifted spirits somewhat, but you'll need to deliver later."; |
|
|
} |
|
|
}, |
|
|
{ |
|
|
text: "Threaten punishment", |
|
|
effect: (state) => { |
|
|
state.morale -= 20; |
|
|
if (state.morale <= 0) { |
|
|
state.morale = 0; |
|
|
checkAchievementProgress('frozen_fingers', state); |
|
|
} |
|
|
return "The threats worked to keep order, but the men resent you for it."; |
|
|
} |
|
|
} |
|
|
] |
|
|
}, |
|
|
{ |
|
|
name: "Navigation Error", |
|
|
description: "Your navigator reports you've drifted off course in the storm.", |
|
|
conditions: (state) => state.progress > 20 && state.progress < 80 && Math.random() < 0.25, |
|
|
choices: [ |
|
|
{ |
|
|
text: "Correct course immediately", |
|
|
effect: (state) => { |
|
|
state.progress -= 8; |
|
|
state.morale -= 5; |
|
|
return "You corrected course but lost valuable time doing so."; |
|
|
} |
|
|
}, |
|
|
{ |
|
|
text: "Press on and adjust gradually", |
|
|
effect: (state) => { |
|
|
const success = Math.random() > 0.5; |
|
|
if (success) { |
|
|
state.progress += 5; |
|
|
return "Your gradual correction worked! You're back on course."; |
|
|
} else { |
|
|
state.progress -= 15; |
|
|
state.morale -= 10; |
|
|
return "The error compounded and you're now further off course than before!"; |
|
|
} |
|
|
} |
|
|
} |
|
|
] |
|
|
}, |
|
|
{ |
|
|
name: "Secret Route Discovery", |
|
|
description: "Your scouts report finding a hidden path through the ice. It's risky but could save time.", |
|
|
conditions: (state) => state.secretRoute && state.progress > 40 && state.progress < 60 && Math.random() < 0.5, |
|
|
choices: [ |
|
|
{ |
|
|
text: "Take the secret route", |
|
|
effect: (state) => { |
|
|
const success = Math.random() > 0.4; |
|
|
if (success) { |
|
|
state.progress += 25; |
|
|
unlockAchievement('secret_route', state); |
|
|
return "The secret route worked perfectly! You've gained significant ground."; |
|
|
} else { |
|
|
state.troops -= Math.floor(Math.random() * 200) + 50; |
|
|
state.morale -= 20; |
|
|
return "The route was a trap! You lost many troops in the ambush."; |
|
|
} |
|
|
} |
|
|
}, |
|
|
{ |
|
|
text: "Stick to the original plan", |
|
|
effect: (state) => { |
|
|
state.progress += 2; |
|
|
return "You decided not to risk the unknown path. Progress is slow but steady."; |
|
|
} |
|
|
} |
|
|
] |
|
|
} |
|
|
]; |
|
|
|
|
|
|
|
|
function startGame(difficulty) { |
|
|
gameState = { |
|
|
difficulty, |
|
|
crossingTime: 'dusk', |
|
|
boatStrategy: 'few', |
|
|
secretRoute: document.getElementById('secret-route').checked, |
|
|
progress: 0, |
|
|
visibility: 50, |
|
|
alertLevel: 20, |
|
|
morale: 80, |
|
|
troops: 2400, |
|
|
events: [], |
|
|
iceFloes: [], |
|
|
patrols: [], |
|
|
startTime: null, |
|
|
endTime: null, |
|
|
eventCooldown: 0, |
|
|
eventTimer: null, |
|
|
eventTimeLeft: 5, |
|
|
achievements: [], |
|
|
secretsFound: [], |
|
|
score: 0 |
|
|
}; |
|
|
|
|
|
document.getElementById('title-screen').classList.add('hidden'); |
|
|
document.getElementById('strategy-screen').classList.remove('hidden'); |
|
|
|
|
|
|
|
|
const weather = weatherConditions[difficulty]; |
|
|
document.getElementById('weather-text').textContent = weather.description; |
|
|
|
|
|
|
|
|
document.getElementById('secret-route').checked = false; |
|
|
} |
|
|
|
|
|
|
|
|
function showInstructions() { |
|
|
document.getElementById('title-screen').classList.add('hidden'); |
|
|
document.getElementById('instructions-screen').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function hideInstructions() { |
|
|
document.getElementById('instructions-screen').classList.add('hidden'); |
|
|
document.getElementById('title-screen').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function showAchievements() { |
|
|
document.getElementById('title-screen').classList.add('hidden'); |
|
|
document.getElementById('achievements-screen').classList.remove('hidden'); |
|
|
|
|
|
|
|
|
loadAchievements(); |
|
|
} |
|
|
|
|
|
|
|
|
function hideAchievements() { |
|
|
document.getElementById('achievements-screen').classList.add('hidden'); |
|
|
document.getElementById('title-screen').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function loadAchievements() { |
|
|
const savedAchievements = localStorage.getItem('achievements'); |
|
|
const savedSecrets = localStorage.getItem('secrets'); |
|
|
|
|
|
if (savedAchievements) { |
|
|
gameState.achievements = JSON.parse(savedAchievements); |
|
|
} |
|
|
|
|
|
if (savedSecrets) { |
|
|
gameState.secretsFound = JSON.parse(savedSecrets); |
|
|
} |
|
|
|
|
|
|
|
|
updateAchievementsDisplay(); |
|
|
} |
|
|
|
|
|
|
|
|
function updateAchievementsDisplay() { |
|
|
const container = document.getElementById('achievements-container'); |
|
|
const secretContainer = document.getElementById('secret-achievements-container'); |
|
|
|
|
|
container.innerHTML = ''; |
|
|
secretContainer.innerHTML = ''; |
|
|
|
|
|
achievements.forEach(ach => { |
|
|
const earned = gameState.achievements.includes(ach.id) || |
|
|
(ach.secret && gameState.secretsFound.includes(ach.id)); |
|
|
|
|
|
const achievementDiv = document.createElement('div'); |
|
|
achievementDiv.className = `achievement-badge p-3 rounded-lg flex items-center ${earned ? ach.color : 'bg-gray-600'} ${earned ? '' : 'opacity-60'}`; |
|
|
|
|
|
achievementDiv.innerHTML = ` |
|
|
<div class="mr-3 text-2xl"> |
|
|
<i class="fas ${ach.icon}"></i> |
|
|
</div> |
|
|
<div> |
|
|
<div class="font-bold">${ach.title}</div> |
|
|
<div class="text-sm">${earned ? ach.description : (ach.secret ? '???' : ach.description)}</div> |
|
|
${earned ? '<div class="text-xs mt-1"><i class="fas fa-check"></i> Unlocked</div>' : ''} |
|
|
</div> |
|
|
`; |
|
|
|
|
|
if (ach.secret) { |
|
|
if (earned) { |
|
|
secretContainer.appendChild(achievementDiv); |
|
|
} else { |
|
|
|
|
|
const secretDiv = document.createElement('div'); |
|
|
secretDiv.className = 'achievement-badge p-3 rounded-lg flex items-center bg-gray-700 opacity-60'; |
|
|
secretDiv.innerHTML = ` |
|
|
<div class="mr-3 text-2xl"> |
|
|
<i class="fas fa-lock"></i> |
|
|
</div> |
|
|
<div> |
|
|
<div class="font-bold">Secret Achievement</div> |
|
|
<div class="text-sm">???</div> |
|
|
</div> |
|
|
`; |
|
|
secretContainer.appendChild(secretDiv); |
|
|
} |
|
|
} else { |
|
|
container.appendChild(achievementDiv); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function saveAchievements() { |
|
|
localStorage.setItem('achievements', JSON.stringify(gameState.achievements)); |
|
|
localStorage.setItem('secrets', JSON.stringify(gameState.secretsFound)); |
|
|
} |
|
|
|
|
|
|
|
|
function unlockAchievement(id, state) { |
|
|
const achievement = achievements.find(a => a.id === id); |
|
|
if (!achievement) return; |
|
|
|
|
|
|
|
|
if ((!achievement.secret && state.achievements.includes(id)) || |
|
|
(achievement.secret && state.secretsFound.includes(id))) { |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (achievement.secret) { |
|
|
state.secretsFound.push(id); |
|
|
} else { |
|
|
state.achievements.push(id); |
|
|
} |
|
|
|
|
|
|
|
|
saveAchievements(); |
|
|
|
|
|
|
|
|
showAchievementToast(achievement); |
|
|
|
|
|
|
|
|
if (!achievement.secret) { |
|
|
const standardAchievements = achievements.filter(a => !a.secret && a.id !== 'washington_reborn'); |
|
|
const hasAll = standardAchievements.every(a => state.achievements.includes(a.id)); |
|
|
|
|
|
if (hasAll && !state.achievements.includes('washington_reborn')) { |
|
|
unlockAchievement('washington_reborn', state); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function checkAchievementProgress(id, state) { |
|
|
|
|
|
unlockAchievement(id, state); |
|
|
} |
|
|
|
|
|
|
|
|
function showAchievementToast(achievement) { |
|
|
const toast = document.getElementById('achievement-toast'); |
|
|
const title = document.getElementById('toast-title'); |
|
|
const desc = document.getElementById('toast-description'); |
|
|
|
|
|
title.textContent = achievement.title; |
|
|
desc.textContent = achievement.description; |
|
|
|
|
|
toast.classList.remove('hidden'); |
|
|
toast.classList.remove('translate-x-full'); |
|
|
|
|
|
|
|
|
if (achievement.secret) { |
|
|
toast.classList.add('glow'); |
|
|
} else { |
|
|
toast.classList.remove('glow'); |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
toast.classList.add('translate-x-full'); |
|
|
setTimeout(() => toast.classList.add('hidden'), 300); |
|
|
}, 5000); |
|
|
} |
|
|
|
|
|
|
|
|
function beginCrossing() { |
|
|
|
|
|
gameState.crossingTime = document.querySelector('input[name="time"]:checked').value; |
|
|
gameState.boatStrategy = document.querySelector('input[name="boats"]:checked').value; |
|
|
gameState.secretRoute = document.getElementById('secret-route').checked; |
|
|
|
|
|
|
|
|
if (gameState.crossingTime === 'midnight') { |
|
|
gameState.visibility = 30; |
|
|
gameState.morale -= 10; |
|
|
} else if (gameState.crossingTime === 'dawn') { |
|
|
gameState.visibility = 70; |
|
|
} |
|
|
|
|
|
if (gameState.boatStrategy === 'moderate') { |
|
|
|
|
|
} else if (gameState.boatStrategy === 'many') { |
|
|
gameState.visibility += 20; |
|
|
} |
|
|
|
|
|
|
|
|
gameState.startTime = new Date(); |
|
|
|
|
|
|
|
|
document.getElementById('strategy-screen').classList.add('hidden'); |
|
|
document.getElementById('crossing-screen').classList.remove('hidden'); |
|
|
|
|
|
|
|
|
gameLoop(); |
|
|
|
|
|
|
|
|
if (gameState.difficulty === 'hard') { |
|
|
createSnowEffect(); |
|
|
} |
|
|
|
|
|
|
|
|
createIceFloes(); |
|
|
|
|
|
|
|
|
createPatrols(); |
|
|
} |
|
|
|
|
|
|
|
|
function createIceFloes() { |
|
|
const riverContainer = document.getElementById('river-container'); |
|
|
const weather = weatherConditions[gameState.difficulty]; |
|
|
|
|
|
for (let i = 0; i < weather.ice; i++) { |
|
|
const ice = document.createElement('div'); |
|
|
ice.classList.add('ice-floe'); |
|
|
|
|
|
const size = Math.random() * 40 + 20; |
|
|
ice.style.width = `${size}px`; |
|
|
ice.style.height = `${size}px`; |
|
|
|
|
|
ice.style.left = `${Math.random() * 90 + 5}%`; |
|
|
ice.style.top = `${Math.random() * 60 + 10}%`; |
|
|
|
|
|
riverContainer.appendChild(ice); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function createPatrols() { |
|
|
const riverContainer = document.getElementById('river-container'); |
|
|
const patrolCount = gameState.difficulty === 'easy' ? 2 : |
|
|
gameState.difficulty === 'medium' ? 4 : 6; |
|
|
|
|
|
for (let i = 0; i < patrolCount; i++) { |
|
|
const patrol = document.createElement('div'); |
|
|
patrol.classList.add('patrol'); |
|
|
|
|
|
patrol.style.left = `${Math.random() * 90 + 5}%`; |
|
|
patrol.style.top = `${Math.random() * 60 + 10}%`; |
|
|
|
|
|
|
|
|
const duration = Math.random() * 10 + 10; |
|
|
patrol.style.animation = `wave ${duration}s infinite linear`; |
|
|
|
|
|
riverContainer.appendChild(patrol); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function gameLoop() { |
|
|
|
|
|
const speed = getCrossingSpeed(); |
|
|
gameState.progress += speed; |
|
|
|
|
|
|
|
|
updateUI(); |
|
|
|
|
|
|
|
|
checkForEvents(); |
|
|
|
|
|
|
|
|
if (gameState.eventCooldown > 0) { |
|
|
gameState.eventCooldown--; |
|
|
} |
|
|
|
|
|
|
|
|
if (gameState.progress >= 100) { |
|
|
endGame(true); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (gameState.alertLevel >= 100) { |
|
|
endGame(false, "The British have spotted you! The element of surprise is lost."); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (gameState.morale <= 0) { |
|
|
endGame(false, "Your troops mutinied and refused to continue!"); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (gameState.troops <= 0) { |
|
|
endGame(false, "You've lost all your troops! The crossing has failed."); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(gameLoop, 200); |
|
|
} |
|
|
|
|
|
|
|
|
function getCrossingSpeed() { |
|
|
let speed = 0.5; |
|
|
|
|
|
|
|
|
if (gameState.boatStrategy === 'moderate') { |
|
|
speed = 0.8; |
|
|
} else if (gameState.boatStrategy === 'many') { |
|
|
speed = 1.2; |
|
|
} |
|
|
|
|
|
|
|
|
const weather = weatherConditions[gameState.difficulty]; |
|
|
speed *= 1 - (weather.wind / 100); |
|
|
|
|
|
|
|
|
speed *= gameState.morale / 100; |
|
|
|
|
|
|
|
|
speed *= (0.9 + Math.random() * 0.2); |
|
|
|
|
|
return speed; |
|
|
} |
|
|
|
|
|
|
|
|
function updateUI() { |
|
|
|
|
|
const progressBar = document.getElementById('progress-bar'); |
|
|
const clampedProgress = Math.min(100, Math.max(0, gameState.progress)); |
|
|
progressBar.style.width = `${clampedProgress}%`; |
|
|
document.getElementById('distance-text').textContent = `${Math.floor(clampedProgress)}%`; |
|
|
|
|
|
|
|
|
const boat = document.getElementById('boat'); |
|
|
const riverWidth = document.getElementById('river-container').offsetWidth; |
|
|
const boatPosition = (riverWidth - 64) * (clampedProgress / 100); |
|
|
boat.style.left = `${boatPosition}px`; |
|
|
|
|
|
|
|
|
document.getElementById('visibility-value').textContent = |
|
|
gameState.visibility < 40 ? 'Low' : |
|
|
gameState.visibility < 70 ? 'Medium' : 'High'; |
|
|
|
|
|
document.getElementById('alert-value').textContent = |
|
|
gameState.alertLevel < 40 ? 'Low' : |
|
|
gameState.alertLevel < 70 ? 'Medium' : 'High'; |
|
|
|
|
|
document.getElementById('morale-value').textContent = |
|
|
gameState.morale < 40 ? 'Low' : |
|
|
gameState.morale < 70 ? 'Medium' : 'High'; |
|
|
|
|
|
document.getElementById('troops-value').textContent = gameState.troops; |
|
|
|
|
|
|
|
|
document.getElementById('visibility-value').className = 'font-bold text-lg ' + |
|
|
(gameState.visibility < 40 ? 'text-green-500' : |
|
|
gameState.visibility < 70 ? 'text-yellow-500' : 'text-red-500'); |
|
|
|
|
|
document.getElementById('alert-value').className = 'font-bold text-lg ' + |
|
|
(gameState.alertLevel < 40 ? 'text-green-500' : |
|
|
gameState.alertLevel < 70 ? 'text-yellow-500' : 'text-red-500'); |
|
|
|
|
|
document.getElementById('morale-value').className = 'font-bold text-lg ' + |
|
|
(gameState.morale < 40 ? 'text-red-500' : |
|
|
gameState.morale < 70 ? 'text-yellow-500' : 'text-green-500'); |
|
|
} |
|
|
|
|
|
|
|
|
function checkForEvents() { |
|
|
if (gameState.eventCooldown > 0) return; |
|
|
|
|
|
|
|
|
for (const event of events) { |
|
|
if (event.conditions(gameState)) { |
|
|
triggerEvent(event); |
|
|
gameState.eventCooldown = 20; |
|
|
return; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function triggerEvent(event) { |
|
|
const eventDisplay = document.getElementById('event-display'); |
|
|
document.getElementById('event-title').textContent = event.name; |
|
|
document.getElementById('event-description').textContent = event.description; |
|
|
|
|
|
|
|
|
const choicesContainer = document.getElementById('event-choices'); |
|
|
choicesContainer.innerHTML = ''; |
|
|
|
|
|
|
|
|
gameState.eventTimeLeft = 5; |
|
|
updateEventTimer(); |
|
|
|
|
|
if (gameState.eventTimer) { |
|
|
clearInterval(gameState.eventTimer); |
|
|
} |
|
|
|
|
|
gameState.eventTimer = setInterval(() => { |
|
|
gameState.eventTimeLeft -= 0.1; |
|
|
updateEventTimer(); |
|
|
|
|
|
if (gameState.eventTimeLeft <= 0) { |
|
|
clearInterval(gameState.eventTimer); |
|
|
eventTimeout(event); |
|
|
} |
|
|
}, 100); |
|
|
|
|
|
|
|
|
event.choices.forEach((choice, index) => { |
|
|
const button = document.createElement('button'); |
|
|
button.textContent = choice.text; |
|
|
button.className = 'w-full bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-lg transition'; |
|
|
button.onclick = () => { |
|
|
clearInterval(gameState.eventTimer); |
|
|
const result = choice.effect(gameState); |
|
|
document.getElementById('event-description').textContent = result; |
|
|
|
|
|
|
|
|
Array.from(choicesContainer.children).forEach((child, i) => { |
|
|
if (i !== index) child.remove(); |
|
|
}); |
|
|
|
|
|
|
|
|
const continueButton = document.createElement('button'); |
|
|
continueButton.textContent = 'Continue'; |
|
|
continueButton.className = 'w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg transition mt-2'; |
|
|
continueButton.onclick = () => { |
|
|
eventDisplay.classList.add('hidden'); |
|
|
updateUI(); |
|
|
}; |
|
|
choicesContainer.appendChild(continueButton); |
|
|
}; |
|
|
choicesContainer.appendChild(button); |
|
|
}); |
|
|
|
|
|
eventDisplay.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function updateEventTimer() { |
|
|
const timerBar = document.getElementById('event-timer'); |
|
|
const timeText = document.getElementById('event-time-remaining'); |
|
|
|
|
|
const percentage = Math.max(0, (gameState.eventTimeLeft / 5) * 100); |
|
|
timerBar.style.width = `${percentage}%`; |
|
|
timeText.textContent = `${gameState.eventTimeLeft.toFixed(1)}s`; |
|
|
|
|
|
|
|
|
if (gameState.eventTimeLeft < 1) { |
|
|
timerBar.className = 'timer-bar h-2 bg-red-700 rounded-full'; |
|
|
} else if (gameState.eventTimeLeft < 2) { |
|
|
timerBar.className = 'timer-bar h-2 bg-red-500 rounded-full'; |
|
|
} else if (gameState.eventTimeLeft < 3) { |
|
|
timerBar.className = 'timer-bar h-2 bg-yellow-500 rounded-full'; |
|
|
} else { |
|
|
timerBar.className = 'timer-bar h-2 bg-green-500 rounded-full'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function eventTimeout(event) { |
|
|
const eventDisplay = document.getElementById('event-display'); |
|
|
const choicesContainer = document.getElementById('event-choices'); |
|
|
|
|
|
|
|
|
let result = "You hesitated too long! "; |
|
|
|
|
|
|
|
|
const effect = Math.floor(Math.random() * 3); |
|
|
switch(effect) { |
|
|
case 0: |
|
|
gameState.troops -= Math.floor(Math.random() * 100) + 50; |
|
|
result += `The delay caused ${gameState.troops < 0 ? "many" : "some"} of your troops to be lost.`; |
|
|
break; |
|
|
case 1: |
|
|
gameState.alertLevel += 30; |
|
|
result += "The enemy spotted you during your indecision!"; |
|
|
break; |
|
|
case 2: |
|
|
gameState.morale -= 25; |
|
|
result += "Your troops lost confidence in your leadership."; |
|
|
break; |
|
|
} |
|
|
|
|
|
document.getElementById('event-description').textContent = result; |
|
|
|
|
|
|
|
|
choicesContainer.innerHTML = ''; |
|
|
|
|
|
|
|
|
const continueButton = document.createElement('button'); |
|
|
continueButton.textContent = 'Continue'; |
|
|
continueButton.className = 'w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg transition mt-2'; |
|
|
continueButton.onclick = () => { |
|
|
eventDisplay.classList.add('hidden'); |
|
|
updateUI(); |
|
|
}; |
|
|
choicesContainer.appendChild(continueButton); |
|
|
|
|
|
|
|
|
if (gameState.eventTimeLeft > -0.1) { |
|
|
checkAchievementProgress('last_minute', gameState); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function endGame(success, message = null) { |
|
|
gameState.endTime = new Date(); |
|
|
|
|
|
document.getElementById('crossing-screen').classList.add('hidden'); |
|
|
document.getElementById('results-screen').classList.remove('hidden'); |
|
|
|
|
|
const resultTitle = document.getElementById('result-title'); |
|
|
const resultIcon = document.getElementById('result-icon'); |
|
|
const resultDescription = document.getElementById('result-description'); |
|
|
const newAchievements = document.getElementById('new-achievements'); |
|
|
|
|
|
|
|
|
const newAchievementsEarned = []; |
|
|
|
|
|
if (success) { |
|
|
resultTitle.textContent = "Crossing Successful!"; |
|
|
resultIcon.textContent = "🎖️"; |
|
|
resultDescription.textContent = message || "You successfully crossed the Delaware River and surprised the Hessians at Trenton!"; |
|
|
|
|
|
|
|
|
unlockAchievement('first_crossing', gameState); |
|
|
|
|
|
if (gameState.troops === 2400) { |
|
|
unlockAchievement('perfect_crossing', gameState); |
|
|
newAchievementsEarned.push('perfect_crossing'); |
|
|
} |
|
|
|
|
|
if (gameState.alertLevel < 30) { |
|
|
unlockAchievement('stealth_master', gameState); |
|
|
newAchievementsEarned.push('stealth_master'); |
|
|
} |
|
|
|
|
|
if (gameState.difficulty === 'hard') { |
|
|
unlockAchievement('hardcore_victory', gameState); |
|
|
newAchievementsEarned.push('hardcore_victory'); |
|
|
} |
|
|
|
|
|
if (gameState.crossingTime === 'midnight') { |
|
|
unlockAchievement('midnight_crossing', gameState); |
|
|
newAchievementsEarned.push('midnight_crossing'); |
|
|
} |
|
|
|
|
|
if (gameState.morale < 30) { |
|
|
unlockAchievement('frozen_fingers', gameState); |
|
|
newAchievementsEarned.push('frozen_fingers'); |
|
|
} |
|
|
|
|
|
|
|
|
const timeTaken = (gameState.endTime - gameState.startTime) / 1000; |
|
|
if (timeTaken < 30) { |
|
|
unlockAchievement('speed_demon', gameState); |
|
|
newAchievementsEarned.push('speed_demon'); |
|
|
} |
|
|
} else { |
|
|
resultTitle.textContent = "Crossing Failed"; |
|
|
resultIcon.textContent = "💀"; |
|
|
resultDescription.textContent = message || "Your crossing was detected and the element of surprise was lost."; |
|
|
|
|
|
|
|
|
if (gameState.alertLevel >= 100) { |
|
|
unlockAchievement('full_alert', gameState); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (newAchievementsEarned.length > 0) { |
|
|
newAchievements.classList.remove('hidden'); |
|
|
const container = document.getElementById('achievements-earned'); |
|
|
container.innerHTML = ''; |
|
|
|
|
|
newAchievementsEarned.forEach(achId => { |
|
|
const achievement = achievements.find(a => a.id === achId); |
|
|
if (achievement) { |
|
|
const div = document.createElement('div'); |
|
|
div.className = `achievement-badge p-3 rounded-lg flex items-center ${achievement.color}`; |
|
|
div.innerHTML = ` |
|
|
<div class="mr-3 text-2xl"> |
|
|
<i class="fas ${achievement.icon}"></i> |
|
|
</div> |
|
|
<div> |
|
|
<div class="font-bold">${achievement.title}</div> |
|
|
<div class="text-sm">${achievement.description}</div> |
|
|
</div> |
|
|
`; |
|
|
container.appendChild(div); |
|
|
} |
|
|
}); |
|
|
} else { |
|
|
newAchievements.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
const timeTaken = (gameState.endTime - gameState.startTime) / 1000; |
|
|
document.getElementById('stat-time').textContent = `${timeTaken.toFixed(1)} seconds`; |
|
|
document.getElementById('stat-troops').textContent = `${2400 - gameState.troops} lost (${gameState.troops} remaining)`; |
|
|
document.getElementById('stat-alert').textContent = |
|
|
gameState.alertLevel < 40 ? 'Low' : |
|
|
gameState.alertLevel < 70 ? 'Medium' : 'High'; |
|
|
document.getElementById('stat-difficulty').textContent = |
|
|
gameState.difficulty === 'easy' ? 'Easy' : |
|
|
gameState.difficulty === 'medium' ? 'Standard' : 'Hardcore'; |
|
|
document.getElementById('stat-morale').textContent = |
|
|
gameState.morale < 40 ? 'Low' : |
|
|
gameState.morale < 70 ? 'Medium' : 'High'; |
|
|
|
|
|
|
|
|
calculateScore(success, timeTaken); |
|
|
} |
|
|
|
|
|
|
|
|
function calculateScore(success, timeTaken) { |
|
|
if (!success) { |
|
|
gameState.score = 0; |
|
|
document.getElementById('stat-score').textContent = '0'; |
|
|
return; |
|
|
} |
|
|
|
|
|
let score = 1000; |
|
|
|
|
|
|
|
|
score += Math.max(0, 500 - (timeTaken * 10)); |
|
|
|
|
|
|
|
|
score += (gameState.troops / 2400) * 500; |
|
|
|
|
|
|
|
|
score += (gameState.morale / 100) * 300; |
|
|
|
|
|
|
|
|
score -= gameState.alertLevel * 2; |
|
|
|
|
|
|
|
|
if (gameState.difficulty === 'medium') score *= 1.5; |
|
|
if (gameState.difficulty === 'hard') score *= 2; |
|
|
|
|
|
gameState.score = Math.floor(Math.max(0, score)); |
|
|
document.getElementById('stat-score').textContent = gameState.score; |
|
|
} |
|
|
|
|
|
|
|
|
function returnToTitle() { |
|
|
document.getElementById('results-screen').classList.add('hidden'); |
|
|
document.getElementById('title-screen').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function createSnowEffect() { |
|
|
const riverContainer = document.getElementById('river-container'); |
|
|
|
|
|
for (let i = 0; i < 50; i++) { |
|
|
createSnowflake(riverContainer); |
|
|
} |
|
|
|
|
|
setInterval(() => { |
|
|
createSnowflake(riverContainer); |
|
|
}, 300); |
|
|
} |
|
|
|
|
|
function createSnowflake(container) { |
|
|
const snowflake = document.createElement('div'); |
|
|
snowflake.classList.add('snowflake'); |
|
|
|
|
|
const size = Math.random() * 5 + 2; |
|
|
snowflake.style.width = `${size}px`; |
|
|
snowflake.style.height = `${size}px`; |
|
|
|
|
|
snowflake.style.left = `${Math.random() * 100}%`; |
|
|
snowflake.style.opacity = Math.random() * 0.5 + 0.3; |
|
|
|
|
|
const animationDuration = Math.random() * 5 + 5; |
|
|
snowflake.style.animationDuration = `${animationDuration}s`; |
|
|
|
|
|
container.appendChild(snowflake); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
snowflake.remove(); |
|
|
}, animationDuration * 1000); |
|
|
} |
|
|
|
|
|
|
|
|
function initAchievements() { |
|
|
const savedAchievements = localStorage.getItem('achievements'); |
|
|
const savedSecrets = localStorage.getItem('secrets'); |
|
|
|
|
|
if (savedAchievements) { |
|
|
gameState.achievements = JSON.parse(savedAchievements); |
|
|
} else { |
|
|
gameState.achievements = []; |
|
|
} |
|
|
|
|
|
if (savedSecrets) { |
|
|
gameState.secretsFound = JSON.parse(savedSecrets); |
|
|
} else { |
|
|
gameState.secretsFound = []; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
initAchievements(); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Hypergenius/washington-s-crossing" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |