|
|
本帖最后由 a66 于 2026-3-21 11:14 编辑
- <!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>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
- }
- body {
- background: linear-gradient(135deg, #1a2a3a, #0d1620);
- color: #fff;
- min-height: 100vh;
- overflow-x: hidden;
- }
- .game-container {
- max-width: 1600px;
- margin: 0 auto;
- padding: 20px;
- }
- .game-header {
- text-align: center;
- padding: 24px 20px;
- margin-bottom: 20px;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 20px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
- backdrop-filter: blur(10px);
- border: 1px solid rgba(255, 255, 255, 0.1);
- }
- .game-title {
- font-size: 40px;
- color: #ffd700;
- text-shadow: 0 0 15px rgba(255, 215, 0, 0.6);
- letter-spacing: 2px;
- }
- .tip-panel {
- text-align: center;
- padding: 16px;
- background: rgba(255, 215, 0, 0.08);
- border: 1px solid rgba(255, 215, 0, 0.4);
- border-radius: 16px;
- font-size: 18px;
- color: #ffd700;
- font-weight: bold;
- margin-bottom: 20px;
- box-shadow: 0 4px 20px rgba(255, 215, 0, 0.15);
- }
- .score-panel {
- display: flex;
- justify-content: space-around;
- background: rgba(255, 255, 255, 0.05);
- padding: 20px;
- border-radius: 18px;
- margin-bottom: 20px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
- backdrop-filter: blur(10px);
- border: 1px solid rgba(255, 255, 255, 0.1);
- }
- .score-item {
- text-align: center;
- min-width: 120px;
- }
- .score-label {
- font-size: 15px;
- color: #ccc;
- margin-bottom: 6px;
- }
- .score-num {
- font-size: 30px;
- font-weight: bold;
- color: #ffd700;
- text-shadow: 0 0 10px rgba(255, 215, 0, 0.4);
- }
- .suit-select {
- display: flex;
- justify-content: center;
- gap: 18px;
- margin-bottom: 20px;
- }
- .suit-btn {
- width: 68px;
- height: 68px;
- font-size: 34px;
- border-radius: 14px;
- border: none;
- background: rgba(255, 255, 255, 0.9);
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
- cursor: pointer;
- transition: all 0.25s ease;
- }
- .suit-btn:hover {
- transform: scale(1.08);
- }
- .suit-btn.active {
- transform: scale(1.12);
- box-shadow: 0 0 20px #ffd700;
- background: #ffd700;
- }
- .players-area {
- display: grid;
- grid-template-areas:
- ". top ."
- "left center right"
- ". bottom .";
- grid-template-columns: 1fr 2.4fr 1fr;
- gap: 22px;
- }
- .player {
- background: rgba(255, 255, 255, 0.04);
- border-radius: 20px;
- padding: 18px;
- border: 1px solid rgba(255, 255, 255, 0.08);
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
- backdrop-filter: blur(10px);
- transition: all 0.3s ease;
- }
- .player.active {
- border-color: rgba(255, 215, 0, 0.6);
- box-shadow: 0 0 25px rgba(255, 215, 0, 0.25);
- background: rgba(255, 215, 0, 0.06);
- }
- .player.banker {
- border-color: rgba(255, 80, 80, 0.6);
- box-shadow: 0 0 25px rgba(255, 80, 80, 0.2);
- background: rgba(255, 80, 80, 0.05);
- }
- .player-name {
- text-align: center;
- font-size: 18px;
- margin-bottom: 16px;
- color: #fff;
- text-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
- }
- .player-top { grid-area: top; }
- .player-left { grid-area: left; }
- .player-right { grid-area: right; }
- .player-bottom { grid-area: bottom; }
- .center-area {
- grid-area: center;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
- .play-area {
- width: 420px;
- height: 260px;
- background: rgba(255, 255, 255, 0.06);
- border-radius: 22px;
- border: 2px dashed rgba(255, 215, 0, 0.4);
- display: flex;
- align-items: center;
- justify-content: center;
- flex-wrap: wrap;
- gap: 10px;
- padding: 20px;
- margin-bottom: 20px;
- box-shadow: 0 0 30px rgba(255, 215, 0, 0.1);
- }
- .cards-container {
- display: flex;
- flex-wrap: wrap;
- gap: 7px;
- justify-content: center;
- max-height: 210px;
- overflow-y: auto;
- padding: 6px;
- }
- .poker-card {
- width: 46px;
- height: 68px;
- background: linear-gradient(160deg, #ffffff, #f6f6f6);
- color: #000;
- border-radius: 8px;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- padding: 5px;
- font-weight: bold;
- cursor: pointer;
- transition: transform 0.25s ease, box-shadow 0.25s ease;
- border: 1px solid rgba(0,0,0,0.1);
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
- position: relative;
- }
- /* 标准纸质扑克牌背面样式 */
- .poker-card.back {
- background: linear-gradient(45deg, #1a237e, #d32f2f);
- background-image: repeating-linear-gradient(
- 45deg,
- rgba(255,255,255,0.1),
- rgba(255,255,255,0.1) 5px,
- rgba(0,0,0,0.1) 5px,
- rgba(0,0,0,0.1) 10px
- );
- cursor: default;
- overflow: hidden;
- }
- .poker-card.back::before {
- content: "";
- position: absolute;
- top: 50%;
- left: 50%;
- width: 20px;
- height: 20px;
- background: rgba(255,255,255,0.2);
- border-radius: 50%;
- transform: translate(-50%, -50%);
- }
- .poker-card.back:hover {
- transform: none;
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
- z-index: auto;
- }
- .poker-card:hover {
- transform: translateY(-10px) scale(1.06);
- box-shadow: 0 12px 24px rgba(0, 0, 0, 0.35);
- z-index: 10;
- }
- .poker-card.selected {
- transform: translateY(-8px);
- border: 3px solid #00d8ff;
- box-shadow: 0 0 16px #00d8ff;
- background: linear-gradient(160deg, #e0f7ff, #c7f2ff);
- }
- .poker-card.main {
- border: 2px solid #ff4444;
- box-shadow: 0 0 10px rgba(255, 68, 68, 0.4);
- }
- .poker-card.red {
- color: #e53935;
- }
- .card-top, .card-bottom {
- font-size: 14px;
- }
- .card-bottom {
- align-self: flex-end;
- transform: rotate(180deg);
- }
- .card-center {
- font-size: 22px;
- text-align: center;
- }
- .cards-container::-webkit-scrollbar {
- width: 6px;
- }
- .cards-container::-webkit-scrollbar-thumb {
- background: rgba(255, 215, 0, 0.4);
- border-radius: 3px;
- }
- .control-panel {
- text-align: center;
- margin-top: 28px;
- padding: 24px 20px;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 20px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
- backdrop-filter: blur(10px);
- border: 1px solid rgba(255, 255, 255, 0.1);
- }
- .btn {
- padding: 14px 28px;
- margin: 0 10px 10px;
- font-size: 16px;
- font-weight: 500;
- border-radius: 14px;
- border: none;
- background: linear-gradient(145deg, #ffd700, #e6bc00);
- color: #111;
- cursor: pointer;
- transition: all 0.25s ease;
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
- letter-spacing: 1px;
- }
- .btn:hover {
- transform: translateY(-3px);
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
- background: linear-gradient(145deg, #ffed4e, #ffd700);
- }
- .btn:disabled {
- background: #444;
- color: #888;
- cursor: not-allowed;
- transform: none;
- box-shadow: none;
- }
- @media (max-width: 768px) {
- .players-area {
- grid-template-areas: "top" "left" "center" "right" "bottom";
- grid-template-columns: 1fr;
- gap: 16px;
- }
- .game-title { font-size: 28px; }
- .play-area { width: 92%; height: 200px; }
- .poker-card { width: 40px; height: 60px; }
- .btn { padding: 12px 20px; font-size: 14px; }
- }
- </style>
- </head>
- <body>
- <div class="game-container">
- <div class="game-header">
- <h1 class="game-title">🎆 四副牌拖拉机🎆</h1>
- </div>
- <div class="tip-panel" id="tip-text">老哥,点【开始新游戏】咱们开干!</div>
- <div class="suit-select" id="suit-select" style="display:none;">
- <button class="suit-btn" data-suit="♠">♠</button>
- <button class="suit-btn" data-suit="♥">♥</button>
- <button class="suit-btn" data-suit="♣">♣</button>
- <button class="suit-btn" data-suit="♦">♦</button>
- </div>
- <div class="score-panel">
- <div class="score-item">
- <div class="score-label">庄家等级</div>
- <div class="score-num" id="banker-level">2</div>
- </div>
- <div class="score-item">
- <div class="score-label">闲家得分</div>
- <div class="score-num" id="player-score">0</div>
- </div>
- <div class="score-item">
- <div class="score-label">底牌数量</div>
- <div class="score-num" id="di-pai">12</div>
- </div>
- <div class="score-item">
- <div class="score-label">当前主牌</div>
- <div class="score-num" id="main-suit">2</div>
- </div>
- </div>
- <div class="players-area">
- <div class="player player-top" id="p-top">
- <div class="player-name">玩家2(上家)</div>
- <div class="cards-container" id="top-cards"></div>
- </div>
- <div class="player player-left" id="p-left">
- <div class="player-name">玩家3(对家)</div>
- <div class="cards-container" id="left-cards"></div>
- </div>
- <div class="center-area">
- <div class="play-area" id="play-area">
- <div style="color:gold; font-size:24px;">等待出牌</div>
- </div>
- <div style="color:gold; font-size:20px; text-shadow:0 0 10px gold;" id="main-info">主花色:未选择</div>
- </div>
- <div class="player player-right" id="p-right">
- <div class="player-name">玩家4(下家)</div>
- <div class="cards-container" id="right-cards"></div>
- </div>
- <div class="player player-bottom active" id="p-bottom">
- <div class="player-name">我(庄家)</div>
- <div class="cards-container" id="bottom-cards"></div>
- </div>
- </div>
- <div class="control-panel">
- <button class="btn" id="start-btn">✅ 开始新游戏</button>
- <button class="btn" id="bury-btn" disabled>📥 埋底牌</button>
- <button class="btn" id="hint-bury" disabled>💡 帮我选底</button>
- <button class="btn" id="play-card" disabled>🃏 出牌</button>
- <button class="btn" id="hint-play" disabled>💡 帮我出牌</button>
- <button class="btn" id="pass-btn" disabled>⏭️ 不出</button>
- <button class="btn" id="reset-btn">🔄 重置</button>
- </div>
- </div>
- <script>
- const cardTypes = ['♠','♥','♣','♦'];
- const cardNumbers = ['2','3','4','5','6','7','8','9','10','J','Q','K','A'];
- const numOrder = {'A':0,'K':1,'Q':2,'J':3,'10':4,'9':5,'8':6,'7':7,'6':8,'5':9,'4':10,'3':11,'2':12};
- let game = {
- phase: 'init',
- mainLevel: '2',
- mainSuit: null,
- banker: 3,
- bankerLevel: 2,
- playerScore: 0,
- players: [[],[],[],[]],
- diPai: [],
- selected: [],
- roundCards: [],
- leadSuit: null,
- leadType: null,
- currentPlayer: 3
- };
- function shuffle(a) {
- for(let i=a.length-1;i>0;i--){
- const j=Math.floor(Math.random()*(i+1));
- [a[i],a[j]]=[a[j],a[i]];
- }
- }
- function isMain(card) {
- return card.num === game.mainLevel || card.type === game.mainSuit;
- }
- function sortCards(cards) {
- return [...cards].sort((a,b)=>{
- const am = isMain(a), bm = isMain(b);
- if(am&&!bm)return -1; if(!am&&bm)return 1;
- if(a.type!==b.type)return ['♠','♥','♣','♦'].indexOf(a.type)-['♠','♥','♣','♦'].indexOf(b.type);
- return numOrder[a.num]-numOrder[b.num];
- });
- }
- function getCardGroupType(cards) {
- if(cards.length===1)return 'single';
- const cnt = {};
- cards.forEach(c=>cnt[c.num]=(cnt[c.num]||0)+1);
- const vals = Object.values(cnt).sort((a,b)=>b-a);
- const nums = Object.keys(cnt).sort((a,b)=>numOrder[a]-numOrder[b]);
- let isTractor = true;
- for(let i=1;i<nums.length;i++){
- if(numOrder[nums[i]]-numOrder[nums[i-1]]!==1)isTractor=false;
- }
- if(isTractor){
- if(vals.every(v=>v===2))return 'tractor';
- if(vals.every(v=>v===3))return 'tractor3';
- if(vals.every(v=>v===4))return 'tractor4';
- }
- if(vals[0]===4)return 'four';
- if(vals[0]===3)return 'triple';
- if(vals[0]===2)return 'pair';
- return 'invalid';
- }
- function renderCard(card, idx){
- const cls = isMain(card)?'poker-card main':'poker-card';
- const red = card.type==='♥'||card.type==='♦'?'red':'';
- return `<div class="${cls} ${red}" data-idx="${idx}" onclick="selectCard(${idx})">
- <div class="card-top">${card.num}</div>
- <div class="card-center">${card.type}</div>
- <div class="card-bottom">${card.num}</div>
- </div>`;
- }
- // 渲染其他玩家牌背面
- function renderBackCards(count){
- let html = '';
- for(let i=0;i<count;i++){
- html += '<div class="poker-card back"></div>';
- }
- return html;
- }
- function renderAll(){
- // 上家、对家、下家:只显示背面
- document.getElementById('top-cards').innerHTML = renderBackCards(game.players[0].length);
- document.getElementById('left-cards').innerHTML = renderBackCards(game.players[1].length);
- document.getElementById('right-cards').innerHTML = renderBackCards(game.players[2].length);
-
- // 自己:显示真实牌面
- document.getElementById('bottom-cards').innerHTML =
- sortCards(game.players[3]).map((c,j)=>renderCard(c,j)).join('');
- document.getElementById('banker-level').innerText=game.bankerLevel;
- document.getElementById('player-score').innerText=game.playerScore;
- document.getElementById('di-pai').innerText=game.diPai.length;
- document.getElementById('main-info').innerText=game.mainSuit?`主花色:${game.mainSuit} ${game.mainLevel}`:'主花色:未选择';
- document.querySelectorAll('.player').forEach((el,i)=>{
- el.classList.toggle('active',i===game.currentPlayer);
- el.classList.toggle('banker',i===game.banker);
- });
- }
- function setTip(t){document.getElementById('tip-text').innerText=t;}
- function selectCard(idx){
- if(game.phase!=='bury'&&game.phase!=='play')return;
- const card = document.querySelectorAll('#bottom-cards .poker-card')[idx];
- if(!card)return;
- card.classList.toggle('selected');
- const found = game.selected.includes(idx);
- if(found)game.selected=game.selected.filter(x=>x!==idx);
- else game.selected.push(idx);
- }
- document.getElementById('hint-bury').onclick = () => {
- if(game.phase !== 'bury') return;
- document.querySelectorAll('.poker-card.selected').forEach(e=>e.classList.remove('selected'));
- game.selected = [];
- const hand = sortCards(game.players[3]);
- const scoreValues = {'5':5,'10':10,'K':10};
- const scored = hand.filter(c=>scoreValues[c.num]);
- const nonMainNonScore = hand.filter(c=>!isMain(c)&&!scoreValues[c.num]);
- let suggest = [];
- if(nonMainNonScore.length>=12){
- suggest = nonMainNonScore.slice(0,12);
- }else{
- suggest = [...nonMainNonScore,...scored].slice(0,12);
- }
- suggest.forEach(card=>{
- const idx = hand.findIndex(x=>x===card);
- if(idx!==-1) selectCard(idx);
- });
- setTip("✅ 帮你选好底啦!尽量埋副牌、不留分,放心埋!");
- };
- document.getElementById('hint-play').onclick = () => {
- if(game.phase !== 'play' || game.currentPlayer !==3) return;
- document.querySelectorAll('.poker-card.selected').forEach(e=>e.classList.remove('selected'));
- game.selected = [];
- const hand = sortCards(game.players[3]);
- let suggest = [];
- if(game.roundCards.length === 0){
- const mainCards = hand.filter(isMain);
- suggest = mainCards.length >= 1 ? [mainCards[0]] : [hand[0]];
- setTip("💡 第一次出牌,建议先出主牌,稳得住!");
- }else{
- const needSuit = game.leadSuit;
- const needLen = game.roundCards[0].cards.length;
- const sameSuit = hand.filter(c=>c.type === needSuit);
- if(sameSuit.length >= needLen){
- suggest = sameSuit.slice(0, needLen);
- setTip("👉 有这花色,必须跟!我帮你选好了同花色!");
- }else{
- suggest = hand.slice(0, needLen);
- setTip("⚠️ 没这花色了,随便垫,我帮你挑几张垫!");
- }
- }
- suggest.forEach(card=>{
- const idx = hand.findIndex(x=>x===card);
- if(idx!==-1) selectCard(idx);
- });
- };
- document.getElementById('start-btn').onclick=()=>{
- let deck=[];
- for(let d=0;d<4;d++)cardTypes.forEach(t=>cardNumbers.forEach(n=>deck.push({type:t,num:n})));
- shuffle(deck);
- game.players=[[],[],[],[]];
- for(let i=0;i<4;i++)game.players[i]=deck.splice(0,51);
- game.diPai=deck.splice(0,12);
- game.phase='selectSuit';
- game.playerScore=0;
- document.getElementById('suit-select').style.display='flex';
- document.getElementById('hint-bury').disabled = true;
- document.getElementById('hint-play').disabled = true;
- setTip("庄家请亮主!选个你最牛的花色当主!");
- renderAll();
- };
- document.querySelectorAll('.suit-btn').forEach(btn=>{
- btn.onclick=()=>{
- game.mainSuit=btn.dataset.suit;
- game.phase='bury';
- document.getElementById('suit-select').style.display='none';
- document.getElementById('bury-btn').disabled=false;
- document.getElementById('hint-bury').disabled = false;
- setTip("该埋底啦!选12张牌,点【帮我选底】更省事~");
- renderAll();
- }
- });
- document.getElementById('bury-btn').onclick=()=>{
- if(game.selected.length!==12){setTip("兄弟,必须选12张埋底,不多不少!");return;}
- const sorted = sortCards(game.players[3]);
- game.selected.sort((a,b)=>b-a).forEach(i=>{
- const card = sorted[i];
- const real = game.players[3].findIndex(c=>c===card);
- if(real!==-1)game.diPai.push(game.players[3].splice(real,1)[0]);
- });
- game.selected=[];
- game.phase='play';
- document.getElementById('bury-btn').disabled=true;
- document.getElementById('hint-bury').disabled=true;
- document.getElementById('play-card').disabled=false;
- document.getElementById('hint-play').disabled=false;
- document.getElementById('pass-btn').disabled=false;
- setTip("✅ 埋底完成!开打!该你出牌啦~");
- renderAll();
- };
- document.getElementById('play-card').onclick=()=>{
- if(game.currentPlayer!==3||game.selected.length===0){setTip("先点牌再出牌啊老哥!");return;}
- const sorted = sortCards(game.players[3]);
- const cards = game.selected.map(i=>sorted[i]);
- const type = getCardGroupType(cards);
- if(type==='invalid'){setTip("兄弟,这牌型不对,出不了!");return;}
- if(game.roundCards.length===0){
- game.leadSuit=cards[0].type;
- game.leadType=type;
- }
- cards.forEach(c=>{
- const real=game.players[3].findIndex(x=>x===c);
- if(real!==-1)game.players[3].splice(real,1);
- });
- game.roundCards.push({p:3,cards,type});
- game.selected=[];
- document.querySelectorAll('.poker-card.selected').forEach(x=>x.classList.remove('selected'));
- showCards(cards);
- nextPlayer();
- renderAll();
- };
- function showCards(cards){
- const area=document.getElementById('play-area');
- area.innerHTML=cards.map(c=>`<div class="poker-card ${c.type=='♥'||c.type=='♦'?'red':''} ${isMain(c)?'main':''}">
- <div class="card-top">${c.num}</div><div class="card-center">${c.type}</div><div class="card-bottom">${c.num}</div>
- </div>`).join('');
- }
- function nextPlayer(){
- game.currentPlayer=(game.currentPlayer+1)%4;
- if(game.currentPlayer===3){endRound();return;}
- setTimeout(()=>{
- const p=game.currentPlayer;
- const hand=game.players[p];
- let play=[];
- if(game.roundCards.length===0){
- play=[hand.shift()];
- game.leadSuit=play[0].type;
- game.leadType='single';
- }else{
- const suitCards=hand.filter(c=>c.type===game.leadSuit);
- if(suitCards.length>=game.roundCards[0].cards.length){
- play=suitCards.slice(0,game.roundCards[0].cards.length);
- play.forEach(c=>{
- const i=game.players[p].findIndex(x=>x===c);
- if(i!==-1)game.players[p].splice(i,1);
- });
- }else{
- play=hand.splice(0,game.roundCards[0].cards.length);
- }
- }
- game.roundCards.push({p,cards:play,type:getCardGroupType(play)});
- showCards(play);
- game.currentPlayer=(game.currentPlayer+1)%4;
- setTip("轮到你啦,该你出牌!");
- renderAll();
- },1000);
- }
- function endRound(){
- let win=0, maxPower=-1;
- let score=0;
- game.roundCards.forEach(({cards})=>{
- cards.forEach(c=>{
- if(c.num==='5')score+=5;
- if(c.num==='10'||c.num==='K')score+=10;
- });
- });
- const power={tractor4:100,tractor3:90,four:80,tractor:70,triple:60,pair:50,single:40};
- game.roundCards.forEach(({p,cards,type})=>{
- let pow=power[type]||0;
- if(isMain(cards[0]))pow+=20;
- if(pow>maxPower){maxPower=pow;win=p;}
- });
- if([0,2].includes(win))game.playerScore+=score;
- const lastType=game.roundCards[win].type;
- if(game.players.every(p=>p.length===0)){
- let diScore=0;
- game.diPai.forEach(c=>{
- if(c.num==='5')diScore+=5;
- if(c.num==='10'||c.num==='K')diScore+=10;
- });
- if(['pair','triple','four','tractor','tractor3','tractor4'].includes(lastType))diScore*=2;
- game.playerScore+=diScore;
- }
- game.roundCards=[];
- game.leadSuit=null;
- game.leadType=null;
- document.getElementById('play-area').innerHTML=`<div style="color:gold; font-size:22px; text-shadow:0 0 10px gold">这墩玩家${win+1}拿了!</div>`;
- if(game.players.every(p=>p.length===0)){
- if(game.playerScore>=400){
- setTip(`🏆 闲家干到${game.playerScore}分,破庄成功!庄家下台升级!`);
- }else{
- setTip(`✅ 闲家才${game.playerScore}分,没破庄!庄家继续坐庄!`);
- }
- game.phase='gameEnd';
- alert(document.getElementById('tip-text').innerText);
- }else{
- game.currentPlayer=win;
- setTip(`这墩拿了${score}分,目前总分:${game.playerScore},继续干!`);
- }
- renderAll();
- }
- document.getElementById('pass-btn').onclick=()=>{setTip("🙅♂️ 我不出,你们上!");};
- document.getElementById('reset-btn').onclick=()=>location.reload();
- window.onload=renderAll;
- </script>
- </body>
- </html>
复制代码 |
|