anycoder-3f389d81 / index.html
Mousco's picture
Upload folder using huggingface_hub
74a5ae4 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Naruto Shippuden - Duel Ninja</title>
<!-- Importation de FontAwesome pour les icônes -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Police Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Bangers&family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--naruto-orange: #ff5e00;
--naruto-yellow: #ffcc00;
--sasuke-blue: #2e3192;
--sasuke-purple: #662d91;
--bg-dark: #1a1a1a;
--ui-panel: rgba(0, 0, 0, 0.8);
--text-main: #ffffff;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
font-family: 'Roboto', sans-serif;
background-color: #050505;
color: var(--text-main);
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
background-image: radial-gradient(circle at center, #2a2a2a 0%, #000000 100%);
}
/* --- Header --- */
header {
height: 60px;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
z-index: 10;
backdrop-filter: blur(5px);
}
.logo {
font-family: 'Bangers', cursive;
font-size: 1.8rem;
letter-spacing: 2px;
color: var(--naruto-orange);
text-shadow: 2px 2px 0px #fff;
display: flex;
align-items: center;
gap: 10px;
}
.logo i {
color: var(--sasuke-blue);
}
.built-with {
font-size: 0.85rem;
color: #888;
text-decoration: none;
transition: color 0.3s;
}
.built-with:hover {
color: white;
}
/* --- Main Game Area --- */
main {
flex: 1;
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
#game-wrapper {
position: relative;
width: 100%;
height: 100%;
max-width: 1200px;
max-height: 800px;
background: #222;
box-shadow: 0 0 50px rgba(0, 0, 0, 0.5);
overflow: hidden;
border: 2px solid #333;
}
canvas {
display: block;
width: 100%;
height: 100%;
}
/* --- UI Overlay (HUD) --- */
#ui-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* Health Bars */
.hud-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
width: 100%;
}
.player-status {
width: 45%;
display: flex;
flex-direction: column;
gap: 5px;
}
.p1-status { align-items: flex-start; }
.p2-status { align-items: flex-end; }
.name-tag {
font-family: 'Bangers', cursive;
font-size: 1.5rem;
text-transform: uppercase;
margin-bottom: 5px;
}
.p1-name { color: var(--naruto-yellow); }
.p2-name { color: #aaddff; }
.bar-container {
width: 100%;
height: 25px;
background: #333;
border: 2px solid #555;
position: relative;
transform: skewX(-20deg);
}
.hp-bar {
height: 100%;
background: linear-gradient(90deg, #ff3333, #ff8888);
width: 100%;
transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.p2-hp { background: linear-gradient(90deg, #8888ff, #3333ff); float: right; }
.chakra-bar-container {
width: 80%;
height: 10px;
background: #222;
border: 1px solid #444;
margin-top: 5px;
position: relative;
transform: skewX(-20deg);
}
.chakra-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #00ffff, #0088ff);
transition: width 0.1s linear;
}
.p2-chakra { background: linear-gradient(90deg, #aa00ff, #ff00ff); float: right; }
/* Timer */
.timer-box {
background: rgba(0,0,0,0.7);
padding: 10px 20px;
border: 2px solid #555;
border-radius: 5px;
text-align: center;
}
.timer {
font-family: 'Bangers', cursive;
font-size: 2.5rem;
color: #fff;
}
/* Controls Hint */
.controls-hint {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 15px;
background: rgba(0, 0, 0, 0.7);
padding: 10px 20px;
border-radius: 30px;
border: 1px solid rgba(255,255,255,0.1);
}
.key-group {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.8rem;
color: #aaa;
}
.key-icon {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-weight: bold;
color: #fff;
font-size: 0.9rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.5);
}
.k-x { background: #e74c3c; } /* Jump */
.k-square { background: #3498db; } /* Light */
.k-triangle { background: #2ecc71; } /* Heavy */
.k-circle { background: #f1c40f; color: #333; } /* Jutsu */
/* Screens (Start / Game Over) */
.overlay-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 20;
backdrop-filter: blur(8px);
transition: opacity 0.3s;
}
.hidden { opacity: 0; pointer-events: none; }
.game-title {
font-family: 'Bangers', cursive;
font-size: 4rem;
color: transparent;
-webkit-text-stroke: 2px #fff;
margin-bottom: 20px;
text-transform: uppercase;
letter-spacing: 5px;
}
.game-title span { color: var(--naruto-orange); -webkit-text-stroke: 0; }
.btn-start {
padding: 15px 40px;
font-size: 1.5rem;
font-family: 'Bangers', cursive;
background: var(--naruto-orange);
color: white;
border: none;
cursor: pointer;
transform: skewX(-10deg);
transition: transform 0.2s, background 0.2s;
box-shadow: 0 0 20px rgba(255, 94, 0, 0.5);
}
.btn-start:hover {
transform: skewX(-10deg) scale(1.1);
background: #fff;
color: var(--naruto-orange);
}
/* Toast Messages */
#toast-container {
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
pointer-events: none;
}
.combo-text {
font-family: 'Bangers', cursive;
font-size: 2rem;
color: #fff;
text-shadow: 0 0 10px var(--naruto-orange);
animation: popUp 0.8s ease-out forwards;
}
@keyframes popUp {
0% { transform: scale(0.5); opacity: 0; }
50% { transform: scale(1.2); opacity: 1; }
100% { transform: scale(1.0) translateY(-20px); opacity: 0; }
}
/* Responsive */
@media (max-width: 768px) {
.controls-hint { display: none; } /* Hide hints on mobile */
.game-title { font-size: 2.5rem; }
}
</style>
</head>
<body>
<header>
<div class="logo">
<i class="fa-solid fa-bolt"></i>
<span>SHIPPUDEN <span>DUEL</span></span>
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">
Built with anycoder <i class="fa-solid fa-external-link-alt"></i>
</a>
</header>
<main>
<div id="game-wrapper">
<canvas id="gameCanvas"></canvas>
<!-- UI Layer -->
<div id="ui-layer">
<!-- HUD -->
<div class="hud-top">
<!-- Player 1 (Naruto) -->
<div class="player-status p1-status">
<div class="name-tag p1-name">Naruto</div>
<div class="bar-container">
<div class="hp-bar" id="p1-hp" style="width: 100%;"></div>
</div>
<div class="chakra-bar-container">
<div class="chakra-bar" id="p1-chakra"></div>
</div>
</div>
<!-- Timer -->
<div class="timer-box">
<div class="timer" id="game-timer">60</div>
</div>
<!-- Player 2 (Sasuke) -->
<div class="player-status p2-status">
<div class="name-tag p2-name">Sasuke (AI)</div>
<div class="bar-container">
<div class="hp-bar p2-hp" id="p2-hp" style="width: 100%;"></div>
</div>
<div class="chakra-bar-container">
<div class="chakra-bar p2-chakra" id="p2-chakra"></div>
</div>
</div>
</div>
<!-- Toasts -->
<div id="toast-container"></div>
<!-- Controls Hint -->
<div class="controls-hint">
<div class="key-group"><div class="key-icon k-x">X</div> Saut</div>
<div class="key-group"><div class="key-icon k-square"></div> Frappe</div>
<div class="key-group"><div class="key-icon k-triangle"></div> Lourd</div>
<div class="key-group"><div class="key-icon k-circle"></div> Jutsu</div>
</div>
</div>
<!-- Start Screen -->
<div id="start-screen" class="overlay-screen">
<h1 class="game-title">Ninja <span>Duel</span></h1>
<p style="margin-bottom: 30px; color: #ccc;">Utilisez Z, E, R et Espace</p>
<button class="btn-start" onclick="startGame()">COMBATTRE</button>
</div>
<!-- Game Over Screen -->
<div id="game-over-screen" class="overlay-screen hidden">
<h1 class="game-title" id="winner-text">VICTOIRE</h1>
<button class="btn-start" onclick="resetGame()">REJOUER</button>
</div>
</div>
</main>
<script>
// --- Configuration ---
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// Game Constants
const GRAVITY = 0.8;
const GROUND_Y = 550; // Ajusté dynamiquement selon la hauteur
const FRICTION = 0.8;
// Inputs Mapping
// X = Space (Jump)
// Square = Z (Light Hit)
// Triangle = E (Heavy Hit)
// Circle = R (Jutsu)
const KEYS = {
LEFT: 'ArrowLeft',
RIGHT: 'ArrowRight',
JUMP: ' ',
LIGHT: 'z', // Carré
HEAVY: 'e', // Triangle
JUTSU: 'r' // Rond
};
// --- Game State ---
let gameState = 'MENU'; // MENU, PLAYING, GAMEOVER
let animationId;
let lastTime = 0;
let timer = 60;
let timerInterval;
let cameraShake = 0;
// --- Classes ---
class Sprite {
constructor({ position, velocity, color, isPlayer, name }) {
this.position = position;
this.velocity = velocity;
this.width = 50;
this.height = 100;
this.color = color;
this.isPlayer = isPlayer;
this.name = name;
// Stats
this.hp = 100;
this.maxHp = 100;
this.chakra = 0;
this.maxChakra = 100;
this.isDead = false;
// Combat
this.isAttacking = false;
this.isBlocking = false;
this.attackCooldown = 0;
this.facingRight = isPlayer; // P1 regarde droite, P2 regarde gauche
this.hitbox = { x: 0, y: 0, width: 0, height: 0 };
}
draw() {
ctx.save();
// Flip if facing left
if (!this.facingRight) {
ctx.translate(this.position.x + this.width, this.position.y);
ctx.scale(-1, 1);
ctx.translate(-(this.position.x + this.width), -this.position.y);
}
// Body (Jumpsuit)
ctx.fillStyle = this.color;
ctx.fillRect(this.position.x, this.position.y, this.width, this.height);
// Head Band
ctx.fillStyle = '#333';
ctx.fillRect(this.position.x - 5, this.position.y + 10, this.width + 10, 10);
ctx.fillStyle = '#ccc'; // Metal plate
ctx.fillRect(this.position.x + 5, this.position.y + 12, 30, 6);
// Eyes (Simple)
ctx.fillStyle = this.isPlayer ? '#00ccff' : '#6600cc';
ctx.fillRect(this.position.x + 30, this.position.y + 25, 8, 5);
// Spiky Hair (Procedural)
ctx.fillStyle = this.isPlayer ? '#ffcc00' : '#000000';
ctx.beginPath();
ctx.moveTo(this.position.x - 5, this.position.y + 10);
ctx.lineTo(this.position.x + 10, this.position.y - 15);
ctx.lineTo(this.position.x + 25, this.position.y + 5);
ctx.lineTo(this.position.x + 40, this.position.y - 20);
ctx.lineTo(this.position.x + 55, this.position.y + 10);
ctx.fill();
// Attack Hitbox (Debug visual - optional, usually invisible)
if (this.isAttacking) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.fillRect(this.hitbox.x, this.hitbox.y, this.hitbox.width, this.hitbox.height);
}
ctx.restore();
}
update() {
this.draw();
if (!this.isDead) {
this.position.x += this.velocity.x;
this.position.y += this.velocity.y;
// Gravity
if (this.position.y + this.height + this.velocity.y >= canvas.height - 50) {
this.velocity.y = 0;
this.position.y = canvas.height - 50 - this.height;
} else {
this.velocity.y += GRAVITY;
}
// Boundaries
if (this.position.x < 0) this.position.x = 0;
if (this.position.x + this.width > canvas.width) this.position.x = canvas.width - this.width;
}
// Face opponent
if (this.isPlayer) {
this.facingRight = this.position.x < enemy.position.x;
} else {
this.facingRight = this.position.x > player.position.x;
}
// Cooldowns
if (this.attackCooldown > 0) this.attackCooldown--;
// Passive Chakra Regen
if (this.chakra < this.maxChakra) this.chakra += 0.05;
}
attack(type) {
if (this.attackCooldown > 0 || this.isDead) return;
this.isAttacking = true;
// Define Hitbox
const reach = type === 'heavy' ? 80 : 60;
const damage = type === 'heavy' ? 15 : 5;
const cooldown = type === 'heavy' ? 40 : 20;
this.hitbox = {
x: this.facingRight ? this.position.x + this.width : this.position.x - reach,
y: this.position.y + 20,
width: reach,
height: 40
};
this.attackCooldown = cooldown;
setTimeout(() => {
this.isAttacking = false;
}, 100);
return damage;
}
jutsu() {
if (this.chakra < 50 || this.attackCooldown > 0 || this.isDead) return false;
this.chakra -= 50;
this.attackCooldown = 60; // Long cooldown
// Create Projectile
const direction = this.facingRight ? 1 : -1;
const startX = this.facingRight ? this.position.x + this.width : this.position.x - 40;
const startY = this.position.y + 40;
const type = this.isPlayer ? 'rasengan' : 'chidori';
projectiles.push(new Projectile({
position: { x: startX, y: startY },
velocity: { x: 10 * direction, y: 0 },
owner: this,
type: type
}));
showFloatingText(type === 'rasengan' ? "RASENGAN!" : "CHIDORI!", this.position.x, this.position.y - 20, this.isPlayer ? '#00ffff' : '#aa00ff');
return true;
}
takeHit(damage) {
this.hp -= damage;
if (this.hp < 0) this.hp = 0;
// Knockback
const dir = this.isPlayer ? -1 : 1;
this.velocity.x = 10 * dir;
this.velocity.y = -5;
if (this.hp <= 0) {
this.isDead = true;
endGame(!this.isPlayer); // Opponent wins
}
}
}
class Projectile {
constructor({ position, velocity, owner, type }) {
this.position = position;
this.velocity = velocity;
this.owner = owner;
this.type = type;
this.radius = 25;
this.active = true;
}
draw() {
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, Math.PI * 2);
if (this.type === 'rasengan') {
ctx.fillStyle = '#00ccff';
ctx.shadowBlur = 20;
ctx.shadowColor = '#00ffff';
} else {
ctx.fillStyle = '#aa00ff';
ctx.shadowBlur = 20;
ctx.shadowColor = '#ff00ff';
}
ctx.fill();
ctx.shadowBlur = 0;
ctx.closePath();
}
update() {
this.draw();
this.position.x += this.velocity.x;
// Remove if off screen
if (this.position.x < 0 || this.position.x > canvas.width) {
this.active = false;
}
}
}
class Particle {
constructor({ position, velocity, color }) {
this.position = position;
this.velocity = velocity;
this.color = color;
this.alpha = 1;
}
draw() {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.fillStyle = this.color;
ctx.fillRect(this.position.x, this.position.y, 4, 4);
ctx.restore();
}
update() {
this.draw();
this.position.x += this.velocity.x;
this.position.y += this.velocity.y;
this.alpha -= 0.02;
}
}
// --- Instances ---
let player;
let enemy;
let projectiles = [];
let particles = [];
// --- Input Handling ---
const keys = {
ArrowRight: { pressed: false },
ArrowLeft: { pressed: false },
ArrowUp: { pressed: false }
};
window.addEventListener('keydown', (e) => {
if (gameState !== 'PLAYING') return;
switch (e.key.toLowerCase()) {
case KEYS.RIGHT: keys.ArrowRight.pressed = true; break;
case KEYS.LEFT: keys.ArrowLeft.pressed = true; break;
case KEYS.JUMP:
if (player.velocity.y === 0) player.velocity.y = -20;
break;
case KEYS.LIGHT: // Square
player.attack('light');
checkCollision(player, enemy, 5);
break;
case KEYS.HEAVY: // Triangle
player.attack('heavy');
checkCollision(player, enemy, 15);
break;
case KEYS.JUTSU: // Circle
player.jutsu();
break;
}
});
window.addEventListener('keyup', (e) => {
switch (e.key.toLowerCase()) {
case KEYS.RIGHT: keys.ArrowRight.pressed = false; break;
case KEYS.LEFT: keys.ArrowLeft.pressed = false; break;
}
});
// --- Game Logic ---
function initGame() {
resizeCanvas();
player = new Sprite({
position: { x: 100, y: 0 },
velocity: { x: 0, y: 0 },
color: '#ff5e00', // Naruto Orange
isPlayer: true,
name: 'Naruto'
});
enemy = new Sprite({
position: { x: canvas.width - 150, y: 0 },
velocity: { x: 0, y: 0 },
color: '#2e3192', // Sasuke Blue
isPlayer: false,
name: 'Sasuke'
});
projectiles = [];
particles = [];
timer = 60;
cameraShake = 0;
document.getElementById('p1-hp').style.width = '100%';
document.getElementById('p2-hp').style.width = '100%';
document.getElementById('game-timer').textContent = timer;
}
function checkCollision(attacker, receiver, damage) {
if (
attacker.hitbox.x < receiver.position.x + receiver.width &&
attacker.hitbox.x + attacker.hitbox.width > receiver.position.x &&
attacker.hitbox.y < receiver.position.y + receiver.height &&
attacker.hitbox.y + attacker.hitbox.height > receiver.position.y
) {
receiver.takeHit(damage);
createParticles(receiver.position.x + receiver.width/2, receiver.position.y + receiver.height/2, 10, '#fff');
startShake(5);
updateUI();
}
}
function createParticles(x, y, count, color) {
for (let i = 0; i < count; i++) {
particles.push(new Particle({
position: { x, y },
velocity: {
x: (Math.random() - 0.5) * 10,
y: (Math.random() - 0.5) * 10
},
color: color
}));
}
}
function startShake(intensity) {
cameraShake = intensity;
}
function updateUI() {
document.getElementById('p1-hp').style.width = player.hp + '%';
document.getElementById('p2-hp').style.width = enemy.hp + '%';
document.getElementById('p1-chakra').style.width = player.chakra + '%';
document.getElementById('p2-chakra').style.width = enemy.chakra + '%';
}
function showFloatingText(text, x, y, color) {
const toast = document.createElement('div');
toast.className = 'combo-text';
toast.textContent = text;
toast.style.color = color;
toast.style.left = x + 'px';
toast.style.top = y + 'px';
document.getElementById('toast-container').appendChild(toast);
setTimeout(() => toast.remove(), 1000);
}
// --- AI Logic (Simple) ---
function updateAI() {
if (enemy.isDead || gameState !== 'PLAYING') return;
const distance = Math.abs(player.position.x - enemy.position.x);
const action = Math.random();
// Movement
if (distance > 80) {
enemy.velocity.x = player.position.x < enemy.position.x ? -3 : 3;
} else {
enemy.velocity.x = 0;
}
// Jump randomly or if attacked
if (action < 0.01 && enemy.velocity.y === 0) enemy.velocity.y = -15;
// Attack
if (distance < 100) {
if (action < 0.05) {
enemy.attack('light');
checkCollision(enemy, player, 5);
} else if (action < 0.02) {
enemy.attack('heavy');
checkCollision(enemy, player, 15);
}
}
// Jutsu if far and chakra full
if (distance > 200 && enemy.chakra >= 50 && action < 0.02) {
enemy.jutsu();
}
}
// --- Main Loop ---
function animate() {
animationId = requestAnimationFrame(animate);
// Clear Screen
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw Background (Simple Floor)
ctx.fillStyle = '#111';
ctx.fillRect(0, canvas.height - 50, canvas.width, 50);
// Grid effect on floor
ctx.strokeStyle = '#333';
ctx.beginPath();
ctx.moveTo(0, canvas.height - 50);
ctx.lineTo(canvas.width, canvas.height - 50);
ctx.stroke();
// Camera Shake
if (cameraShake > 0) {
ctx.save();
const dx = (Math.random() - 0.5) * cameraShake;
const dy = (Math.random() - 0.5) * cameraShake;
ctx.translate(dx, dy);
cameraShake *= 0.9;
if (cameraShake < 0.5) cameraShake = 0;
}
// Player Movement
player.velocity.x = 0;
if (keys.ArrowRight.pressed) player.velocity.x = 5;
else if (keys.ArrowLeft.pressed) player.velocity.x = -5;
// Update Entities
player.update();
enemy.update();
updateAI();
// Projectiles
projectiles.forEach((proj, index) => {
proj.update();
// Hit detection
const target = proj.owner === player ? enemy : player;
if (
proj.position.x < target.position.x + target.width &&
proj.position.x + proj.radius > target.position.x &&
proj.position.y < target.position.y + target.height &&
proj.position.y + proj.radius > target.position.y
) {
target.takeHit(35);
proj.active = false;
createParticles(proj.position.x, proj.position.y, 15, proj.type === 'rasengan' ? '#00ffff' : '#ff00ff');
startShake(15);
updateUI();
}
if (!proj.active) projectiles.splice(index, 1);
});
// Particles
particles.forEach((part, index) => {
if (part.alpha <= 0) particles.splice(index, 1);
else part.update();
});
// Restore shake context
if (cameraShake > 0 || ctx.getTransform().e !== 0) ctx.restore();
}
// --- Game Flow Control ---
function startGame() {
document.getElementById('start-screen').classList.add('hidden');
document.getElementById('game-over-screen').classList.add('hidden');
gameState = 'PLAYING';
initGame();
clearInterval(timerInterval);
timerInterval = setInterval(() => {
if (gameState === 'PLAYING') {
timer--;
document.getElementById('game-timer').textContent = timer;
if (timer <= 0) {
endGame(player.hp > enemy.hp); // Who has more HP wins
}
}
}, 1000);
}
function endGame(playerWins) {
gameState = 'GAMEOVER';
clearInterval(timerInterval);
const winnerText = document.getElementById('winner-text');
if (playerWins) {
winnerText.innerHTML = '<span style="color:#ffcc00">NARUTO</span> GAGNE!';
} else {
winnerText.innerHTML = '<span style="color:#aaddff">SASUKE</span> GAGNE!';
}
document.getElementById('game-over-screen').classList.remove('hidden');
}
function resetGame() {
startGame();
}
// --- Utils ---
function resizeCanvas() {
const wrapper = document.getElementById('game-wrapper');
canvas.width = wrapper.clientWidth;
canvas.height = wrapper.clientHeight;
}
window.addEventListener('resize', resizeCanvas);
// Start Loop
resizeCanvas();
animate();
</script>
</body>
</html>