Spaces:
Running
Running
| <html lang="fa" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>بازی دوز طنز | Neon Edition</title> | |
| <!-- Importing Vazirmatn Font --> | |
| <link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css" rel="stylesheet" | |
| type="text/css" /> | |
| <!-- Importing Font Awesome for Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --bg-color: #05050a; | |
| --container-bg: #10101a; | |
| --accent-color: #2a2a40; | |
| --neon-blue: #00f3ff; | |
| --neon-pink: #ff00ff; | |
| --neon-purple: #bc13fe; | |
| --neon-green: #0aff0a; | |
| --neon-yellow: #ffff00; | |
| --text-color: #ffffff; | |
| --shadow-intensity: 0 0 20px; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Vazirmatn', sans-serif; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| body { | |
| background-color: var(--bg-color); | |
| background-image: | |
| radial-gradient(circle at 20% 30%, rgba(0, 243, 255, 0.15) 0%, transparent 40%), | |
| radial-gradient(circle at 80% 70%, rgba(255, 0, 255, 0.15) 0%, transparent 40%); | |
| color: var(--text-color); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 100vh; | |
| padding: 20px; | |
| overflow-x: hidden; | |
| position: relative; | |
| } | |
| /* Add some floating background particles for humor */ | |
| .floating-object { | |
| position: absolute; | |
| font-size: 2rem; | |
| animation: floatAround 10s infinite linear; | |
| opacity: 0.2; | |
| z-index: 0; | |
| } | |
| @keyframes floatAround { | |
| 0% { | |
| transform: translate(0, 0) rotate(0deg); | |
| } | |
| 25% { | |
| transform: translate(20px, -50px) rotate(90deg); | |
| } | |
| 50% { | |
| transform: translate(-20px, -100px) rotate(180deg); | |
| } | |
| 75% { | |
| transform: translate(-50px, -20px) rotate(270deg); | |
| } | |
| 100% { | |
| transform: translate(0, 0) rotate(360deg); | |
| } | |
| } | |
| /* Canvas for Confetti */ | |
| #confetti-canvas { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| pointer-events: none; | |
| z-index: 999; | |
| } | |
| /* Header Section */ | |
| header { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| width: 100%; | |
| max-width: 500px; | |
| position: relative; | |
| z-index: 10; | |
| } | |
| h1 { | |
| font-size: 3.5rem; | |
| margin-bottom: 10px; | |
| text-transform: uppercase; | |
| letter-spacing: 5px; | |
| background: linear-gradient(90deg, var(--neon-blue), var(--neon-purple), var(--neon-pink)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| animation: gradientShift 3s ease infinite; | |
| text-shadow: 0 0 30px rgba(188, 19, 254, 0.3); | |
| } | |
| @keyframes gradientShift { | |
| 0% { | |
| filter: hue-rotate(0deg); | |
| } | |
| 50% { | |
| filter: hue-rotate(45deg); | |
| } | |
| 100% { | |
| filter: hue-rotate(0deg); | |
| } | |
| } | |
| .subtitle { | |
| color: #a0a0a0; | |
| font-size: 1.2rem; | |
| letter-spacing: 1px; | |
| margin-bottom: 20px; | |
| } | |
| .anycoder-link { | |
| display: inline-block; | |
| margin-top: 15px; | |
| font-size: 0.9rem; | |
| color: var(--neon-blue); | |
| text-decoration: none; | |
| transition: all 0.3s; | |
| border: 1px solid var(--neon-blue); | |
| padding: 8px 20px; | |
| border-radius: 30px; | |
| background: rgba(0, 243, 255, 0.1); | |
| box-shadow: 0 0 10px rgba(0, 243, 255, 0.2); | |
| } | |
| .anycoder-link:hover { | |
| background: var(--neon-blue); | |
| color: #000; | |
| box-shadow: 0 0 20px var(--neon-blue); | |
| transform: translateY(-2px); | |
| } | |
| /* Score Board */ | |
| .scoreboard { | |
| display: flex; | |
| justify-content: space-between; | |
| width: 100%; | |
| max-width: 450px; | |
| background-color: var(--container-bg); | |
| padding: 20px; | |
| border-radius: 25px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5), inset 0 0 0 1px rgba(255, 255, 255, 0.1); | |
| margin-bottom: 40px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .scoreboard::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 4px; | |
| background: linear-gradient(90deg, var(--neon-blue), var(--neon-pink)); | |
| } | |
| .player-score { | |
| text-align: center; | |
| padding: 15px; | |
| border-radius: 15px; | |
| transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
| flex: 1; | |
| position: relative; | |
| cursor: default; | |
| /* Remove pointer */ | |
| } | |
| .player-score.active { | |
| background: rgba(255, 255, 255, 0.05); | |
| box-shadow: 0 0 25px rgba(0, 243, 255, 0.2); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .player-score.active::after { | |
| content: '-turn'; | |
| position: absolute; | |
| bottom: -25px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| font-size: 0.8rem; | |
| color: var(--neon-blue); | |
| font-weight: bold; | |
| letter-spacing: 2px; | |
| animation: bounceText 1s infinite alternate; | |
| } | |
| .player-score.o.active::after { | |
| color: var(--neon-pink); | |
| } | |
| @keyframes bounceText { | |
| from { | |
| transform: translateX(-50%) translateY(0); | |
| } | |
| to { | |
| transform: translateX(-50%) translateY(-5px); | |
| } | |
| } | |
| .player-name { | |
| font-size: 1.6rem; | |
| font-weight: 800; | |
| margin-bottom: 5px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| } | |
| .player-x { | |
| color: var(--neon-blue); | |
| text-shadow: 0 0 10px rgba(0, 243, 255, 0.5); | |
| } | |
| .player-o { | |
| color: var(--neon-pink); | |
| text-shadow: 0 0 10px rgba(255, 0, 255, 0.5); | |
| } | |
| .score-value { | |
| font-size: 3rem; | |
| font-weight: 900; | |
| line-height: 1; | |
| color: #fff; | |
| } | |
| /* Game Board Container - FIXED GRID */ | |
| .game-container { | |
| position: relative; | |
| background-color: var(--container-bg); | |
| padding: 25px; | |
| border-radius: 30px; | |
| box-shadow: | |
| 0 20px 50px rgba(0, 0, 0, 0.6), | |
| inset 0 0 0 2px rgba(255, 255, 255, 0.05); | |
| margin-bottom: 30px; | |
| perspective: 1000px; | |
| /* Ensure container doesn't shrink */ | |
| width: 100%; | |
| max-width: 400px; | |
| } | |
| .board { | |
| display: flex; | |
| flex-wrap: wrap; | |
| width: 100%; | |
| /* Force 3 items per row using flex-basis */ | |
| gap: 15px; | |
| } | |
| .cell { | |
| background-color: var(--bg-color); | |
| border-radius: 20px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 4.5rem; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| box-shadow: | |
| 5px 5px 10px rgba(0, 0, 0, 0.4), | |
| -2px -2px 5px rgba(255, 255, 255, 0.05); | |
| position: relative; | |
| overflow: hidden; | |
| /* CRITICAL FIX: Force cells to be equal squares */ | |
| width: 32%; /* Approximately 1/3rd of container */ | |
| min-width: 100px; /* Prevent shrinking on small screens */ | |
| min-height: 100px; | |
| aspect-ratio: 1 / 1; /* Ensures it stays a perfect square */ | |
| flex: 1 1 32%; | |
| } | |
| /* Sneaky Hover Effect - Cell tilts slightly to look at cursor but stays in place */ | |
| .cell:hover { | |
| background-color: #151525; | |
| box-shadow: | |
| 8px 8px 15px rgba(0, 0, 0, 0.5), | |
| inset 0 0 0 1px rgba(255, 255, 255, 0.1), | |
| 0 0 15px rgba(0, 243, 255, 0.2); | |
| /* Glow effect instead of movement */ | |
| } | |
| .cell.x { | |
| color: var(--neon-blue); | |
| text-shadow: 0 0 15px var(--neon-blue), 0 0 30px var(--neon-blue); | |
| animation: flyInLeft 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards; | |
| } | |
| .cell.o { | |
| color: var(--neon-pink); | |
| text-shadow: 0 0 15px var(--neon-pink), 0 0 30px var(--neon-pink); | |
| animation: flyInRight 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards; | |
| } | |
| /* Humorous Animations for Marks */ | |
| @keyframes flyInLeft { | |
| 0% { | |
| transform: translateX(-100px) rotate(-45deg) scale(0); | |
| opacity: 0; | |
| } | |
| 50% { | |
| transform: translateX(20px) rotate(20deg) scale(1.2); | |
| } | |
| 70% { | |
| transform: translateX(-10px) rotate(-10deg) scale(1); | |
| } | |
| 100% { | |
| transform: translateX(0) rotate(0deg) scale(1); | |
| opacity: 1; | |
| } | |
| } | |
| @keyframes flyInRight { | |
| 0% { | |
| transform: translateX(100px) rotate(45deg) scale(0); | |
| opacity: 0; | |
| } | |
| 50% { | |
| transform: translateX(-20px) rotate(-20deg) scale(1.2); | |
| } | |
| 70% { | |
| transform: translateX(10px) rotate(10deg) scale(1); | |
| } | |
| 100% { | |
| transform: translateX(0) rotate(0deg) scale(1); | |
| opacity: 1; | |
| } | |
| } | |
| /* Winning Animation - Violent Shake for humor */ | |
| .cell.winner { | |
| background-color: rgba(255, 255, 255, 0.1); | |
| animation: shakeWinner 0.5s infinite; | |
| z-index: 5; | |
| color: var(--neon-yellow) ; | |
| text-shadow: 0 0 20px var(--neon-yellow) ; | |
| } | |
| @keyframes shakeWinner { | |
| 0% { | |
| transform: translate(1px, 1px) rotate(0deg); | |
| } | |
| 10% { | |
| transform: translate(-1px, -2px) rotate(-1deg); | |
| } | |
| 20% { | |
| transform: translate(-3px, 0px) rotate(1deg); | |
| } | |
| 30% { | |
| transform: translate(3px, 2px) rotate(0deg); | |
| } | |
| 40% { | |
| transform: translate(1px, -1px) rotate(1deg); | |
| } | |
| 50% { | |
| transform: translate(-1px, 2px) rotate(-1deg); | |
| } | |
| 60% { | |
| transform: translate(-3px, 1px) rotate(0deg); | |
| } | |
| 70% { | |
| transform: translate(3px, 1px) rotate(-1deg); | |
| } | |
| 80% { | |
| transform: translate(-1px, -1px) rotate(1deg); | |
| } | |
| 90% { | |
| transform: translate(1px, 2px) rotate(0deg); | |
| } | |
| 100% { | |
| transform: translate(1px, -2px) rotate(-1deg); | |
| } | |
| } | |
| /* Laser Line Animation */ | |
| .laser-line { | |
| position: absolute; | |
| background-color: #fff; | |
| box-shadow: 0 0 10px #fff, 0 0 20px var(--neon-green), 0 0 40px var(--neon-green); | |
| opacity: 0; | |
| z-index: 4; | |
| transition: opacity 0.3s ease; | |
| height: 8px; | |
| border-radius: 4px; | |
| } | |
| /* Status and Controls */ | |
| .status-message { | |
| text-align: center; | |
| margin: 25px 0; | |
| font-size: 1.8rem; | |
| height: 50px; | |
| font-weight: bold; | |
| text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); | |
| transition: all 0.3s; | |
| } | |
| .status-message.gag-message { | |
| font-size: 2.5rem; | |
| color: var(--neon-yellow); | |
| animation: jumpText 0.5s ease; | |
| } | |
| @keyframes jumpText { | |
| 0% { | |
| transform: scale(1); | |
| } | |
| 50% { | |
| transform: scale(1.5) rotate(10deg); | |
| } | |
| 100% { | |
| transform: scale(1); | |
| } | |
| } | |
| .btn { | |
| background: linear-gradient(45deg, var(--neon-purple), var(--neon-blue)); | |
| color: white; | |
| border: none; | |
| padding: 15px 50px; | |
| font-size: 1.3rem; | |
| border-radius: 50px; | |
| cursor: pointer; | |
| transition: all 0.4s; | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| margin: 0 auto; | |
| box-shadow: 0 10px 20px rgba(188, 19, 254, 0.3); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .btn:hover { | |
| transform: translateY(-5px) scale(1.05); | |
| box-shadow: 0 15px 30px rgba(0, 243, 255, 0.4); | |
| } | |
| .btn:active { | |
| transform: translateY(2px) scale(0.98); | |
| } | |
| .btn::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: -100%; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); | |
| transition: 0.5s; | |
| } | |
| .btn:hover::before { | |
| left: 100%; | |
| } | |
| /* Modal for Game Over */ | |
| .modal { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(5, 5, 10, 0.9); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 100; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 0.4s ease; | |
| backdrop-filter: blur(8px); | |
| } | |
| .modal.show { | |
| opacity: 1; | |
| pointer-events: all; | |
| } | |
| .modal-content { | |
| background: linear-gradient(145deg, #1a1a2e, #16213e); | |
| padding: 50px; | |
| border-radius: 30px; | |
| text-align: center; | |
| border: 2px solid var(--neon-purple); | |
| box-shadow: 0 25px 50px rgba(0, 0, 0, 0.7), 0 0 50px rgba(188, 19, 254, 0.2); | |
| animation: modalPop 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
| max-width: 90%; | |
| width: 400px; | |
| position: relative; | |
| } | |
| @keyframes modalPop { | |
| from { | |
| transform: scale(0.5) rotate(-15deg); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: scale(1) rotate(0); | |
| opacity: 1; | |
| } | |
| } | |
| .winner-icon { | |
| font-size: 6rem; | |
| margin-bottom: 20px; | |
| display: block; | |
| animation: bounce 2s infinite; | |
| } | |
| @keyframes bounce { | |
| 0%, | |
| 20%, | |
| 50%, | |
| 80%, | |
| 100% { | |
| transform: translateY(0); | |
| } | |
| 40% { | |
| transform: translateY(-20px) rotate(10deg); | |
| } | |
| 60% { | |
| transform: translateY(-10px) rotate(-10deg); | |
| } | |
| } | |
| .winner-text { | |
| font-size: 3rem; | |
| margin-bottom: 40px; | |
| color: #fff; | |
| font-weight: 900; | |
| } | |
| .gag-text { | |
| font-size: 1.5rem; | |
| color: var(--neon-yellow); | |
| margin-bottom: 20px; | |
| display: block; | |
| font-weight: normal; | |
| } | |
| /* Responsive Design - Adjustments for Mobile */ | |
| @media (max-width: 450px) { | |
| .game-container { | |
| padding: 15px; | |
| width: 95%; | |
| } | |
| .board { | |
| gap: 10px; | |
| } | |
| .cell { | |
| font-size: 3rem; | |
| border-radius: 15px; | |
| min-width: 80px; | |
| min-height: 80px; | |
| } | |
| h1 { | |
| font-size: 2.5rem; | |
| } | |
| .scoreboard { | |
| flex-direction: column; | |
| gap: 15px; | |
| } | |
| .player-score { | |
| flex-direction: row; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .player-name { | |
| margin-bottom: 0; | |
| } | |
| .score-value { | |
| margin-right: 15px; | |
| } | |
| .anycoder-link { | |
| width: 100%; | |
| text-align: center; | |
| box-sizing: border-box; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Decorative floating objects --> | |
| <div class="floating-object" style="top: 10%; left: 10%; animation-duration: 15s;"><i class="fas fa-ghost"></i></div> | |
| <div class="floating-object" style="top: 80%; right: 10%; animation-duration: 12s;"><i class="fas fa-robot"></i></div> | |
| <div class="floating-object" style="top: 40%; right: 20%; animation-duration: 18s;"><i class="fas fa-rocket"></i> | |
| </div> | |
| <canvas id="confetti-canvas"></canvas> | |
| <header> | |
| <h1>بازی دوز طنز</h1> | |
| <div class="subtitle">نسخه با انیمیشنهای خندهدار</div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link"> | |
| <i class="fas fa-rocket"></i> Built with anycoder | |
| </a> | |
| </header> | |
| <div class="scoreboard"> | |
| <div class="player-score active" id="score-x-container"> | |
| <div class="player-name player-x"><i class="fas fa-times"></i> بازیکن X</div> | |
| <div class="score-value" id="score-x">0</div> | |
| </div> | |
| <div class="player-score" id="score-o-container"> | |
| <div class="player-name player-o"><i class="fas fa-circle"></i> بازیکن O</div> | |
| <div class="score-value" id="score-o">0</div> | |
| </div> | |
| </div> | |
| <div class="status-message" id="status-message"> | |
| <span class="player-x">نوبت بازیکن X است</span> | |
| </div> | |
| <div class="game-container"> | |
| <div class="board" id="board"> | |
| <div class="cell" data-index="0"></div> | |
| <div class="cell" data-index="1"></div> | |
| <div class="cell" data-index="2"></div> | |
| <div class="cell" data-index="3"></div> | |
| <div class="cell" data-index="4"></div> | |
| <div class="cell" data-index="5"></div> | |
| <div class="cell" data-index="6"></div> | |
| <div class="cell" data-index="7"></div> | |
| <div class="cell" data-index="8"></div> | |
| </div> | |
| </div> | |
| <button class="btn" id="restart-btn"> | |
| <i class="fas fa-redo"></i> شروع مجدد بازی | |
| </button> | |
| <!-- Game Over Modal --> | |
| <div class="modal" id="game-over-modal"> | |
| <div class="modal-content"> | |
| <span class="winner-icon" id="winner-icon"></span> | |
| <div class="winner-text" id="winner-text"></div> | |
| <div class="gag-text" id="winner-gag"></div> | |
| <button class="btn" id="modal-restart-btn"> | |
| <i class="fas fa-play"></i> بازی بعدی | |
| </button> | |
| </div> | |
| </div> | |
| <script> | |
| // Game State | |
| const state = { | |
| board: Array(9).fill(''), | |
| currentPlayer: 'X', | |
| gameActive: true, | |
| scores: { X: 0, O: 0 } | |
| }; | |
| // DOM Elements | |
| const boardElement = document.getElementById('board'); | |
| const cells = document.querySelectorAll('.cell'); | |
| const statusMessage = document.getElementById('status-message'); | |
| const scoreXElement = document.getElementById('score-x'); | |
| const scoreOElement = document.getElementById('score-o'); | |
| const scoreXContainer = document.getElementById('score-x-container'); | |
| const scoreOContainer = document.getElementById('score-o-container'); | |
| const restartBtn = document.getElementById('restart-btn'); | |
| const modal = document.getElementById('game-over-modal'); | |
| const modalRestartBtn = document.getElementById('modal-restart-btn'); | |
| const winnerTextElement = document.getElementById('winner-text'); | |
| const winnerIconElement = document.getElementById('winner-icon'); | |
| const winnerGagElement = document.getElementById('winner-gag'); | |
| const canvas = document.getElementById('confetti-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| // Winning Combinations | |
| const winningConditions = [ | |
| [0, 1, 2], [3, 4, 5], [6, 7, 8], // Rows | |
| [0, 3, 6], [1, 4, 7], [2, 5, 8], // Columns | |
| [0, 4, 8], [2, 4, 6] // Diagonals | |
| ]; | |
| // Humorous Gags for different scenarios | |
| const gagPhrases = [ | |
| "دقت کن! داری اشتباه میزنی!", | |
| "این خانه خالیه، دیوانه نیستی؟", | |
| "بازی دوز نیست که اینطوری بزنی!", | |
| "یه ذره دقت کن عزیزم!", | |
| "آفرین! دوباره همینو بزن!", | |
| "من دارم تماشا میکنم!" | |
| ]; | |
| // Initialize Canvas for Confetti | |
| let particles = []; | |
| function resizeCanvas() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| window.addEventListener('resize', resizeCanvas); | |
| resizeCanvas(); | |
| class Particle { | |
| constructor(x, y, color) { | |
| this.x = x; | |
| this.y = y; | |
| this.color = color; | |
| this.size = Math.random() * 7 + 3; | |
| this.speedX = Math.random() * 6 - 3; | |
| this.speedY = Math.random() * -6 - 3; | |
| this.gravity = 0.2; | |
| this.alpha = 1; | |
| this.rotation = Math.random() * 360; | |
| this.rotationSpeed = Math.random() * 10 - 5; | |
| this.emoji = ['😂', '😎', '🥳', '🤡', '👻'][Math.floor(Math.random() * 5)]; // Humorous emojis | |
| this.showEmoji = Math.random() > 0.5; | |
| } | |
| update() { | |
| this.x += this.speedX; | |
| this.y += this.speedY; | |
| this.speedY += this.gravity; | |
| this.alpha -= 0.015; | |
| this.rotation += this.rotationSpeed; | |
| this.size *= 0.96; | |
| } | |
| draw() { | |
| ctx.save(); | |
| ctx.translate(this.x, this.y); | |
| ctx.rotate(this.rotation * Math.PI / 180); | |
| ctx.globalAlpha = this.alpha; | |
| if (this.showEmoji) { | |
| ctx.font = `${this.size * 2}px Arial`; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'middle'; | |
| ctx.fillText(this.emoji, 0, 0); | |
| } else { | |
| ctx.fillStyle = this.color; | |
| ctx.fillRect(-this.size / 2, -this.size / 2, this.size, this.size); | |
| } | |
| ctx.restore(); | |
| } | |
| } | |
| function createConfetti() { | |
| particles = []; | |
| const colors = ['#00f3ff', '#ff00ff', '#bc13fe', '#0aff0a', '#ffffff']; | |
| for (let i = 0; i < 150; i++) { | |
| const x = window.innerWidth / 2; | |
| const y = window.innerHeight / 2; | |
| const color = colors[Math.floor(Math.random() * colors.length)]; | |
| particles.push(new Particle(x, y, color)); | |
| } | |
| animateConfetti(); | |
| } | |
| function animateConfetti() { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| particles.forEach((p, index) => { | |
| p.update(); | |
| p.draw(); | |
| if (p.alpha <= 0) { | |
| particles.splice(index, 1); | |
| } | |
| }); | |
| if (particles.length > 0) { | |
| requestAnimationFrame(animateConfetti); | |
| } | |
| } | |
| // Initialize Game | |
| function initGame() { | |
| state.board.fill(''); | |
| state.gameActive = true; | |
| state.currentPlayer = 'X'; | |
| updateStatusMessage(); | |
| updateTurnIndicator(); | |
| cells.forEach(cell => { | |
| cell.innerText = ''; | |
| cell.classList.remove('x', 'o', 'winner'); | |
| }); | |
| // Remove laser lines if any exist | |
| const existingLines = document.querySelectorAll('.laser-line'); | |
| existingLines.forEach(line => line.remove()); | |
| modal.classList.remove('show'); | |
| } | |
| // Handle Cell Click | |
| function handleCellClick(e) { | |
| const clickedCell = e.target; | |
| const clickedCellIndex = parseInt(clickedCell.getAttribute('data-index')); | |
| if (state.board[clickedCellIndex] !== '' || !state.gameActive) { | |
| return; | |
| } | |
| updateCell(clickedCell, clickedCellIndex); | |
| checkResult(); | |
| } | |
| // Update Cell UI and State | |
| function updateCell(cell, index) { | |
| state.board[index] = state.currentPlayer; | |
| cell.innerText = state.currentPlayer; | |
| cell.classList.add(state.currentPlayer.toLowerCase()); | |
| } | |
| // Switch Player | |
| function switchPlayer() { | |
| state.currentPlayer = state.currentPlayer === 'X' ? 'O' : 'X'; | |
| updateStatusMessage(); | |
| updateTurnIndicator(); | |
| } | |
| // Update Status Text | |
| function updateStatusMessage() { | |
| if (state.gameActive) { | |
| const playerClass = state.currentPlayer === 'X' ? 'player-x' : 'player-o'; | |
| statusMessage.innerHTML = `<span class="${playerClass}">نوبت بازیکن ${state.currentPlayer} است</span>`; | |
| } else { | |
| const winner = checkWinner(); | |
| if (winner) { | |
| const playerClass = winner === 'X' ? 'player-x' : 'player-o'; | |
| statusMessage.innerHTML = `<span class="${playerClass}">بازیکن ${winner} برنده شد!</span>`; | |
| } else { | |
| statusMessage.innerHTML = `<span style="color: #fff; font-weight: bold; text-shadow: 0 0 10px #fff;">بازی مساوی شد! چه سختی!</span>`; | |
| } | |
| } | |
| } | |
| // Update Scoreboard Active State | |
| function updateTurnIndicator() { | |
| if (state.currentPlayer === 'X') { | |
| scoreXContainer.classList.add('active'); | |
| scoreOContainer.classList.remove('active'); | |
| } else { | |
| scoreOContainer.classList.add('active'); | |
| scoreXContainer.classList.remove('active'); | |
| } | |
| } | |
| // Check Game Result | |
| function checkResult() { | |
| let roundWon = false; | |
| let winningLine = []; | |
| for (let i = 0; i < winningConditions.length; i++) { | |
| const [a, b, c] = winningConditions[i]; | |
| const valA = state.board[a]; | |
| const valB = state.board[b]; | |
| const valC = state.board[c]; | |
| if (valA === '' || valB === '' || valC === '') { | |
| continue; | |
| } | |
| if (valA === valB && valB === valC) { | |
| roundWon = true; | |
| winningLine = [a, b, c]; | |
| break; | |
| } | |
| } | |
| if (roundWon) { | |
| state.gameActive = false; | |
| highlightWinningCells(winningLine); | |
| updateScore(state.currentPlayer); | |
| showGameOverModal(state.currentPlayer); | |
| createConfetti(); | |
| return; | |
| } | |
| const roundDraw = !state.board.includes(''); | |
| if (roundDraw) { | |
| state.gameActive = false; | |
| updateStatusMessage(); | |
| showGameOverModal('draw'); | |
| return; | |
| } | |
| switchPlayer(); | |
| } | |
| // Highlight Winning Cells & Draw Laser Line | |
| function highlightWinningCells(indices) { | |
| indices.forEach(index => { | |
| cells[index].classList.add('winner'); | |
| }); | |
| // Calculate laser line position | |
| const [a, b, c] = indices; | |
| const cellA = cells[a]; | |
| const cellC = cells[c]; | |
| // Get coordinates relative to the board container | |
| const boardRect = boardElement.getBoundingClientRect(); | |
| const rectA = cellA.getBoundingClientRect(); | |
| const rectC = cellC.getBoundingClientRect(); | |
| const x1 = rectA.left + rectA.width / 2 - boardRect.left; | |
| const y1 = rectA.top + rectA.height / 2 - boardRect.top; | |
| const x2 = rectC.left + rectC.width / 2 - boardRect.left; | |
| const y2 = rectC.top + rectC.height / 2 - boardRect.top; | |
| const distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); | |
| const angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI; | |
| const laserLine = document.createElement('div'); | |
| laserLine.classList.add('laser-line'); | |
| laserLine.style.width = `${distance}px`; | |
| laserLine.style.height = '6px'; | |
| laserLine.style.left = `${x1}px`; | |
| laserLine.style.top = `${y1}px`; | |
| laserLine.style.transform = `rotate(${angle}deg)`; | |
| laserLine.style.transformOrigin = '0 50%'; | |
| // Add color based on winner | |
| const winnerColor = state.currentPlayer === 'X' ? 'var(--neon-blue)' : 'var(--neon-pink)'; | |
| laserLine.style.background = winnerColor; | |
| laserLine.style.boxShadow = `0 0 10px #fff, 0 0 20px ${winnerColor}, 0 0 40px ${winnerColor}`; | |
| boardElement.appendChild(laserLine); | |
| // Animate laser | |
| requestAnimationFrame(() => { | |
| laserLine.style.opacity = '1'; | |
| laserLine.style.transition = 'width 0.4s ease-out, opacity 0.3s ease'; | |
| // Force reflow | |
| laserLine.offsetWidth; | |
| laserLine.style.width = `${distance}px`; | |
| }); | |
| } | |
| // Update Score | |
| function updateScore(winner) { | |
| state.scores[winner]++; | |
| scoreXElement.innerText = state.scores.X; | |
| scoreOElement.innerText = state.scores.O; | |
| } | |
| // Show Game Over Modal with Humor | |
| function showGameOverModal(result) { | |
| setTimeout(() => { | |
| if (result === 'draw') { | |
| winnerTextElement.innerText = "بازی مساوی شد!"; | |
| winnerGagElement.innerText = "هیچکس برنده نشد... خسته شدید؟"; | |
| winnerIconElement.innerHTML = '<i class="fas fa-handshake" style="color: #fff;"></i>'; | |
| winnerTextElement.style.color = '#fff'; | |
| } else { | |
| const color = result === 'X' ? 'var(--neon-blue)' : 'var(--neon-pink)'; | |
| winnerTextElement.innerText = `بازیکن ${result} برنده شد!`; | |
| winnerTextElement.style.color = color; | |
| winnerIconElement.innerHTML = `<i class="fas fa-trophy" style="color: ${color};"></i>`; | |
| // Pick a random gag phrase | |
| const randomGag = gagPhrases[Math.floor(Math.random() * gagPhrases.length)]; | |
| winnerGagElement.innerText = randomGag; | |
| // Add a little jump animation to the text if it's a gag | |
| if (result === 'O') { | |
| winnerGagElement.style.animation = "jumpText 0.5s ease"; | |
| setTimeout(() => winnerGagElement.style.animation = "", 500); | |
| } | |
| } | |
| modal.classList.add('show'); | |
| }, 800); | |
| } | |
| // Event Listeners | |
| cells.forEach(cell => cell.addEventListener('click', handleCellClick)); | |
| restartBtn.addEventListener('click', initGame); | |
| modalRestartBtn.addEventListener('click', initGame); | |
| // Start the game | |
| initGame(); | |
| </script> | |
| </body> | |
| </html> |