/** * 斗地主UI控制器 * 连接游戏逻辑和界面 */ let game = null; let userScore = 0; // 当前用户积分 // ========== 用户积分相关 ========== const GAME_ID = 'doudizhu'; const API_BASE = ''; // 同域,相对路径 // 获取用户积分 async function loadUserScore() { try { const response = await fetch(`${API_BASE}/api/user/score/${GAME_ID}`, { credentials: 'include' }); if (response.status === 401) { // 未登录,显示提示 document.getElementById('userName').textContent = '未登录'; document.getElementById('scoreValue').textContent = '-'; return; } const data = await response.json(); if (data.score !== undefined) { userScore = data.score; document.getElementById('scoreValue').textContent = userScore; } } catch (error) { console.error('获取积分失败:', error); document.getElementById('scoreValue').textContent = '-'; } } // 获取用户名 async function loadUserInfo() { try { const response = await fetch(`${API_BASE}/api/auth/status`, { credentials: 'include' }); const data = await response.json(); if (data.authenticated && data.user) { document.getElementById('userName').textContent = data.user.username; } else { document.getElementById('userName').textContent = '游客'; } } catch (error) { document.getElementById('userName').textContent = '游客'; } } // 提交游戏积分 async function submitScore(points) { if (points <= 0) return; try { const response = await fetch(`${API_BASE}/api/user/score`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ game: GAME_ID, points: points }) }); if (response.ok) { const data = await response.json(); if (data.success) { userScore = data.score; document.getElementById('scoreValue').textContent = userScore; // 显示获得积分动画 showScoreAnimation(points); } } } catch (error) { console.error('提交积分失败:', error); } } // 显示获得积分动画 function showScoreAnimation(points) { const scoreEl = document.getElementById('scoreValue'); const anim = document.createElement('div'); anim.textContent = `+${points}`; anim.style.cssText = ` position: fixed; top: 40px; right: 20px; color: #ffd700; font-size: 24px; font-weight: bold; text-shadow: 0 0 20px rgba(255, 215, 0, 0.8); pointer-events: none; z-index: 1001; animation: scoreFloat 1.5s ease-out forwards; `; document.body.appendChild(anim); // 高亮分数 scoreEl.style.transform = 'scale(1.3)'; scoreEl.style.transition = 'transform 0.3s'; setTimeout(() => { scoreEl.style.transform = 'scale(1)'; }, 300); setTimeout(() => anim.remove(), 1500); } // 添加分数浮动动画 const style = document.createElement('style'); style.textContent = ` @keyframes scoreFloat { 0% { opacity: 1; transform: translateY(0) scale(1); } 100% { opacity: 0; transform: translateY(-30px) scale(1.2); } } `; document.head.appendChild(style); // ========== 牌的HTML渲染 ========== function createCardHTML(card, selectable = true) { if (!card) return ''; if (card.isJoker) { const color = card.rank === '大王' ? '#e74c3c' : '#3498db'; return `
${card.rank}
🃏
${card.rank}
`; } const isRed = card.suit === '♥' || card.suit === '♦'; const color = isRed ? '#e74c3c' : '#2c3e50'; return `
${card.rank}
${card.suit}
${card.suit}
${card.rank}
${card.suit}
`; } function createCardBackHTML() { return '
'; } // ========== 选牌交互 ========== function toggleSelect(cardEl) { if (game && game.currentPlayer !== 0) return; // 不是玩家回合不能选 cardEl.classList.toggle('selected'); updateActionButtons(); } function updateActionButtons() { const selected = document.querySelectorAll('.hand-cards .card.selected'); const hasSelection = selected.length > 0; const isPlayerTurn = game && game.currentPlayer === 0 && game.gamePhase === 'playing'; document.getElementById('btn-play').disabled = !hasSelection || !isPlayerTurn; document.getElementById('btn-pass').disabled = !isPlayerTurn; document.getElementById('btn-hint').disabled = !isPlayerTurn; } // ========== 游戏流程控制 ========== function startGame() { document.getElementById('start-screen').style.display = 'none'; game = new DoudizhuGame(); game.init(); // 发牌动画 document.getElementById('game-status').textContent = '正在发牌...'; animateDeal(); } function animateDeal() { const handContainer = document.getElementById('hand-cards'); handContainer.innerHTML = ''; handContainer.classList.add('dealing'); // 先快速发完(动画效果) let cardIndex = 0; const dealInterval = setInterval(() => { const playerIndex = cardIndex % 3; if (playerIndex === 0) { // 玩家显示背面 const cardEl = document.createElement('div'); cardEl.innerHTML = createCardBackHTML(); handContainer.appendChild(cardEl.firstElementChild); } updateAICardCounts(); cardIndex++; if (cardIndex >= 51) { clearInterval(dealInterval); handContainer.classList.remove('dealing'); // 真正发牌 const result = game.deal(); // 显示底牌 showBottomCardsBack(); // 显示玩家手牌 renderPlayerCards(); // 进入叫地主 setTimeout(() => { showCallLordModal(); }, 500); } }, 30); } function renderPlayerCards() { const container = document.getElementById('hand-cards'); container.innerHTML = ''; const playerCards = game.players[0].cards; playerCards.forEach(card => { const div = document.createElement('div'); div.innerHTML = createCardHTML(card, true); container.appendChild(div.firstElementChild); }); } function updateAICardCounts() { document.querySelector('.left-player .card-count').textContent = game ? game.players[1].cards.length + '张' : '17张'; document.querySelector('.right-player .card-count').textContent = game ? game.players[2].cards.length + '张' : '17张'; } function showBottomCardsBack() { const deckArea = document.querySelector('.deck-cards'); deckArea.innerHTML = ''; for (let i = 0; i < 3; i++) { deckArea.innerHTML += createCardBackHTML(); } } function showBottomCardsFront(cards) { const deckArea = document.querySelector('.deck-cards'); deckArea.innerHTML = ''; cards.forEach(card => { const div = document.createElement('div'); div.innerHTML = createCardHTML(card, false); deckArea.appendChild(div.firstElementChild); }); } // ========== 叫地主 ========== function showCallLordModal() { document.getElementById('call-lord-modal').style.display = 'flex'; document.getElementById('game-status').textContent = '轮到你了:是否叫地主?'; } function callLord(want) { document.getElementById('call-lord-modal').style.display = 'none'; const result = game.callLord(0, want); if (result.success) { becomeLord(0); } else { // AI叫地主 aiCallLordProcess(); } } function becomeLord(playerId) { const player = game.players[playerId]; // 显示底牌 showBottomCardsFront(game.bottomCards); // 更新角色显示 updateRoleDisplay(); // 如果是玩家,更新手牌 if (playerId === 0) { renderPlayerCards(); document.getElementById('game-status').textContent = '你是地主,请出牌'; updateActionButtons(); } else { document.getElementById('game-status').textContent = player.name + '是地主,游戏开始'; setTimeout(() => aiTurn(), 1500); } } function updateRoleDisplay() { game.players.forEach(p => { const roleEl = document.getElementById(p.id === 0 ? 'my-role' : (p.id === 1 ? 'left-role' : 'right-role')); if (roleEl) { roleEl.textContent = p.isLord ? '地主' : '农民'; } }); } function aiCallLordProcess() { document.getElementById('game-status').textContent = '等待其他玩家...'; setTimeout(() => { // AI1叫地主 if (game.players[1].ai.wantToBeLord(game.players[1].cards)) { game.callLord(1, true); becomeLord(1); return; } // AI2叫地主 setTimeout(() => { if (game.players[2].ai.wantToBeLord(game.players[2].cards)) { game.callLord(2, true); becomeLord(2); return; } // 都没叫,玩家默认当地主 game.callLord(0, true); becomeLord(0); }, 1000); }, 1000); } // ========== 出牌逻辑 ========== function playCards() { const selected = document.querySelectorAll('.hand-cards .card.selected'); if (selected.length === 0) return; const cardIds = Array.from(selected).map(el => parseInt(el.dataset.id)); const cards = game.players[0].cards.filter(c => cardIds.includes(c.id)); const result = game.playCards(0, cards); if (!result.success) { showToast(result.error); return; } // 更新UI executePlayUI(0, cards, result.cardType); if (result.gameOver) { endGame(0); return; } // AI回合 setTimeout(() => aiTurn(), 1000); } function passTurn() { // 先取消所有选中的牌(让牌回归原位) document.querySelectorAll('.hand-cards .card.selected').forEach(card => { card.classList.remove('selected'); }); updateActionButtons(); const result = game.pass(0); showPlayedCards([], 0, '不出'); if (result.clearDesktop) { clearDesktop(); } setTimeout(() => aiTurn(), 1000); } function executePlayUI(playerId, cards, cardType) { // 从手牌移除 if (playerId === 0) { renderPlayerCards(); } // 显示到桌面 const typeName = cardType ? cardType.name : '出牌'; showPlayedCards(cards, playerId, typeName); // 更新AI牌数 updateAICardCounts(); // 取消选中 updateActionButtons(); } function showPlayedCards(cards, playerId, actionName) { const container = document.querySelector('.played-cards'); const playerName = game.players[playerId].name; const playerRole = game.players[playerId].isLord ? '【地主】' : '【农民】'; // 如果是"不出",只更新文字,不清空牌(保留上一轮出的牌) if (cards.length === 0) { document.querySelector('.played-by').textContent = `${playerName}${playerRole}: 不出`; document.getElementById('game-status').textContent = `${playerName}选择不出`; return; } // 有牌出,更新牌显示 container.innerHTML = ''; container.classList.add('playing'); cards.forEach(card => { const div = document.createElement('div'); div.innerHTML = createCardHTML(card, false); container.appendChild(div.firstElementChild); }); // 显示出牌者 + 牌型 document.querySelector('.played-by').textContent = `${playerName}${playerRole}: ${actionName}`; document.getElementById('game-status').textContent = `${playerName}出了 ${actionName}`; setTimeout(() => container.classList.remove('playing'), 300); } function clearDesktop() { setTimeout(() => { document.querySelector('.played-cards').innerHTML = ''; document.querySelector('.played-by').textContent = '新一轮出牌'; game.desktopCards = null; }, 1000); } // ========== AI回合 ========== function aiTurn() { if (!game || game.gamePhase !== 'playing') return; const currentId = game.currentPlayer; if (currentId === 0) { document.getElementById('game-status').textContent = '轮到你了'; updateActionButtons(); return; } const player = game.players[currentId]; document.getElementById('game-status').textContent = player.name + '正在思考...'; setTimeout(() => { const result = game.aiPlay(); if (result.success) { if (result.cardType) { // AI出了牌 executePlayUI(currentId, result.cardType.cards, result.cardType); } else { // AI选择不出 showPlayedCards([], currentId, '不出'); } if (result.clearDesktop) { clearDesktop(); } if (result.gameOver) { endGame(result.winner); return; } // 继续下一个 setTimeout(() => aiTurn(), 1500); } }, 1500); } // ========== 提示功能 ========== function showHint() { if (!game || game.currentPlayer !== 0) return; const cards = game.players[0].cards; if (cards.length === 0) return; // 取消之前的选中 document.querySelectorAll('.hand-cards .card.selected').forEach(c => c.classList.remove('selected')); // 找出一个合法的出牌 const ai = new DoudizhuAI(0); const validPlays = ai.findAllValidPlays(cards, game.desktopCards); if (validPlays.length > 0) { // 选最小的 validPlays.sort((a, b) => a.type.value - b.type.value); const hintCards = validPlays[0].cards; hintCards.forEach(card => { const cardEl = document.querySelector(`.hand-cards .card[data-id="${card.id}"]`); if (cardEl) cardEl.classList.add('selected'); }); updateActionButtons(); showToast('提示: ' + validPlays[0].type.name); } else { showToast('没有可出的牌,建议不出'); } } // ========== 游戏结束 ========== function endGame(winnerId) { const winner = game.players[winnerId]; const isPlayerWin = winnerId === 0; const isLordWin = winner.isLord; let message; let earnedPoints = 0; if (isPlayerWin) { // 玩家获胜,计算积分 earnedPoints = isLordWin ? 3 : 1; const roleName = isLordWin ? '地主' : '农民'; message = `恭喜你,${roleName}获胜!\n获得 ${earnedPoints} 积分`; // 提交积分 submitScore(earnedPoints); } else { message = isLordWin ? `${winner.name}(地主)获胜!` : '农民获胜!\n你输了'; } document.getElementById('game-status').textContent = message.replace('\n', ' '); setTimeout(() => { if (confirm(message + '\n\n是否重新开始?')) { location.reload(); } }, 800); } // ========== 工具函数 ========== function showToast(message) { // 简单的提示 const status = document.getElementById('game-status'); const oldText = status.textContent; status.textContent = message; setTimeout(() => { status.textContent = oldText; }, 2000); } // ========== 登录检查 ========== async function checkLogin() { try { const response = await fetch('/api/auth/status', { credentials: 'include' }); const data = await response.json(); if (!data.authenticated) { // 未登录,显示遮罩 showLoginRequired(); return false; } return true; } catch (error) { showLoginRequired(); return false; } } function showLoginRequired() { // 创建登录提示遮罩 const overlay = document.createElement('div'); overlay.id = 'login-required-overlay'; overlay.innerHTML = `
🔒
请先登录
登录后才能游玩斗地主
`; document.body.appendChild(overlay); } // ========== 事件绑定 ========== document.addEventListener('DOMContentLoaded', async function() { // 先检查登录 const isLoggedIn = await checkLogin(); if (!isLoggedIn) return; // 加载用户信息和积分 loadUserInfo(); loadUserScore(); document.getElementById('btn-start').addEventListener('click', startGame); document.getElementById('btn-call-yes').addEventListener('click', () => callLord(true)); document.getElementById('btn-call-no').addEventListener('click', () => callLord(false)); document.getElementById('btn-play').addEventListener('click', playCards); document.getElementById('btn-pass').addEventListener('click', passTurn); document.getElementById('btn-hint').addEventListener('click', showHint); });