Spaces:
Running
Running
| <html lang="pt-BR"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Jogo de Plinko</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 20px; | |
| color: white; | |
| } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 20px; | |
| width: 100%; | |
| } | |
| .header h1 { | |
| font-size: 2.5rem; | |
| margin-bottom: 10px; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
| } | |
| .built-with { | |
| margin-top: 10px; | |
| font-size: 0.9rem; | |
| opacity: 0.8; | |
| } | |
| .built-with a { | |
| color: #ffeb3b; | |
| text-decoration: none; | |
| transition: color 0.3s; | |
| } | |
| .built-with a:hover { | |
| color: #fff; | |
| text-decoration: underline; | |
| } | |
| .game-container { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 20px; | |
| max-width: 800px; | |
| width: 100%; | |
| } | |
| .stats-panel { | |
| background: rgba(255, 255, 255, 0.1); | |
| backdrop-filter: blur(10px); | |
| padding: 15px 25px; | |
| border-radius: 15px; | |
| display: flex; | |
| justify-content: space-between; | |
| width: 100%; | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.1); | |
| } | |
| .stat { | |
| text-align: center; | |
| } | |
| .stat-value { | |
| font-size: 1.8rem; | |
| font-weight: bold; | |
| color: #ffeb3b; | |
| } | |
| .stat-label { | |
| font-size: 0.9rem; | |
| opacity: 0.8; | |
| } | |
| .game-board { | |
| position: relative; | |
| width: 100%; | |
| height: 500px; | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 15px; | |
| overflow: hidden; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.2); | |
| } | |
| .peg-container { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| .peg { | |
| position: absolute; | |
| width: 12px; | |
| height: 12px; | |
| background: #ffeb3b; | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| box-shadow: 0 0 10px rgba(255, 235, 59, 0.5); | |
| } | |
| .ball { | |
| position: absolute; | |
| width: 20px; | |
| height: 20px; | |
| background: #ff4081; | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| box-shadow: 0 0 15px rgba(255, 64, 129, 0.7); | |
| z-index: 10; | |
| transition: transform 0.1s; | |
| } | |
| .slot-container { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 60px; | |
| display: flex; | |
| justify-content: space-around; | |
| padding: 0 10px; | |
| } | |
| .slot { | |
| flex: 1; | |
| height: 100%; | |
| margin: 0 5px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 10px 10px 0 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: bold; | |
| font-size: 1.2rem; | |
| color: #ffeb3b; | |
| transition: all 0.3s; | |
| } | |
| .slot.active { | |
| background: rgba(255, 235, 59, 0.3); | |
| transform: scale(1.05); | |
| } | |
| .controls { | |
| display: flex; | |
| gap: 15px; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| .btn { | |
| padding: 12px 25px; | |
| background: rgba(255, 255, 255, 0.2); | |
| border: none; | |
| border-radius: 25px; | |
| color: white; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| backdrop-filter: blur(10px); | |
| } | |
| .btn:hover { | |
| background: rgba(255, 255, 255, 0.3); | |
| transform: translateY(-2px); | |
| } | |
| .btn:active { | |
| transform: translateY(0); | |
| } | |
| .btn-primary { | |
| background: linear-gradient(45deg, #ff4081, #ff9800); | |
| } | |
| .btn-primary:hover { | |
| background: linear-gradient(45deg, #f50057, #ff5722); | |
| } | |
| .message { | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(0, 0, 0, 0.8); | |
| padding: 30px 50px; | |
| border-radius: 15px; | |
| text-align: center; | |
| z-index: 100; | |
| backdrop-filter: blur(10px); | |
| display: none; | |
| } | |
| .message h2 { | |
| margin-bottom: 15px; | |
| color: #ffeb3b; | |
| } | |
| @media (max-width: 768px) { | |
| .game-board { | |
| height: 400px; | |
| } | |
| .header h1 { | |
| font-size: 2rem; | |
| } | |
| .stat-value { | |
| font-size: 1.5rem; | |
| } | |
| .btn { | |
| padding: 10px 20px; | |
| font-size: 0.9rem; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .game-board { | |
| height: 350px; | |
| } | |
| .stats-panel { | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .controls { | |
| flex-direction: column; | |
| width: 100%; | |
| } | |
| .btn { | |
| width: 100%; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>🎯 Jogo de Plinko</h1> | |
| <p>Solte a bola e veja onde ela vai cair!</p> | |
| <div class="built-with"> | |
| Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a> | |
| </div> | |
| </div> | |
| <div class="game-container"> | |
| <div class="stats-panel"> | |
| <div class="stat"> | |
| <div class="stat-value" id="score">0</div> | |
| <div class="stat-label">Pontuação</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="stat-value" id="balls-left">10</div> | |
| <div class="stat-label">Bolas Restantes</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="stat-value" id="multiplier">1x</div> | |
| <div class="stat-label">Multiplicador</div> | |
| </div> | |
| </div> | |
| <div class="game-board" id="gameBoard"> | |
| <div class="peg-container" id="pegContainer"></div> | |
| <div class="slot-container" id="slotContainer"></div> | |
| </div> | |
| <div class="controls"> | |
| <button class="btn btn-primary" id="dropBall">Soltar Bola</button> | |
| <button class="btn" id="resetGame">Reiniciar Jogo</button> | |
| </div> | |
| </div> | |
| <div class="message" id="gameOverMessage"> | |
| <h2>Fim de Jogo!</h2> | |
| <p>Sua pontuação final: <span id="finalScore">0</span></p> | |
| <button class="btn btn-primary" id="playAgain">Jogar Novamente</button> | |
| </div> | |
| <script> | |
| class PlinkoGame { | |
| constructor() { | |
| this.gameBoard = document.getElementById('gameBoard'); | |
| this.pegContainer = document.getElementById('pegContainer'); | |
| this.slotContainer = document.getElementById('slotContainer'); | |
| this.scoreElement = document.getElementById('score'); | |
| this.ballsLeftElement = document.getElementById('balls-left'); | |
| this.multiplierElement = document.getElementById('multiplier'); | |
| this.dropBallBtn = document.getElementById('dropBall'); | |
| this.resetGameBtn = document.getElementById('resetGame'); | |
| this.gameOverMessage = document.getElementById('gameOverMessage'); | |
| this.finalScoreElement = document.getElementById('finalScore'); | |
| this.playAgainBtn = document.getElementById('playAgain'); | |
| this.score = 0; | |
| this.ballsLeft = 10; | |
| this.multiplier = 1; | |
| this.isDropping = false; | |
| this.balls = []; | |
| this.pegs = []; | |
| this.slots = []; | |
| this.init(); | |
| this.setupEventListeners(); | |
| } | |
| init() { | |
| this.createPegs(); | |
| this.createSlots(); | |
| this.updateUI(); | |
| } | |
| createPegs() { | |
| const rows = 10; | |
| const boardWidth = this.gameBoard.clientWidth; | |
| const boardHeight = this.gameBoard.clientHeight - 60; // Subtract slot height | |
| for (let row = 1; row <= rows; row++) { | |
| const pegsInRow = row + 5; | |
| const y = (row / (rows + 1)) * boardHeight + 50; | |
| for (let col = 0; col < pegsInRow; col++) { | |
| const x = (col + 0.5) / pegsInRow * boardWidth; | |
| const peg = document.createElement('div'); | |
| peg.className = 'peg'; | |
| peg.style.left = `${x}px`; | |
| peg.style.top = `${y}px`; | |
| this.pegContainer.appendChild(peg); | |
| this.pegs.push({ | |
| element: peg, | |
| x: x, | |
| y: y, | |
| radius: 6 | |
| }); | |
| } | |
| } | |
| } | |
| createSlots() { | |
| const slotCount = 7; | |
| const slotValues = [1, 2, 5, 10, 5, 2, 1]; | |
| for (let i = 0; i < slotCount; i++) { | |
| const slot = document.createElement('div'); | |
| slot.className = 'slot'; | |
| slot.textContent = slotValues[i]; | |
| slot.dataset.value = slotValues[i]; | |
| this.slotContainer.appendChild(slot); | |
| this.slots.push({ | |
| element: slot, | |
| value: slotValues[i], | |
| x: (i + 0.5) * (this.gameBoard.clientWidth / slotCount), | |
| width: this.gameBoard.clientWidth / slotCount | |
| }); | |
| } | |
| } | |
| dropBall() { | |
| if (this.isDropping || this.ballsLeft <= 0) return; | |
| this.isDropping = true; | |
| this.ballsLeft--; | |
| const startX = this.gameBoard.clientWidth / 2; | |
| const ball = document.createElement('div'); | |
| ball.className = 'ball'; | |
| ball.style.left = `${startX}px`; | |
| ball.style.top = '30px'; | |
| this.gameBoard.appendChild(ball); | |
| const ballObj = { | |
| element: ball, | |
| x: startX, | |
| y: 30, | |
| vx: (Math.random() - 0.5) * 2, | |
| vy: 0, | |
| radius: 10, | |
| active: true | |
| }; | |
| this.balls.push(ballObj); | |
| this.animateBall(ballObj); | |
| this.updateUI(); | |
| } | |
| animateBall(ball) { | |
| const gravity = 0.2; | |
| const friction = 0.99; | |
| const bounce = 0.7; | |
| const animate = () => { | |
| if (!ball.active) return; | |
| // Apply gravity | |
| ball.vy += gravity; | |
| // Update position | |
| ball.x += ball.vx; | |
| ball.y += ball.vy; | |
| // Check collisions with pegs | |
| this.checkPegCollisions(ball); | |
| // Check wall collisions | |
| if (ball.x <= ball.radius || ball.x >= this.gameBoard.clientWidth - ball.radius) { | |
| ball.vx *= -bounce; | |
| ball.x = Math.max(ball.radius, Math.min(this.gameBoard.clientWidth - ball.radius, ball.x)); | |
| } | |
| // Check if ball reached bottom | |
| if (ball.y >= this.gameBoard.clientHeight - 60 - ball.radius) { | |
| this.handleBallLanded(ball); | |
| return; | |
| } | |
| // Apply friction | |
| ball.vx *= friction; | |
| ball.vy *= friction; | |
| // Update ball position | |
| ball.element.style.left = `${ball.x}px`; | |
| ball.element.style.top = `${ball.y}px`; | |
| requestAnimationFrame(animate); | |
| }; | |
| animate(); | |
| } | |
| checkPegCollisions(ball) { | |
| for (const peg of this.pegs) { | |
| const dx = ball.x - peg.x; | |
| const dy = ball.y - peg.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance < ball.radius + peg.radius) { | |
| // Collision detected | |
| const angle = Math.atan2(dy, dx); | |
| const targetX = peg.x + Math.cos(angle) * (ball.radius + peg.radius); | |
| const targetY = peg.y + Math.sin(angle) * (ball.radius + peg.radius); | |
| const ax = (targetX - ball.x) * 0.1; | |
| const ay = (targetY - ball.y) * 0.1; | |
| ball.vx -= ax; | |
| ball.vy -= ay; | |
| // Add some random variation | |
| ball.vx += (Math.random() - 0.5) * 0.5; | |
| // Move ball outside peg | |
| ball.x = targetX; | |
| ball.y = targetY; | |
| } | |
| } | |
| } | |
| handleBallLanded(ball) { | |
| ball.active = false; | |
| this.isDropping = false; | |
| // Find which slot the ball landed in | |
| const slotIndex = this.findSlotForBall(ball.x); | |
| if (slotIndex !== -1) { | |
| const slotValue = this.slots[slotIndex].value; | |
| const points = slotValue * this.multiplier; | |
| this.score += points; | |
| this.multiplier = Math.min(this.multiplier + 0.2, 5); | |
| // Highlight the slot | |
| this.slots[slotIndex].element.classList.add('active'); | |
| setTimeout(() => { | |
| this.slots[slotIndex].element.classList.remove('active'); | |
| }, 1000); | |
| // Remove ball after a delay | |
| setTimeout(() => { | |
| ball.element.remove(); | |
| this.balls = this.balls.filter(b => b !== ball); | |
| }, 500); | |
| } | |
| this.updateUI(); | |
| // Check game over | |
| if (this.ballsLeft === 0 && this.balls.length === 0) { | |
| setTimeout(() => this.showGameOver(), 1000); | |
| } | |
| } | |
| findSlotForBall(x) { | |
| for (let i = 0; i < this.slots.length; i++) { | |
| const slot = this.slots[i]; | |
| const slotLeft = slot.x - slot.width / 2; | |
| const slotRight = slot.x + slot.width / 2; | |
| if (x >= slotLeft && x <= slotRight) { | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| updateUI() { | |
| this.scoreElement.textContent = this.score; | |
| this.ballsLeftElement.textContent = this.ballsLeft; | |
| this.multiplierElement.textContent = `${this.multiplier.toFixed(1)}x`; | |
| // Update button state | |
| this.dropBallBtn.disabled = this.isDropping || this.ballsLeft <= 0; | |
| } | |
| showGameOver() { | |
| this.finalScoreElement.textContent = this.score; | |
| this.gameOverMessage.style.display = 'block'; | |
| } | |
| resetGame() { | |
| // Remove all balls | |
| this.balls.forEach(ball => { | |
| ball.element.remove(); | |
| }); | |
| this.balls = []; | |
| this.score = 0; | |
| this.ballsLeft = 10; | |
| this.multiplier = 1; | |
| this.isDropping = false; | |
| this.gameOverMessage.style.display = 'none'; | |
| this.updateUI(); | |
| } | |
| setupEventListeners() { | |
| this.dropBallBtn.addEventListener('click', () => this.dropBall()); | |
| this.resetGameBtn.addEventListener('click', () => this.resetGame()); | |
| this.playAgainBtn.addEventListener('click', () => this.resetGame()); | |
| // Allow dropping ball by pressing spacebar | |
| document.addEventListener('keydown', (e) => { | |
| if (e.code === 'Space' && !this.isDropping && this.ballsLeft > 0) { | |
| e.preventDefault(); | |
| this.dropBall(); | |
| } | |
| }); | |
| } | |
| } | |
| // Initialize the game when the page loads | |
| document.addEventListener('DOMContentLoaded', () => { | |
| new PlinkoGame(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |