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> |