|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Super Mario Lite</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<style> |
|
|
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); |
|
|
|
|
|
body { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
overflow: hidden; |
|
|
font-family: 'Press Start 2P', cursive; |
|
|
background-color: #6be0fd; |
|
|
touch-action: manipulation; |
|
|
} |
|
|
|
|
|
#game-container { |
|
|
position: relative; |
|
|
width: 100vw; |
|
|
height: 100vh; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
#game-canvas { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
} |
|
|
|
|
|
#start-screen, #game-over-screen, #win-screen { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
background-color: rgba(0, 0, 0, 0.7); |
|
|
z-index: 10; |
|
|
} |
|
|
|
|
|
#game-over-screen, #win-screen { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.btn { |
|
|
background-color: #e52521; |
|
|
color: white; |
|
|
border: none; |
|
|
padding: 15px 30px; |
|
|
font-size: 16px; |
|
|
font-family: 'Press Start 2P', cursive; |
|
|
cursor: pointer; |
|
|
border-radius: 10px; |
|
|
box-shadow: 0 5px 0 #81261f; |
|
|
transition: all 0.1s; |
|
|
margin-top: 20px; |
|
|
letter-spacing: 1px; |
|
|
} |
|
|
|
|
|
.btn:active { |
|
|
transform: translateY(5px); |
|
|
box-shadow: 0 0 0 #81261f; |
|
|
} |
|
|
|
|
|
.title { |
|
|
color: white; |
|
|
font-size: 36px; |
|
|
text-align: center; |
|
|
text-shadow: 4px 4px 0 #e52521; |
|
|
margin-bottom: 30px; |
|
|
} |
|
|
|
|
|
.score-display { |
|
|
position: absolute; |
|
|
top: 20px; |
|
|
left: 20px; |
|
|
color: white; |
|
|
font-size: 18px; |
|
|
text-shadow: 2px 2px 0 #000; |
|
|
z-index: 5; |
|
|
} |
|
|
|
|
|
.time-display { |
|
|
position: absolute; |
|
|
top: 20px; |
|
|
right: 20px; |
|
|
color: white; |
|
|
font-size: 18px; |
|
|
text-shadow: 2px 2px 0 #000; |
|
|
z-index: 5; |
|
|
} |
|
|
|
|
|
.ground { |
|
|
position: absolute; |
|
|
bottom: 0; |
|
|
width: 300%; |
|
|
height: 60px; |
|
|
background-color: #56b949; |
|
|
z-index: 2; |
|
|
background-image: |
|
|
linear-gradient(transparent 95%, #3a7d33 95%), |
|
|
linear-gradient(90deg, transparent 50%, #3a7d33 50%); |
|
|
background-size: 20px 20px; |
|
|
} |
|
|
|
|
|
.brick { |
|
|
position: absolute; |
|
|
width: 40px; |
|
|
height: 40px; |
|
|
background-color: #b85a20; |
|
|
border-radius: 3px; |
|
|
border: 3px solid #8c3f10; |
|
|
box-shadow: inset -3px -3px 0 rgba(0,0,0,0.2); |
|
|
z-index: 2; |
|
|
} |
|
|
|
|
|
.coin { |
|
|
position: absolute; |
|
|
width: 20px; |
|
|
height: 20px; |
|
|
background-color: #f9d423; |
|
|
border-radius: 50%; |
|
|
border: 2px solid #e2b007; |
|
|
box-shadow: 0 0 5px gold; |
|
|
z-index: 2; |
|
|
animation: coinSpin 1.5s infinite linear; |
|
|
} |
|
|
|
|
|
@keyframes coinSpin { |
|
|
0% { transform: rotateY(0); } |
|
|
100% { transform: rotateY(360deg); } |
|
|
} |
|
|
|
|
|
.mario { |
|
|
position: absolute; |
|
|
width: 40px; |
|
|
height: 60px; |
|
|
background-color: #e52521; |
|
|
border-radius: 10px 10px 0 0; |
|
|
z-index: 3; |
|
|
transition: transform 0.1s; |
|
|
} |
|
|
|
|
|
.mario::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: -10px; |
|
|
left: 5px; |
|
|
width: 30px; |
|
|
height: 15px; |
|
|
background-color: #e52521; |
|
|
border-radius: 50% 50% 0 0; |
|
|
} |
|
|
|
|
|
.mario::after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: -5px; |
|
|
left: 10px; |
|
|
width: 20px; |
|
|
height: 10px; |
|
|
background-color: #f3b9b8; |
|
|
border-radius: 10px 10px 0 0; |
|
|
} |
|
|
|
|
|
.mario .cap { |
|
|
position: absolute; |
|
|
top: -15px; |
|
|
left: 5px; |
|
|
width: 30px; |
|
|
height: 10px; |
|
|
background-color: #1144ee; |
|
|
border-radius: 5px 0 0 5px; |
|
|
} |
|
|
|
|
|
.mario .cap::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: -5px; |
|
|
left: 0; |
|
|
width: 30px; |
|
|
height: 5px; |
|
|
background-color: #1144ee; |
|
|
border-radius: 5px 5px 0 0; |
|
|
} |
|
|
|
|
|
.mario .eyes { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 10px; |
|
|
width: 20px; |
|
|
height: 5px; |
|
|
} |
|
|
|
|
|
.mario .eyes::before, |
|
|
.mario .eyes::after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
width: 5px; |
|
|
height: 5px; |
|
|
background-color: #000; |
|
|
border-radius: 50%; |
|
|
} |
|
|
|
|
|
.mario .eyes::before { |
|
|
left: 0; |
|
|
} |
|
|
|
|
|
.mario .eyes::after { |
|
|
right: 0; |
|
|
} |
|
|
|
|
|
.mario .mustache { |
|
|
position: absolute; |
|
|
top: 5px; |
|
|
left: 5px; |
|
|
width: 30px; |
|
|
height: 5px; |
|
|
background-color: #4d2a1e; |
|
|
border-radius: 5px; |
|
|
} |
|
|
|
|
|
.mario .mustache::before, |
|
|
.mario .mustache::after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
width: 5px; |
|
|
height: 3px; |
|
|
background-color: #4d2a1e; |
|
|
border-radius: 50%; |
|
|
} |
|
|
|
|
|
.mario .mustache::before { |
|
|
left: 0; |
|
|
top: 2px; |
|
|
} |
|
|
|
|
|
.mario .mustache::after { |
|
|
right: 0; |
|
|
top: 2px; |
|
|
} |
|
|
|
|
|
.mario .torso { |
|
|
position: absolute; |
|
|
top: 20px; |
|
|
left: 5px; |
|
|
width: 30px; |
|
|
height: 25px; |
|
|
background-color: #e52521; |
|
|
} |
|
|
|
|
|
.mario .overalls { |
|
|
position: absolute; |
|
|
top: 25px; |
|
|
left: 0; |
|
|
width: 40px; |
|
|
height: 25px; |
|
|
background-color: #1144ee; |
|
|
border-radius: 0 0 5px 5px; |
|
|
} |
|
|
|
|
|
.mario .overalls::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 15px; |
|
|
width: 10px; |
|
|
height: 10px; |
|
|
background-color: #f3b9b8; |
|
|
border-radius: 2px; |
|
|
} |
|
|
|
|
|
.mario .buttons { |
|
|
position: absolute; |
|
|
top: 30px; |
|
|
left: 15px; |
|
|
width: 10px; |
|
|
height: 10px; |
|
|
} |
|
|
|
|
|
.mario .buttons::before, |
|
|
.mario .buttons::after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
width: 10px; |
|
|
height: 2px; |
|
|
background-color: gold; |
|
|
border-radius: 2px; |
|
|
} |
|
|
|
|
|
.mario .buttons::before { |
|
|
top: 0; |
|
|
} |
|
|
|
|
|
.mario .buttons::after { |
|
|
top: 5px; |
|
|
} |
|
|
|
|
|
.mario .arms { |
|
|
position: absolute; |
|
|
top: 25px; |
|
|
left: 0; |
|
|
width: 40px; |
|
|
height: 10px; |
|
|
} |
|
|
|
|
|
.mario .arms::before, |
|
|
.mario .arms::after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
width: 5px; |
|
|
height: 15px; |
|
|
background-color: #e52521; |
|
|
border-radius: 2px; |
|
|
} |
|
|
|
|
|
.mario .arms::before { |
|
|
left: 0; |
|
|
} |
|
|
|
|
|
.mario .arms::after { |
|
|
right: 0; |
|
|
} |
|
|
|
|
|
.mario .legs { |
|
|
position: absolute; |
|
|
bottom: 0; |
|
|
left: 5px; |
|
|
width: 30px; |
|
|
height: 15px; |
|
|
} |
|
|
|
|
|
.mario .legs::before, |
|
|
.mario .legs::after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
width: 10px; |
|
|
height: 15px; |
|
|
background-color: #1144ee; |
|
|
border-radius: 0 0 2px 2px; |
|
|
} |
|
|
|
|
|
.mario .legs::before { |
|
|
left: 0; |
|
|
} |
|
|
|
|
|
.mario .legs::after { |
|
|
right: 0; |
|
|
} |
|
|
|
|
|
.enemy { |
|
|
position: absolute; |
|
|
width: 40px; |
|
|
height: 40px; |
|
|
background-color: #6abe30; |
|
|
border-radius: 50%; |
|
|
z-index: 2; |
|
|
} |
|
|
|
|
|
.enemy::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 40px; |
|
|
height: 20px; |
|
|
background-color: #6abe30; |
|
|
border-radius: 20px 20px 0 0; |
|
|
} |
|
|
|
|
|
.enemy::after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: -3px; |
|
|
left: 5px; |
|
|
width: 30px; |
|
|
height: 15px; |
|
|
background-color: #8dce68; |
|
|
border-radius: 15px 15px 0 0; |
|
|
} |
|
|
|
|
|
.enemy .eyes { |
|
|
position: absolute; |
|
|
top: 5px; |
|
|
left: 10px; |
|
|
width: 20px; |
|
|
height: 5px; |
|
|
} |
|
|
|
|
|
.enemy .eyes::before, |
|
|
.enemy .eyes::after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
width: 5px; |
|
|
height: 5px; |
|
|
background-color: #000; |
|
|
border-radius: 50%; |
|
|
} |
|
|
|
|
|
.enemy .eyes::before { |
|
|
left: 0; |
|
|
} |
|
|
|
|
|
.enemy .eyes::after { |
|
|
right: 0; |
|
|
} |
|
|
|
|
|
.castle { |
|
|
position: absolute; |
|
|
width: 80px; |
|
|
height: 120px; |
|
|
background-color: #853a11; |
|
|
z-index: 2; |
|
|
border-radius: 10px 10px 0 0; |
|
|
} |
|
|
|
|
|
.castle::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: -20px; |
|
|
left: 10px; |
|
|
width: 60px; |
|
|
height: 20px; |
|
|
background-color: #853a11; |
|
|
clip-path: polygon(0% 100%, 10% 0%, 20% 100%, 30% 0%, 40% 100%, 50% 0%, 60% 100%, 70% 0%, 80% 100%, 90% 0%, 100% 100%); |
|
|
} |
|
|
|
|
|
.castle .door { |
|
|
position: absolute; |
|
|
bottom: 0; |
|
|
left: 20px; |
|
|
width: 40px; |
|
|
height: 60px; |
|
|
background-color: #5a2a0c; |
|
|
border-radius: 5px 5px 0 0; |
|
|
} |
|
|
|
|
|
.castle .flag-pole { |
|
|
position: absolute; |
|
|
top: -40px; |
|
|
left: 5px; |
|
|
width: 3px; |
|
|
height: 80px; |
|
|
background-color: #fff; |
|
|
} |
|
|
|
|
|
.castle .flag { |
|
|
position: absolute; |
|
|
top: -40px; |
|
|
left: 8px; |
|
|
width: 30px; |
|
|
height: 20px; |
|
|
background-color: #e52521; |
|
|
clip-path: polygon(0% 0%, 80% 0%, 100% 50%, 80% 100%, 0% 100%); |
|
|
} |
|
|
|
|
|
.pipe { |
|
|
position: absolute; |
|
|
width: 60px; |
|
|
height: 80px; |
|
|
background-color: #379937; |
|
|
z-index: 2; |
|
|
border-radius: 5px 5px 0 0; |
|
|
border: 4px solid #266926; |
|
|
} |
|
|
|
|
|
.cloud { |
|
|
position: absolute; |
|
|
background-color: white; |
|
|
border-radius: 50%; |
|
|
opacity: 0.9; |
|
|
z-index: 0; |
|
|
} |
|
|
|
|
|
@keyframes jump { |
|
|
0%, 100% { transform: translateY(0); } |
|
|
50% { transform: translateY(-20px); } |
|
|
} |
|
|
|
|
|
.jumping { |
|
|
animation: jump 0.5s ease; |
|
|
} |
|
|
|
|
|
.crouching { |
|
|
transform: scaleY(0.9) translateY(10px); |
|
|
} |
|
|
|
|
|
.sliding { |
|
|
transform: translateX(-10px); |
|
|
} |
|
|
|
|
|
.small { |
|
|
transform: scale(0.7); |
|
|
height: 40px !important; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div id="game-container"> |
|
|
<div class="score-display" id="score">SCORE: 0</div> |
|
|
<div class="time-display" id="time">TIME: 300</div> |
|
|
|
|
|
<div id="start-screen"> |
|
|
<h1 class="title">SUPER MARIO LITE</h1> |
|
|
<button class="btn" id="start-btn">START GAME</button> |
|
|
<div class="mt-8 text-white text-center text-sm"> |
|
|
<p>Arrow Keys to Move</p> |
|
|
<p>Space to Jump</p> |
|
|
<p>Collect Coins, Avoid Enemies</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="game-over-screen"> |
|
|
<h1 class="title">GAME OVER</h1> |
|
|
<div class="text-white text-center mb-6"> |
|
|
<p>SCORE: <span id="final-score">0</span></p> |
|
|
<p>HIGH SCORE: <span id="high-score">0</span></p> |
|
|
</div> |
|
|
<button class="btn" id="restart-btn">PLAY AGAIN</button> |
|
|
</div> |
|
|
|
|
|
<div id="win-screen"> |
|
|
<h1 class="title">YOU WIN!</h1> |
|
|
<div class="text-white text-center mb-6"> |
|
|
<p>SCORE: <span id="win-score">0</span></p> |
|
|
<p>HIGH SCORE: <span id="win-high-score">0</span></p> |
|
|
</div> |
|
|
<button class="btn" id="win-restart-btn">PLAY AGAIN</button> |
|
|
</div> |
|
|
|
|
|
<div class="ground"></div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
const gameContainer = document.getElementById('game-container'); |
|
|
const startScreen = document.getElementById('start-screen'); |
|
|
const gameOverScreen = document.getElementById('game-over-screen'); |
|
|
const winScreen = document.getElementById('win-screen'); |
|
|
const startBtn = document.getElementById('start-btn'); |
|
|
const restartBtn = document.getElementById('restart-btn'); |
|
|
const winRestartBtn = document.getElementById('win-restart-btn'); |
|
|
const scoreDisplay = document.getElementById('score'); |
|
|
const timeDisplay = document.getElementById('time'); |
|
|
const finalScoreDisplay = document.getElementById('final-score'); |
|
|
const highScoreDisplay = document.getElementById('high-score'); |
|
|
const winScoreDisplay = document.getElementById('win-score'); |
|
|
const winHighScoreDisplay = document.getElementById('win-high-score'); |
|
|
|
|
|
let gameRunning = false; |
|
|
let score = 0; |
|
|
let highScore = localStorage.getItem('marioHighScore') || 0; |
|
|
let time = 300; |
|
|
let mario; |
|
|
let ground; |
|
|
let enemies = []; |
|
|
let coins = []; |
|
|
let bricks = []; |
|
|
let pipes = []; |
|
|
let clouds = []; |
|
|
let castle = null; |
|
|
let gravity = 0.5; |
|
|
let jumpForce = -12; |
|
|
let marioVelocityY = 0; |
|
|
let marioPositionX = 100; |
|
|
let marioPositionY = 300; |
|
|
let marioDirection = 'right'; |
|
|
let isJumping = false; |
|
|
let isCrouching = false; |
|
|
let isSliding = false; |
|
|
let isInvincible = false; |
|
|
let gameSpeed = 3; |
|
|
let cameraOffset = 0; |
|
|
let lastTime = 0; |
|
|
let gameLoopId; |
|
|
let timeInterval; |
|
|
let isAlive = true; |
|
|
let levelComplete = false; |
|
|
|
|
|
highScoreDisplay.textContent = highScore; |
|
|
winHighScoreDisplay.textContent = highScore; |
|
|
|
|
|
|
|
|
function createMario() { |
|
|
mario = document.createElement('div'); |
|
|
mario.className = 'mario'; |
|
|
mario.innerHTML = ` |
|
|
<div class="cap"></div> |
|
|
<div class="eyes"></div> |
|
|
<div class="mustache"></div> |
|
|
<div class="torso"></div> |
|
|
<div class="overalls"></div> |
|
|
<div class="buttons"></div> |
|
|
<div class="arms"></div> |
|
|
<div class="legs"></div> |
|
|
`; |
|
|
mario.style.left = marioPositionX + 'px'; |
|
|
mario.style.bottom = marioPositionY + 'px'; |
|
|
gameContainer.appendChild(mario); |
|
|
} |
|
|
|
|
|
|
|
|
function createGround() { |
|
|
ground = document.querySelector('.ground'); |
|
|
} |
|
|
|
|
|
|
|
|
function createEnemy(x, y) { |
|
|
const enemy = document.createElement('div'); |
|
|
enemy.className = 'enemy'; |
|
|
enemy.innerHTML = `<div class="eyes"></div>`; |
|
|
enemy.style.left = x + 'px'; |
|
|
enemy.style.bottom = y + 'px'; |
|
|
gameContainer.appendChild(enemy); |
|
|
|
|
|
enemies.push({ |
|
|
element: enemy, |
|
|
x: x, |
|
|
y: y, |
|
|
direction: -1, |
|
|
speed: Math.random() * 1 + 1 |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function createCoin(x, y) { |
|
|
const coin = document.createElement('div'); |
|
|
coin.className = 'coin'; |
|
|
coin.style.left = x + 'px'; |
|
|
coin.style.bottom = y + 'px'; |
|
|
gameContainer.appendChild(coin); |
|
|
|
|
|
coins.push({ |
|
|
element: coin, |
|
|
x: x, |
|
|
y: y, |
|
|
collected: false |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function createBrick(x, y, hasCoin = false) { |
|
|
const brick = document.createElement('div'); |
|
|
brick.className = 'brick'; |
|
|
brick.style.left = x + 'px'; |
|
|
brick.style.bottom = y + 'px'; |
|
|
gameContainer.appendChild(brick); |
|
|
|
|
|
bricks.push({ |
|
|
element: brick, |
|
|
x: x, |
|
|
y: y, |
|
|
hasCoin: hasCoin, |
|
|
hit: false |
|
|
}); |
|
|
|
|
|
if (hasCoin) { |
|
|
createCoin(x + 10, y + 45); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function createPipe(x, y, height = 80) { |
|
|
const pipe = document.createElement('div'); |
|
|
pipe.className = 'pipe'; |
|
|
pipe.style.left = x + 'px'; |
|
|
pipe.style.bottom = y + 'px'; |
|
|
pipe.style.height = height + 'px'; |
|
|
gameContainer.appendChild(pipe); |
|
|
|
|
|
pipes.push({ |
|
|
element: pipe, |
|
|
x: x, |
|
|
y: y, |
|
|
height: height |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function createCloud(x, y, size) { |
|
|
const cloud = document.createElement('div'); |
|
|
cloud.className = 'cloud'; |
|
|
cloud.style.left = x + 'px'; |
|
|
cloud.style.bottom = y + 'px'; |
|
|
cloud.style.width = size + 'px'; |
|
|
cloud.style.height = size / 2 + 'px'; |
|
|
gameContainer.appendChild(cloud); |
|
|
|
|
|
clouds.push({ |
|
|
element: cloud, |
|
|
x: x, |
|
|
y: y, |
|
|
size: size |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function createCastle(x) { |
|
|
castle = document.createElement('div'); |
|
|
castle.className = 'castle'; |
|
|
castle.innerHTML = ` |
|
|
<div class="door"></div> |
|
|
<div class="flag-pole"></div> |
|
|
<div class="flag"></div> |
|
|
`; |
|
|
castle.style.left = x + 'px'; |
|
|
castle.style.bottom = '60px'; |
|
|
gameContainer.appendChild(castle); |
|
|
|
|
|
return { |
|
|
element: castle, |
|
|
x: x |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
function setupLevel() { |
|
|
|
|
|
const existingEnemies = document.querySelectorAll('.enemy'); |
|
|
const existingCoins = document.querySelectorAll('.coin'); |
|
|
const existingBricks = document.querySelectorAll('.brick'); |
|
|
const existingPipes = document.querySelectorAll('.pipe'); |
|
|
const existingClouds = document.querySelectorAll('.cloud'); |
|
|
const existingCastle = document.querySelector('.castle'); |
|
|
|
|
|
existingEnemies.forEach(enemy => enemy.remove()); |
|
|
existingCoins.forEach(coin => coin.remove()); |
|
|
existingBricks.forEach(brick => brick.remove()); |
|
|
existingPipes.forEach(pipe => pipe.remove()); |
|
|
existingClouds.forEach(cloud => cloud.remove()); |
|
|
if (existingCastle) existingCastle.remove(); |
|
|
|
|
|
|
|
|
enemies = []; |
|
|
coins = []; |
|
|
bricks = []; |
|
|
pipes = []; |
|
|
clouds = []; |
|
|
castle = null; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
createCloud(200, 400, 80); |
|
|
createCloud(500, 350, 100); |
|
|
createCloud(800, 450, 60); |
|
|
|
|
|
|
|
|
for (let i = 0; i < 5; i++) { |
|
|
createBrick(300 + i * 50, 100, i % 2 === 0); |
|
|
} |
|
|
|
|
|
|
|
|
createCoin(200, 150); |
|
|
createCoin(250, 200); |
|
|
createCoin(700, 150); |
|
|
|
|
|
|
|
|
createPipe(400, 60, 100); |
|
|
createPipe(600, 60, 80); |
|
|
|
|
|
|
|
|
createEnemy(500, 60); |
|
|
createEnemy(800, 60); |
|
|
|
|
|
|
|
|
castle = createCastle(1200); |
|
|
} |
|
|
|
|
|
|
|
|
function jump() { |
|
|
if (!isJumping && gameRunning && isAlive && !levelComplete) { |
|
|
isJumping = true; |
|
|
marioVelocityY = jumpForce; |
|
|
mario.classList.add('jumping'); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
mario.classList.remove('jumping'); |
|
|
}, 500); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function crouch() { |
|
|
if (!isJumping && gameRunning && isAlive && !levelComplete) { |
|
|
isCrouching = true; |
|
|
mario.classList.add('crouching'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function stopCrouch() { |
|
|
if (isCrouching) { |
|
|
isCrouching = false; |
|
|
mario.classList.remove('crouching'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function slide() { |
|
|
if (!isSliding && gameRunning && isAlive && !levelComplete) { |
|
|
isSliding = true; |
|
|
mario.classList.add('sliding'); |
|
|
|
|
|
setTimeout(() => { |
|
|
isSliding = false; |
|
|
mario.classList.remove('sliding'); |
|
|
}, 200); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function checkCollisions() { |
|
|
|
|
|
const marioLeft = marioPositionX + 5; |
|
|
const marioRight = marioPositionX + 35; |
|
|
const marioTop = marioPositionY + 50; |
|
|
const marioBottom = marioPositionY + 10; |
|
|
|
|
|
|
|
|
if (marioPositionY <= 60) { |
|
|
marioPositionY = 60; |
|
|
marioVelocityY = 0; |
|
|
isJumping = false; |
|
|
} |
|
|
|
|
|
|
|
|
for (const brick of bricks) { |
|
|
const brickLeft = brick.x; |
|
|
const brickRight = brick.x + 40; |
|
|
const brickTop = brick.y + 40; |
|
|
const brickBottom = brick.y; |
|
|
|
|
|
|
|
|
if (marioRight > brickLeft && |
|
|
marioLeft < brickRight && |
|
|
marioBottom < brickTop && |
|
|
marioTop > brickBottom && |
|
|
marioVelocityY > 0) { |
|
|
|
|
|
marioPositionY = brickBottom - 60; |
|
|
marioVelocityY = 0; |
|
|
isJumping = false; |
|
|
|
|
|
if (!brick.hit && brick.hasCoin) { |
|
|
|
|
|
score += 100; |
|
|
scoreDisplay.textContent = `SCORE: ${score}`; |
|
|
brick.hit = true; |
|
|
brick.element.classList.add('hit'); |
|
|
|
|
|
|
|
|
for (const coin of coins) { |
|
|
if (coin.x === brick.x + 10 && coin.y === brick.y + 45 && !coin.collected) { |
|
|
coin.collected = true; |
|
|
coin.element.style.display = 'none'; |
|
|
|
|
|
|
|
|
const effect = document.createElement('div'); |
|
|
effect.className = 'coin'; |
|
|
effect.style.left = (brick.x + 10) + 'px'; |
|
|
effect.style.bottom = (brick.y + 45) + 'px'; |
|
|
gameContainer.appendChild(effect); |
|
|
|
|
|
|
|
|
let effectY = brick.y + 45; |
|
|
let effectVelocity = -10; |
|
|
|
|
|
const animateCoin = () => { |
|
|
effectY += effectVelocity; |
|
|
effectVelocity += 0.5; |
|
|
effect.style.bottom = effectY + 'px'; |
|
|
|
|
|
if (effectY < brick.y + 45 - 100 || effectY > brick.y + 45) { |
|
|
effect.remove(); |
|
|
} else { |
|
|
requestAnimationFrame(animateCoin); |
|
|
} |
|
|
}; |
|
|
|
|
|
animateCoin(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (const pipe of pipes) { |
|
|
const pipeLeft = pipe.x; |
|
|
const pipeRight = pipe.x + 60; |
|
|
const pipeHeight = pipe.height; |
|
|
|
|
|
if (marioRight > pipeLeft && |
|
|
marioLeft < pipeRight && |
|
|
marioPositionY < pipeHeight) { |
|
|
|
|
|
|
|
|
if (marioDirection === 'right') { |
|
|
marioPositionX = pipeLeft - 40; |
|
|
} else { |
|
|
marioPositionX = pipeRight + 5; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (!isInvincible) { |
|
|
for (const enemy of enemies) { |
|
|
const enemyLeft = enemy.x; |
|
|
const enemyRight = enemy.x + 40; |
|
|
const enemyTop = enemy.y + 40; |
|
|
const enemyBottom = enemy.y; |
|
|
|
|
|
|
|
|
if (marioRight > enemyLeft && |
|
|
marioLeft < enemyRight && |
|
|
marioBottom < enemyTop && |
|
|
marioTop > enemyBottom) { |
|
|
|
|
|
|
|
|
if (marioVelocityY > 0 && marioBottom < enemyTop + 20) { |
|
|
enemy.element.remove(); |
|
|
enemies = enemies.filter(e => e !== enemy); |
|
|
score += 200; |
|
|
scoreDisplay.textContent = `SCORE: ${score}`; |
|
|
|
|
|
|
|
|
marioVelocityY = jumpForce / 2; |
|
|
isJumping = true; |
|
|
} else { |
|
|
|
|
|
getHurt(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (const coin of coins) { |
|
|
if (!coin.collected) { |
|
|
const coinLeft = coin.x; |
|
|
const coinRight = coin.x + 20; |
|
|
const coinTop = coin.y + 20; |
|
|
const coinBottom = coin.y; |
|
|
|
|
|
if (marioRight > coinLeft && |
|
|
marioLeft < coinRight && |
|
|
marioBottom < coinTop && |
|
|
marioTop > coinBottom) { |
|
|
|
|
|
coin.collected = true; |
|
|
coin.element.remove(); |
|
|
score += 100; |
|
|
scoreDisplay.textContent = `SCORE: ${score}`; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (castle) { |
|
|
const castleLeft = castle.x; |
|
|
const castleRight = castle.x + 80; |
|
|
|
|
|
if (marioRight > castleLeft && marioLeft < castleRight) { |
|
|
completeLevel(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function getHurt() { |
|
|
if (!isInvincible) { |
|
|
isInvincible = true; |
|
|
isAlive = false; |
|
|
|
|
|
|
|
|
marioVelocityY = -10; |
|
|
|
|
|
|
|
|
mario.classList.add('small'); |
|
|
|
|
|
setTimeout(() => { |
|
|
gameOver(); |
|
|
}, 1000); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function completeLevel() { |
|
|
levelComplete = true; |
|
|
gameRunning = false; |
|
|
|
|
|
|
|
|
clearInterval(timeInterval); |
|
|
cancelAnimationFrame(gameLoopId); |
|
|
|
|
|
|
|
|
const timeBonus = Math.floor(time / 10) * 50; |
|
|
score += timeBonus; |
|
|
|
|
|
|
|
|
if (score > highScore) { |
|
|
highScore = score; |
|
|
localStorage.setItem('marioHighScore', highScore); |
|
|
} |
|
|
|
|
|
winScoreDisplay.textContent = score; |
|
|
winHighScoreDisplay.textContent = highScore; |
|
|
|
|
|
|
|
|
winScreen.style.display = 'flex'; |
|
|
|
|
|
|
|
|
createFireworks(); |
|
|
} |
|
|
|
|
|
|
|
|
function createFireworks() { |
|
|
const colors = ['#ff0000', '#ff9900', '#ffff00', '#33cc33', '#3399ff', '#cc66ff']; |
|
|
|
|
|
for (let i = 0; i < 10; i++) { |
|
|
setTimeout(() => { |
|
|
const x = Math.random() * window.innerWidth; |
|
|
const y = Math.random() * window.innerHeight / 2; |
|
|
const color = colors[Math.floor(Math.random() * colors.length)]; |
|
|
|
|
|
const firework = document.createElement('div'); |
|
|
firework.style.position = 'absolute'; |
|
|
firework.style.left = x + 'px'; |
|
|
firework.style.top = y + 'px'; |
|
|
firework.style.width = '5px'; |
|
|
firework.style.height = '5px'; |
|
|
firework.style.backgroundColor = color; |
|
|
firework.style.borderRadius = '50%'; |
|
|
firework.style.zIndex = '5'; |
|
|
gameContainer.appendChild(firework); |
|
|
|
|
|
|
|
|
let particles = []; |
|
|
for (let j = 0; j < 20; j++) { |
|
|
const particle = document.createElement('div'); |
|
|
particle.style.position = 'absolute'; |
|
|
particle.style.left = x + 'px'; |
|
|
particle.style.top = y + 'px'; |
|
|
particle.style.width = '3px'; |
|
|
particle.style.height = '3px'; |
|
|
particle.style.backgroundColor = color; |
|
|
particle.style.borderRadius = '50%'; |
|
|
particle.style.zIndex = '5'; |
|
|
gameContainer.appendChild(particle); |
|
|
|
|
|
particles.push({ |
|
|
element: particle, |
|
|
velocityX: (Math.random() - 0.5) * 8, |
|
|
velocityY: (Math.random() - 0.5) * 8 |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const animateParticles = () => { |
|
|
let allOut = true; |
|
|
|
|
|
for (const particle of particles) { |
|
|
const rect = particle.element.getBoundingClientRect(); |
|
|
|
|
|
|
|
|
const currentLeft = parseFloat(particle.element.style.left); |
|
|
const currentTop = parseFloat(particle.element.style.top); |
|
|
|
|
|
particle.element.style.left = (currentLeft + particle.velocityX) + 'px'; |
|
|
particle.element.style.top = (currentTop + particle.velocityY) + 'px'; |
|
|
|
|
|
|
|
|
particle.velocityY += 0.2; |
|
|
|
|
|
|
|
|
const currentOpacity = parseFloat(particle.element.style.opacity || 1); |
|
|
particle.element.style.opacity = (currentOpacity - 0.03) + ''; |
|
|
|
|
|
|
|
|
if (currentOpacity > 0 && |
|
|
currentTop < window.innerHeight && |
|
|
currentLeft < window.innerWidth && |
|
|
currentLeft > 0) { |
|
|
allOut = false; |
|
|
} |
|
|
} |
|
|
|
|
|
if (!allOut) { |
|
|
requestAnimationFrame(animateParticles); |
|
|
} else { |
|
|
|
|
|
firework.remove(); |
|
|
for (const particle of particles) { |
|
|
particle.element.remove(); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
animateParticles(); |
|
|
|
|
|
}, i * 300); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function gameOver() { |
|
|
gameRunning = false; |
|
|
isAlive = false; |
|
|
|
|
|
|
|
|
clearInterval(timeInterval); |
|
|
cancelAnimationFrame(gameLoopId); |
|
|
|
|
|
|
|
|
if (score > highScore) { |
|
|
highScore = score; |
|
|
localStorage.setItem('marioHighScore', highScore); |
|
|
highScoreDisplay.textContent = highScore; |
|
|
} |
|
|
|
|
|
finalScoreDisplay.textContent = score; |
|
|
|
|
|
|
|
|
gameOverScreen.style.display = 'flex'; |
|
|
} |
|
|
|
|
|
|
|
|
function startTimer() { |
|
|
time = 300; |
|
|
timeDisplay.textContent = `TIME: ${time}`; |
|
|
|
|
|
timeInterval = setInterval(() => { |
|
|
if (gameRunning) { |
|
|
time--; |
|
|
timeDisplay.textContent = `TIME: ${time}`; |
|
|
|
|
|
if (time <= 0) { |
|
|
gameOver(); |
|
|
} |
|
|
} |
|
|
}, 1000); |
|
|
} |
|
|
|
|
|
|
|
|
function gameLoop(timestamp) { |
|
|
if (!gameRunning || !isAlive || levelComplete) return; |
|
|
|
|
|
|
|
|
const deltaTime = timestamp - lastTime; |
|
|
lastTime = timestamp; |
|
|
|
|
|
|
|
|
if (rightPressed && !levelComplete) { |
|
|
marioDirection = 'right'; |
|
|
marioPositionX += gameSpeed; |
|
|
|
|
|
|
|
|
if (marioPositionX > window.innerWidth / 3) { |
|
|
cameraOffset = marioPositionX - window.innerWidth / 3; |
|
|
} |
|
|
} else if (leftPressed && marioPositionX > 0 && !levelComplete) { |
|
|
marioDirection = 'left'; |
|
|
marioPositionX -= gameSpeed; |
|
|
|
|
|
|
|
|
if (marioPositionX < cameraOffset) { |
|
|
marioPositionX = cameraOffset; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
marioVelocityY += gravity; |
|
|
marioPositionY += marioVelocityY; |
|
|
|
|
|
|
|
|
mario.style.left = (marioPositionX - cameraOffset) + 'px'; |
|
|
mario.style.bottom = marioPositionY + 'px'; |
|
|
|
|
|
|
|
|
if (marioDirection === 'left') { |
|
|
mario.style.transform = 'scaleX(-1)'; |
|
|
} else { |
|
|
mario.style.transform = 'scaleX(1)'; |
|
|
} |
|
|
|
|
|
|
|
|
for (const enemy of enemies) { |
|
|
enemy.x += enemy.speed * enemy.direction; |
|
|
enemy.element.style.left = (enemy.x - cameraOffset) + 'px'; |
|
|
|
|
|
|
|
|
if (enemy.x - cameraOffset < 0 || enemy.x - cameraOffset > window.innerWidth - 40) { |
|
|
enemy.direction *= -1; |
|
|
} |
|
|
|
|
|
|
|
|
let hasGround = false; |
|
|
|
|
|
|
|
|
if (enemy.y === 60) { |
|
|
hasGround = true; |
|
|
} else { |
|
|
|
|
|
for (const brick of bricks) { |
|
|
const brickLeft = brick.x; |
|
|
const brickRight = brick.x + 40; |
|
|
const brickTop = brick.y + 40; |
|
|
|
|
|
if (enemy.x + 20 > brickLeft && |
|
|
enemy.x + 20 < brickRight && |
|
|
enemy.y + 40 >= brickTop && |
|
|
enemy.y + 40 <= brickTop + 20) { |
|
|
hasGround = true; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (!hasGround) { |
|
|
enemy.direction *= -1; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (const cloud of clouds) { |
|
|
cloud.x -= 0.5; |
|
|
cloud.element.style.left = (cloud.x - cameraOffset) + 'px'; |
|
|
|
|
|
|
|
|
if (cloud.x - cameraOffset < -cloud.size) { |
|
|
cloud.x = window.innerWidth + cameraOffset; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
ground.style.transform = `translateX(-${cameraOffset * 0.7}px)`; |
|
|
|
|
|
|
|
|
checkCollisions(); |
|
|
|
|
|
|
|
|
gameLoopId = requestAnimationFrame(gameLoop); |
|
|
} |
|
|
|
|
|
|
|
|
let rightPressed = false; |
|
|
let leftPressed = false; |
|
|
let downPressed = false; |
|
|
|
|
|
document.addEventListener('keydown', (e) => { |
|
|
if (!gameRunning) return; |
|
|
|
|
|
switch (e.key) { |
|
|
case 'ArrowRight': |
|
|
rightPressed = true; |
|
|
break; |
|
|
case 'ArrowLeft': |
|
|
leftPressed = true; |
|
|
break; |
|
|
case 'ArrowDown': |
|
|
downPressed = true; |
|
|
crouch(); |
|
|
break; |
|
|
case ' ': |
|
|
jump(); |
|
|
break; |
|
|
} |
|
|
}); |
|
|
|
|
|
document.addEventListener('keyup', (e) => { |
|
|
switch (e.key) { |
|
|
case 'ArrowRight': |
|
|
rightPressed = false; |
|
|
break; |
|
|
case 'ArrowLeft': |
|
|
leftPressed = false; |
|
|
break; |
|
|
case 'ArrowDown': |
|
|
downPressed = false; |
|
|
stopCrouch(); |
|
|
break; |
|
|
case ' ': |
|
|
|
|
|
break; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
let touchStartX = 0; |
|
|
|
|
|
document.addEventListener('touchstart', (e) => { |
|
|
if (!gameRunning) return; |
|
|
|
|
|
touchStartX = e.touches[0].clientX; |
|
|
|
|
|
|
|
|
if (e.touches[0].clientY > window.innerHeight / 2) { |
|
|
jump(); |
|
|
} |
|
|
}); |
|
|
|
|
|
document.addEventListener('touchmove', (e) => { |
|
|
if (!gameRunning) return; |
|
|
|
|
|
const touchX = e.touches[0].clientX; |
|
|
const diff = touchX - touchStartX; |
|
|
|
|
|
if (Math.abs(diff) > 10) { |
|
|
if (diff > 0) { |
|
|
|
|
|
rightPressed = true; |
|
|
leftPressed = false; |
|
|
} else { |
|
|
|
|
|
leftPressed = true; |
|
|
rightPressed = false; |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
document.addEventListener('touchend', () => { |
|
|
rightPressed = false; |
|
|
leftPressed = false; |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener('click', (e) => { |
|
|
if (!gameRunning && (e.target === startBtn || e.target === restartBtn || e.target === winRestartBtn)) { |
|
|
return; |
|
|
} |
|
|
|
|
|
if (gameRunning && isAlive && !levelComplete && e.clientY > window.innerHeight / 2) { |
|
|
jump(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
startBtn.addEventListener('click', () => { |
|
|
startScreen.style.display = 'none'; |
|
|
startGame(); |
|
|
}); |
|
|
|
|
|
restartBtn.addEventListener('click', startGame); |
|
|
winRestartBtn.addEventListener('click', startGame); |
|
|
|
|
|
|
|
|
function startGame() { |
|
|
|
|
|
gameRunning = true; |
|
|
isAlive = true; |
|
|
levelComplete = false; |
|
|
score = 0; |
|
|
time = 300; |
|
|
marioPositionX = 100; |
|
|
marioPositionY = 300; |
|
|
marioVelocityY = 0; |
|
|
cameraOffset = 0; |
|
|
rightPressed = false; |
|
|
leftPressed = false; |
|
|
downPressed = false; |
|
|
|
|
|
|
|
|
scoreDisplay.textContent = `SCORE: ${score}`; |
|
|
timeDisplay.textContent = `TIME: ${time}`; |
|
|
|
|
|
|
|
|
startScreen.style.display = 'none'; |
|
|
gameOverScreen.style.display = 'none'; |
|
|
winScreen.style.display = 'none'; |
|
|
|
|
|
|
|
|
const existingMario = document.querySelector('.mario'); |
|
|
if (existingMario) existingMario.remove(); |
|
|
|
|
|
|
|
|
createMario(); |
|
|
createGround(); |
|
|
setupLevel(); |
|
|
|
|
|
|
|
|
clearInterval(timeInterval); |
|
|
startTimer(); |
|
|
|
|
|
|
|
|
lastTime = performance.now(); |
|
|
gameLoopId = requestAnimationFrame(gameLoop); |
|
|
} |
|
|
|
|
|
|
|
|
startScreen.style.display = 'flex'; |
|
|
}); |
|
|
</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=Assad3l/alda" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> |
|
|
</html> |