| <!DOCTYPE html> |
| <html lang="zh-TW"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>數戰棋 - 互動式數學遊戲</title> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'PingFang TC', 'Heiti TC', 'Microsoft JhengHei', sans-serif; |
| background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%); |
| color: #f1f5f9; |
| min-height: 100vh; |
| padding: 20px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .container { |
| width: 100%; |
| max-width: 1200px; |
| background: rgba(30, 41, 59, 0.7); |
| backdrop-filter: blur(12px); |
| -webkit-backdrop-filter: blur(12px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| border-radius: 20px; |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); |
| overflow: hidden; |
| } |
| |
| .header { |
| background: linear-gradient(90deg, #10b981, #059669); |
| color: white; |
| padding: 24px; |
| text-align: center; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| .header h1 { |
| font-size: 2.6em; |
| margin-bottom: 8px; |
| text-shadow: 0 4px 6px rgba(0,0,0,0.2); |
| font-weight: 800; |
| letter-spacing: 2px; |
| } |
| |
| .header p { |
| font-size: 1.1em; |
| opacity: 0.9; |
| } |
| |
| .game-area { |
| display: flex; |
| gap: 24px; |
| padding: 24px; |
| flex-wrap: wrap; |
| } |
| |
| .board-container { |
| flex: 1; |
| min-width: 550px; |
| } |
| |
| .board { |
| display: grid; |
| grid-template-columns: repeat(10, 1fr); |
| gap: 4px; |
| background: #0f172a; |
| padding: 12px; |
| border-radius: 16px; |
| margin: 20px 0; |
| border: 2px solid rgba(255, 255, 255, 0.05); |
| box-shadow: inset 0 2px 8px rgba(0,0,0,0.5); |
| } |
| |
| .cell { |
| aspect-ratio: 1; |
| background: #1e293b; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-weight: bold; |
| font-size: 1.1em; |
| cursor: pointer; |
| position: relative; |
| transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); |
| border-radius: 8px; |
| color: #64748b; |
| user-select: none; |
| } |
| |
| .cell:hover { |
| background: #334155; |
| color: #f1f5f9; |
| transform: scale(1.05); |
| z-index: 10; |
| } |
| |
| .cell.white-territory { |
| background: rgba(33, 150, 243, 0.1); |
| border: 1px solid rgba(33, 150, 243, 0.3); |
| color: #60a5fa; |
| } |
| |
| .cell.black-territory { |
| background: rgba(233, 30, 99, 0.1); |
| border: 1px solid rgba(233, 30, 99, 0.3); |
| color: #f472b6; |
| } |
| |
| .cell.possible-move { |
| background: rgba(16, 185, 129, 0.25) !important; |
| border: 2px solid #10b981; |
| color: #34d399; |
| animation: pulse 1.2s infinite; |
| } |
| |
| @keyframes pulse { |
| 0 { transform: scale(1); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); } |
| 70% { transform: scale(1.03); box-shadow: 0 0 0 8px rgba(16, 185, 129, 0); } |
| 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); } |
| } |
| |
| .piece { |
| width: 85%; |
| height: 85%; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-weight: 800; |
| color: white; |
| text-shadow: 0 2px 4px rgba(0,0,0,0.5); |
| cursor: pointer; |
| transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); |
| position: relative; |
| z-index: 5; |
| box-shadow: 0 4px 6px rgba(0,0,0,0.3); |
| } |
| |
| .piece.white { |
| background: linear-gradient(135deg, #3b82f6, #1d4ed8); |
| border: 2px solid #60a5fa; |
| } |
| |
| .piece.black { |
| background: linear-gradient(135deg, #ec4899, #be185d); |
| border: 2px solid #f472b6; |
| } |
| |
| .piece.selected { |
| transform: scale(1.15); |
| box-shadow: 0 0 15px #f59e0b; |
| border-color: #f59e0b; |
| animation: selectionGlow 1s infinite alternate; |
| } |
| |
| @keyframes selectionGlow { |
| from { box-shadow: 0 0 10px rgba(245, 158, 11, 0.5); } |
| to { box-shadow: 0 0 20px rgba(245, 158, 11, 0.9); } |
| } |
| |
| .piece.white.upgraded { |
| background: linear-gradient(135deg, #fbbf24, #1d4ed8); |
| border: 2px solid #fef08a; |
| box-shadow: 0 0 15px rgba(59, 130, 246, 0.8); |
| } |
| |
| .piece.black.upgraded { |
| background: linear-gradient(135deg, #fbbf24, #be185d); |
| border: 2px solid #fef08a; |
| box-shadow: 0 0 15px rgba(236, 72, 153, 0.8); |
| } |
| |
| .piece.upgraded::after { |
| content: '★'; |
| position: absolute; |
| top: -6px; |
| right: -6px; |
| font-size: 14px; |
| color: #fef08a; |
| text-shadow: 0 1px 2px rgba(0,0,0,0.8); |
| } |
| |
| .controls { |
| flex: 0 0 320px; |
| padding: 24px; |
| background: rgba(15, 23, 42, 0.4); |
| border-radius: 16px; |
| display: flex; |
| flex-direction: column; |
| gap: 16px; |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| } |
| |
| .game-info { |
| background: rgba(30, 41, 59, 0.6); |
| padding: 16px; |
| border-radius: 12px; |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| } |
| |
| .game-info h3 { |
| color: #34d399; |
| margin-bottom: 6px; |
| font-size: 1.1em; |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| } |
| |
| .current-player { |
| font-size: 1.1em; |
| font-weight: bold; |
| text-align: center; |
| padding: 12px; |
| border-radius: 10px; |
| transition: all 0.3s ease; |
| letter-spacing: 1px; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.15); |
| } |
| |
| .current-player.white { |
| background: rgba(59, 130, 246, 0.15); |
| color: #60a5fa; |
| border: 1px solid rgba(59, 130, 246, 0.3); |
| } |
| |
| .current-player.black { |
| background: rgba(236, 72, 153, 0.15); |
| color: #f472b6; |
| border: 1px solid rgba(236, 72, 153, 0.3); |
| } |
| |
| .button { |
| background: linear-gradient(135deg, #10b981 0%, #059669 100%); |
| color: white; |
| border: none; |
| padding: 12px 20px; |
| border-radius: 12px; |
| cursor: pointer; |
| font-size: 1em; |
| font-weight: bold; |
| transition: all 0.2s ease; |
| width: 100%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 8px; |
| box-shadow: 0 4px 6px rgba(0,0,0,0.15); |
| } |
| |
| .button:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 6px 12px rgba(16, 185, 129, 0.3); |
| filter: brightness(1.1); |
| } |
| |
| .button:active { |
| transform: translateY(0); |
| } |
| |
| .button:disabled { |
| background: #475569; |
| color: #94a3b8; |
| cursor: not-allowed; |
| transform: none; |
| box-shadow: none; |
| filter: none; |
| } |
| |
| .button.secondary { |
| background: linear-gradient(135deg, #475569 0%, #334155 100%); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| .button.secondary:hover { |
| box-shadow: 0 6px 12px rgba(0,0,0,0.2); |
| } |
| |
| .instructions { |
| background: rgba(30, 41, 59, 0.6); |
| padding: 16px; |
| border-radius: 12px; |
| font-size: 0.9em; |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| } |
| |
| .instructions h3 { |
| color: #38bdf8; |
| margin-bottom: 10px; |
| font-size: 1.1em; |
| } |
| |
| .instructions ul { |
| list-style-type: none; |
| padding: 0; |
| display: flex; |
| flex-direction: column; |
| gap: 8px; |
| } |
| |
| .instructions li { |
| padding-bottom: 6px; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.05); |
| line-height: 1.4; |
| color: #cbd5e1; |
| } |
| |
| .instructions li:last-child { |
| border-bottom: none; |
| padding-bottom: 0; |
| } |
| |
| |
| .modal { |
| position: fixed; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%) scale(0.9); |
| background: #1e293b; |
| border: 1px solid rgba(255, 255, 255, 0.15); |
| padding: 32px; |
| border-radius: 20px; |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7); |
| text-align: center; |
| z-index: 1000; |
| display: none; |
| width: 95%; |
| max-width: 600px; |
| color: #f1f5f9; |
| transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); |
| } |
| |
| .modal.show { |
| display: block; |
| transform: translate(-50%, -50%) scale(1); |
| } |
| |
| .modal h2 { |
| font-size: 2em; |
| color: #10b981; |
| margin-bottom: 16px; |
| font-weight: 800; |
| } |
| |
| .modal-body { |
| text-align: left; |
| background: #0f172a; |
| padding: 20px; |
| border-radius: 12px; |
| margin-bottom: 24px; |
| max-height: 220px; |
| overflow-y: auto; |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| } |
| |
| .modal-body h4 { |
| color: #38bdf8; |
| margin-bottom: 8px; |
| } |
| |
| .modal-body p { |
| margin-bottom: 12px; |
| font-size: 0.95em; |
| line-height: 1.5; |
| color: #cbd5e1; |
| } |
| |
| .mode-selection { |
| display: grid; |
| grid-template-columns: repeat(2, 1fr); |
| gap: 16px; |
| } |
| |
| .mode-btn { |
| padding: 16px 12px; |
| border-radius: 12px; |
| border: 2px solid rgba(255, 255, 255, 0.1); |
| background: #1e293b; |
| color: white; |
| cursor: pointer; |
| font-weight: bold; |
| font-size: 1.05em; |
| transition: all 0.2s ease; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .mode-btn:hover { |
| border-color: #10b981; |
| background: rgba(16, 185, 129, 0.1); |
| transform: translateY(-4px); |
| box-shadow: 0 4px 12px rgba(0,0,0,0.3); |
| } |
| |
| .mode-btn .icon { |
| font-size: 2em; |
| } |
| |
| |
| .ai-selection-list { |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| margin-bottom: 20px; |
| text-align: left; |
| } |
| |
| .ai-card { |
| background: rgba(15, 23, 42, 0.6); |
| border: 1px solid rgba(255, 255, 255, 0.08); |
| border-radius: 12px; |
| padding: 14px 18px; |
| cursor: pointer; |
| transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); |
| display: flex; |
| flex-direction: column; |
| gap: 6px; |
| } |
| |
| .ai-card:hover { |
| transform: translateY(-2px); |
| background: rgba(30, 41, 59, 0.8); |
| box-shadow: 0 10px 20px -10px rgba(0, 0, 0, 0.5); |
| } |
| |
| .ai-card.easy { |
| border-left: 5px solid #10b981; |
| } |
| .ai-card.easy:hover { |
| border-color: #10b981; |
| box-shadow: 0 8px 16px -8px rgba(16, 185, 129, 0.3); |
| } |
| |
| .ai-card.medium { |
| border-left: 5px solid #f59e0b; |
| } |
| .ai-card.medium:hover { |
| border-color: #f59e0b; |
| box-shadow: 0 8px 16px -8px rgba(245, 158, 11, 0.3); |
| } |
| |
| .ai-card.hard { |
| border-left: 5px solid #ef4444; |
| } |
| .ai-card.hard:hover { |
| border-color: #ef4444; |
| box-shadow: 0 8px 16px -8px rgba(239, 68, 68, 0.3); |
| } |
| |
| .ai-card-title { |
| font-size: 1.1em; |
| font-weight: 800; |
| color: #f8fafc; |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| } |
| |
| .ai-card-desc { |
| font-size: 0.9em; |
| color: #94a3b8; |
| line-height: 1.4; |
| } |
| |
| .ai-card-desc b, .ai-card-strategy b { |
| color: #cbd5e1; |
| } |
| |
| .ai-card-strategy { |
| font-size: 0.85em; |
| color: #64748b; |
| border-top: 1px solid rgba(255, 255, 255, 0.05); |
| padding-top: 6px; |
| margin-top: 2px; |
| } |
| |
| .overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: rgba(15, 23, 42, 0.8); |
| backdrop-filter: blur(8px); |
| -webkit-backdrop-filter: blur(8px); |
| z-index: 999; |
| display: none; |
| } |
| |
| .overlay.show { |
| display: block; |
| } |
| |
| .score-board { |
| display: flex; |
| justify-content: space-between; |
| margin-bottom: 15px; |
| gap: 16px; |
| } |
| |
| .score { |
| flex: 1; |
| text-align: center; |
| padding: 12px; |
| border-radius: 10px; |
| font-weight: bold; |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); |
| } |
| |
| .score.white { |
| background: rgba(59, 130, 246, 0.15); |
| color: #60a5fa; |
| border: 1px solid rgba(59, 130, 246, 0.3); |
| } |
| |
| .score.black { |
| background: rgba(236, 72, 153, 0.15); |
| color: #f472b6; |
| border: 1px solid rgba(236, 72, 153, 0.3); |
| } |
| |
| .score-value { |
| font-size: 1.8em; |
| font-weight: 800; |
| margin-top: 4px; |
| } |
| |
| |
| .toast-container { |
| position: fixed; |
| top: 24px; |
| right: 24px; |
| z-index: 2000; |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| max-width: 350px; |
| } |
| |
| .toast { |
| background: #1e293b; |
| color: #f1f5f9; |
| padding: 16px 20px; |
| border-radius: 12px; |
| border-left: 4px solid #10b981; |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3); |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| transform: translateX(120%); |
| transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); |
| font-weight: 500; |
| } |
| |
| .toast.show { |
| transform: translateX(0); |
| } |
| |
| .toast.info { border-left-color: #3b82f6; } |
| .toast.warning { border-left-color: #f59e0b; } |
| .toast.error { border-left-color: #ef4444; } |
| |
| @media (max-width: 900px) { |
| .game-area { |
| flex-direction: column; |
| } |
| |
| .board-container { |
| min-width: auto; |
| } |
| |
| .cell { |
| font-size: 0.9em; |
| } |
| |
| .piece { |
| font-size: 0.85em; |
| } |
| } |
| |
| @media (max-width: 480px) { |
| body { |
| padding: 8px; |
| } |
| .header h1 { |
| font-size: 1.8em; |
| } |
| .game-area { |
| padding: 12px; |
| } |
| .controls { |
| padding: 16px; |
| } |
| .mode-selection { |
| grid-template-columns: 1fr; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <div class="header"> |
| <h1>🎲 數戰棋 🎲</h1> |
| <p>透過整除規則學習因數與倍數 - <span id="gameModeText">模式載入中</span></p> |
| </div> |
| |
| <div class="game-area"> |
| <div class="board-container"> |
| <div class="score-board"> |
| <div class="score white"> |
| <div>藍方棋子 (玩家)</div> |
| <div class="score-value" id="whiteScore">10</div> |
| </div> |
| <div class="score black"> |
| <div id="blackTitle">紅方棋子 (對手)</div> |
| <div class="score-value" id="blackScore">10</div> |
| </div> |
| </div> |
| |
| <div class="board" id="gameBoard"> |
| |
| </div> |
| |
| <div class="current-player" id="currentPlayer"> |
| 藍方回合 - 選擇要移動的棋子 |
| </div> |
| </div> |
| |
| <div class="controls"> |
| <div class="game-info"> |
| <h3>🎯 遊戲目標</h3> |
| <p>將所有對手的棋子吃掉,或是讓對手無路可走即獲勝!</p> |
| </div> |
| |
| <div class="instructions"> |
| <h3>📋 遊戲規則簡介</h3> |
| <ul> |
| <li>🔵 藍方向下前進,🔴 紅方向上前進。</li> |
| <li>📐 普通棋子只能移動到能被「棋子自身數字」整除的相鄰列格子。</li> |
| <li>🍽️ 可以吃掉目標格子上的敵方棋子,<b>但不能重疊在自己同色棋子之上</b>。</li> |
| <li>⭐ 抵達對手底線領地升級為 <b>★ 棋子</b>,自此可任意往上或往下移動,且不受整除限制!</li> |
| </ul> |
| </div> |
| |
| <button class="button" onclick="resetGame()">🔄 重新開始</button> |
| <button class="button secondary" onclick="switchSides()">🔀 交換執棋</button> |
| <button class="button secondary" onclick="showHint()">💡 顯示移動提示</button> |
| <button class="button secondary" onclick="showWelcomeModal()">⚙️ 切換遊戲模式</button> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="overlay" id="overlay"></div> |
| |
| <div class="modal" id="welcomeModal"> |
| <h2>🎲 歡迎來到「數戰棋」</h2> |
| <div class="modal-body"> |
| <h4>💡 遊戲玩法大解析:</h4> |
| <p>1. <b>移動限制</b>:藍棋只能向下、紅棋只能向上(皆每次移動 1 列)。</p> |
| <p>2. <b>整除原理</b>:想前往的格子編號,必須要能變為你的棋子數字的「倍數」。例如:3 號棋子只能走 12、15、18 等格子。</p> |
| <p>3. <b>吃子限制</b>:若目標格有敵棋即可吃掉。<b>但請注意,您不能踩到自己人的棋子上喔!</b></p> |
| <p>4. <b>升級神將 (★)</b>:當棋子殺到對手底線時會升級,升級後突破禁忌,可自由前進或後退 1 列,並不再受整除限制!</p> |
| </div> |
| |
| |
| <div id="mainModeSelect"> |
| <p style="margin-bottom: 16px; font-weight: bold; color: #cbd5e1;">請選擇您的遊玩模式:</p> |
| <div class="mode-selection" style="grid-template-columns: repeat(2, 1fr);"> |
| <button class="mode-btn" onclick="selectMode('pvp')"> |
| <span class="icon">👥</span> |
| <span>雙人本地對戰</span> |
| </button> |
| <button class="mode-btn" onclick="showAiDifficultySelect()"> |
| <span class="icon">🤖</span> |
| <span>與 AI 對戰</span> |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="aiDifficultySelect" style="display: none;"> |
| <p style="margin-bottom: 16px; font-weight: bold; color: #cbd5e1;">請點選要對抗的 AI 角色:</p> |
| <div class="ai-selection-list"> |
| |
| <div class="ai-card easy" onclick="selectMode('ai_easy')"> |
| <div class="ai-card-title">🟢 萌新嘟嘟 (簡單)</div> |
| <div class="ai-card-desc"><b>個性:</b>活潑好奇、粗心大意。剛學會九九乘法表,常常看錯整除方向或把棋子白白送給你吃,最喜歡盲目亂衝!</div> |
| <div class="ai-card-strategy"><b>策略:</b>60% 機率完全憑感覺隨機亂走;只有 40% 機率會稍微考慮往前或吃子,防守意識幾乎為零。</div> |
| </div> |
| |
| <div class="ai-card medium" onclick="selectMode('ai_medium')"> |
| <div class="ai-card-title">🟡 管家阿麥 (中等)</div> |
| <div class="ai-card-desc"><b>個性:</b>謹慎規矩、有條不紊。計算能力良好,知道要保護自己,但偶爾也會看漏眼犯點小迷糊。</div> |
| <div class="ai-card-strategy"><b>策略:</b>攻守平衡。會主動避開明顯的致命陷阱。思考時會加入些許隨機性,下棋風格非常接近一般人類玩家。</div> |
| </div> |
| |
| <div class="ai-card hard" onclick="selectMode('ai_hard')"> |
| <div class="ai-card-title">🔴 戰神諸葛墨 (困難)</div> |
| <div class="ai-card-desc"><b>個性:</b>冷靜沉著、算無遺策。擁有極致的數學思維與大局觀,會精準預判你的攻勢,壓迫感十足!</div> |
| <div class="ai-card-strategy"><b>策略:</b>精確獵殺與防守。絕不走入危險格子,一旦發現破綻便毫不留情吃子;升級後的「★」會主動展開極速獵殺與包夾。</div> |
| </div> |
| </div> |
| <button class="button secondary" onclick="backToMainMode()" style="width: auto; padding: 8px 16px; display: inline-flex; margin: 0 auto; gap: 4px;">⬅️ 回上一步</button> |
| </div> |
| </div> |
|
|
| |
| <div class="modal" id="winnerMessage"> |
| <h2 id="winnerText"></h2> |
| <p style="font-size: 1.2em; margin-bottom: 24px; color: #cbd5e1;">恭喜獲勝!太厲害了 🎉</p> |
| <button class="button" onclick="hideWinnerMessage()">確認並繼續</button> |
| </div> |
|
|
| |
| <div id="toastContainer" class="toast-container"></div> |
|
|
| <script> |
| |
| function toast(message, type = 'info') { |
| const container = document.getElementById('toastContainer'); |
| const toastEl = document.createElement('div'); |
| toastEl.className = `toast ${type}`; |
| |
| let emoji = 'ℹ️'; |
| if (type === 'warning') emoji = '⚠️'; |
| if (type === 'error') emoji = '❌'; |
| if (type === 'success') emoji = '🎉'; |
| |
| toastEl.innerHTML = `<span>${emoji}</span><span>${message}</span>`; |
| container.appendChild(toastEl); |
| |
| |
| setTimeout(() => { |
| toastEl.classList.add('show'); |
| }, 50); |
| |
| |
| setTimeout(() => { |
| toastEl.classList.remove('show'); |
| setTimeout(() => { |
| toastEl.remove(); |
| }, 300); |
| }, 3000); |
| } |
| |
| class MathChessGame { |
| constructor() { |
| this.board = Array(100).fill(null); |
| this.currentPlayer = 'white'; |
| this.selectedPiece = null; |
| this.gameEnded = false; |
| this.whiteScore = 10; |
| this.blackScore = 10; |
| this.gameMode = 'pvp'; |
| this.aiDifficulty = 'medium'; |
| this.isAiThinking = false; |
| |
| this.initializeGame(); |
| } |
| |
| initializeGame() { |
| this.createBoard(); |
| this.placePieces(); |
| this.updateDisplay(); |
| } |
| |
| createBoard() { |
| const boardElement = document.getElementById('gameBoard'); |
| boardElement.innerHTML = ''; |
| |
| for (let i = 0; i < 100; i++) { |
| const cell = document.createElement('div'); |
| cell.className = 'cell'; |
| cell.textContent = i + 1; |
| cell.addEventListener('click', () => this.handleCellClick(i)); |
| |
| |
| if (i < 10) { |
| cell.classList.add('white-territory'); |
| } else if (i >= 90) { |
| cell.classList.add('black-territory'); |
| } |
| |
| boardElement.appendChild(cell); |
| } |
| } |
| |
| placePieces() { |
| this.board = Array(100).fill(null); |
| |
| |
| for (let i = 0; i < 10; i++) { |
| this.board[i] = { |
| player: 'white', |
| number: i + 1, |
| upgraded: false |
| }; |
| } |
| |
| |
| for (let i = 90; i < 100; i++) { |
| this.board[i] = { |
| player: 'black', |
| number: i - 89, |
| upgraded: false |
| }; |
| } |
| |
| this.renderPieces(); |
| } |
| |
| renderPieces() { |
| const cells = document.querySelectorAll('.cell'); |
| |
| cells.forEach((cell, index) => { |
| const existingPiece = cell.querySelector('.piece'); |
| if (existingPiece) { |
| existingPiece.remove(); |
| } |
| |
| const piece = this.board[index]; |
| if (piece) { |
| const pieceElement = document.createElement('div'); |
| pieceElement.className = `piece ${piece.player}`; |
| if (piece.upgraded) { |
| pieceElement.classList.add('upgraded'); |
| } |
| pieceElement.textContent = piece.upgraded ? '★' : piece.number; |
| pieceElement.addEventListener('click', (e) => { |
| e.stopPropagation(); |
| this.handlePieceClick(index); |
| }); |
| cell.appendChild(pieceElement); |
| } |
| }); |
| } |
| |
| handlePieceClick(position) { |
| if (this.gameEnded || this.isAiThinking) return; |
| |
| |
| if (this.gameMode === 'ai' && this.currentPlayer === 'black') { |
| return; |
| } |
| |
| const piece = this.board[position]; |
| |
| |
| if (this.selectedPiece !== null && piece && piece.player !== this.currentPlayer) { |
| this.handleCellClick(position); |
| return; |
| } |
| |
| if (!piece || piece.player !== this.currentPlayer) return; |
| |
| this.clearSelection(); |
| |
| this.selectedPiece = position; |
| const pieceElement = document.querySelectorAll('.cell')[position].querySelector('.piece'); |
| if (pieceElement) { |
| pieceElement.classList.add('selected'); |
| } |
| |
| this.showPossibleMoves(position); |
| this.updateCurrentPlayerDisplay('選擇移動目標'); |
| } |
| |
| handleCellClick(position) { |
| if (this.gameEnded || this.selectedPiece === null || this.isAiThinking) return; |
| |
| if (this.isValidMove(this.selectedPiece, position)) { |
| this.makeMove(this.selectedPiece, position); |
| this.clearSelection(); |
| |
| if (this.checkGameEnd()) return; |
| |
| this.switchPlayer(); |
| } else { |
| toast('該位置不符合移動或整除規則,或已有己方棋子!', 'warning'); |
| } |
| } |
| |
| isValidMove(from, to) { |
| const piece = this.board[from]; |
| if (!piece) return false; |
| |
| |
| const targetPiece = this.board[to]; |
| if (targetPiece && targetPiece.player === piece.player) { |
| return false; |
| } |
| |
| const fromRow = Math.floor(from / 10); |
| const toRow = Math.floor(to / 10); |
| |
| |
| if (piece.upgraded) { |
| return Math.abs(toRow - fromRow) === 1; |
| } |
| |
| |
| if (piece.player === 'white') { |
| |
| if (toRow !== fromRow + 1) return false; |
| } else { |
| |
| if (toRow !== fromRow - 1) return false; |
| } |
| |
| |
| const targetNumber = to + 1; |
| return targetNumber % piece.number === 0; |
| } |
| |
| showPossibleMoves(position) { |
| const piece = this.board[position]; |
| if (!piece) return; |
| |
| const currentRow = Math.floor(position / 10); |
| |
| |
| if (piece.upgraded) { |
| const adjacentRows = [currentRow - 1, currentRow + 1]; |
| adjacentRows.forEach(targetRow => { |
| if (targetRow >= 0 && targetRow < 10) { |
| for (let col = 0; col < 10; col++) { |
| const targetPosition = targetRow * 10 + col; |
| if (this.isValidMove(position, targetPosition)) { |
| const cell = document.querySelectorAll('.cell')[targetPosition]; |
| cell.classList.add('possible-move'); |
| } |
| } |
| } |
| }); |
| return; |
| } |
| |
| |
| let targetRow = (piece.player === 'white') ? currentRow + 1 : currentRow - 1; |
| if (targetRow < 0 || targetRow >= 10) return; |
| |
| for (let col = 0; col < 10; col++) { |
| const targetPosition = targetRow * 10 + col; |
| if (this.isValidMove(position, targetPosition)) { |
| const cell = document.querySelectorAll('.cell')[targetPosition]; |
| cell.classList.add('possible-move'); |
| } |
| } |
| } |
| |
| makeMove(from, to) { |
| const piece = this.board[from]; |
| const targetPiece = this.board[to]; |
| |
| |
| if (targetPiece && targetPiece.player !== piece.player) { |
| if (targetPiece.player === 'white') { |
| this.whiteScore--; |
| toast(`紅方吃掉了藍方的 ${targetPiece.upgraded ? '★' : targetPiece.number} 號棋子!`, 'error'); |
| } else { |
| this.blackScore--; |
| toast(`藍方吃掉了紅方的 ${targetPiece.upgraded ? '★' : targetPiece.number} 號棋子!`, 'success'); |
| } |
| } |
| |
| |
| this.board[to] = piece; |
| this.board[from] = null; |
| |
| |
| const targetRow = Math.floor(to / 10); |
| if (piece.player === 'white' && targetRow >= 9 && !piece.upgraded) { |
| piece.upgraded = true; |
| toast('藍方棋子成功抵達底線,升級為超級「★」將軍!', 'success'); |
| } else if (piece.player === 'black' && targetRow <= 0 && !piece.upgraded) { |
| piece.upgraded = true; |
| toast('紅方棋子成功抵達頂線,升級為超級「★」將軍!', 'error'); |
| } |
| |
| this.renderPieces(); |
| this.updateScoreDisplay(); |
| } |
| |
| clearSelection() { |
| this.selectedPiece = null; |
| |
| document.querySelectorAll('.piece.selected').forEach(piece => { |
| piece.classList.remove('selected'); |
| }); |
| |
| document.querySelectorAll('.cell.possible-move').forEach(cell => { |
| cell.classList.remove('possible-move'); |
| }); |
| } |
| |
| switchPlayer() { |
| this.currentPlayer = this.currentPlayer === 'white' ? 'black' : 'white'; |
| this.updateCurrentPlayerDisplay(); |
| |
| |
| if (this.gameMode === 'ai' && this.currentPlayer === 'black' && !this.gameEnded) { |
| this.triggerAiMove(); |
| } |
| } |
| |
| triggerAiMove() { |
| this.isAiThinking = true; |
| this.updateCurrentPlayerDisplay('AI 正在思考棋步...'); |
| |
| |
| setTimeout(() => { |
| if (this.gameEnded) return; |
| |
| const bestMove = this.calculateBestAiMove(); |
| if (bestMove) { |
| this.makeMove(bestMove.from, bestMove.to); |
| this.clearSelection(); |
| |
| if (this.checkGameEnd()) { |
| this.isAiThinking = false; |
| return; |
| } |
| } else { |
| |
| this.endGame('藍方 (玩家)'); |
| this.isAiThinking = false; |
| return; |
| } |
| |
| this.isAiThinking = false; |
| this.switchPlayer(); |
| }, 800); |
| } |
| |
| |
| calculateBestAiMove() { |
| const possibleMoves = []; |
| |
| |
| for (let from = 0; from < 100; from++) { |
| const piece = this.board[from]; |
| if (piece && piece.player === 'black') { |
| for (let to = 0; to < 100; to++) { |
| if (this.isValidMove(from, to)) { |
| possibleMoves.push({ from, to, piece }); |
| } |
| } |
| } |
| } |
| |
| if (possibleMoves.length === 0) return null; |
| |
| |
| |
| |
| for (let i = possibleMoves.length - 1; i > 0; i--) { |
| const j = Math.floor(Math.random() * (i + 1)); |
| const temp = possibleMoves[i]; |
| possibleMoves[i] = possibleMoves[j]; |
| possibleMoves[j] = temp; |
| } |
| |
| |
| if (this.aiDifficulty === 'easy') { |
| |
| if (Math.random() < 0.6) { |
| return possibleMoves[0]; |
| } |
| |
| const scoredMoves = possibleMoves.map(move => { |
| let score = 0; |
| const targetPiece = this.board[move.to]; |
| if (targetPiece && targetPiece.player === 'white') { |
| score += 100; |
| } |
| const fromRow = Math.floor(move.from / 10); |
| const toRow = Math.floor(move.to / 10); |
| score += (fromRow - toRow) * 10; |
| return { ...move, score }; |
| }); |
| scoredMoves.sort((a, b) => b.score - a.score); |
| return scoredMoves[0]; |
| } |
| |
| |
| if (this.aiDifficulty === 'medium') { |
| const scoredMoves = possibleMoves.map(move => { |
| let score = 0; |
| const targetPiece = this.board[move.to]; |
| |
| |
| if (targetPiece && targetPiece.player === 'white') { |
| score += 200; |
| score += targetPiece.upgraded ? 100 : targetPiece.number * 5; |
| } |
| |
| |
| const toRow = Math.floor(move.to / 10); |
| if (!move.piece.upgraded && toRow === 0) { |
| score += 150; |
| } |
| |
| |
| const tempOriginal = this.board[move.to]; |
| this.board[move.to] = move.piece; |
| this.board[move.from] = null; |
| |
| let isThreatened = false; |
| for (let pFrom = 0; pFrom < 100; pFrom++) { |
| const pPiece = this.board[pFrom]; |
| if (pPiece && pPiece.player === 'white') { |
| if (this.isValidMove(pFrom, move.to)) { |
| isThreatened = true; |
| break; |
| } |
| } |
| } |
| |
| this.board[move.from] = move.piece; |
| this.board[move.to] = tempOriginal; |
| |
| if (isThreatened) { |
| score -= 80; |
| } |
| |
| |
| if (!move.piece.upgraded) { |
| const fromRow = Math.floor(move.from / 10); |
| score += (fromRow - toRow) * 10; |
| } else { |
| |
| let minDistance = 100; |
| for (let i = 0; i < 100; i++) { |
| const p = this.board[i]; |
| if (p && p.player === 'white') { |
| const dist = Math.abs(Math.floor(move.to / 10) - Math.floor(i / 10)) + Math.abs((move.to % 10) - (i % 10)); |
| if (dist < minDistance) minDistance = dist; |
| } |
| } |
| score += (10 - minDistance) * 4; |
| } |
| |
| |
| score += Math.random() * 20; |
| |
| return { ...move, score }; |
| }); |
| |
| scoredMoves.sort((a, b) => b.score - a.score); |
| return scoredMoves[0]; |
| } |
| |
| |
| if (this.aiDifficulty === 'hard') { |
| const scoredMoves = possibleMoves.map(move => { |
| let score = 0; |
| const targetPiece = this.board[move.to]; |
| const fromRow = Math.floor(move.from / 10); |
| const toRow = Math.floor(move.to / 10); |
| |
| |
| if (targetPiece && targetPiece.player === 'white') { |
| score += 350; |
| score += targetPiece.upgraded ? 250 : targetPiece.number * 12; |
| } |
| |
| |
| if (!move.piece.upgraded && toRow === 0) { |
| score += 250; |
| } |
| |
| |
| const tempOriginal = this.board[move.to]; |
| this.board[move.to] = move.piece; |
| this.board[move.from] = null; |
| |
| |
| let isThreatenedAfterMove = false; |
| for (let pFrom = 0; pFrom < 100; pFrom++) { |
| const pPiece = this.board[pFrom]; |
| if (pPiece && pPiece.player === 'white') { |
| if (this.isValidMove(pFrom, move.to)) { |
| isThreatenedAfterMove = true; |
| break; |
| } |
| } |
| } |
| |
| |
| let isThreatenedBeforeMove = false; |
| for (let pFrom = 0; pFrom < 100; pFrom++) { |
| const pPiece = this.board[pFrom]; |
| if (pPiece && pPiece.player === 'white') { |
| if (this.isValidMove(pFrom, move.from)) { |
| isThreatenedBeforeMove = true; |
| break; |
| } |
| } |
| } |
| |
| |
| this.board[move.from] = move.piece; |
| this.board[move.to] = tempOriginal; |
| |
| |
| if (isThreatenedAfterMove) { |
| const myValue = move.piece.upgraded ? 300 : move.piece.number * 10; |
| const targetValue = targetPiece ? (targetPiece.upgraded ? 400 : targetPiece.number * 15) : 0; |
| score -= (myValue - targetValue + 350); |
| } |
| |
| |
| if (isThreatenedBeforeMove && !isThreatenedAfterMove) { |
| score += 150; |
| } |
| |
| |
| if (!move.piece.upgraded) { |
| score += (fromRow - toRow) * 15; |
| } else { |
| |
| let bestTargetDist = 100; |
| let bestTargetValue = 0; |
| for (let i = 0; i < 100; i++) { |
| const p = this.board[i]; |
| if (p && p.player === 'white') { |
| const dist = Math.abs(toRow - Math.floor(i / 10)) + Math.abs((move.to % 10) - (i % 10)); |
| const val = p.upgraded ? 300 : p.number * 10; |
| if (dist < bestTargetDist) { |
| bestTargetDist = dist; |
| bestTargetValue = val; |
| } |
| } |
| } |
| score += (10 - bestTargetDist) * 8 + (bestTargetValue * 0.1); |
| } |
| |
| |
| return { ...move, score }; |
| }); |
| |
| scoredMoves.sort((a, b) => b.score - a.score); |
| return scoredMoves[0]; |
| } |
| } |
| |
| updateCurrentPlayerDisplay(customAction = null) { |
| const playerDisplay = document.getElementById('currentPlayer'); |
| let playerName = this.currentPlayer === 'white' ? '藍方' : '紅方'; |
| |
| if (this.gameMode === 'ai' && this.currentPlayer === 'black') { |
| let diffZh = '管家阿麥'; |
| if (this.aiDifficulty === 'easy') diffZh = '萌新嘟嘟'; |
| if (this.aiDifficulty === 'hard') diffZh = '戰神諸葛墨'; |
| playerName = `智慧對手【${diffZh}】`; |
| } |
| |
| if (customAction) { |
| playerDisplay.textContent = customAction; |
| } else { |
| playerDisplay.textContent = `${playerName}回合 - 選擇要移動的棋子`; |
| } |
| |
| playerDisplay.className = `current-player ${this.currentPlayer}`; |
| } |
| |
| updateScoreDisplay() { |
| document.getElementById('whiteScore').textContent = this.whiteScore; |
| document.getElementById('blackScore').textContent = this.blackScore; |
| } |
| |
| updateDisplay() { |
| this.updateCurrentPlayerDisplay(); |
| this.updateScoreDisplay(); |
| |
| |
| const modeText = document.getElementById('gameModeText'); |
| const blackTitle = document.getElementById('blackTitle'); |
| if (this.gameMode === 'ai') { |
| let diffZh = '管家阿麥'; |
| if (this.aiDifficulty === 'easy') diffZh = '萌新嘟嘟'; |
| if (this.aiDifficulty === 'hard') diffZh = '戰神諸葛墨'; |
| modeText.textContent = `🤖 對戰【${diffZh}】`; |
| blackTitle.textContent = `紅方棋子 (${diffZh})`; |
| } else { |
| modeText.textContent = '👥 雙人本地對戰'; |
| blackTitle.textContent = '紅方棋子 (玩家)'; |
| } |
| } |
| |
| checkGameEnd() { |
| if (this.whiteScore === 0) { |
| let winnerName = '紅方'; |
| if (this.gameMode === 'ai') { |
| let diffZh = '管家阿麥'; |
| if (this.aiDifficulty === 'easy') diffZh = '萌新嘟嘟'; |
| if (this.aiDifficulty === 'hard') diffZh = '戰神諸葛墨'; |
| winnerName = `智慧對手【${diffZh}】`; |
| } |
| this.endGame(winnerName); |
| return true; |
| } else if (this.blackScore === 0) { |
| this.endGame('藍方 (玩家)'); |
| return true; |
| } |
| return false; |
| } |
| |
| endGame(winner) { |
| this.gameEnded = true; |
| document.getElementById('winnerText').textContent = `${winner} 榮獲勝利!`; |
| document.getElementById('overlay').classList.add('show'); |
| document.getElementById('winnerMessage').classList.add('show'); |
| } |
| |
| reset() { |
| this.gameEnded = false; |
| this.currentPlayer = 'white'; |
| this.selectedPiece = null; |
| this.whiteScore = 10; |
| this.blackScore = 10; |
| this.isAiThinking = false; |
| this.clearSelection(); |
| this.placePieces(); |
| this.updateDisplay(); |
| toast('遊戲已重置,藍方先手!', 'info'); |
| } |
| |
| switchSides() { |
| if (this.isAiThinking) return; |
| |
| |
| const newBoard = Array(100).fill(null); |
| |
| for (let i = 0; i < 100; i++) { |
| const piece = this.board[i]; |
| if (piece) { |
| const newPosition = 99 - i; |
| newBoard[newPosition] = { |
| player: piece.player === 'white' ? 'black' : 'white', |
| number: piece.number, |
| upgraded: piece.upgraded |
| }; |
| } |
| } |
| |
| this.board = newBoard; |
| |
| const tempScore = this.whiteScore; |
| this.whiteScore = this.blackScore; |
| this.blackScore = tempScore; |
| |
| this.currentPlayer = this.currentPlayer === 'white' ? 'black' : 'white'; |
| this.clearSelection(); |
| this.renderPieces(); |
| this.updateDisplay(); |
| |
| toast('雙方交換陣營!已重新整理戰局', 'success'); |
| |
| |
| if (this.gameMode === 'ai' && this.currentPlayer === 'black' && !this.gameEnded) { |
| this.triggerAiMove(); |
| } |
| } |
| } |
| |
| |
| let game = new MathChessGame(); |
| |
| |
| window.addEventListener('DOMContentLoaded', () => { |
| showWelcomeModal(); |
| }); |
| |
| function showWelcomeModal() { |
| document.getElementById('overlay').classList.add('show'); |
| document.getElementById('welcomeModal').classList.add('show'); |
| |
| document.getElementById('mainModeSelect').style.display = 'block'; |
| document.getElementById('aiDifficultySelect').style.display = 'none'; |
| } |
| |
| function showAiDifficultySelect() { |
| document.getElementById('mainModeSelect').style.display = 'none'; |
| document.getElementById('aiDifficultySelect').style.display = 'block'; |
| } |
| |
| function backToMainMode() { |
| document.getElementById('mainModeSelect').style.display = 'block'; |
| document.getElementById('aiDifficultySelect').style.display = 'none'; |
| } |
| |
| function selectMode(mode) { |
| if (mode === 'pvp') { |
| game.gameMode = 'pvp'; |
| } else { |
| game.gameMode = 'ai'; |
| if (mode === 'ai_easy') game.aiDifficulty = 'easy'; |
| if (mode === 'ai_medium') game.aiDifficulty = 'medium'; |
| if (mode === 'ai_hard') game.aiDifficulty = 'hard'; |
| } |
| hideWelcomeModal(); |
| game.reset(); |
| } |
| |
| function hideWelcomeModal() { |
| document.getElementById('overlay').classList.remove('show'); |
| document.getElementById('welcomeModal').classList.remove('show'); |
| } |
| |
| function resetGame() { |
| game.reset(); |
| } |
| |
| function switchSides() { |
| game.switchSides(); |
| } |
| |
| function showHint() { |
| if (game.isAiThinking) return; |
| |
| const piece = game.board[game.selectedPiece]; |
| if (!piece) { |
| toast('請先在棋盤上選擇您的一個棋子!', 'warning'); |
| return; |
| } |
| |
| let hint = `您選擇的 ${piece.number} 號棋,可以移動到下方(目標格編號能被 ${piece.number} 整除)的格子。\n`; |
| hint += `例如:${piece.number}、${piece.number * 2}、${piece.number * 3} ... 的位置。`; |
| |
| if (piece.upgraded) { |
| hint = '此棋已升級!可以無視整除限制,自由往「上」或「下」一列的任意位置移動!'; |
| } |
| |
| toast(hint, 'info'); |
| } |
| |
| function hideWinnerMessage() { |
| document.getElementById('overlay').classList.remove('show'); |
| document.getElementById('winnerMessage').classList.remove('show'); |
| game.reset(); |
| } |
| </script> |
| </body> |
| </html> |