Deepseek辅助生成的网页版小游戏 -- 斗地主(修行版)
《斗地主-修行版》是一款融合佛教元素的HTML5游戏,采用响应式设计适配移动端。游戏包含完整斗地主玩法,创新性地将"功德"作为积分系统,获胜可获得功德点数并支持"功德回向"功能。界面采用黄色佛系配色,牌面设计融入佛教符号,支持背景音乐和音效。技术特点包括:1) 完善的牌型判断逻辑;2) AI对手自动出牌;3) 功德积分动画效果;4) 横竖屏自适应布局。游戏通
恭请赏玩:
网页源码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<!-- 针对iOS设备的优化 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>斗地主 - 修行版</title>
<style>
/* 基础重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent; /* 移除点击高亮 */
-webkit-touch-callout: none; /* 禁止长按菜单 */
-webkit-user-select: none; /* 禁止文本选择 */
user-select: none;
}
/* 针对iOS Safari的特定修复 */
input {
-webkit-user-select: text; /* 输入框允许选择文本 */
user-select: text;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background-image: url('lianchi.jpg');
background-repeat: repeat;
background-size: auto;
color: #fff;
min-height: 100vh;
display: flex;
flex-direction: column;
padding: 10px;
overflow-x: hidden;
/* 防止iOS橡皮筋效果 */
position: fixed;
width: 100%;
height: 100%;
}
/* 游戏区域容器 */
.game-container {
flex: 1;
display: flex;
flex-direction: column;
max-width: 1200px;
margin: 0 auto;
width: 100%;
overflow: hidden;
}
.header {
text-align: center;
padding: 12px 0;
margin-bottom: 10px;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 12px;
margin: 10px;
border: 2px solid #ffcc00;
position: relative;
z-index: 10;
}
h1 {
color: #ffcc00;
font-size: 1.8rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
margin-bottom: 5px;
}
/* 游戏信息区域 - 优化移动端显示 */
.game-info {
display: flex;
justify-content: center;
margin-bottom: 15px;
width: 100%;
padding: 0 10px;
}
.info-item {
background-color: rgba(0, 0, 0, 0.7);
border-radius: 10px;
padding: 12px 20px;
text-align: center;
border: 2px solid #ffcc00;
min-width: 180px;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.info-label {
font-size: 0.95rem;
color: #ffcc00;
margin-bottom: 6px;
}
.info-value {
font-size: 1.5rem;
font-weight: bold;
}
/* 游戏主区域 */
.game-area {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
position: relative;
width: 100%;
padding: 0 5px;
}
/* 对手区域优化 */
.opponent-area {
display: flex;
justify-content: space-between;
width: 100%;
margin-bottom: 20px;
min-height: 120px;
}
.opponent {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 10px;
transition: all 0.3s;
border: 2px solid #4a752c;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.opponent.active {
background-color: rgba(255, 204, 0, 0.3);
border: 2px solid #ffcc00;
box-shadow: 0 0 15px rgba(255, 204, 0, 0.5);
}
.opponent-name {
font-size: 1rem;
margin-bottom: 8px;
color: #ffcc00;
text-align: center;
}
/* 牌背样式 */
.card-stack {
display: flex;
height: 80px;
position: relative;
justify-content: center;
width: 100%;
min-width: 80px;
}
.card-back {
width: 50px;
height: 70px;
background-image: url('wuliangsofo_s.jpg');
background-size: cover;
background-position: center;
border-radius: 6px;
border: 2px solid #fff;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
position: absolute;
top: 0;
}
/* 中央区域 - 优化移动端 */
.center-area {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 10px;
width: 100%;
min-height: 180px;
}
/* 出牌区域 - 移动端优化 */
.played-cards {
display: flex;
justify-content: center;
flex-wrap: wrap;
min-height: 120px;
width: 100%;
position: relative;
margin-bottom: 15px;
padding: 10px;
gap: 5px;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 12px;
border: 3px solid #ffcc00;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
overflow-x: auto;
-webkit-overflow-scrolling: touch; /* iOS滑动优化 */
}
/* 已出牌样式 */
.played-card {
width: 60px;
height: 85px;
border-radius: 8px;
border: 2px solid #fff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 4px;
background-color: white;
color: #333;
flex-shrink: 0;
overflow: hidden;
}
.played-card .card-corner {
font-size: 0.8rem;
display: flex;
flex-direction: column;
align-items: center;
line-height: 1;
margin: 1px 0;
}
.played-card .card-center {
font-size: 1.5rem;
align-self: center;
line-height: 1;
margin: 3px 0;
}
/* 游戏状态 */
.game-status {
text-align: center;
font-size: 1.1rem;
color: #ffcc00;
padding: 12px 15px;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 10px;
width: 100%;
min-height: 50px;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #ffcc00;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* 玩家区域 - 移动端优化 */
.player-area {
width: 100%;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 12px;
padding: 15px;
border: 2px solid #4a752c;
transition: all 0.3s;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
min-height: 180px;
display: flex;
flex-direction: column;
}
.player-area.active {
border-color: #ffcc00;
box-shadow: 0 0 20px rgba(255, 204, 0, 0.6);
}
.player-name {
font-size: 1.2rem;
color: #ffcc00;
text-align: center;
margin-bottom: 15px;
}
/* 玩家手牌容器 - 优化触摸区域 */
.player-cards-container {
display: flex;
justify-content: center;
position: relative;
width: 100%;
padding: 8px 0;
overflow-x: auto;
-webkit-overflow-scrolling: touch; /* iOS滑动优化 */
flex: 1;
min-height: 100px;
}
.player-card-stack {
display: flex;
position: relative;
height: 100px;
min-width: min-content;
padding: 0 5px;
align-items: flex-end;
}
/* 玩家手牌 - 增大触摸区域 */
.player-card {
width: 60px;
height: 85px;
border-radius: 8px;
border: 2px solid #fff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 5px;
position: relative;
transition: transform 0.2s, margin-left 0.2s, z-index 0.2s;
background-color: white;
color: #333;
cursor: pointer;
flex-shrink: 0;
margin-left: -15px;
overflow: hidden;
}
.player-card .card-corner {
font-size: 0.9rem;
display: flex;
flex-direction: column;
align-items: center;
line-height: 1;
margin: 1px 0;
}
.player-card .card-center {
font-size: 1.8rem;
align-self: center;
line-height: 1;
margin: 3px 0;
}
.player-card:first-child {
margin-left: 0;
}
/* 优化触摸交互 */
.player-card:hover,
.player-card:active {
transform: translateY(-10px);
z-index: 100 !important;
}
.player-card.selected {
transform: translateY(-15px);
border-color: #ffcc00;
box-shadow: 0 8px 16px rgba(255, 204, 0, 0.5);
z-index: 200 !important;
}
/* 颜色样式 */
.card.red {
color: #e53935;
}
.card.black {
color: #333;
}
.card-joker {
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
border-radius: 6px;
}
/* 按钮区域 - 优化移动端 */
.controls-container {
width: 100%;
max-width: 1200px;
margin: 15px auto 10px;
padding: 0 10px;
}
.controls {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
width: 100%;
}
.btn {
padding: 12px 8px;
font-size: 0.95rem;
background-color: #ffcc00;
color: #8b0000;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
transition: all 0.2s;
box-shadow: 0 3px 0 #cc9900;
min-height: 44px; /* iOS最小触摸区域 */
-webkit-appearance: none;
}
.btn:active {
transform: translateY(2px);
box-shadow: 0 1px 0 #cc9900;
}
.btn:disabled {
background-color: #666;
color: #999;
cursor: not-allowed;
transform: none;
box-shadow: 0 3px 0 #444;
}
/* 音乐按钮 */
.btn.music-btn {
background-color: #4a752c;
color: #ffcc00;
box-shadow: 0 3px 0 #3a5c22;
}
.btn.music-btn.music-on {
background-color: #8b0000;
color: #ffcc00;
box-shadow: 0 3px 0 #660000;
}
/* 弹窗样式优化 */
.landlord-selection,
.merit-transfer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.95);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1000;
padding: 20px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.selection-box,
.merit-box {
background-color: #2a5c2a;
padding: 20px;
border-radius: 15px;
border: 3px solid #ffcc00;
text-align: center;
width: 90%;
max-width: 400px;
margin-bottom: 20px;
}
.selection-title,
.merit-title {
color: #ffcc00;
font-size: 1.5rem;
margin-bottom: 15px;
}
.selection-buttons,
.merit-buttons {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 20px;
width: 100%;
}
.selection-btn,
.merit-btn {
padding: 14px;
font-size: 1.1rem;
background-color: #ffcc00;
color: #8b0000;
border: none;
border-radius: 10px;
cursor: pointer;
font-weight: bold;
min-height: 50px;
}
.merit-input {
padding: 12px;
font-size: 1rem;
border: 2px solid #ffcc00;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.9);
color: #333;
width: 100%;
margin-top: 10px;
}
/* 功德牌样式 */
.landlord-cards {
display: flex;
justify-content: center;
gap: 10px;
margin: 15px 0;
flex-wrap: wrap;
}
.landlord-card-back {
width: 60px;
height: 85px;
background-image: url('wuliangsofo_s.jpg');
background-size: cover;
background-position: center;
border-radius: 8px;
border: 2px solid #ffcc00;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
/* 功德数动画 */
.merit-value {
color: #ffcc00;
text-shadow: 0 0 10px rgba(255, 204, 0, 0.7);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.merit-up {
animation: meritUp 1s ease-out;
}
@keyframes meritUp {
0% { transform: scale(1); }
50% { transform: scale(1.3); }
100% { transform: scale(1); }
}
/* 功德回向确认消息 */
.merit-confirm {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #2a5c2a;
padding: 20px;
border-radius: 12px;
border: 3px solid #ffcc00;
text-align: center;
z-index: 1002;
max-width: 350px;
width: 90%;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
}
.merit-confirm-title {
color: #ffcc00;
font-size: 1.3rem;
margin-bottom: 10px;
}
.merit-confirm-text {
color: #fff;
font-size: 1.1rem;
margin-bottom: 15px;
line-height: 1.4;
}
.merit-confirm-btn {
padding: 10px 15px;
font-size: 1rem;
background-color: #ffcc00;
color: #8b0000;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
}
/* 隐藏类 */
.hidden {
display: none !important;
}
/* iPad横屏优化 */
@media (min-width: 768px) and (max-width: 1024px) {
.opponent {
width: 30%;
}
.player-card {
width: 70px;
height: 100px;
margin-left: -20px;
}
.played-card {
width: 70px;
height: 100px;
}
.controls {
grid-template-columns: repeat(3, 1fr);
}
.btn {
padding: 12px;
font-size: 1rem;
}
}
/* iPhone横屏优化 */
@media (max-width: 767px) and (orientation: landscape) {
.game-area {
flex-direction: row;
flex-wrap: wrap;
gap: 10px;
}
.opponent-area {
width: 40%;
flex-direction: column;
margin-bottom: 0;
}
.opponent {
width: 100%;
margin-bottom: 10px;
min-height: 100px;
}
.center-area {
width: 60%;
margin-bottom: 0;
}
.player-area {
width: 100%;
margin-top: 0;
min-height: 150px;
}
.player-card {
width: 50px;
height: 70px;
margin-left: -12px;
}
.played-card {
width: 50px;
height: 70px;
}
.controls {
grid-template-columns: repeat(3, 1fr);
gap: 5px;
}
}
/* 小屏幕手机优化 */
@media (max-width: 480px) {
h1 {
font-size: 1.5rem;
}
.info-item {
min-width: 150px;
padding: 10px 15px;
}
.opponent-area {
flex-direction: column;
align-items: center;
gap: 10px;
}
.opponent {
width: 100%;
max-width: 200px;
}
.opponent:nth-child(2) {
order: -1;
}
.player-card {
width: 50px;
height: 70px;
margin-left: -12px;
}
.played-card {
width: 45px;
height: 65px;
}
.played-card .card-center {
font-size: 1.2rem;
}
.controls {
grid-template-columns: repeat(2, 1fr);
}
.btn {
font-size: 0.9rem;
padding: 10px 6px;
}
}
/* 超小屏幕优化 */
@media (max-width: 350px) {
.player-card {
width: 45px;
height: 63px;
margin-left: -10px;
}
.played-card {
width: 40px;
height: 60px;
}
.controls {
grid-template-columns: 1fr;
}
}
/* 防止iOS缩放 */
@supports (-webkit-touch-callout: none) {
body {
cursor: pointer;
}
input, textarea {
font-size: 16px !important; /* 防止iOS缩放 */
}
}
</style>
</head>
<body>
<!-- 游戏容器 -->
<div class="game-container">
<div class="header">
<h1>斗地主 - 修行版</h1>
</div>
<div class="game-info">
<div class="info-item">
<div class="info-label">功德数</div>
<div id="merit-score" class="info-value merit-value">0</div>
</div>
</div>
<div class="game-area">
<div class="opponent-area">
<div id="opponent1" class="opponent">
<div class="opponent-name">又见音</div>
<div id="opponent1-cards" class="card-stack"></div>
</div>
<div id="opponent2" class="opponent">
<div class="opponent-name">执力至</div>
<div id="opponent2-cards" class="card-stack"></div>
</div>
</div>
<div class="center-area">
<div id="played-cards" class="played-cards"></div>
<div class="game-status" id="game-status">点击"开始游戏"按钮开始</div>
</div>
<div id="player-area" class="player-area">
<div class="player-name">玩家</div>
<div id="player-cards-container" class="player-cards-container"></div>
</div>
</div>
</div>
<!-- 控制按钮 -->
<div class="controls-container">
<div class="controls">
<button id="start-btn" class="btn">开始游戏</button>
<button id="play-btn" class="btn" disabled>出牌</button>
<button id="pass-btn" class="btn" disabled>不出</button>
<button id="hint-btn" class="btn" disabled>提示</button>
<button id="music-toggle-btn" class="btn music-btn">背景音乐: 开</button>
<button id="merit-transfer-btn" class="btn">功德回向</button>
</div>
</div>
<!-- 弹窗和遮罩层 -->
<div id="landlord-selection" class="landlord-selection hidden">
<div class="selection-box">
<div class="selection-title">是否叫功德主?</div>
<div id="selection-info" style="margin-bottom: 15px; color: #ffcc00; font-size: 1.1rem;">您有3张底牌</div>
<div id="landlord-cards" class="landlord-cards"></div>
<div class="selection-buttons">
<button id="call-landlord-btn" class="selection-btn">叫功德主</button>
<button id="pass-landlord-btn" class="selection-btn">不叫</button>
</div>
</div>
</div>
<div id="merit-transfer" class="merit-transfer hidden">
<div class="merit-box">
<div class="merit-title">功德回向</div>
<div id="merit-transfer-info" style="margin-bottom: 15px; color: #ffcc00; font-size: 1.1rem;">
您当前有 <span id="current-merit-display">0</span> 点功德
</div>
<div class="merit-buttons">
<button id="merit-to-all-btn" class="merit-btn">回向一切众生</button>
<button id="merit-to-loved-btn" class="merit-btn">回向所爱的人</button>
<div id="loved-name-input-container" style="display: none; width: 100%;">
<input type="text" id="loved-name-input" class="merit-input" placeholder="输入名字" maxlength="20">
<button id="submit-loved-name-btn" class="merit-btn" style="margin-top: 10px;">确认回向</button>
</div>
<button id="merit-cancel-btn" class="merit-btn">再想想</button>
</div>
</div>
</div>
<!-- 功德回向确认消息 -->
<div id="merit-confirm" class="merit-confirm hidden">
<div class="merit-confirm-title">功德回向成功</div>
<div id="merit-confirm-text" class="merit-confirm-text"></div>
<button id="merit-confirm-btn" class="merit-confirm-btn">确认</button>
</div>
<!-- 音频元素 -->
<audio id="bg-music" loop>
<source src="beijing.ogg" type="audio/ogg">
您的浏览器不支持音频元素。
</audio>
<audio id="play-sound">
<source src="xiaochu.mp3" type="audio/mpeg">
您的浏览器不支持音频元素。
</audio>
<audio id="win-sound">
<source src="win.mp3" type="audio/mpeg">
您的浏览器不支持音频元素。
</audio>
<audio id="ai-sound">
<source src="ai.mp3" type="audio/mpeg">
您的浏览器不支持音频元素。
</audio>
<audio id="merit-sound">
<source src="xiaochu.mp3" type="audio/mpeg">
您的浏览器不支持音频元素。
</audio>
<script>
// 游戏状态
const gameState = {
players: ['player', 'opponent1', 'opponent2'],
currentPlayer: 'player',
deck: [],
playerCards: [],
opponent1Cards: [],
opponent2Cards: [],
lastPlayedCards: [],
lastPlayer: null,
gameStarted: false,
landlord: null,
meritScore: 0,
musicEnabled: true,
soundEnabled: true,
landlordCards: [],
selectingLandlord: false,
playerPassed: false,
consecutivePasses: 0,
gameEnded: false
};
// 牌型定义
const cardSuits = [
{ name: 'spade', symbol: '♠', color: 'black' },
{ name: 'heart', symbol: '♥', color: 'red' },
{ name: 'club', symbol: '♣', color: 'black' },
{ name: 'diamond', symbol: '♦', color: 'red' }
];
const cardValues = ['3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A', '2'];
const cardDisplay = {
'J': 'J',
'Q': 'Q',
'K': 'K',
'A': 'A',
'2': '2'
};
// 音频元素
const bgMusic = document.getElementById('bg-music');
const playSound = document.getElementById('play-sound');
const winSound = document.getElementById('win-sound');
const aiSound = document.getElementById('ai-sound');
const meritSound = document.getElementById('merit-sound');
// DOM元素
const playerCardsContainer = document.getElementById('player-cards-container');
const opponent1CardsEl = document.getElementById('opponent1-cards');
const opponent2CardsEl = document.getElementById('opponent2-cards');
const playedCardsEl = document.getElementById('played-cards');
const meritScoreEl = document.getElementById('merit-score');
const gameStatusEl = document.getElementById('game-status');
const startBtn = document.getElementById('start-btn');
const playBtn = document.getElementById('play-btn');
const passBtn = document.getElementById('pass-btn');
const hintBtn = document.getElementById('hint-btn');
const musicToggleBtn = document.getElementById('music-toggle-btn');
const meritTransferBtn = document.getElementById('merit-transfer-btn');
const playerAreaEl = document.getElementById('player-area');
const opponent1El = document.getElementById('opponent1');
const opponent2El = document.getElementById('opponent2');
const landlordSelectionEl = document.getElementById('landlord-selection');
const callLandlordBtn = document.getElementById('call-landlord-btn');
const passLandlordBtn = document.getElementById('pass-landlord-btn');
const selectionInfoEl = document.getElementById('selection-info');
const landlordCardsEl = document.getElementById('landlord-cards');
const meritTransferEl = document.getElementById('merit-transfer');
const currentMeritDisplay = document.getElementById('current-merit-display');
const meritToAllBtn = document.getElementById('merit-to-all-btn');
const meritToLovedBtn = document.getElementById('merit-to-loved-btn');
const meritCancelBtn = document.getElementById('merit-cancel-btn');
const lovedNameInputContainer = document.getElementById('loved-name-input-container');
const lovedNameInput = document.getElementById('loved-name-input');
const submitLovedNameBtn = document.getElementById('submit-loved-name-btn');
const meritConfirmEl = document.getElementById('merit-confirm');
const meritConfirmText = document.getElementById('merit-confirm-text');
const meritConfirmBtn = document.getElementById('merit-confirm-btn');
// 初始化游戏
function initGame() {
// 清除上一局的出牌
gameState.lastPlayedCards = [];
gameState.gameEnded = false;
createDeck();
shuffleDeck();
dealCards();
// 更新UI显示所有玩家的牌
updateUI();
// 显示功德主选择界面
setTimeout(() => {
showLandlordSelection();
}, 500);
gameState.gameStarted = true;
gameState.playerPassed = false;
gameState.consecutivePasses = 0;
// 播放背景音乐
if (gameState.musicEnabled) {
bgMusic.play().catch(e => console.log("自动播放被阻止,请手动播放音乐"));
}
}
// 创建一副牌
function createDeck() {
gameState.deck = [];
// 添加普通牌
for (let suit of cardSuits) {
for (let value of cardValues) {
gameState.deck.push({
suit: suit.name,
symbol: suit.symbol,
value: value,
color: suit.color,
numericValue: getCardValueIndex(value)
});
}
}
// 添加大小王
gameState.deck.push({
suit: 'joker',
symbol: '大王',
value: 'Joker',
color: 'red',
numericValue: 17,
jokerType: 'big',
image: 'wuliangsofo_s.jpg'
});
gameState.deck.push({
suit: 'joker',
symbol: '小王',
value: 'Joker',
color: 'black',
numericValue: 16,
jokerType: 'small',
image: 'wuliangsofo_s2.jpg'
});
}
// 洗牌
function shuffleDeck() {
for (let i = gameState.deck.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[gameState.deck[i], gameState.deck[j]] = [gameState.deck[j], gameState.deck[i]];
}
}
// 发牌
function dealCards() {
// 清空玩家手牌
gameState.playerCards = [];
gameState.opponent1Cards = [];
gameState.opponent2Cards = [];
gameState.landlordCards = [];
// 每位玩家发17张牌
for (let i = 0; i < 17; i++) {
gameState.playerCards.push(gameState.deck.pop());
gameState.opponent1Cards.push(gameState.deck.pop());
gameState.opponent2Cards.push(gameState.deck.pop());
}
// 保留3张底牌
for (let i = 0; i < 3; i++) {
gameState.landlordCards.push(gameState.deck.pop());
}
// 排序手牌
sortCards(gameState.playerCards);
sortCards(gameState.opponent1Cards);
sortCards(gameState.opponent2Cards);
}
// 显示功德主选择界面
function showLandlordSelection() {
gameState.selectingLandlord = true;
// 显示底牌(背面)
landlordCardsEl.innerHTML = '';
for (let i = 0; i < gameState.landlordCards.length; i++) {
const cardBack = document.createElement('div');
cardBack.className = 'landlord-card-back';
landlordCardsEl.appendChild(cardBack);
}
landlordSelectionEl.classList.remove('hidden');
updateGameStatus("请选择是否叫功德主");
}
// 增加功德数
function addMeritScore(amount, reason = "") {
gameState.meritScore += amount;
updateMeritScore();
// 播放功德增加音效
if (gameState.soundEnabled) {
meritSound.currentTime = 0;
meritSound.play();
}
// 添加动画效果
meritScoreEl.classList.add('merit-up');
setTimeout(() => {
meritScoreEl.classList.remove('merit-up');
}, 1000);
// 只有在非胜利出牌时才显示功德增加提示
if (reason && !gameState.gameEnded) {
const oldStatus = gameStatusEl.textContent;
updateGameStatus(`${reason},功德+${amount}`);
setTimeout(() => {
updateGameStatus(oldStatus);
}, 2000);
}
}
// 更新功德数显示
function updateMeritScore() {
meritScoreEl.textContent = gameState.meritScore;
}
// 玩家选择叫功德主
function callLandlord() {
gameState.landlord = 'player';
// 玩家获得底牌
for (let card of gameState.landlordCards) {
gameState.playerCards.push(card);
}
sortCards(gameState.playerCards);
gameState.landlordCards = [];
landlordSelectionEl.classList.add('hidden');
gameState.selectingLandlord = false;
// 更新玩家手牌显示
renderPlayerCards();
startGameplay();
}
// 玩家选择不叫功德主
function passLandlord() {
// 随机选择一个电脑玩家作为功德主
const randomIndex = Math.floor(Math.random() * 2) + 1;
gameState.landlord = gameState.players[randomIndex];
// 功德主获得底牌
if (gameState.landlord === 'opponent1') {
for (let card of gameState.landlordCards) {
gameState.opponent1Cards.push(card);
}
sortCards(gameState.opponent1Cards);
} else {
for (let card of gameState.landlordCards) {
gameState.opponent2Cards.push(card);
}
sortCards(gameState.opponent2Cards);
}
gameState.landlordCards = [];
landlordSelectionEl.classList.add('hidden');
gameState.selectingLandlord = false;
startGameplay();
}
// 开始游戏
function startGameplay() {
// 如果玩家是功德主,自动激活玩家回合
if (gameState.landlord === 'player') {
gameState.currentPlayer = 'player';
activatePlayerTurn();
updateGameStatus("您是功德主,请先出牌");
} else {
// 否则,激活对应电脑玩家的回合
gameState.currentPlayer = gameState.landlord;
updateGameStatus(`${gameState.landlord === 'opponent1' ? '又见音' : '执力至'}是功德主`);
setTimeout(computerPlay, 1500);
}
// 更新按钮状态
startBtn.disabled = true;
playBtn.disabled = false;
passBtn.disabled = false;
hintBtn.disabled = false;
}
// 排序手牌
function sortCards(cards) {
cards.sort((a, b) => {
if (a.numericValue !== b.numericValue) {
return a.numericValue - b.numericValue;
}
// 如果牌值相同,按花色排序
const suitOrder = { 'spade': 4, 'heart': 3, 'club': 2, 'diamond': 1, 'joker': 0 };
return suitOrder[b.suit] - suitOrder[a.suit];
});
}
// 获取牌值索引
function getCardValueIndex(value) {
if (value === 'Joker') {
return 0;
}
const index = cardValues.indexOf(value);
if (value === '2') {
return 15;
} else if (value === 'A') {
return 14;
} else if (value === 'K') {
return 13;
} else if (value === 'Q') {
return 12;
} else if (value === 'J') {
return 11;
} else if (value === '10') {
return 10;
} else if (value === '9') {
return 9;
} else if (value === '8') {
return 8;
} else if (value === '7') {
return 7;
} else if (value === '6') {
return 6;
} else if (value === '5') {
return 5;
} else if (value === '4') {
return 4;
} else if (value === '3') {
return 3;
}
return index + 3;
}
// 更新UI
function updateUI() {
// 更新玩家手牌
renderPlayerCards();
// 更新电脑玩家手牌
renderOpponentCards();
// 更新已出牌区域
renderPlayedCards();
// 更新玩家区域激活状态
if (gameState.currentPlayer === 'player' && !gameState.selectingLandlord) {
playerAreaEl.classList.add('active');
} else {
playerAreaEl.classList.remove('active');
}
// 更新电脑玩家激活状态
if (gameState.currentPlayer === 'opponent1') {
opponent1El.classList.add('active');
} else {
opponent1El.classList.remove('active');
}
if (gameState.currentPlayer === 'opponent2') {
opponent2El.classList.add('active');
} else {
opponent2El.classList.remove('active');
}
}
// 渲染玩家手牌
function renderPlayerCards() {
playerCardsContainer.innerHTML = '';
if (gameState.playerCards.length === 0) {
const emptyMsg = document.createElement('div');
emptyMsg.textContent = '手牌已出完';
emptyMsg.style.color = '#ffcc00';
emptyMsg.style.fontSize = '1.2rem';
emptyMsg.style.margin = 'auto';
playerCardsContainer.appendChild(emptyMsg);
return;
}
const stackContainer = document.createElement('div');
stackContainer.className = 'player-card-stack';
// 计算需要的总宽度
const cardWidth = 60;
const cardOverlap = 15;
const totalCards = gameState.playerCards.length;
const totalWidth = cardWidth + (totalCards - 1) * (cardWidth - cardOverlap);
// 设置容器最小宽度
stackContainer.style.minWidth = `${totalWidth}px`;
// 每张牌轻微层叠
gameState.playerCards.forEach((card, index) => {
const cardEl = createCardElement(card, index);
stackContainer.appendChild(cardEl);
});
playerCardsContainer.appendChild(stackContainer);
// 确保容器宽度不会导致滚动条
playerCardsContainer.style.overflowX = 'auto';
playerCardsContainer.style.maxWidth = '100%';
}
// 渲染电脑玩家手牌
function renderOpponentCards() {
// 清空现有牌背
opponent1CardsEl.innerHTML = '';
opponent2CardsEl.innerHTML = '';
// 为电脑玩家1渲染牌背
const cardCount1 = gameState.opponent1Cards.length;
if (cardCount1 > 0) {
const overlap = 6;
const cardWidth = 50;
const totalWidth = (cardCount1 - 1) * overlap + cardWidth;
// 从右向左排列牌背
for (let i = 0; i < Math.min(cardCount1, 15); i++) {
const cardBack = document.createElement('div');
cardBack.className = 'card-back';
// 计算位置
const leftPosition = totalWidth - cardWidth - (i * overlap);
cardBack.style.left = `${leftPosition}px`;
cardBack.style.zIndex = i;
opponent1CardsEl.appendChild(cardBack);
}
// 如果牌数超过15张,显示一个额外的牌背表示更多
if (cardCount1 > 15) {
const moreCards = document.createElement('div');
moreCards.className = 'card-back';
moreCards.style.left = '0px';
moreCards.style.zIndex = 15;
moreCards.style.opacity = '0.7';
opponent1CardsEl.appendChild(moreCards);
}
}
// 为电脑玩家2渲染牌背
const cardCount2 = gameState.opponent2Cards.length;
if (cardCount2 > 0) {
const overlap = 6;
const cardWidth = 50;
const totalWidth = (cardCount2 - 1) * overlap + cardWidth;
for (let i = 0; i < Math.min(cardCount2, 15); i++) {
const cardBack = document.createElement('div');
cardBack.className = 'card-back';
const leftPosition = totalWidth - cardWidth - (i * overlap);
cardBack.style.left = `${leftPosition}px`;
cardBack.style.zIndex = i;
opponent2CardsEl.appendChild(cardBack);
}
if (cardCount2 > 15) {
const moreCards = document.createElement('div');
moreCards.className = 'card-back';
moreCards.style.left = '0px';
moreCards.style.zIndex = 15;
moreCards.style.opacity = '0.7';
opponent2CardsEl.appendChild(moreCards);
}
}
}
// 渲染已出牌
function renderPlayedCards() {
playedCardsEl.innerHTML = '';
if (gameState.lastPlayedCards.length === 0) {
const emptyMsg = document.createElement('div');
emptyMsg.textContent = '暂无出牌';
emptyMsg.style.color = '#ccc';
emptyMsg.style.fontSize = '1.2rem';
emptyMsg.style.margin = 'auto';
playedCardsEl.appendChild(emptyMsg);
return;
}
// 创建紧凑显示区域
gameState.lastPlayedCards.forEach((card, index) => {
const cardEl = createPlayedCardElement(card);
playedCardsEl.appendChild(cardEl);
});
}
// 创建已出牌元素
function createPlayedCardElement(card) {
const cardEl = document.createElement('div');
cardEl.className = `played-card card ${card.color}`;
// 如果是大小王,使用图片
if (card.value === 'Joker') {
const jokerImage = document.createElement('div');
jokerImage.className = 'card-joker';
jokerImage.style.backgroundImage = `url('${card.image}')`;
cardEl.appendChild(jokerImage);
} else {
// 普通牌
const topCorner = document.createElement('div');
topCorner.className = 'card-corner';
topCorner.innerHTML = `<div>${getCardValueDisplay(card.value)}</div><div>${card.symbol}</div>`;
const center = document.createElement('div');
center.className = 'card-center';
center.textContent = card.symbol;
const bottomCorner = document.createElement('div');
bottomCorner.className = 'card-corner';
bottomCorner.innerHTML = `<div>${getCardValueDisplay(card.value)}</div><div>${card.symbol}</div>`;
bottomCorner.style.transform = 'rotate(180deg)';
cardEl.appendChild(topCorner);
cardEl.appendChild(center);
cardEl.appendChild(bottomCorner);
}
return cardEl;
}
// 创建牌元素(玩家手牌)
function createCardElement(card, index) {
const cardEl = document.createElement('div');
cardEl.className = `player-card card ${card.color}`;
cardEl.dataset.index = index;
cardEl.dataset.value = card.value;
cardEl.dataset.suit = card.suit;
// 如果是大小王,使用图片
if (card.value === 'Joker') {
const jokerImage = document.createElement('div');
jokerImage.className = 'card-joker';
jokerImage.style.backgroundImage = `url('${card.image}')`;
cardEl.appendChild(jokerImage);
} else {
// 普通牌
const topCorner = document.createElement('div');
topCorner.className = 'card-corner';
topCorner.innerHTML = `<div>${getCardValueDisplay(card.value)}</div><div>${card.symbol}</div>`;
const center = document.createElement('div');
center.className = 'card-center';
center.textContent = card.symbol;
const bottomCorner = document.createElement('div');
bottomCorner.className = 'card-corner';
bottomCorner.innerHTML = `<div>${getCardValueDisplay(card.value)}</div><div>${card.symbol}</div>`;
bottomCorner.style.transform = 'rotate(180deg)';
cardEl.appendChild(topCorner);
cardEl.appendChild(center);
cardEl.appendChild(bottomCorner);
}
if (index >= 0) {
cardEl.addEventListener('click', (e) => {
e.stopPropagation();
if (gameState.currentPlayer === 'player' && gameState.gameStarted && !gameState.selectingLandlord && !gameState.gameEnded) {
cardEl.classList.toggle('selected');
// 如果选中了牌,确保它显示在最前面
if (cardEl.classList.contains('selected')) {
cardEl.style.zIndex = 1000;
} else {
cardEl.style.zIndex = 1;
}
}
});
}
return cardEl;
}
// 获取牌值显示文本
function getCardValueDisplay(value) {
if (value === 'Joker') {
return '';
}
return value in cardDisplay ? cardDisplay[value] : value;
}
// 激活玩家回合
function activatePlayerTurn() {
gameState.currentPlayer = 'player';
gameState.playerPassed = false;
updateUI();
updateGameStatus("您的回合,请选择要出的牌");
}
// 玩家出牌
function playerPlay() {
const selectedCards = document.querySelectorAll('#player-cards-container .player-card.selected');
if (selectedCards.length === 0) {
updateGameStatus("请选择要出的牌");
return;
}
// 获取选中的牌
const selectedIndices = Array.from(selectedCards).map(card => parseInt(card.dataset.index));
selectedIndices.sort((a, b) => b - a);
// 检查出牌是否合法
const cardsToPlay = selectedIndices.map(index => gameState.playerCards[index]);
if (!isValidPlay(cardsToPlay, gameState.lastPlayedCards, gameState.lastPlayer === 'player')) {
updateGameStatus("出牌不合法,请重新选择");
return;
}
// 从玩家手牌中移除
for (let index of selectedIndices) {
gameState.playerCards.splice(index, 1);
}
// 更新上一轮出牌
gameState.lastPlayedCards = cardsToPlay;
gameState.lastPlayer = 'player';
gameState.consecutivePasses = 0;
// 检查游戏是否结束
if (gameState.playerCards.length === 0) {
// 玩家出完所有牌
updateUI();
setTimeout(() => {
endGame('player');
}, 500);
return;
}
// 增加功德数
addMeritScore(100);
// 播放出牌音效
if (gameState.soundEnabled) {
playSound.currentTime = 0;
playSound.play();
}
// 切换到下一个玩家
nextTurn();
}
// 玩家不出
function playerPass() {
if (gameState.lastPlayer === null || gameState.lastPlayer === 'player') {
updateGameStatus("您是首家出牌,不能不出");
return;
}
gameState.playerPassed = true;
gameState.consecutivePasses++;
updateGameStatus("您选择不出");
// 如果连续两个玩家都选择不出,重新开始一轮
if (gameState.consecutivePasses >= 2) {
gameState.lastPlayedCards = [];
gameState.lastPlayer = null;
gameState.consecutivePasses = 0;
updateGameStatus("");
}
nextTurn();
}
// 电脑玩家出牌
function computerPlay() {
const player = gameState.currentPlayer;
let cards;
if (player === 'opponent1') {
cards = gameState.opponent1Cards;
} else {
cards = gameState.opponent2Cards;
}
// 获取可出的牌
const playableCards = getPlayableCards(cards, gameState.lastPlayedCards, gameState.lastPlayer === player);
if (playableCards.length > 0) {
// 播放AI音效
if (gameState.soundEnabled) {
aiSound.currentTime = 0;
aiSound.play();
}
// 从电脑手牌中移除
const cardsToPlay = playableCards;
for (let cardToRemove of cardsToPlay) {
const index = cards.findIndex(card =>
card.value === cardToRemove.value &&
card.suit === cardToRemove.suit);
if (index > -1) {
cards.splice(index, 1);
}
}
// 更新上一轮出牌
gameState.lastPlayedCards = cardsToPlay;
gameState.lastPlayer = player;
gameState.consecutivePasses = 0;
// 播放出牌音效
if (gameState.soundEnabled) {
setTimeout(() => {
playSound.currentTime = 0;
playSound.play();
}, 500);
}
// 检查游戏是否结束
if (cards.length === 0) {
updateGameStatus(`${player === 'opponent1' ? '又见音' : '执力至'}出完了所有牌!`);
setTimeout(() => {
endGame(player);
}, 1000);
return;
}
// 获取牌型描述
const cardType = getCardType(cardsToPlay);
let typeDescription = "";
switch(cardType.type) {
case 'single': typeDescription = "单张"; break;
case 'pair': typeDescription = "对子"; break;
case 'triple': typeDescription = "三张"; break;
case 'triple_with_single': typeDescription = "三带一"; break;
case 'triple_with_pair': typeDescription = "三带二"; break;
case 'straight': typeDescription = "顺子"; break;
case 'consecutive_pairs': typeDescription = "连对"; break;
case 'plane': typeDescription = "飞机"; break;
case 'plane_with_single': typeDescription = "飞机带单"; break;
case 'plane_with_pair': typeDescription = "飞机带对"; break;
case 'bomb': typeDescription = "炸弹"; break;
case 'four_with_two_singles': typeDescription = "四带二单"; break;
case 'four_with_two_pairs': typeDescription = "四带二对"; break;
case 'rocket': typeDescription = "火箭"; break;
default: typeDescription = "牌组";
}
updateGameStatus(`${player === 'opponent1' ? '又见音' : '执力至'}出了${cardsToPlay.length}张牌 (${typeDescription})`);
// 切换到下一个玩家
setTimeout(nextTurn, 1000);
} else {
// 电脑选择不出
updateGameStatus(`${player === 'opponent1' ? '又见音' : '执力至'}选择不出`);
gameState.consecutivePasses++;
// 如果连续两个玩家都选择不出,重新开始一轮
if (gameState.consecutivePasses >= 2) {
gameState.lastPlayedCards = [];
gameState.lastPlayer = null;
gameState.consecutivePasses = 0;
updateGameStatus("");
}
setTimeout(nextTurn, 1000);
}
}
// 获取可出的牌(AI逻辑)
function getPlayableCards(cards, lastCards, isFirstPlay) {
// 如果没有上一轮出牌或者是首家出牌,可以出任何合法牌型
if (lastCards.length === 0 || isFirstPlay) {
// 尝试出最小的合法牌型
return findSmallestValidCards(cards);
}
// 如果不是首家出牌,需要出比上一轮大的牌
const lastType = getCardType(lastCards);
const allPossiblePlays = getAllPossiblePlays(cards);
// 找出比上一轮大的牌
for (let play of allPossiblePlays) {
const playType = getCardType(play);
if (compareCardTypes(playType, lastType) > 0) {
return play;
}
}
// 如果没有合适的牌,返回空数组(不出)
return [];
}
// 找出最小的合法牌
function findSmallestValidCards(cards) {
// 尝试单张
if (cards.length >= 1) {
return [cards[0]];
}
return [];
}
// 获取所有可能的出牌组合
function getAllPossiblePlays(cards) {
const plays = [];
// 按牌值分组
const cardsByValue = {};
cards.forEach(card => {
if (!cardsByValue[card.value]) {
cardsByValue[card.value] = [];
}
cardsByValue[card.value].push(card);
});
// 单张
cards.forEach(card => {
plays.push([card]);
});
// 对子
for (let value in cardsByValue) {
if (cardsByValue[value].length >= 2) {
plays.push(cardsByValue[value].slice(0, 2));
}
}
// 三张
for (let value in cardsByValue) {
if (cardsByValue[value].length >= 3) {
plays.push(cardsByValue[value].slice(0, 3));
}
}
// 顺子
const straightPlays = getStraightPlays(cards);
plays.push(...straightPlays);
// 炸弹
for (let value in cardsByValue) {
if (cardsByValue[value].length >= 4) {
plays.push(cardsByValue[value].slice(0, 4));
}
}
// 火箭
const bigJoker = cards.find(c => c.value === 'Joker' && c.jokerType === 'big');
const smallJoker = cards.find(c => c.value === 'Joker' && c.jokerType === 'small');
if (bigJoker && smallJoker) {
plays.push([bigJoker, smallJoker]);
}
return plays;
}
// 获取顺子组合
function getStraightPlays(cards) {
const plays = [];
// 按数值排序
const sortedCards = [...cards].sort((a, b) => a.numericValue - b.numericValue);
// 移除2和大小王,因为它们不能组成顺子
const validCards = sortedCards.filter(card =>
card.value !== '2' && card.value !== 'Joker');
if (validCards.length < 5) return plays;
// 找出所有可能的顺子
for (let i = 0; i <= validCards.length - 5; i++) {
let straight = [validCards[i]];
for (let j = i + 1; j < validCards.length; j++) {
const lastCard = straight[straight.length - 1];
const currentCard = validCards[j];
// 检查是否连续
if (currentCard.numericValue === lastCard.numericValue + 1) {
straight.push(currentCard);
// 如果顺子长度达到5或以上,添加到可出牌组
if (straight.length >= 5) {
plays.push([...straight]);
}
} else if (currentCard.numericValue > lastCard.numericValue + 1) {
// 不连续,重新开始
break;
}
// 如果牌值相同,跳过(顺子不能有重复牌值)
}
}
// 移除重复的顺子
const uniquePlays = [];
const seen = new Set();
for (let play of plays) {
const key = play.map(card => card.numericValue).join('-');
if (!seen.has(key)) {
seen.add(key);
uniquePlays.push(play);
}
}
return uniquePlays;
}
// 检查出牌是否合法
function isValidPlay(cards, lastCards, isFirstPlay) {
if (cards.length === 0) return false;
// 检查牌型是否合法
const cardType = getCardType(cards);
if (!cardType.valid) return false;
// 如果是首家出牌,任何合法牌型都可以
if (lastCards.length === 0 || isFirstPlay) {
return true;
}
// 如果不是首家出牌,需要比上一轮牌大
const lastType = getCardType(lastCards);
return compareCardTypes(cardType, lastType) > 0;
}
// 获取牌型 - 修复了顺子判断逻辑
function getCardType(cards) {
const len = cards.length;
if (len === 0) return { type: 'invalid', valid: false };
const values = cards.map(card => card.numericValue);
const uniqueValues = [...new Set(values)];
// 火箭
if (len === 2 && cards[0].value === 'Joker' && cards[1].value === 'Joker') {
return { type: 'rocket', value: 100, valid: true };
}
// 单张
if (len === 1) {
return { type: 'single', value: values[0], valid: true };
}
// 对子
if (len === 2 && uniqueValues.length === 1) {
return { type: 'pair', value: values[0], valid: true };
}
// 三张
if (len === 3 && uniqueValues.length === 1) {
return { type: 'triple', value: values[0], valid: true };
}
// 三带一
if (len === 4 && uniqueValues.length === 2) {
const valueCounts = countCardValues(cards);
const tripleValue = Object.keys(valueCounts).find(v => valueCounts[v] === 3);
if (tripleValue) {
return { type: 'triple_with_single', value: parseInt(tripleValue), valid: true };
}
}
// 三带二
if (len === 5 && uniqueValues.length === 2) {
const valueCounts = countCardValues(cards);
const tripleValue = Object.keys(valueCounts).find(v => valueCounts[v] === 3);
const pairValue = Object.keys(valueCounts).find(v => valueCounts[v] === 2);
if (tripleValue && pairValue) {
return { type: 'triple_with_pair', value: parseInt(tripleValue), valid: true };
}
}
// 炸弹
if (len === 4 && uniqueValues.length === 1) {
return { type: 'bomb', value: values[0], valid: true };
}
// 顺子 - 修复判断逻辑
if (len >= 5 && isStraight(cards)) {
// 顺子的值取最大牌的值
const maxValue = Math.max(...values);
return { type: 'straight', value: maxValue, valid: true, length: len };
}
// 连对
if (len >= 6 && len % 2 === 0 && isConsecutivePairs(cards)) {
return { type: 'consecutive_pairs', value: Math.max(...values), valid: true, length: len / 2 };
}
// 飞机(无翅膀)
if (len >= 6 && len % 3 === 0 && isPlane(cards)) {
return { type: 'plane', value: Math.max(...values), valid: true, length: len / 3 };
}
return { type: 'invalid', valid: false };
}
// 辅助函数:统计牌值数量
function countCardValues(cards) {
const counts = {};
cards.forEach(card => {
counts[card.numericValue] = (counts[card.numericValue] || 0) + 1;
});
return counts;
}
// 辅助函数:检查是否为顺子 - 修复版本
function isStraight(cards) {
if (cards.length < 5) return false;
// 排序
const sortedCards = [...cards].sort((a, b) => a.numericValue - b.numericValue);
// 检查是否包含2或大小王,它们不能组成顺子
for (let card of sortedCards) {
if (card.value === '2' || card.value === 'Joker') {
return false;
}
}
// 检查牌值是否连续
for (let i = 1; i < sortedCards.length; i++) {
if (sortedCards[i].numericValue !== sortedCards[i-1].numericValue + 1) {
return false;
}
}
return true;
}
// 辅助函数:检查是否为连对
function isConsecutivePairs(cards) {
if (cards.length < 6 || cards.length % 2 !== 0) return false;
const valueCounts = countCardValues(cards);
// 每张牌必须出现2次
for (let count of Object.values(valueCounts)) {
if (count !== 2) return false;
}
const uniqueValues = Object.keys(valueCounts).map(Number).sort((a, b) => a - b);
// 检查是否连续
for (let i = 1; i < uniqueValues.length; i++) {
if (uniqueValues[i] !== uniqueValues[i-1] + 1) {
return false;
}
}
return true;
}
// 辅助函数:检查是否为飞机(无翅膀)
function isPlane(cards) {
if (cards.length < 6 || cards.length % 3 !== 0) return false;
const valueCounts = countCardValues(cards);
// 每张牌必须出现3次
for (let count of Object.values(valueCounts)) {
if (count !== 3) return false;
}
const uniqueValues = Object.keys(valueCounts).map(Number).sort((a, b) => a - b);
// 检查是否连续
for (let i = 1; i < uniqueValues.length; i++) {
if (uniqueValues[i] !== uniqueValues[i-1] + 1) {
return false;
}
}
return true;
}
// 比较牌型大小
function compareCardTypes(type1, type2) {
// 火箭最大
if (type1.type === 'rocket') return 1;
if (type2.type === 'rocket') return -1;
// 炸弹比其他牌型大(除了火箭)
if (type1.type === 'bomb' && type2.type !== 'bomb') return 1;
if (type2.type === 'bomb' && type1.type !== 'bomb') return -1;
// 相同牌型比较
if (type1.type === type2.type) {
// 特殊牌型需要长度也相同
const lengthSensitiveTypes = ['straight', 'consecutive_pairs', 'plane'];
if (lengthSensitiveTypes.includes(type1.type)) {
if (type1.length !== type2.length) return 0; // 长度不同不能比较
}
// 比较牌值,值大的牌型更大
return type1.value - type2.value;
}
// 不同牌型不能比较(炸弹除外,已在上面处理)
return 0;
}
// 切换到下一个回合
function nextTurn() {
const currentIndex = gameState.players.indexOf(gameState.currentPlayer);
const nextIndex = (currentIndex + 1) % 3;
gameState.currentPlayer = gameState.players[nextIndex];
updateUI();
if (gameState.currentPlayer === 'player') {
activatePlayerTurn();
} else {
setTimeout(computerPlay, 1500);
}
}
// 结束游戏
function endGame(winner) {
gameState.gameStarted = false;
gameState.gameEnded = true;
// 播放胜利音效
if (gameState.soundEnabled) {
winSound.currentTime = 0;
winSound.play();
}
// 停止背景音乐
bgMusic.pause();
bgMusic.currentTime = 0;
if (winner === 'player') {
// 玩家获胜,根据是否为功德主增加功德数
if (gameState.landlord === 'player') {
// 玩家是功德主,增加3000功德
const roundMerit = 3000;
addMeritScore(roundMerit, "功德主获胜");
updateGameStatus(`恭喜!您本局获得功德${roundMerit}点!累计功德${gameState.meritScore}点!`);
} else {
// 玩家是农民,增加1000功德
const roundMerit = 1000;
addMeritScore(roundMerit, "农民获胜");
updateGameStatus(`恭喜!您本局获得功德${roundMerit}点!累计功德${gameState.meritScore}点!`);
}
} else {
updateGameStatus("游戏结束!" + (winner === 'opponent1' ? "又见音" : "执力至") + "获得了胜利!");
}
// 禁用出牌按钮
startBtn.disabled = false;
playBtn.disabled = true;
passBtn.disabled = true;
hintBtn.disabled = true;
}
// 更新游戏状态信息
function updateGameStatus(message) {
gameStatusEl.textContent = message;
}
// 提示功能
function showHint() {
// 简单提示:选择最小的合法出牌
const cards = gameState.playerCards;
const hintCards = getPlayableCards(cards, gameState.lastPlayedCards, gameState.lastPlayer === 'player');
if (hintCards.length > 0) {
// 高亮提示的牌
const cardElements = document.querySelectorAll('#player-cards-container .player-card');
cardElements.forEach(cardEl => {
cardEl.classList.remove('selected');
const index = parseInt(cardEl.dataset.index);
const card = cards[index];
if (hintCards.some(hintCard =>
hintCard.value === card.value && hintCard.suit === card.suit)) {
cardEl.classList.add('selected');
}
});
updateGameStatus("已提示可出的牌");
} else {
updateGameStatus("没有合适的牌可出,建议点击'不出'");
}
}
// 显示功德回向界面
function showMeritTransfer() {
if (gameState.meritScore <= 0) {
updateGameStatus("您当前没有功德可以回向");
return;
}
// 显示当前功德数
currentMeritDisplay.textContent = gameState.meritScore;
// 隐藏输入框
lovedNameInputContainer.style.display = 'none';
lovedNameInput.value = '';
// 显示功德回向界面
meritTransferEl.classList.remove('hidden');
}
// 功德回向给一切众生
function meritToAllBeings() {
const currentMerit = gameState.meritScore;
// 清空功德数
gameState.meritScore = 0;
updateMeritScore();
// 显示确认消息
meritConfirmText.textContent = `您已将 ${currentMerit} 点功德全部回向给一切众生!`;
meritConfirmEl.classList.remove('hidden');
// 隐藏功德回向界面
meritTransferEl.classList.add('hidden');
// 更新游戏状态
updateGameStatus(`已将 ${currentMerit} 点功德回向一切众生`);
}
// 功德回向给所爱的人
function meritToLovedOnes() {
// 显示输入框
lovedNameInputContainer.style.display = 'block';
}
// 提交所爱人的名字
function submitLovedName() {
const lovedName = lovedNameInput.value.trim();
const currentMerit = gameState.meritScore;
if (!lovedName) {
updateGameStatus("请输入所爱人的名字");
return;
}
// 清空功德数
gameState.meritScore = 0;
updateMeritScore();
// 显示确认消息
meritConfirmText.textContent = `您已将 ${currentMerit} 点功德全部回向给 ${lovedName}!`;
meritConfirmEl.classList.remove('hidden');
// 隐藏功德回向界面和输入框
meritTransferEl.classList.add('hidden');
lovedNameInputContainer.style.display = 'none';
lovedNameInput.value = '';
// 更新游戏状态
updateGameStatus(`已将 ${currentMerit} 点功德回向给 ${lovedName}`);
}
// 取消功德回向
function cancelMeritTransfer() {
// 隐藏功德回向界面
meritTransferEl.classList.add('hidden');
lovedNameInputContainer.style.display = 'none';
lovedNameInput.value = '';
// 返回游戏
updateGameStatus("已取消功德回向");
}
// 音频控制 - 背景音乐开关
function toggleMusic() {
gameState.musicEnabled = !gameState.musicEnabled;
// 更新按钮文本和样式
if (gameState.musicEnabled) {
musicToggleBtn.textContent = "背景音乐: 开";
musicToggleBtn.classList.remove("music-on");
bgMusic.play().catch(e => console.log("播放音乐失败"));
} else {
musicToggleBtn.textContent = "背景音乐: 关";
musicToggleBtn.classList.add("music-on");
bgMusic.pause();
}
}
// 确认功德回向
function confirmMeritTransfer() {
meritConfirmEl.classList.add('hidden');
}
// 事件监听
startBtn.addEventListener('click', initGame);
playBtn.addEventListener('click', playerPlay);
passBtn.addEventListener('click', playerPass);
hintBtn.addEventListener('click', showHint);
musicToggleBtn.addEventListener('click', toggleMusic);
meritTransferBtn.addEventListener('click', showMeritTransfer);
callLandlordBtn.addEventListener('click', callLandlord);
passLandlordBtn.addEventListener('click', passLandlord);
meritToAllBtn.addEventListener('click', meritToAllBeings);
meritToLovedBtn.addEventListener('click', meritToLovedOnes);
meritCancelBtn.addEventListener('click', cancelMeritTransfer);
submitLovedNameBtn.addEventListener('click', submitLovedName);
meritConfirmBtn.addEventListener('click', confirmMeritTransfer);
// 初始UI更新
updateMeritScore();
updateUI();
// 初始设置背景音乐按钮样式
if (gameState.musicEnabled) {
musicToggleBtn.textContent = "背景音乐: 开";
musicToggleBtn.classList.remove("music-on");
} else {
musicToggleBtn.textContent = "背景音乐: 关";
musicToggleBtn.classList.add("music-on");
}
// 初始播放背景音乐
if (gameState.musicEnabled) {
setTimeout(() => {
bgMusic.play().catch(e => {
console.log("背景音乐自动播放被阻止,用户可能需要手动播放");
});
}, 1000);
}
// iOS触摸事件优化
document.addEventListener('touchstart', function() {}, {passive: true});
document.addEventListener('touchmove', function(e) {
// 防止滚动时页面缩放
if (e.scale !== 1) {
e.preventDefault();
}
}, {passive: false});
</script>
</body>
</html>
更多推荐



所有评论(0)