Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Jeu des 3 Gobelets - Thimble Game</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"> | |
| <style> | |
| :root { | |
| --primary-color: #ff4757; | |
| --secondary-color: #2ed573; | |
| --bg-color: #2f3542; | |
| --table-color: #1e3728; /* Vert table de jeu */ | |
| --cup-color: #f1c40f; | |
| --cup-border: #d4ac0d; | |
| --text-color: #ffffff; | |
| --accent-color: #3742fa; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| body { | |
| background-color: var(--bg-color); | |
| color: var(--text-color); | |
| display: flex; | |
| flex-direction: column; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* En-tête */ | |
| header { | |
| background-color: rgba(0, 0, 0, 0.8); | |
| padding: 1rem 2rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.3); | |
| z-index: 100; | |
| } | |
| h1 { | |
| font-size: 1.5rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .anycoder-link { | |
| color: var(--secondary-color); | |
| text-decoration: none; | |
| font-weight: bold; | |
| font-size: 0.9rem; | |
| border: 1px solid var(--secondary-color); | |
| padding: 5px 10px; | |
| border-radius: 4px; | |
| transition: all 0.3s ease; | |
| } | |
| .anycoder-link:hover { | |
| background-color: var(--secondary-color); | |
| color: #fff; | |
| } | |
| /* Zone principale du jeu */ | |
| main { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| background: radial-gradient(circle at center, var(--table-color), #0f1f15); | |
| padding: 20px; | |
| position: relative; | |
| } | |
| /* Panneau de contrôle et score */ | |
| .game-info { | |
| background: rgba(0, 0, 0, 0.5); | |
| padding: 15px 30px; | |
| border-radius: 50px; | |
| display: flex; | |
| gap: 20px; | |
| margin-bottom: 40px; | |
| backdrop-filter: blur(5px); | |
| border: 1px solid rgba(255,255,255,0.1); | |
| } | |
| .score-box { | |
| font-size: 1.2rem; | |
| font-weight: bold; | |
| } | |
| .score-value { | |
| color: var(--secondary-color); | |
| } | |
| .status-message { | |
| color: #fff; | |
| font-weight: 500; | |
| text-align: center; | |
| min-width: 200px; | |
| } | |
| /* Table de jeu */ | |
| .game-table { | |
| position: relative; | |
| width: 100%; | |
| max-width: 800px; | |
| height: 300px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: flex-end; | |
| padding-bottom: 20px; | |
| } | |
| /* Les gobelets */ | |
| .cup-container { | |
| position: absolute; | |
| bottom: 0; | |
| width: 100px; | |
| height: 140px; | |
| cursor: pointer; | |
| transition: transform 0.4s ease-in-out; /* Animation fluide pour le mélange */ | |
| z-index: 10; | |
| /* Les positions seront définies par JS, mais voici une base */ | |
| left: 50%; | |
| transform: translateX(-50%); | |
| } | |
| .cup-body { | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(135deg, var(--cup-color), var(--cup-border)); | |
| border-radius: 5px 5px 40px 40px; | |
| position: relative; | |
| box-shadow: 0 10px 20px rgba(0,0,0,0.5); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| overflow: visible; | |
| } | |
| /* Effet de brillance/relief sur le gobelet */ | |
| .cup-body::after { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 10px; | |
| width: 20px; | |
| height: 90%; | |
| background: rgba(255,255,255,0.2); | |
| border-radius: 50px; | |
| } | |
| /* Intérieur du gobelet (pour masquer la boule) */ | |
| .cup-inside { | |
| position: absolute; | |
| top: 5px; | |
| left: 5px; | |
| right: 5px; | |
| height: 100%; | |
| background: #000; | |
| border-radius: 0 0 35px 35px; | |
| z-index: -1; | |
| opacity: 0.3; | |
| } | |
| /* La boule */ | |
| .ball { | |
| position: absolute; | |
| bottom: 10px; | |
| width: 40px; | |
| height: 40px; | |
| background: radial-gradient(circle at 30% 30%, #ff6b6b, #c0392b); | |
| border-radius: 50%; | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.5); | |
| z-index: 5; /* Derrière le gobelet si le gobelet est opaque, mais ici on gère l'affichage */ | |
| display: none; /* Cachée par défaut ou gérée par JS */ | |
| } | |
| /* Animation de soulèvement du gobelet */ | |
| .cup-container.lift .cup-body { | |
| transform: translateY(-80px); | |
| } | |
| /* Ombre au sol */ | |
| .shadow { | |
| position: absolute; | |
| bottom: -10px; | |
| left: 10%; | |
| width: 80%; | |
| height: 10px; | |
| background: rgba(0,0,0,0.4); | |
| border-radius: 50%; | |
| filter: blur(3px); | |
| transition: transform 0.4s; | |
| } | |
| .cup-container.lift .shadow { | |
| transform: scale(0.5); | |
| opacity: 0.2; | |
| } | |
| /* Boutons */ | |
| .btn { | |
| background-color: var(--accent-color); | |
| color: white; | |
| border: none; | |
| padding: 12px 30px; | |
| font-size: 1.1rem; | |
| border-radius: 30px; | |
| cursor: pointer; | |
| box-shadow: 0 4px 15px rgba(55, 66, 250, 0.4); | |
| transition: transform 0.2s, background-color 0.2s; | |
| margin-top: 50px; | |
| } | |
| .btn:hover { | |
| background-color: #5352ed; | |
| transform: translateY(-2px); | |
| } | |
| .btn:disabled { | |
| background-color: #747d8c; | |
| cursor: not-allowed; | |
| transform: none; | |
| box-shadow: none; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 600px) { | |
| .cup-container { | |
| width: 70px; | |
| height: 100px; | |
| } | |
| .game-table { | |
| height: 200px; | |
| } | |
| .game-info { | |
| flex-direction: column; | |
| gap: 10px; | |
| text-align: center; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1><i class="fas fa-dice"></i> Jeu des Gobelets</h1> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a> | |
| </header> | |
| <main> | |
| <div class="game-info"> | |
| <div class="status-message" id="statusMessage">Prêt à jouer ?</div> | |
| <div class="score-box">Score: <span id="scoreValue" class="score-value">0</span></div> | |
| </div> | |
| <div class="game-table" id="gameTable"> | |
| <!-- La boule sera positionnée absolument sous les gobelets --> | |
| <div class="ball" id="ball"></div> | |
| <!-- Gobelet 0 --> | |
| <div class="cup-container" id="cup0" onclick="handleCupClick(0)"> | |
| <div class="cup-body"> | |
| <div class="cup-inside"></div> | |
| </div> | |
| <div class="shadow"></div> | |
| </div> | |
| <!-- Gobelet 1 --> | |
| <div class="cup-container" id="cup1" onclick="handleCupClick(1)"> | |
| <div class="cup-body"> | |
| <div class="cup-inside"></div> | |
| </div> | |
| <div class="shadow"></div> | |
| </div> | |
| <!-- Gobelet 2 --> | |
| <div class="cup-container" id="cup2" onclick="handleCupClick(2)"> | |
| <div class="cup-body"> | |
| <div class="cup-inside"></div> | |
| </div> | |
| <div class="shadow"></div> | |
| </div> | |
| </div> | |
| <button class="btn" id="startBtn" onclick="startGame()">Commencer la partie</button> | |
| </main> | |
| <script> | |
| // Variables d'état du jeu | |
| let score = 0; | |
| let isShuffling = false; | |
| let canPick = false; | |
| let ballPosition = 1; // 0, 1 ou 2 (Centre par défaut) | |
| // Configuration des positions (en pourcentage de la largeur de la table) | |
| const positions = [15, 50, 85]; // Gauche, Centre, Droite | |
| // Éléments DOM | |
| const cups = [ | |
| document.getElementById('cup0'), | |
| document.getElementById('cup1'), | |
| document.getElementById('cup2') | |
| ]; | |
| const ball = document.getElementById('ball'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const statusMsg = document.getElementById('statusMessage'); | |
| const scoreDisplay = document.getElementById('scoreValue'); | |
| // Initialisation des positions visuelles | |
| function initPositions() { | |
| cups.forEach((cup, index) => { | |
| cup.style.left = `${positions[index]}%`; | |
| cup.style.transform = 'translateX(-50%)'; | |
| cup.classList.remove('lift'); | |
| }); | |
| updateBallPosition(); | |
| } | |
| // Met à jour la position de la boule pour qu'elle soit sous le bon gobelet | |
| function updateBallPosition() { | |
| // Trouver quel gobelet est actuellement à la position logique où se trouve la boule | |
| // Note: Dans notre logique, 'ballPosition' est l'index du gobelet qui cache la boule. | |
| // Mais visuellement, les gobelets bougent. | |
| // Simplification : On place la boule visuellement sous le gobelet qui a l'index 'ballPosition' | |
| // Comme les gobelets ont des ID fixes (cup0, cup1, cup2), on place la balle sous l'élément DOM correspondant. | |
| const targetCup = cups[ballPosition]; | |
| const cupRect = targetCup.getBoundingClientRect(); | |
| const tableRect = document.getElementById('gameTable').getBoundingClientRect(); | |
| // Calculer la position relative à la table | |
| // On centre la boule par rapport au gobelet | |
| const ballLeft = targetCup.offsetLeft + (targetCup.offsetWidth / 2) - 20; // 20 est la moitié de la largeur de la balle | |
| ball.style.left = `${ballLeft}px`; | |
| ball.style.bottom = '20px'; | |
| ball.style.display = 'block'; | |
| } | |
| // Fonction utilitaire pour attendre (sleep) | |
| function sleep(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| // Mélanger les gobelets (Algorithme d'échange) | |
| async function shuffleCups() { | |
| isShuffling = true; | |
| canPick = false; | |
| statusMsg.textContent = "Regardez bien..."; | |
| startBtn.disabled = true; | |
| // Nombre de swaps | |
| const swaps = 5 + Math.floor(Math.random() * 5); // 5 à 10 swaps | |
| let speed = 500; // Vitesse initiale en ms | |
| for (let i = 0; i < swaps; i++) { | |
| // Choisir deux gobelets au hasard à échanger | |
| let idx1 = Math.floor(Math.random() * 3); | |
| let idx2 = Math.floor(Math.random() * 3); | |
| while (idx1 === idx2) { | |
| idx2 = Math.floor(Math.random() * 3); | |
| } | |
| // Échanger les positions visuelles | |
| // On stocke les positions actuelles (left %) | |
| let pos1 = cups[idx1].style.left; | |
| let pos2 = cups[idx2].style.left; | |
| cups[idx1].style.left = pos2; | |
| cups[idx2].style.left = pos1; | |
| // Attendre un peu avant le prochain mouvement | |
| await sleep(speed); | |
| // Accélérer légèrement à chaque swap pour rendre difficile | |
| if (speed > 200) speed -= 30; | |
| } | |
| isShuffling = false; | |
| canPick = true; | |
| statusMsg.textContent = "Où est la boule ?"; | |
| } | |
| // Démarrer le jeu | |
| async function startGame() { | |
| // Révéler la boule au début | |
| ballPosition = Math.floor(Math.random() * 3); // Choisir un gobelet aléatoire | |
| // S'assurer que tout est remis à zéro | |
| initPositions(); | |
| // Lever le gobelet pour montrer la boule | |
| cups[ballPosition].classList.add('lift'); | |
| statusMsg.textContent = "Mémorisez la position..."; | |
| startBtn.disabled = true; | |
| await sleep(1500); // Attendre 1.5 seconde | |
| // Cacher la boule (baisser le gobelet) | |
| cups[ballPosition].classList.remove('lift'); | |
| await sleep(500); | |
| // Commencer le mélange | |
| await shuffleCups(); | |
| } | |
| // Gérer le clic sur un gobelet | |
| function handleCupClick(index) { | |
| if (!canPick || isShuffling) return; | |
| canPick = false; // Empêcher les clics multiples | |
| const clickedCup = cups[index]; | |
| // Lever le gobelet cliqué | |
| clickedCup.classList.add('lift'); | |
| if (index === ballPosition) { | |
| // Gagné | |
| score += 10; | |
| statusMsg.textContent = "Bravo ! Vous avez trouvé ! +10 points"; | |
| statusMsg.style.color = "var(--secondary-color)"; | |
| } else { | |
| // Perdu | |
| statusMsg.textContent = "Perdu ! La boule était ailleurs."; | |
| statusMsg.style.color = "var(--primary-color)"; | |
| // Montrer où était la vraie boule après un délai | |
| setTimeout(() => { | |
| cups[ballPosition].classList.add('lift'); | |
| }, 500); | |
| } | |
| // Mettre à jour le score | |
| scoreDisplay.textContent = score; | |
| startBtn.disabled = false; | |
| startBtn.textContent = "Rejouer"; | |
| } | |
| // Initialisation au chargement | |
| window.onload = () => { | |
| initPositions(); | |
| ball.style.display = 'none'; // Cacher la boule au tout début | |
| }; | |
| // Gestion du redimensionnement pour garder la boule alignée | |
| window.onresize = () => { | |
| if (!isShuffling) { | |
| updateBallPosition(); | |
| } | |
| }; | |
| </script> | |
| </body> | |
| </html> |