初始化斗地主残局版项目
This commit is contained in:
57
nginx/html/css/style.css
Normal file
57
nginx/html/css/style.css
Normal file
@@ -0,0 +1,57 @@
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:system-ui,sans-serif;background:linear-gradient(135deg,#1a1a2e,#16213e);min-height:100vh;color:#fff}
|
||||
#app{max-width:1000px;margin:0 auto;padding:20px}
|
||||
.screen{animation:fadeIn .2s}
|
||||
.hidden{display:none!important}
|
||||
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
||||
h1{text-align:center;font-size:2rem;color:#ffd700;margin-bottom:20px}
|
||||
h2{text-align:center;color:#ffd700;margin-bottom:15px}
|
||||
h3{color:#ffd700;margin-bottom:10px}
|
||||
.form{background:rgba(255,255,255,.1);padding:25px;border-radius:12px;max-width:350px;margin:0 auto 20px}
|
||||
.form input,.form select{width:100%;padding:10px;margin-bottom:10px;border:none;border-radius:6px;background:rgba(255,255,255,.15);color:#fff;font-size:15px}
|
||||
.form input::placeholder{color:#aaa}
|
||||
.btn{padding:10px 20px;border:none;border-radius:6px;cursor:pointer;font-size:15px;font-weight:600;transition:.2s}
|
||||
.btn:hover{transform:translateY(-1px)}
|
||||
.btn:disabled{opacity:.5;cursor:not-allowed;transform:none}
|
||||
.btn.primary{background:linear-gradient(135deg,#ffd700,#ff8c00);color:#1a1a2e}
|
||||
.btn.danger{background:linear-gradient(135deg,#ff4757,#c0392b)}
|
||||
.btn.sm{padding:6px 12px;font-size:13px}
|
||||
.join-row{display:flex;gap:8px;margin-top:10px}
|
||||
.join-row input{flex:1}
|
||||
.rules{background:rgba(255,255,255,.05);padding:15px;border-radius:8px;max-width:350px;margin:0 auto}
|
||||
.rules ul{list-style:none}
|
||||
.rules li{padding:5px 0;color:#ccc;font-size:14px}
|
||||
.room-id{font-size:1.3rem;color:#ffd700;letter-spacing:1px}
|
||||
#playerList{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;margin:15px 0}
|
||||
.player-card{background:rgba(255,255,255,.1);padding:12px 20px;border-radius:8px;text-align:center;min-width:100px}
|
||||
.player-card.ready{background:rgba(0,255,0,.2);border:2px solid #0f0}
|
||||
.player-card.current{background:rgba(255,215,0,.3);border:2px solid #ffd700}
|
||||
.player-card .name{font-weight:600}
|
||||
.player-card .status{font-size:12px;color:#aaa;margin-top:4px}
|
||||
.actions{display:flex;justify-content:center;gap:10px;margin-top:15px}
|
||||
.header{display:flex;justify-content:space-between;padding:8px 15px;background:rgba(0,0,0,.3);border-radius:8px;margin-bottom:15px;font-size:14px}
|
||||
.others{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;margin-bottom:15px;min-height:50px}
|
||||
.play-area{min-height:100px;background:rgba(0,0,0,.2);border-radius:10px;display:flex;align-items:center;justify-content:center;margin-bottom:15px}
|
||||
.last-play{text-align:center}
|
||||
.last-play span{color:#ffd700;font-size:14px}
|
||||
.my-area{background:rgba(0,0,0,.3);border-radius:10px;padding:15px}
|
||||
.my-cards{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-bottom:12px;min-height:80px}
|
||||
.card{width:50px;height:70px;background:linear-gradient(135deg,#fff,#f0f0f0);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;cursor:pointer;transition:.15s;user-select:none}
|
||||
.card:hover{transform:translateY(-3px)}
|
||||
.card.selected{transform:translateY(-12px);box-shadow:0 4px 12px rgba(0,0,0,.4);border:2px solid #ffd700}
|
||||
.card.red{color:#e74c3c}
|
||||
.card.black{color:#2d3436}
|
||||
.card.super{background:linear-gradient(135deg,#ffd700,#ff8c00);color:#fff;font-size:12px}
|
||||
.card.joker{background:linear-gradient(135deg,#9b59b6,#8e44ad);color:#fff;font-size:11px}
|
||||
.card.sm{width:35px;height:50px;font-size:11px}
|
||||
.chat{position:fixed;bottom:15px;right:15px;width:250px;background:rgba(0,0,0,.85);border-radius:8px;overflow:hidden}
|
||||
#chatMsgs{height:150px;overflow-y:auto;padding:8px;font-size:13px}
|
||||
#chatMsgs p{margin-bottom:5px}
|
||||
#chatMsgs .sys{color:#888;font-style:italic}
|
||||
#chatMsgs .pn{color:#ffd700}
|
||||
.chat-input{display:flex;padding:8px;gap:6px;background:rgba(0,0,0,.3)}
|
||||
.chat-input input{flex:1;padding:6px;border:none;border-radius:4px;background:rgba(255,255,255,.1);color:#fff;font-size:13px}
|
||||
.modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.8);display:flex;align-items:center;justify-content:center}
|
||||
.modal-box{background:#16213e;padding:30px;border-radius:12px;text-align:center;border:2px solid #ffd700}
|
||||
.modal-box h2{margin-bottom:20px}
|
||||
@media(max-width:600px){#app{padding:10px}.card{width:42px;height:60px;font-size:12px}.chat{width:100%;bottom:0;right:0;border-radius:8px 8px 0 0}}
|
||||
86
nginx/html/index.html
Normal file
86
nginx/html/index.html
Normal file
@@ -0,0 +1,86 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>斗地主残局版</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="lobby" class="screen">
|
||||
<h1>斗地主残局版</h1>
|
||||
<div class="form">
|
||||
<input type="text" id="playerName" placeholder="昵称" maxlength="10">
|
||||
<select id="maxPlayers">
|
||||
<option value="2">2人</option>
|
||||
<option value="3">3人</option>
|
||||
<option value="4" selected>4人</option>
|
||||
<option value="5">5人</option>
|
||||
<option value="6">6人</option>
|
||||
</select>
|
||||
<button id="createBtn" class="btn primary">创建房间</button>
|
||||
<div class="join-row">
|
||||
<input type="text" id="roomIdInput" placeholder="房间号">
|
||||
<button id="joinBtn" class="btn">加入</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rules">
|
||||
<h3>规则</h3>
|
||||
<ul>
|
||||
<li>新增"超人强"为最大单牌</li>
|
||||
<li>每人初始5张牌</li>
|
||||
<li>三张可成顺子/炸弹,两对可成连对</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="waiting" class="screen hidden">
|
||||
<h2>等待中</h2>
|
||||
<p>房间号: <span id="displayRoomId" class="room-id"></span></p>
|
||||
<div id="playerList"></div>
|
||||
<div class="actions">
|
||||
<button id="readyBtn" class="btn primary">准备</button>
|
||||
<button id="leaveBtn" class="btn danger">离开</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="game" class="screen hidden">
|
||||
<div class="header">
|
||||
<span>第<span id="roundNum">1</span>轮</span>
|
||||
<span id="turnInfo"></span>
|
||||
</div>
|
||||
<div id="others" class="others"></div>
|
||||
<div id="playArea" class="play-area">
|
||||
<div id="lastPlay" class="last-play hidden">
|
||||
<span id="lastPlayer"></span>
|
||||
<div id="lastCards"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-area">
|
||||
<div id="myCards" class="my-cards"></div>
|
||||
<div class="actions">
|
||||
<button id="playBtn" class="btn primary" disabled>出牌</button>
|
||||
<button id="passBtn" class="btn" disabled>不出</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="gameOver" class="modal hidden">
|
||||
<div class="modal-box">
|
||||
<h2 id="winnerText"></h2>
|
||||
<button id="againBtn" class="btn primary">再来一局</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chat" class="chat">
|
||||
<div id="chatMsgs"></div>
|
||||
<div class="chat-input">
|
||||
<input type="text" id="chatInput" placeholder="消息">
|
||||
<button id="chatBtn" class="btn sm">发</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/game.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
346
nginx/html/js/game.js
Normal file
346
nginx/html/js/game.js
Normal file
@@ -0,0 +1,346 @@
|
||||
let ws = null, playerId = '', roomId = '', state = null, selected = [];
|
||||
const $ = id => document.getElementById(id);
|
||||
|
||||
function show(id) {
|
||||
document.querySelectorAll('.screen').forEach(s => s.classList.add('hidden'));
|
||||
$(id).classList.remove('hidden');
|
||||
}
|
||||
|
||||
function chat(name, msg, sys) {
|
||||
const p = document.createElement('p');
|
||||
p.innerHTML = sys ? '<span class="sys">' + msg + '</span>' : '<span class="pn">' + name + ':</span> ' + msg;
|
||||
$('chatMsgs').appendChild(p);
|
||||
$('chatMsgs').scrollTop = 1e6;
|
||||
}
|
||||
|
||||
async function create() {
|
||||
const name = $('playerName').value.trim();
|
||||
if (!name) { alert('请输入昵称'); return; }
|
||||
try {
|
||||
const res = await fetch('/api/rooms', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({playerName: name, maxPlayers: +$('maxPlayers').value})
|
||||
});
|
||||
const d = await res.json();
|
||||
if (d.code === 0) {
|
||||
roomId = d.data.roomId;
|
||||
playerId = d.data.playerId;
|
||||
connect();
|
||||
} else {
|
||||
alert(d.message);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('创建失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function join() {
|
||||
const name = $('playerName').value.trim();
|
||||
const rid = $('roomIdInput').value.trim().toLowerCase();
|
||||
if (!name) { alert('请输入昵称'); return; }
|
||||
if (!rid) { alert('请输入房间号'); return; }
|
||||
try {
|
||||
const res = await fetch('/api/rooms/' + rid + '/join', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({playerName: name})
|
||||
});
|
||||
const d = await res.json();
|
||||
if (d.code === 0) {
|
||||
roomId = d.data.roomId;
|
||||
playerId = d.data.playerId;
|
||||
connect();
|
||||
} else {
|
||||
alert(d.message);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('加入失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function connect() {
|
||||
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
ws = new WebSocket(proto + '//' + location.host + '/api/ws?roomId=' + roomId + '&playerId=' + playerId);
|
||||
ws.onopen = function() { chat('', '已连接', true); };
|
||||
ws.onmessage = function(e) {
|
||||
const msg = JSON.parse(e.data);
|
||||
if (msg.type === 'state') render(msg.data);
|
||||
else if (msg.type === 'gameOver') showGameOver(msg.data);
|
||||
else if (msg.type === 'chat') chat(msg.data.playerName, msg.data.message);
|
||||
else if (msg.type === 'error') chat('', msg.data, true);
|
||||
};
|
||||
ws.onerror = function() { chat('', '连接错误', true); };
|
||||
ws.onclose = function() { chat('', '断开连接', true); };
|
||||
}
|
||||
|
||||
function send(type, data) {
|
||||
if (ws && ws.readyState === 1) {
|
||||
ws.send(JSON.stringify({type: type, playerId: playerId, roomId: roomId, data: data}));
|
||||
}
|
||||
}
|
||||
|
||||
function render(s) {
|
||||
state = s;
|
||||
$('displayRoomId').textContent = s.id;
|
||||
$('roundNum').textContent = s.roundCount;
|
||||
var me = null;
|
||||
for (var i = 0; i < s.players.length; i++) {
|
||||
if (s.players[i].id === playerId) { me = s.players[i]; break; }
|
||||
}
|
||||
|
||||
if (s.state === 0) {
|
||||
show('waiting');
|
||||
var html = '';
|
||||
for (var i = 0; i < s.players.length; i++) {
|
||||
var p = s.players[i];
|
||||
html += '<div class="player-card' + (p.isReady ? ' ready' : '') + '">';
|
||||
html += '<div class="name">' + p.name + (p.id === playerId ? ' (你)' : '') + '</div>';
|
||||
html += '<div class="status">' + (p.isReady ? '已准备' : '等待中') + '</div></div>';
|
||||
}
|
||||
$('playerList').innerHTML = html;
|
||||
$('readyBtn').textContent = (me && me.isReady) ? '取消准备' : '准备';
|
||||
} else if (s.state === 1) {
|
||||
show('game');
|
||||
var cur = s.players[s.currentTurn];
|
||||
$('turnInfo').textContent = cur.id === playerId ? '轮到你出牌' : '等待 ' + cur.name + ' 出牌';
|
||||
|
||||
var othersHtml = '';
|
||||
for (var i = 0; i < s.players.length; i++) {
|
||||
var p = s.players[i];
|
||||
if (p.id === playerId) continue;
|
||||
othersHtml += '<div class="player-card' + (s.currentTurn === i ? ' current' : '') + '">';
|
||||
othersHtml += '<div class="name">' + p.name + '</div>';
|
||||
othersHtml += '<div class="status">' + p.cardCount + '张</div></div>';
|
||||
}
|
||||
$('others').innerHTML = othersHtml;
|
||||
|
||||
if (s.lastPlay) {
|
||||
$('lastPlay').classList.remove('hidden');
|
||||
var lpName = '';
|
||||
for (var i = 0; i < s.players.length; i++) {
|
||||
if (s.players[i].id === s.lastPlay.playerId) { lpName = s.players[i].name; break; }
|
||||
}
|
||||
$('lastPlayer').textContent = lpName;
|
||||
$('lastCards').innerHTML = s.lastPlay.cards.map(function(c) { return makeCardHtml(c, true); }).join('');
|
||||
} else {
|
||||
$('lastPlay').classList.add('hidden');
|
||||
}
|
||||
renderMyCards(me);
|
||||
updateBtns();
|
||||
} else if (s.state === 2) {
|
||||
show('waiting');
|
||||
var html = '';
|
||||
for (var i = 0; i < s.players.length; i++) {
|
||||
var p = s.players[i];
|
||||
html += '<div class="player-card">';
|
||||
html += '<div class="name">' + p.name + '</div>';
|
||||
html += '<div class="status">' + (p.isReady ? '已准备' : '等待中') + '</div></div>';
|
||||
}
|
||||
$('playerList').innerHTML = html;
|
||||
$('readyBtn').textContent = (me && me.isReady) ? '取消准备' : '准备';
|
||||
}
|
||||
}
|
||||
|
||||
function getCardClass(c) {
|
||||
if (c.suit === 5) return 'super';
|
||||
if (c.suit === 4) return 'joker';
|
||||
return c.suit < 2 ? 'red' : 'black';
|
||||
}
|
||||
|
||||
function getCardText(c) {
|
||||
if (c.suit === 5) return '超人强';
|
||||
if (c.suit === 4) return c.value === 16 ? '小王' : '大王';
|
||||
var suits = ['♥', '♦', '♣', '♠'];
|
||||
var vals = ['', '', '', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A', '2'];
|
||||
return suits[c.suit] + vals[c.value];
|
||||
}
|
||||
|
||||
function makeCardHtml(c, small) {
|
||||
return '<div class="card ' + getCardClass(c) + (small ? ' sm' : '') + '">' + getCardText(c) + '</div>';
|
||||
}
|
||||
|
||||
function renderMyCards(me) {
|
||||
var el = $('myCards');
|
||||
el.innerHTML = '';
|
||||
if (!me || !me.cards) return;
|
||||
for (var i = 0; i < me.cards.length; i++) {
|
||||
(function(c) {
|
||||
var key = c.suit + '_' + c.value;
|
||||
var div = document.createElement('div');
|
||||
div.className = 'card ' + getCardClass(c) + (selected.indexOf(key) >= 0 ? ' selected' : '');
|
||||
div.textContent = getCardText(c);
|
||||
div.onclick = function() {
|
||||
var idx = selected.indexOf(key);
|
||||
if (idx === -1) { selected.push(key); div.classList.add('selected'); }
|
||||
else { selected.splice(idx, 1); div.classList.remove('selected'); }
|
||||
updateBtns();
|
||||
};
|
||||
el.appendChild(div);
|
||||
})(me.cards[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function getValueCounts(cards) {
|
||||
var counts = {};
|
||||
for (var i = 0; i < cards.length; i++) {
|
||||
var v = cards[i].value;
|
||||
counts[v] = (counts[v] || 0) + 1;
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
function getCardType(cards) {
|
||||
var n = cards.length;
|
||||
if (n === 0) return 0;
|
||||
|
||||
var isRocket = n === 2 && cards.some(function(c) { return c.suit === 4 && c.value === 16; })
|
||||
&& cards.some(function(c) { return c.suit === 4 && c.value === 17; });
|
||||
if (isRocket) return 9;
|
||||
|
||||
var counts = getValueCounts(cards);
|
||||
var keys = Object.keys(counts);
|
||||
|
||||
if (n === 1) return 1;
|
||||
if (n === 2 && keys.length === 1) return 2;
|
||||
if (n === 3 && keys.length === 1) return 8;
|
||||
if (n === 4 && keys.length === 1) return 8;
|
||||
if (n === 4 && keys.length === 2) {
|
||||
for (var k in counts) { if (counts[k] === 3) return 4; }
|
||||
}
|
||||
|
||||
var sorted = cards.slice().sort(function(a, b) { return a.value - b.value; });
|
||||
var isSeq = true;
|
||||
for (var i = 1; i < sorted.length; i++) {
|
||||
if (sorted[i].value !== sorted[i-1].value + 1 || sorted[i].value >= 15) { isSeq = false; break; }
|
||||
}
|
||||
if (n >= 3 && keys.length === n && isSeq) return 6;
|
||||
|
||||
var allPairs = true;
|
||||
for (var k in counts) { if (counts[k] !== 2 || parseInt(k) >= 15) allPairs = false; }
|
||||
var pairVals = keys.map(function(k) { return parseInt(k); }).sort(function(a,b){return a-b;});
|
||||
var seqPairs = true;
|
||||
for (var i = 1; i < pairVals.length; i++) {
|
||||
if (pairVals[i] !== pairVals[i-1] + 1) seqPairs = false;
|
||||
}
|
||||
if (n >= 4 && n % 2 === 0 && allPairs && seqPairs) return 7;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function canPlay(cards, lastPlay) {
|
||||
var cardType = getCardType(cards);
|
||||
if (cardType === 0) return false;
|
||||
if (!lastPlay) return true;
|
||||
|
||||
if (cardType === 9) return true;
|
||||
if (cardType === 8) {
|
||||
if (lastPlay.cardType === 8) {
|
||||
var myMain = 0, lastMain = 0;
|
||||
var myCounts = getValueCounts(cards);
|
||||
var lastCounts = getValueCounts(lastPlay.cards);
|
||||
for (var k in myCounts) { if (myCounts[k] >= 3 && parseInt(k) > myMain) myMain = parseInt(k); }
|
||||
for (var k in lastCounts) { if (lastCounts[k] >= 3 && parseInt(k) > lastMain) lastMain = parseInt(k); }
|
||||
return myMain > lastMain;
|
||||
}
|
||||
return lastPlay.cardType !== 9;
|
||||
}
|
||||
if (lastPlay.cardType === 8 || lastPlay.cardType === 9) return false;
|
||||
if (cardType !== lastPlay.cardType || cards.length !== lastPlay.cards.length) return false;
|
||||
|
||||
var myMain = 0, lastMain = 0;
|
||||
var myCounts = getValueCounts(cards);
|
||||
var lastCounts = getValueCounts(lastPlay.cards);
|
||||
var myMaxC = 0, lastMaxC = 0;
|
||||
for (var k in myCounts) {
|
||||
if (myCounts[k] > myMaxC || (myCounts[k] === myMaxC && parseInt(k) > myMain)) {
|
||||
myMaxC = myCounts[k]; myMain = parseInt(k);
|
||||
}
|
||||
}
|
||||
for (var k in lastCounts) {
|
||||
if (lastCounts[k] > lastMaxC || (lastCounts[k] === lastMaxC && parseInt(k) > lastMain)) {
|
||||
lastMaxC = lastCounts[k]; lastMain = parseInt(k);
|
||||
}
|
||||
}
|
||||
return myMain > lastMain;
|
||||
}
|
||||
|
||||
function updateBtns() {
|
||||
var myTurn = state && state.players[state.currentTurn].id === playerId;
|
||||
var cards = getSelectedCards();
|
||||
var validPlay = selected.length > 0 && canPlay(cards, state ? state.lastPlay : null);
|
||||
$('playBtn').disabled = !myTurn || !validPlay;
|
||||
var canPass = state && state.lastPlay && state.lastPlay.playerId !== playerId;
|
||||
$('passBtn').disabled = !myTurn || !canPass;
|
||||
}
|
||||
|
||||
function getSelectedCards() {
|
||||
if (!state) return [];
|
||||
var me = null;
|
||||
for (var i = 0; i < state.players.length; i++) {
|
||||
if (state.players[i].id === playerId) { me = state.players[i]; break; }
|
||||
}
|
||||
if (!me || !me.cards) return [];
|
||||
return me.cards.filter(function(c) { return selected.indexOf(c.suit + '_' + c.value) >= 0; });
|
||||
}
|
||||
|
||||
function play() {
|
||||
if (selected.length === 0) return;
|
||||
var cards = getSelectedCards();
|
||||
if (!canPlay(cards, state ? state.lastPlay : null)) {
|
||||
chat('', '牌型无效或管不上', true);
|
||||
return;
|
||||
}
|
||||
send('play', {cards: cards});
|
||||
selected = [];
|
||||
}
|
||||
|
||||
function pass() { send('pass', {}); }
|
||||
|
||||
function toggleReady() {
|
||||
var me = null;
|
||||
if (state) {
|
||||
for (var i = 0; i < state.players.length; i++) {
|
||||
if (state.players[i].id === playerId) { me = state.players[i]; break; }
|
||||
}
|
||||
}
|
||||
send('ready', {ready: me ? !me.isReady : true});
|
||||
}
|
||||
|
||||
function leave() {
|
||||
if (ws) ws.close();
|
||||
show('lobby');
|
||||
roomId = ''; playerId = ''; state = null; selected = [];
|
||||
}
|
||||
|
||||
function showGameOver(d) {
|
||||
var w = null;
|
||||
if (state) {
|
||||
for (var i = 0; i < state.players.length; i++) {
|
||||
if (state.players[i].id === d.winnerId) { w = state.players[i]; break; }
|
||||
}
|
||||
}
|
||||
$('winnerText').textContent = w ? (w.id === playerId ? '你赢了!' : w.name + ' 获胜') : '游戏结束';
|
||||
$('gameOver').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function again() {
|
||||
$('gameOver').classList.add('hidden');
|
||||
send('ready', {ready: false});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
$('createBtn').onclick = create;
|
||||
$('joinBtn').onclick = join;
|
||||
$('readyBtn').onclick = toggleReady;
|
||||
$('leaveBtn').onclick = leave;
|
||||
$('playBtn').onclick = play;
|
||||
$('passBtn').onclick = pass;
|
||||
$('againBtn').onclick = again;
|
||||
$('chatBtn').onclick = function() {
|
||||
var v = $('chatInput').value.trim();
|
||||
if (v) { send('chat', v); $('chatInput').value = ''; }
|
||||
};
|
||||
$('chatInput').onkeypress = function(e) { if (e.key === 'Enter') $('chatBtn').click(); };
|
||||
$('roomIdInput').onkeypress = function(e) { if (e.key === 'Enter') join(); };
|
||||
});
|
||||
28
nginx/nginx.conf
Normal file
28
nginx/nginx.conf
Normal file
@@ -0,0 +1,28 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://app:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location /api/ws {
|
||||
proxy_pass http://app:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user