Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Friend Quest</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
/* Custom CSS for pixel art and game elements */ | |
.pixel-art { | |
image-rendering: pixelated; | |
image-rendering: -moz-crisp-edges; | |
image-rendering: crisp-edges; | |
} | |
#gameCanvas { | |
background-color: #6b8cff; | |
border: 4px solid #333; | |
box-shadow: 0 0 20px rgba(0,0,0,0.3); | |
} | |
.quiz-modal { | |
background-color: #f0e6d2; | |
border: 4px solid #8b4513; | |
box-shadow: 0 0 0 8px #d2b48c, 0 0 0 12px #8b4513; | |
} | |
.quiz-option { | |
background-color: #d2b48c; | |
border: 3px solid #8b4513; | |
transition: all 0.2s; | |
} | |
.quiz-option:hover { | |
background-color: #f0e6d2; | |
transform: scale(1.05); | |
} | |
.quiz-option.correct { | |
background-color: #8fbc8f; | |
border-color: #556b2f; | |
} | |
.quiz-option.wrong { | |
background-color: #cd5c5c; | |
border-color: #8b0000; | |
} | |
@keyframes jump { | |
0% { transform: translateY(0); } | |
50% { transform: translateY(-20px); } | |
100% { transform: translateY(0); } | |
} | |
.jump-animation { | |
animation: jump 0.5s ease; | |
} | |
.character { | |
transition: transform 0.1s; | |
} | |
.moving { | |
animation: walk 0.5s steps(4) infinite; | |
} | |
@keyframes walk { | |
from { background-position: 0 0; } | |
to { background-position: -64px 0; } | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4"> | |
<div class="text-center mb-4"> | |
<h1 class="text-4xl font-bold mb-2 text-yellow-300 font-mono">FRIEND QUEST</h1> | |
<p class="text-lg text-gray-300">Find your lost friend by answering questions about them!</p> | |
</div> | |
<div class="relative"> | |
<!-- Game Canvas --> | |
<canvas id="gameCanvas" width="800" height="400" class="pixel-art"></canvas> | |
<!-- Start Screen --> | |
<div id="startScreen" class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center"> | |
<div class="text-center p-8 bg-gray-800 rounded-lg border-4 border-yellow-400 max-w-md"> | |
<h2 class="text-3xl font-bold mb-4 text-yellow-400">FRIEND QUEST</h2> | |
<p class="mb-6">Your friend is lost in this strange world! Jump over obstacles and answer questions about them to find your way.</p> | |
<button id="startButton" class="bg-yellow-500 hover:bg-yellow-400 text-black font-bold py-3 px-6 rounded-lg text-xl transition-all transform hover:scale-105"> | |
START ADVENTURE | |
</button> | |
</div> | |
</div> | |
<!-- Quiz Modal --> | |
<div id="quizModal" class="absolute inset-0 hidden items-center justify-center"> | |
<div class="quiz-modal p-6 rounded-lg w-full max-w-md"> | |
<h3 class="text-2xl font-bold mb-4 text-center text-brown-800">What do you remember about your friend?</h3> | |
<p id="quizQuestion" class="text-lg mb-6 text-center text-brown-800">Loading question...</p> | |
<div class="grid grid-cols-1 gap-3"> | |
<button class="quiz-option py-3 px-4 rounded-lg text-lg" data-answer="1">Option 1</button> | |
<button class="quiz-option py-3 px-4 rounded-lg text-lg" data-answer="2">Option 2</button> | |
<button class="quiz-option py-3 px-4 rounded-lg text-lg" data-answer="3">Option 3</button> | |
<button class="quiz-option py-3 px-4 rounded-lg text-lg" data-answer="4">Option 4</button> | |
</div> | |
<div id="quizFeedback" class="mt-4 text-center text-lg font-bold hidden"></div> | |
</div> | |
</div> | |
<!-- Game Over Screen --> | |
<div id="gameOverScreen" class="absolute inset-0 hidden bg-black bg-opacity-70 flex flex-col items-center justify-center"> | |
<div class="text-center p-8 bg-gray-800 rounded-lg border-4 border-red-500 max-w-md"> | |
<h2 class="text-3xl font-bold mb-4 text-red-500">GAME OVER</h2> | |
<p class="mb-6" id="gameOverMessage">You failed to find your friend!</p> | |
<button id="restartButton" class="bg-red-500 hover:bg-red-400 text-white font-bold py-3 px-6 rounded-lg text-xl transition-all transform hover:scale-105"> | |
TRY AGAIN | |
</button> | |
</div> | |
</div> | |
<!-- Win Screen --> | |
<div id="winScreen" class="absolute inset-0 hidden bg-black bg-opacity-70 flex flex-col items-center justify-center"> | |
<div class="text-center p-8 bg-gray-800 rounded-lg border-4 border-green-500 max-w-md"> | |
<h2 class="text-3xl font-bold mb-4 text-green-500">VICTORY!</h2> | |
<p class="mb-6">You found your friend! Thanks to your great memory about them.</p> | |
<button id="playAgainButton" class="bg-green-500 hover:bg-green-400 text-white font-bold py-3 px-6 rounded-lg text-xl transition-all transform hover:scale-105"> | |
PLAY AGAIN | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="mt-6 flex gap-8"> | |
<div class="text-center"> | |
<p class="text-lg">Controls:</p> | |
<p class="text-gray-400">Arrow keys to move, Space to jump</p> | |
</div> | |
<div class="text-center"> | |
<p class="text-lg">Progress:</p> | |
<p id="scoreDisplay" class="text-yellow-400">Questions: 0/5</p> | |
</div> | |
</div> | |
<script> | |
// Game variables | |
const canvas = document.getElementById('gameCanvas'); | |
const ctx = canvas.getContext('2d'); | |
const startScreen = document.getElementById('startScreen'); | |
const startButton = document.getElementById('startButton'); | |
const quizModal = document.getElementById('quizModal'); | |
const quizQuestion = document.getElementById('quizQuestion'); | |
const quizOptions = document.querySelectorAll('.quiz-option'); | |
const quizFeedback = document.getElementById('quizFeedback'); | |
const gameOverScreen = document.getElementById('gameOverScreen'); | |
const gameOverMessage = document.getElementById('gameOverMessage'); | |
const restartButton = document.getElementById('restartButton'); | |
const winScreen = document.getElementById('winScreen'); | |
const playAgainButton = document.getElementById('playAgainButton'); | |
const scoreDisplay = document.getElementById('scoreDisplay'); | |
// Game state | |
let gameRunning = false; | |
let quizActive = false; | |
let currentQuestion = null; | |
let correctAnswers = 0; | |
const totalQuestions = 5; | |
// Character properties | |
const character = { | |
x: 50, | |
y: 300, | |
width: 32, | |
height: 48, | |
speed: 5, | |
velocityY: 0, | |
gravity: 0.5, | |
jumpForce: -12, | |
isJumping: false, | |
direction: 1, // 1 for right, -1 for left | |
moving: false | |
}; | |
// Game world properties | |
const world = { | |
width: 2000, | |
scrollX: 0, | |
groundHeight: 350 | |
}; | |
// Platform and obstacle data | |
const platforms = [ | |
{ x: 0, y: world.groundHeight, width: 500, height: 50 }, | |
{ x: 600, y: world.groundHeight - 100, width: 200, height: 50 }, | |
{ x: 900, y: world.groundHeight, width: 300, height: 50 }, | |
{ x: 1300, y: world.groundHeight - 150, width: 200, height: 50 }, | |
{ x: 1600, y: world.groundHeight, width: 400, height: 50 } | |
]; | |
// Checkpoints that trigger quizzes | |
const checkpoints = [ | |
{ x: 300, quizTaken: false }, | |
{ x: 800, quizTaken: false }, | |
{ x: 1100, quizTaken: false }, | |
{ x: 1400, quizTaken: false }, | |
{ x: 1800, quizTaken: false } | |
]; | |
// Enemy data | |
const enemies = [ | |
{ x: 400, y: world.groundHeight - 30, width: 32, height: 32, speed: 2, direction: 1 }, | |
{ x: 700, y: world.groundHeight - 130, width: 32, height: 32, speed: 1, direction: -1 }, | |
{ x: 1000, y: world.groundHeight - 30, width: 32, height: 32, speed: 3, direction: 1 }, | |
{ x: 1500, y: world.groundHeight - 180, width: 32, height: 32, speed: 2, direction: -1 } | |
]; | |
// Friend (goal) position | |
const friend = { | |
x: 1900, | |
y: world.groundHeight - 50, | |
width: 32, | |
height: 48 | |
}; | |
// Quiz questions | |
const questions = [ | |
{ | |
question: "What is your friend's favorite color?", | |
options: ["Blue", "Red", "Green", "Yellow"], | |
correct: 0 | |
}, | |
{ | |
question: "What food does your friend dislike?", | |
options: ["Pizza", "Broccoli", "Ice cream", "Pasta"], | |
correct: 1 | |
}, | |
{ | |
question: "Where did you first meet your friend?", | |
options: ["School", "Park", "Library", "Sports club"], | |
correct: 2 | |
}, | |
{ | |
question: "What is your friend's favorite season?", | |
options: ["Winter", "Spring", "Summer", "Fall"], | |
correct: 3 | |
}, | |
{ | |
question: "What is your friend's biggest fear?", | |
options: ["Spiders", "Heights", "Darkness", "Clowns"], | |
correct: 1 | |
} | |
]; | |
// Keyboard state | |
const keys = { | |
ArrowLeft: false, | |
ArrowRight: false, | |
ArrowUp: false, | |
Space: false | |
}; | |
// Event listeners | |
startButton.addEventListener('click', startGame); | |
restartButton.addEventListener('click', resetGame); | |
playAgainButton.addEventListener('click', resetGame); | |
quizOptions.forEach(option => { | |
option.addEventListener('click', () => checkAnswer(option)); | |
}); | |
document.addEventListener('keydown', (e) => { | |
if (e.code in keys) { | |
keys[e.code] = true; | |
e.preventDefault(); | |
} | |
}); | |
document.addEventListener('keyup', (e) => { | |
if (e.code in keys) { | |
keys[e.code] = false; | |
e.preventDefault(); | |
} | |
}); | |
// Game functions | |
function startGame() { | |
gameRunning = true; | |
startScreen.classList.add('hidden'); | |
correctAnswers = 0; | |
updateScoreDisplay(); | |
resetGameState(); | |
requestAnimationFrame(gameLoop); | |
} | |
function resetGame() { | |
gameOverScreen.classList.add('hidden'); | |
winScreen.classList.add('hidden'); | |
resetGameState(); | |
requestAnimationFrame(gameLoop); | |
} | |
function resetGameState() { | |
character.x = 50; | |
character.y = 300; | |
character.velocityY = 0; | |
character.isJumping = false; | |
world.scrollX = 0; | |
// Reset checkpoints | |
checkpoints.forEach(cp => cp.quizTaken = false); | |
// Reset enemies positions | |
enemies[0].x = 400; | |
enemies[1].x = 700; | |
enemies[2].x = 1000; | |
enemies[3].x = 1500; | |
correctAnswers = 0; | |
updateScoreDisplay(); | |
gameRunning = true; | |
quizActive = false; | |
} | |
function updateScoreDisplay() { | |
scoreDisplay.textContent = `Questions: ${correctAnswers}/${totalQuestions}`; | |
} | |
function gameLoop() { | |
if (!gameRunning) return; | |
update(); | |
render(); | |
requestAnimationFrame(gameLoop); | |
} | |
function update() { | |
if (quizActive) return; | |
// Handle movement | |
if (keys.ArrowLeft || keys['a']) { | |
character.x -= character.speed; | |
character.direction = -1; | |
character.moving = true; | |
} else if (keys.ArrowRight || keys['d']) { | |
character.x += character.speed; | |
character.direction = 1; | |
character.moving = true; | |
} else { | |
character.moving = false; | |
} | |
// Handle jumping | |
if ((keys.ArrowUp || keys.Space || keys['w']) && !character.isJumping) { | |
character.velocityY = character.jumpForce; | |
character.isJumping = true; | |
} | |
// Apply gravity | |
character.velocityY += character.gravity; | |
character.y += character.velocityY; | |
// Check for ground collision | |
let onGround = false; | |
for (const platform of platforms) { | |
if (character.y + character.height >= platform.y && | |
character.y + character.height <= platform.y + platform.height && | |
character.x + character.width > platform.x && | |
character.x < platform.x + platform.width && | |
character.velocityY > 0) { | |
character.y = platform.y - character.height; | |
character.velocityY = 0; | |
character.isJumping = false; | |
onGround = true; | |
} | |
} | |
// Check for enemy collisions | |
for (const enemy of enemies) { | |
if (checkCollision(character, enemy)) { | |
gameOver("You got hit by an enemy!"); | |
return; | |
} | |
} | |
// Check for friend collision (win condition) | |
if (checkCollision(character, friend) && correctAnswers === totalQuestions) { | |
gameRunning = false; | |
winScreen.classList.remove('hidden'); | |
return; | |
} | |
// Check for falling off the world | |
if (character.y > canvas.height) { | |
gameOver("You fell off the world!"); | |
return; | |
} | |
// Update enemy positions | |
enemies.forEach(enemy => { | |
enemy.x += enemy.speed * enemy.direction; | |
// Simple enemy AI - turn around at edges | |
let enemyOnPlatform = false; | |
for (const platform of platforms) { | |
if (enemy.x >= platform.x && enemy.x + enemy.width <= platform.x + platform.width && | |
enemy.y + enemy.height >= platform.y && enemy.y + enemy.height <= platform.y + platform.height) { | |
enemyOnPlatform = true; | |
// Check if enemy is at platform edge | |
if ((enemy.x <= platform.x && enemy.direction === -1) || | |
(enemy.x + enemy.width >= platform.x + platform.width && enemy.direction === 1)) { | |
enemy.direction *= -1; | |
} | |
break; | |
} | |
} | |
if (!enemyOnPlatform) { | |
enemy.direction *= -1; | |
} | |
}); | |
// Check for checkpoint collisions | |
checkpoints.forEach((checkpoint, index) => { | |
if (!checkpoint.quizTaken && | |
character.x + character.width > checkpoint.x && | |
character.x < checkpoint.x + 10) { | |
checkpoint.quizTaken = true; | |
showQuiz(index); | |
} | |
}); | |
// Update camera (world scroll) | |
if (character.x > canvas.width / 2) { | |
world.scrollX = character.x - canvas.width / 2; | |
// Don't scroll past the end of the world | |
if (world.scrollX > world.width - canvas.width) { | |
world.scrollX = world.width - canvas.width; | |
} | |
} else { | |
world.scrollX = 0; | |
} | |
} | |
function render() { | |
// Clear canvas | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Draw sky background | |
ctx.fillStyle = '#6b8cff'; | |
ctx.fillRect(0, 0, canvas.width, canvas.height); | |
// Draw clouds (simple background elements) | |
ctx.fillStyle = '#ffffff'; | |
ctx.fillRect(100 - world.scrollX / 3, 50, 60, 20); | |
ctx.fillRect(300 - world.scrollX / 3, 80, 80, 25); | |
ctx.fillRect(500 - world.scrollX / 3, 60, 70, 20); | |
ctx.fillRect(800 - world.scrollX / 3, 70, 90, 30); | |
// Draw platforms | |
ctx.fillStyle = '#8b4513'; // Brown | |
platforms.forEach(platform => { | |
ctx.fillRect(platform.x - world.scrollX, platform.y, platform.width, platform.height); | |
}); | |
// Draw ground | |
ctx.fillStyle = '#556b2f'; // Dark green | |
ctx.fillRect(0, world.groundHeight, world.width, canvas.height - world.groundHeight); | |
// Draw checkpoints (flag markers) | |
ctx.fillStyle = '#ff0000'; | |
checkpoints.forEach(checkpoint => { | |
if (!checkpoint.quizTaken) { | |
ctx.fillRect(checkpoint.x - world.scrollX, world.groundHeight - 60, 10, 60); | |
ctx.fillStyle = '#ffffff'; | |
ctx.beginPath(); | |
ctx.moveTo(checkpoint.x - world.scrollX + 10, world.groundHeight - 60); | |
ctx.lineTo(checkpoint.x - world.scrollX + 30, world.groundHeight - 40); | |
ctx.lineTo(checkpoint.x - world.scrollX + 10, world.groundHeight - 20); | |
ctx.fill(); | |
ctx.fillStyle = '#ff0000'; | |
} | |
}); | |
// Draw enemies (simple pixel art) | |
ctx.fillStyle = '#ff0000'; | |
enemies.forEach(enemy => { | |
// Simple enemy sprite - a red square with eyes | |
ctx.fillRect(enemy.x - world.scrollX, enemy.y, enemy.width, enemy.height); | |
// Eyes | |
ctx.fillStyle = '#ffffff'; | |
ctx.fillRect(enemy.x - world.scrollX + 5, enemy.y + 5, 8, 8); | |
ctx.fillRect(enemy.x - world.scrollX + 19, enemy.y + 5, 8, 8); | |
ctx.fillStyle = '#000000'; | |
ctx.fillRect(enemy.x - world.scrollX + 7, enemy.y + 7, 4, 4); | |
ctx.fillRect(enemy.x - world.scrollX + 21, enemy.y + 7, 4, 4); | |
ctx.fillStyle = '#ff0000'; | |
}); | |
// Draw friend (goal) | |
ctx.fillStyle = '#ffff00'; // Yellow | |
ctx.fillRect(friend.x - world.scrollX, friend.y, friend.width, friend.height); | |
// Face details | |
ctx.fillStyle = '#000000'; | |
ctx.fillRect(friend.x - world.scrollX + 8, friend.y + 10, 4, 4); // Left eye | |
ctx.fillRect(friend.x - world.scrollX + 20, friend.y + 10, 4, 4); // Right eye | |
ctx.fillRect(friend.x - world.scrollX + 10, friend.y + 20, 12, 4); // Mouth | |
// Draw character (simple pixel art) | |
ctx.fillStyle = '#00ff00'; // Green | |
ctx.fillRect(character.x - world.scrollX, character.y, character.width, character.height); | |
// Face details (changes direction) | |
ctx.fillStyle = '#000000'; | |
if (character.direction === 1) { | |
// Facing right | |
ctx.fillRect(character.x - world.scrollX + 8, character.y + 10, 4, 4); // Left eye | |
ctx.fillRect(character.x - world.scrollX + 20, character.y + 10, 4, 4); // Right eye | |
ctx.fillRect(character.x - world.scrollX + 10, character.y + 20, 12, 4); // Mouth | |
} else { | |
// Facing left | |
ctx.fillRect(character.x - world.scrollX + 8, character.y + 10, 4, 4); // Left eye | |
ctx.fillRect(character.x - world.scrollX + 20, character.y + 10, 4, 4); // Right eye | |
ctx.fillRect(character.x - world.scrollX + 10, character.y + 20, 12, 4); // Mouth | |
} | |
// Draw score | |
ctx.fillStyle = '#ffffff'; | |
ctx.font = '20px Arial'; | |
ctx.fillText(`Questions: ${correctAnswers}/${totalQuestions}`, 20, 30); | |
} | |
function checkCollision(obj1, obj2) { | |
return obj1.x < obj2.x + obj2.width && | |
obj1.x + obj1.width > obj2.x && | |
obj1.y < obj2.y + obj2.height && | |
obj1.y + obj1.height > obj2.y; | |
} | |
function showQuiz(index) { | |
if (index >= questions.length) return; | |
gameRunning = false; | |
quizActive = true; | |
currentQuestion = questions[index]; | |
quizQuestion.textContent = currentQuestion.question; | |
quizOptions.forEach((option, i) => { | |
option.textContent = currentQuestion.options[i]; | |
option.classList.remove('correct', 'wrong'); | |
}); | |
quizFeedback.classList.add('hidden'); | |
quizModal.classList.remove('hidden'); | |
} | |
function checkAnswer(selectedOption) { | |
if (!currentQuestion) return; | |
const selectedAnswer = parseInt(selectedOption.dataset.answer) - 1; | |
const isCorrect = selectedAnswer === currentQuestion.correct; | |
if (isCorrect) { | |
selectedOption.classList.add('correct'); | |
quizFeedback.textContent = "Correct! You remember your friend well."; | |
quizFeedback.classList.remove('hidden', 'text-red-500'); | |
quizFeedback.classList.add('text-green-600'); | |
correctAnswers++; | |
updateScoreDisplay(); | |
// After a delay, hide the quiz and resume game | |
setTimeout(() => { | |
quizModal.classList.add('hidden'); | |
quizActive = false; | |
gameRunning = true; | |
requestAnimationFrame(gameLoop); | |
}, 1500); | |
} else { | |
selectedOption.classList.add('wrong'); | |
quizOptions[currentQuestion.correct].classList.add('correct'); | |
quizFeedback.textContent = "Wrong! Try to remember better next time."; | |
quizFeedback.classList.remove('hidden', 'text-green-600'); | |
quizFeedback.classList.add('text-red-500'); | |
// After a delay, hide the quiz and resume game | |
setTimeout(() => { | |
quizModal.classList.add('hidden'); | |
quizActive = false; | |
gameRunning = true; | |
requestAnimationFrame(gameLoop); | |
}, 1500); | |
} | |
} | |
function gameOver(message) { | |
gameRunning = false; | |
gameOverMessage.textContent = message; | |
gameOverScreen.classList.remove('hidden'); | |
} | |
// Initialize with start screen visible | |
startScreen.classList.remove('hidden'); | |
</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=WebashalarForML/friend-quest" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |