我将创建一个简单的国标麻将一抽胡游戏,玩家每次随机获得一个听牌牌型,然后从一组牌中抽取一张,看是否能胡牌。
思路分析
-
随机生成各种国标麻将听牌牌型(缺一张即可胡牌)
-
显示当前牌型,其中一张牌为"?"表示待抽牌
-
准备一组待抽牌,其中包含可以胡牌的牌和其他无关牌
-
玩家点击抽牌,显示抽牌结果和是否胡牌
下面是完整代码实现:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>国标麻将一抽胡</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Microsoft YaHei', sans-serif;
}
body {
background: linear-gradient(135deg, #1a5f23 0%, #0d3b1a 100%);
color: #fff;
min-height: 100vh;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.container {
max-width: 900px;
width: 100%;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 20px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
margin-top: 20px;
}
header {
text-align: center;
margin-bottom: 30px;
width: 100%;
}
h1 {
font-size: 2.8rem;
color: #ffde59;
text-shadow: 3px 3px 0 #d35400;
margin-bottom: 10px;
letter-spacing: 2px;
}
.subtitle {
font-size: 1.2rem;
color: #b8e994;
margin-bottom: 20px;
}
.game-area {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 30px;
}
.hand-section, .draw-section {
flex: 1;
min-width: 300px;
background-color: rgba(10, 60, 20, 0.7);
border-radius: 15px;
padding: 25px;
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
}
.section-title {
font-size: 1.8rem;
color: #ffde59;
margin-bottom: 20px;
text-align: center;
border-bottom: 2px solid #ffde59;
padding-bottom: 10px;
}
.current-hand {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
margin-bottom: 25px;
min-height: 120px;
}
.tile {
width: 70px;
height: 100px;
background-color: #fff3cd;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 1.8rem;
font-weight: bold;
color: #222;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
transition: all 0.3s;
cursor: default;
position: relative;
overflow: hidden;
}
.tile::before {
content: '';
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
.tile.character {
color: #c0392b;
}
.tile.bamboo {
color: #27ae60;
}
.tile.dot {
color: #2980b9;
}
.tile.wind {
color: #8e44ad;
}
.tile.dragon {
color: #d35400;
}
.tile.missing {
background-color: #ffcccc;
color: #c0392b;
font-size: 2.5rem;
}
.tile-value {
font-size: 1.4rem;
}
.tile-type {
font-size: 1rem;
margin-top: 5px;
}
.hand-info {
background-color: rgba(0, 0, 0, 0.3);
padding: 15px;
border-radius: 10px;
text-align: center;
margin-top: 20px;
}
.hand-info h3 {
color: #ffde59;
margin-bottom: 10px;
}
.draw-options {
display: flex;
flex-wrap: wrap;
gap: 15px;
justify-content: center;
margin-bottom: 30px;
}
.draw-tile {
width: 80px;
height: 110px;
background-color: #fff3cd;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 1.8rem;
font-weight: bold;
color: #222;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
transition: all 0.3s;
cursor: pointer;
position: relative;
}
.draw-tile:hover {
transform: translateY(-10px);
box-shadow: 0 15px 20px rgba(0, 0, 0, 0.4);
}
.draw-tile:active {
transform: translateY(-5px);
}
.draw-tile.selected {
transform: translateY(-10px);
box-shadow: 0 0 20px #ffde59;
border: 3px solid #ffde59;
}
.draw-button {
display: block;
margin: 30px auto;
padding: 15px 40px;
font-size: 1.5rem;
background: linear-gradient(to bottom, #ffde59, #f39c12);
color: #8b4513;
border: none;
border-radius: 50px;
cursor: pointer;
font-weight: bold;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
transition: all 0.3s;
}
.draw-button:hover {
transform: scale(1.05);
box-shadow: 0 15px 25px rgba(0, 0, 0, 0.4);
}
.draw-button:active {
transform: scale(0.98);
}
.draw-button:disabled {
background: #cccccc;
color: #666666;
cursor: not-allowed;
transform: none;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
}
.result-section {
margin-top: 40px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 15px;
padding: 25px;
text-align: center;
display: none;
}
.result-section.show {
display: block;
animation: fadeIn 0.8s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.result-title {
font-size: 2.2rem;
margin-bottom: 15px;
}
.result-win {
color: #2ecc71;
}
.result-lose {
color: #e74c3c;
}
.result-details {
font-size: 1.3rem;
line-height: 1.8;
margin-bottom: 20px;
}
.winning-tile {
display: inline-block;
padding: 5px 15px;
background-color: #fff3cd;
color: #c0392b;
border-radius: 5px;
font-weight: bold;
font-size: 1.4rem;
margin: 0 5px;
}
.stats {
display: flex;
justify-content: space-around;
margin-top: 30px;
background-color: rgba(0, 0, 0, 0.3);
padding: 20px;
border-radius: 15px;
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 2.5rem;
font-weight: bold;
color: #ffde59;
}
.stat-label {
font-size: 1.1rem;
color: #b8e994;
}
.rules {
margin-top: 30px;
background-color: rgba(10, 60, 20, 0.7);
padding: 20px;
border-radius: 15px;
font-size: 1.1rem;
line-height: 1.6;
}
.rules h3 {
color: #ffde59;
margin-bottom: 10px;
}
.rules ul {
padding-left: 20px;
}
.rules li {
margin-bottom: 8px;
}
footer {
margin-top: 30px;
text-align: center;
color: #b8e994;
font-size: 0.9rem;
width: 100%;
padding-top: 20px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
@media (max-width: 768px) {
.game-area {
flex-direction: column;
}
.hand-section, .draw-section {
width: 100%;
}
h1 {
font-size: 2rem;
}
.tile {
width: 55px;
height: 80px;
font-size: 1.5rem;
}
.draw-tile {
width: 65px;
height: 90px;
font-size: 1.5rem;
}
}
</style>
</head>
<body>
<header>
<h1>国标麻将一抽胡</h1>
<p class="subtitle">随机生成听牌牌型,只需一抽即可胡牌!</p>
</header>
<div class="container">
<div class="game-area">
<div class="hand-section">
<h2 class="section-title">当前牌型</h2>
<div class="current-hand" id="currentHand">
<!-- 牌型将通过JS动态生成 -->
</div>
<div class="hand-info">
<h3>听牌状态</h3>
<p id="handDescription">您当前有一个听牌牌型,只需要抽取正确的牌即可胡牌!</p>
</div>
</div>
<div class="draw-section">
<h2 class="section-title">抽取一张牌</h2>
<div class="draw-options" id="drawOptions">
<!-- 抽牌选项将通过JS动态生成 -->
</div>
<button class="draw-button" id="drawButton">抽取选中的牌</button>
</div>
</div>
<div class="result-section" id="resultSection">
<!-- 结果将通过JS动态生成 -->
</div>
<div class="stats">
<div class="stat-item">
<div class="stat-value" id="gamesPlayed">0</div>
<div class="stat-label">游戏次数</div>
</div>
<div class="stat-item">
<div class="stat-value" id="wins">0</div>
<div class="stat-label">胡牌次数</div>
</div>
<div class="stat-item">
<div class="stat-value" id="winRate">0%</div>
<div class="stat-label">胡牌率</div>
</div>
</div>
<div class="rules">
<h3>游戏规则</h3>
<ul>
<li>系统随机生成一个国标麻将听牌牌型(缺一张即可胡牌)</li>
<li>牌型中的"?"表示缺的那张牌</li>
<li>从右侧5张牌中选择一张抽取</li>
<li>如果抽到正确的牌,则胡牌!否则不胡</li>
<li>每次游戏都会随机生成新的牌型和待抽牌</li>
<li>国标麻将胡牌牌型包括:平胡、碰碰胡、混一色等</li>
</ul>
</div>
</div>
<footer>
<p>国标麻将一抽胡游戏 | 仅用于娱乐目的 | 使用HTML5、CSS3和JavaScript开发</p>
</footer>
<script>
// 麻将牌数据
const mahjongTiles = {
characters: ['1万', '2万', '3万', '4万', '5万', '6万', '7万', '8万', '9万'],
bamboos: ['1条', '2条', '3条', '4条', '5条', '6条', '7条', '8条', '9条'],
dots: ['1筒', '2筒', '3筒', '4筒', '5筒', '6筒', '7筒', '8筒', '9筒'],
winds: ['东风', '南风', '西风', '北风'],
dragons: ['红中', '发财', '白板']
};
// 所有牌的数组
const allTiles = [
...mahjongTiles.characters,
...mahjongTiles.bamboos,
...mahjongTiles.dots,
...mahjongTiles.winds,
...mahjongTiles.dragons
];
// 预定义的听牌牌型(13张牌+1张听的牌)
const winningHands = [
{
name: "平胡",
tiles: ["1万", "2万", "3万", "4筒", "5筒", "6筒", "7条", "8条", "9条", "东风", "东风", "东风", "5万", "7万"],
winningTile: "6万",
description: "平胡:基本胡牌牌型,由四组顺子或刻子加一对将组成"
},
{
name: "碰碰胡",
tiles: ["1万", "1万", "1万", "3条", "3条", "3条", "5筒", "5筒", "5筒", "北风", "北风", "北风", "红中"],
winningTile: "红中",
description: "碰碰胡:由四组刻子(三张相同)加一对将组成"
},
{
name: "混一色",
tiles: ["1万", "2万", "3万", "4万", "5万", "6万", "7万", "8万", "9万", "东风", "东风", "东风", "5万"],
winningTile: "5万",
description: "混一色:由一种花色牌加上字牌组成的胡牌牌型"
},
{
name: "清一色",
tiles: ["1条", "2条", "3条", "4条", "4条", "4条", "5条", "6条", "7条", "8条", "8条", "8条", "9条"],
winningTile: "9条",
description: "清一色:全部由同一种花色的牌组成的胡牌牌型"
},
{
name: "全带幺",
tiles: ["1万", "1万", "1万", "9万", "9万", "9万", "1筒", "2筒", "3筒", "7筒", "8筒", "9筒", "白板"],
winningTile: "白板",
description: "全带幺:所有顺子、刻子、将牌都包含1或9"
},
{
name: "七对",
tiles: ["1万", "1万", "3万", "3万", "5万", "5万", "7万", "7万", "9条", "9条", "东风", "东风", "发财"],
winningTile: "发财",
description: "七对:由七个对子组成的特殊胡牌牌型"
},
{
name: "十三幺",
tiles: ["1万", "9万", "1条", "9条", "1筒", "9筒", "东风", "南风", "西风", "北风", "红中", "发财", "白板"],
winningTile: "白板",
description: "十三幺:由13种幺九牌各一张,再加上其中任意一张组成"
}
];
// 游戏状态
let gameState = {
currentHand: null,
winningTile: null,
drawOptions: [],
selectedTileIndex: null,
gamesPlayed: 0,
wins: 0
};
// DOM元素
const currentHandEl = document.getElementById('currentHand');
const drawOptionsEl = document.getElementById('drawOptions');
const drawButton = document.getElementById('drawButton');
const resultSection = document.getElementById('resultSection');
const handDescription = document.getElementById('handDescription');
const gamesPlayedEl = document.getElementById('gamesPlayed');
const winsEl = document.getElementById('wins');
const winRateEl = document.getElementById('winRate');
// 初始化游戏
function initGame() {
// 随机选择一个听牌牌型
const handIndex = Math.floor(Math.random() * winningHands.length);
const selectedHand = winningHands[handIndex];
// 复制牌型数组
const handTiles = [...selectedHand.tiles];
gameState.winningTile = selectedHand.winningTile;
// 随机选择一张牌作为缺失的牌(用"?"表示)
const missingTileIndex = Math.floor(Math.random() * handTiles.length);
gameState.currentHand = {
tiles: handTiles,
missingIndex: missingTileIndex,
name: selectedHand.name,
description: selectedHand.description
};
// 更新描述
handDescription.textContent = selectedHand.description;
// 生成待抽牌选项(包含正确的牌和4张随机牌)
gameState.drawOptions = generateDrawOptions(gameState.winningTile);
gameState.selectedTileIndex = null;
// 更新UI
renderCurrentHand();
renderDrawOptions();
// 隐藏结果
resultSection.classList.remove('show');
// 启用抽牌按钮
drawButton.disabled = true;
drawButton.textContent = "抽取选中的牌";
}
// 生成待抽牌选项
function generateDrawOptions(winningTile) {
const options = [winningTile];
// 添加4张随机牌(不能与正确牌重复)
while (options.length < 5) {
const randomTile = allTiles[Math.floor(Math.random() * allTiles.length)];
if (!options.includes(randomTile) && randomTile !== winningTile) {
options.push(randomTile);
}
}
// 打乱数组
return shuffleArray(options);
}
// 打乱数组(Fisher-Yates洗牌算法)
function shuffleArray(array) {
const newArray = [...array];
for (let i = newArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
newArray\[i\], newArray\[j\]\] = \[newArray\[j\], newArray\[i\]\]; } return newArray; } // 渲染当前手牌 function renderCurrentHand() { currentHandEl.innerHTML = ''; const hand = gameState.currentHand; hand.tiles.forEach((tile, index) =\> { const tileEl = document.createElement('div'); tileEl.className = 'tile'; if (index === hand.missingIndex) { tileEl.classList.add('missing'); tileEl.innerHTML = '?'; } else { // 根据牌的类型添加对应的CSS类 if (tile.includes('万')) tileEl.classList.add('character'); else if (tile.includes('条')) tileEl.classList.add('bamboo'); else if (tile.includes('筒')) tileEl.classList.add('dot'); else if (tile.includes('风')) tileEl.classList.add('wind'); else tileEl.classList.add('dragon'); const tileValue = tile.replace(/\[\^0-9\]/g, '') \|\| tile.substring(0, 1); const tileType = tile.replace(/\[0-9\]/g, '') \|\| tile.substring(1); tileEl.innerHTML = \` \
胡牌牌型:\${gameState.currentHand.name}\ \
${gameState.currentHand.description} \`; } else { resultDetails.innerHTML = \` 您抽中了\${selectedTile}\,但这不是胡牌所需要的牌。 \
胡牌需要的牌是:\${gameState.winningTile}\ \
胡牌牌型:\${gameState.currentHand.name}\ \`; } const restartButton = document.createElement('button'); restartButton.className = 'draw-button'; restartButton.textContent = '再来一局'; restartButton.onclick = restartGame; resultSection.appendChild(resultTitle); resultSection.appendChild(resultDetails); resultSection.appendChild(restartButton); resultSection.classList.add('show'); } // 重新开始游戏 function restartGame() { initGame(); drawButton.onclick = drawSelectedTile; } // 更新统计 function updateStats() { gamesPlayedEl.textContent = gameState.gamesPlayed; winsEl.textContent = gameState.wins; const winRate = gameState.gamesPlayed \> 0 ? Math.round((gameState.wins / gameState.gamesPlayed) \* 100) : 0; winRateEl.textContent = \`${winRate}%\`; } // 初始化事件监听器 drawButton.addEventListener('click', drawSelectedTile); // 初始化游戏 initGame(); // 添加键盘事件支持 document.addEventListener('keydown', (e) =\> { if (e.key \>= '1' \&\& e.key \<= '5') { const index = parseInt(e.key) - 1; if (index \< gameState.drawOptions.length) { selectDrawTile(index); } } else if (e.key === 'Enter' \|\| e.key === ' ') { if (!drawButton.disabled) { drawSelectedTile(); } else if (resultSection.classList.contains('show')) { restartGame(); } } else if (e.key === 'r' \|\| e.key === 'R') { restartGame(); } }); \ \