DiviChess / index.html
Lashtw's picture
Update index.html
30a5040 verified
Raw
History Blame Contribute Delete
56.8 kB
<!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;
}
/* 彈出視窗(Mode Selection & Rules) */
.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 卡片式選單樣式 */
.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 Notification */
.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">
<!-- 棋盤格子會由JavaScript生成 -->
</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>
<!-- 第二階段:選擇 AI 對手與個性說明 -->
<div id="aiDifficultySelect" style="display: none;">
<p style="margin-bottom: 16px; font-weight: bold; color: #cbd5e1;">請點選要對抗的 AI 角色:</p>
<div class="ai-selection-list">
<!-- 簡單 AI 卡片 -->
<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>
<!-- 中等 AI 卡片 -->
<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>
<!-- 困難 AI 卡片 -->
<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>
<!-- 自訂 Toast 提示區域 -->
<div id="toastContainer" class="toast-container"></div>
<script>
// 自訂提示元件 (代替 Alert)
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);
// 3秒後自動消失並刪除
setTimeout(() => {
toastEl.classList.remove('show');
setTimeout(() => {
toastEl.remove();
}, 300);
}, 3000);
}
class MathChessGame {
constructor() {
this.board = Array(100).fill(null);
this.currentPlayer = 'white'; // 'white' (藍) or 'black' (紅)
this.selectedPiece = null;
this.gameEnded = false;
this.whiteScore = 10;
this.blackScore = 10;
this.gameMode = 'pvp'; // 'pvp' or 'ai'
this.aiDifficulty = 'medium'; // 'easy' | 'medium' | 'hard'
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);
// 放置藍方棋子(1-10位置)
for (let i = 0; i < 10; i++) {
this.board[i] = {
player: 'white',
number: i + 1,
upgraded: false
};
}
// 放置紅方棋子(91-100位置)
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;
// AI 模式下玩家不能操控 AI 的紅棋
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);
// 1. 如果棋子已升級,可任意往上或往下移動一格 (列距為 1) 且不受整除限制
if (piece.upgraded) {
return Math.abs(toRow - fromRow) === 1;
}
// 2. 未升級棋子的前行方向檢查
if (piece.player === 'white') {
// 藍方向下
if (toRow !== fromRow + 1) return false;
} else {
// 紅方向上
if (toRow !== fromRow - 1) return false;
}
// 3. 未升級棋子的整除規則 (1-based index)
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();
// 如果切換到紅方,且此時是 AI 模式,觸發 AI 計算與行動
if (this.gameMode === 'ai' && this.currentPlayer === 'black' && !this.gameEnded) {
this.triggerAiMove();
}
}
triggerAiMove() {
this.isAiThinking = true;
this.updateCurrentPlayerDisplay('AI 正在思考棋步...');
// 模擬 AI 思考時間 (800ms)
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 {
// AI 無路可走,判定玩家勝出
this.endGame('藍方 (玩家)');
this.isAiThinking = false;
return;
}
this.isAiThinking = false;
this.switchPlayer();
}, 800);
}
// AI 智慧難度決策演算法
calculateBestAiMove() {
const possibleMoves = [];
// 1. 蒐集所有可能合法的紅方棋步
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;
// 【重要修正:隨機洗牌機制】
// 先隨機打亂所有合法的棋步,確保當多個棋步具有「相同最大估值」時,
// 能隨機選出一個,完美解決 AI 單一棋子(如1號棋)重覆走到底的問題。
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') {
// 60% 機率完全亂走隨機棋步(此處可以直接選洗牌後的第1個步子)
if (Math.random() < 0.6) {
return possibleMoves[0];
}
// 40% 機率執行只看「吃子」和「前進」的超基礎評估
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];
// A. 吃子加分
if (targetPiece && targetPiece.player === 'white') {
score += 200;
score += targetPiece.upgraded ? 100 : targetPiece.number * 5;
}
// B. 達底線升級獎勵
const toRow = Math.floor(move.to / 10);
if (!move.piece.upgraded && toRow === 0) {
score += 150;
}
// C. 避險評估 (防守:走完是否會立刻被對方吃?)
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; // 扣分以避免危險
}
// D. 前進與追逐
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;
}
// E. 雜訊 (使 AI 有時會做非最優的選擇,顯得更自然)
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);
// A. 極高權重的獵殺獎勵
if (targetPiece && targetPiece.player === 'white') {
score += 350; // 吃子基礎分
score += targetPiece.upgraded ? 250 : targetPiece.number * 12; // 優先吃高價值棋子 (如 ★ 或 10 號)
}
// B. 追求升級的高權重
if (!move.piece.upgraded && toRow === 0) {
score += 250;
}
// C. 嚴格且深度的避險評估
const tempOriginal = this.board[move.to];
this.board[move.to] = move.piece;
this.board[move.from] = null;
// 1. 走完之後新位置是否會被吃?
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;
}
}
}
// 2. 原來的位置是否本來就處於危險中?
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;
}
// D. 最優路徑追逐
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');
// 如果交換完輪到 AI
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>