fix: 修复牌型识别、获胜显示、出牌区布局、移动端重连
1. 牌型识别: 新增三带一对、飞机、飞机带单/带对、四带二、四带两对 2. 获胜显示: 先显示最后出牌1秒后再弹出获胜对话框 3. 出牌区布局: 横向排列并换行 4. 移动端重连: 切回前台时自动重连WebSocket 5. 更新文档: README.md和API.md补充新增牌型
This commit is contained in:
7
API.md
7
API.md
@@ -427,11 +427,16 @@
|
|||||||
| 2 | 对子 |
|
| 2 | 对子 |
|
||||||
| 3 | 三张 (未使用) |
|
| 3 | 三张 (未使用) |
|
||||||
| 4 | 三带一 |
|
| 4 | 三带一 |
|
||||||
| 5 | 三带对 (未使用) |
|
| 5 | 三带一对 |
|
||||||
| 6 | 顺子 |
|
| 6 | 顺子 |
|
||||||
| 7 | 连对 |
|
| 7 | 连对 |
|
||||||
| 8 | 炸弹 |
|
| 8 | 炸弹 |
|
||||||
| 9 | 火箭 |
|
| 9 | 火箭 |
|
||||||
|
| 10 | 飞机 |
|
||||||
|
| 11 | 飞机带单 |
|
||||||
|
| 12 | 飞机带对 |
|
||||||
|
| 13 | 四带二 |
|
||||||
|
| 14 | 四带两对 |
|
||||||
|
|
||||||
### RoomState (房间状态)
|
### RoomState (房间状态)
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,12 @@
|
|||||||
| 顺子 | 三张或以上连续点数的牌(不含2和王) |
|
| 顺子 | 三张或以上连续点数的牌(不含2和王) |
|
||||||
| 连对 | 两对或以上连续点数的对子(不含2和王) |
|
| 连对 | 两对或以上连续点数的对子(不含2和王) |
|
||||||
| 三带一 | 三张相同点数+任意一张 |
|
| 三带一 | 三张相同点数+任意一张 |
|
||||||
|
| 三带一对 | 三张相同点数+一对 |
|
||||||
|
| 飞机 | 两个或以上连续的三张(不含2和王) |
|
||||||
|
| 飞机带单 | 飞机+同数量单张 |
|
||||||
|
| 飞机带对 | 飞机+同数量对子 |
|
||||||
|
| 四带二 | 四张相同点数+两张单牌 |
|
||||||
|
| 四带两对 | 四张相同点数+两对 |
|
||||||
| 炸弹 | 三张或以上相同点数的牌 |
|
| 炸弹 | 三张或以上相同点数的牌 |
|
||||||
| 火箭 | 小王+大王 |
|
| 火箭 | 小王+大王 |
|
||||||
|
|
||||||
|
|||||||
@@ -75,59 +75,100 @@ func (cl *CardLogic) GetCardType(cards []models.Card) models.CardType {
|
|||||||
cl.SortCards(sorted)
|
cl.SortCards(sorted)
|
||||||
counts := cl.getValueCounts(sorted)
|
counts := cl.getValueCounts(sorted)
|
||||||
|
|
||||||
switch n {
|
// 统计各种点数的数量
|
||||||
case 1:
|
var singles, pairs, triples, quads []int
|
||||||
return models.CardTypeSingle
|
for v, c := range counts {
|
||||||
case 2:
|
switch c {
|
||||||
if len(counts) == 1 {
|
case 1:
|
||||||
return models.CardTypePair
|
singles = append(singles, v)
|
||||||
|
case 2:
|
||||||
|
pairs = append(pairs, v)
|
||||||
|
case 3:
|
||||||
|
triples = append(triples, v)
|
||||||
|
case 4:
|
||||||
|
quads = append(quads, v)
|
||||||
}
|
}
|
||||||
return models.CardTypeInvalid
|
|
||||||
case 3:
|
|
||||||
if len(counts) == 1 {
|
|
||||||
return models.CardTypeBomb
|
|
||||||
}
|
|
||||||
if cl.isStraight(sorted) {
|
|
||||||
return models.CardTypeStraight
|
|
||||||
}
|
|
||||||
return models.CardTypeInvalid
|
|
||||||
case 4:
|
|
||||||
if len(counts) == 1 {
|
|
||||||
return models.CardTypeBomb
|
|
||||||
}
|
|
||||||
if len(counts) == 2 {
|
|
||||||
for _, c := range counts {
|
|
||||||
if c == 3 {
|
|
||||||
return models.CardTypeTripleOne
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cl.isStraight(sorted) {
|
|
||||||
return models.CardTypeStraight
|
|
||||||
}
|
|
||||||
if cl.isDoubleStraight(sorted, counts) {
|
|
||||||
return models.CardTypeDoubleStraight
|
|
||||||
}
|
|
||||||
return models.CardTypeInvalid
|
|
||||||
default:
|
|
||||||
if cl.isStraight(sorted) {
|
|
||||||
return models.CardTypeStraight
|
|
||||||
}
|
|
||||||
if cl.isDoubleStraight(sorted, counts) {
|
|
||||||
return models.CardTypeDoubleStraight
|
|
||||||
}
|
|
||||||
hasTriple := false
|
|
||||||
for _, c := range counts {
|
|
||||||
if c >= 3 {
|
|
||||||
hasTriple = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hasTriple && (n == 4 || n == 5) {
|
|
||||||
return models.CardTypeTripleOne
|
|
||||||
}
|
|
||||||
return models.CardTypeInvalid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 炸弹(三张或以上相同)
|
||||||
|
if len(counts) == 1 && n >= 3 {
|
||||||
|
return models.CardTypeBomb
|
||||||
|
}
|
||||||
|
|
||||||
|
// 四带二(4张相同+2单张)
|
||||||
|
if len(quads) == 1 && len(singles) == 2 && len(pairs) == 0 && len(triples) == 0 && n == 6 {
|
||||||
|
return models.CardTypeFourWithTwo
|
||||||
|
}
|
||||||
|
|
||||||
|
// 四带两对(4张相同+2对)
|
||||||
|
if len(quads) == 1 && len(pairs) == 2 && len(singles) == 0 && len(triples) == 0 && n == 8 {
|
||||||
|
return models.CardTypeFourWithTwoPairs
|
||||||
|
}
|
||||||
|
|
||||||
|
// 飞机(连续的三张)
|
||||||
|
if len(triples) >= 2 && cl.isConsecutive(triples) {
|
||||||
|
planeCount := len(triples)
|
||||||
|
// 纯飞机
|
||||||
|
if n == planeCount*3 {
|
||||||
|
return models.CardTypeAirplane
|
||||||
|
}
|
||||||
|
// 飞机带单张
|
||||||
|
if n == planeCount*3+planeCount && len(singles) == planeCount {
|
||||||
|
return models.CardTypeAirplaneSingle
|
||||||
|
}
|
||||||
|
// 飞机带对子
|
||||||
|
if n == planeCount*3+planeCount*2 && len(pairs) == planeCount {
|
||||||
|
return models.CardTypeAirplanePair
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单张
|
||||||
|
if n == 1 {
|
||||||
|
return models.CardTypeSingle
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对子
|
||||||
|
if n == 2 && len(counts) == 1 {
|
||||||
|
return models.CardTypePair
|
||||||
|
}
|
||||||
|
|
||||||
|
// 三带一(3张相同+1单张)
|
||||||
|
if n == 4 && len(triples) == 1 && len(singles) == 1 {
|
||||||
|
return models.CardTypeTripleOne
|
||||||
|
}
|
||||||
|
|
||||||
|
// 三带一对(3张相同+1对)
|
||||||
|
if n == 5 && len(triples) == 1 && len(pairs) == 1 {
|
||||||
|
return models.CardTypeTriplePair
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顺子
|
||||||
|
if cl.isStraight(sorted) {
|
||||||
|
return models.CardTypeStraight
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连对
|
||||||
|
if cl.isDoubleStraight(sorted, counts) {
|
||||||
|
return models.CardTypeDoubleStraight
|
||||||
|
}
|
||||||
|
|
||||||
|
return models.CardTypeInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *CardLogic) isConsecutive(values []int) bool {
|
||||||
|
if len(values) < 2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
sort.Ints(values)
|
||||||
|
for i := 1; i < len(values); i++ {
|
||||||
|
if values[i] != values[i-1]+1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if values[i] >= 15 { // 2和王不能参与
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values[0] < 15
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *CardLogic) isRocket(cards []models.Card) bool {
|
func (cl *CardLogic) isRocket(cards []models.Card) bool {
|
||||||
@@ -230,9 +271,36 @@ func (cl *CardLogic) CanPlay(cards []models.Card, lastPlay *models.PlayRecord) b
|
|||||||
if cardType != lastPlay.CardType || len(cards) != len(lastPlay.Cards) {
|
if cardType != lastPlay.CardType || len(cards) != len(lastPlay.Cards) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cardType == models.CardTypeAirplane || cardType == models.CardTypeAirplaneSingle || cardType == models.CardTypeAirplanePair {
|
||||||
|
return cl.compareAirplanes(cards, lastPlay.Cards)
|
||||||
|
}
|
||||||
|
|
||||||
return cl.getMainValue(cards) > cl.getMainValue(lastPlay.Cards)
|
return cl.getMainValue(cards) > cl.getMainValue(lastPlay.Cards)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cl *CardLogic) compareAirplanes(cards, lastCards []models.Card) bool {
|
||||||
|
myTriples := cl.getTripleValues(cards)
|
||||||
|
lastTriples := cl.getTripleValues(lastCards)
|
||||||
|
if len(myTriples) != len(lastTriples) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sort.Ints(myTriples)
|
||||||
|
sort.Ints(lastTriples)
|
||||||
|
return myTriples[len(myTriples)-1] > lastTriples[len(lastTriples)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *CardLogic) getTripleValues(cards []models.Card) []int {
|
||||||
|
counts := cl.getValueCounts(cards)
|
||||||
|
var triples []int
|
||||||
|
for v, c := range counts {
|
||||||
|
if c >= 3 {
|
||||||
|
triples = append(triples, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return triples
|
||||||
|
}
|
||||||
|
|
||||||
func (cl *CardLogic) getMainValue(cards []models.Card) int {
|
func (cl *CardLogic) getMainValue(cards []models.Card) int {
|
||||||
counts := cl.getValueCounts(cards)
|
counts := cl.getValueCounts(cards)
|
||||||
maxValue, maxCount := 0, 0
|
maxValue, maxCount := 0, 0
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ const (
|
|||||||
CardTypeDoubleStraight
|
CardTypeDoubleStraight
|
||||||
CardTypeBomb
|
CardTypeBomb
|
||||||
CardTypeRocket
|
CardTypeRocket
|
||||||
|
CardTypeAirplane
|
||||||
|
CardTypeAirplaneSingle
|
||||||
|
CardTypeAirplanePair
|
||||||
|
CardTypeFourWithTwo
|
||||||
|
CardTypeFourWithTwoPairs
|
||||||
)
|
)
|
||||||
|
|
||||||
type Player struct {
|
type Player struct {
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ func (c *Client) handleMessage(data []byte) {
|
|||||||
}
|
}
|
||||||
room, _ := c.Hub.GameMgr.GetRoom(c.RoomID)
|
room, _ := c.Hub.GameMgr.GetRoom(c.RoomID)
|
||||||
if room != nil && room.State == models.RoomStateFinished {
|
if room != nil && room.State == models.RoomStateFinished {
|
||||||
|
c.Hub.broadcastFinalState(c.RoomID, room)
|
||||||
c.Hub.broadcastToRoom(c.RoomID, models.Message{
|
c.Hub.broadcastToRoom(c.RoomID, models.Message{
|
||||||
Type: models.MsgTypeGameOver,
|
Type: models.MsgTypeGameOver,
|
||||||
Data: map[string]string{"winnerId": room.LastWinner},
|
Data: map[string]string{"winnerId": room.LastWinner},
|
||||||
@@ -313,6 +314,34 @@ func (h *Hub) broadcastRoomState(roomID string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Hub) broadcastFinalState(roomID string, room *models.Room) {
|
||||||
|
h.mu.RLock()
|
||||||
|
clients := make([]*Client, 0)
|
||||||
|
for _, c := range h.Clients {
|
||||||
|
if c.RoomID == roomID {
|
||||||
|
clients = append(clients, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.mu.RUnlock()
|
||||||
|
|
||||||
|
for _, c := range clients {
|
||||||
|
state := h.GameMgr.GetRoomState(roomID, c.ID)
|
||||||
|
if state != nil {
|
||||||
|
state.State = models.RoomStatePlaying
|
||||||
|
data, _ := json.Marshal(models.Message{
|
||||||
|
Type: models.MsgTypeState,
|
||||||
|
Data: state,
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
})
|
||||||
|
select {
|
||||||
|
case c.Send <- data:
|
||||||
|
case <-c.done:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Hub) BroadcastRoomLeft(roomID, playerID string) {
|
func (h *Hub) BroadcastRoomLeft(roomID, playerID string) {
|
||||||
h.mu.RLock()
|
h.mu.RLock()
|
||||||
clients := make([]*Client, 0)
|
clients := make([]*Client, 0)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ h3{color:#ffd700;margin-bottom:10px}
|
|||||||
.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}
|
.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{text-align:center}
|
||||||
.last-play span{color:#ffd700;font-size:14px}
|
.last-play span{color:#ffd700;font-size:14px}
|
||||||
|
#lastCards{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-top:8px}
|
||||||
.my-area{background:rgba(0,0,0,.3);border-radius:10px;padding:15px}
|
.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}
|
.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{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}
|
||||||
@@ -63,6 +64,6 @@ h3{color:#ffd700;margin-bottom:10px}
|
|||||||
.chat-input{display:flex;padding:8px;gap:6px;background:rgba(0,0,0,.3)}
|
.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}
|
.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{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{background:#16213e;padding:30px;border-radius:12px;text-align:center;border:2px solid #ffd700;max-width:400px}
|
||||||
.modal-box h2{margin-bottom:20px}
|
.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}}
|
@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}}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
let ws = null, playerId = '', roomId = '', state = null, selected = [];
|
let ws = null, playerId = '', roomId = '', state = null, selected = [];
|
||||||
let token = '', userId = '', username = '', nickname = '';
|
let token = '', userId = '', username = '', nickname = '';
|
||||||
let loginCaptchaId = '', regCaptchaId = '';
|
let loginCaptchaId = '', regCaptchaId = '';
|
||||||
|
let reconnectTimer = null;
|
||||||
|
let reconnectDelay = 1000;
|
||||||
const $ = id => document.getElementById(id);
|
const $ = id => document.getElementById(id);
|
||||||
|
|
||||||
function show(id) {
|
function show(id) {
|
||||||
@@ -246,6 +248,10 @@ async function leaveRoom() {
|
|||||||
headers: authHeaders()
|
headers: authHeaders()
|
||||||
});
|
});
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
if (reconnectTimer) {
|
||||||
|
clearTimeout(reconnectTimer);
|
||||||
|
reconnectTimer = null;
|
||||||
|
}
|
||||||
if (ws) ws.close();
|
if (ws) ws.close();
|
||||||
show('lobby');
|
show('lobby');
|
||||||
roomId = '';
|
roomId = '';
|
||||||
@@ -257,7 +263,14 @@ async function leaveRoom() {
|
|||||||
function connect() {
|
function connect() {
|
||||||
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
ws = new WebSocket(proto + '//' + location.host + '/api/ws?roomId=' + roomId + '&playerId=' + playerId);
|
ws = new WebSocket(proto + '//' + location.host + '/api/ws?roomId=' + roomId + '&playerId=' + playerId);
|
||||||
ws.onopen = function() { chat('', '已连接', true); };
|
ws.onopen = function() {
|
||||||
|
chat('', '已连接', true);
|
||||||
|
reconnectDelay = 1000;
|
||||||
|
if (reconnectTimer) {
|
||||||
|
clearTimeout(reconnectTimer);
|
||||||
|
reconnectTimer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
ws.onmessage = function(e) {
|
ws.onmessage = function(e) {
|
||||||
const msg = JSON.parse(e.data);
|
const msg = JSON.parse(e.data);
|
||||||
if (msg.type === 'state') render(msg.data);
|
if (msg.type === 'state') render(msg.data);
|
||||||
@@ -267,9 +280,41 @@ function connect() {
|
|||||||
else if (msg.type === 'error') chat('', msg.data, true);
|
else if (msg.type === 'error') chat('', msg.data, true);
|
||||||
};
|
};
|
||||||
ws.onerror = function() { chat('', '连接错误', true); };
|
ws.onerror = function() { chat('', '连接错误', true); };
|
||||||
ws.onclose = function() { chat('', '断开连接', true); };
|
ws.onclose = function() {
|
||||||
|
chat('', '断开连接', true);
|
||||||
|
scheduleReconnect();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scheduleReconnect() {
|
||||||
|
if (reconnectTimer) return;
|
||||||
|
if (!roomId || !playerId) return;
|
||||||
|
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
||||||
|
reconnectTimer = setTimeout(function() {
|
||||||
|
reconnectTimer = null;
|
||||||
|
if (roomId && playerId) {
|
||||||
|
chat('', '正在重连...', true);
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
}, reconnectDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', function() {
|
||||||
|
if (document.hidden) {
|
||||||
|
if (ws) {
|
||||||
|
try { ws.close(); } catch (e) {}
|
||||||
|
ws = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!ws || ws.readyState !== 1) {
|
||||||
|
if (roomId && playerId) {
|
||||||
|
reconnectDelay = 500;
|
||||||
|
scheduleReconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function send(type, data) {
|
function send(type, data) {
|
||||||
if (ws && ws.readyState === 1) {
|
if (ws && ws.readyState === 1) {
|
||||||
ws.send(JSON.stringify({type: type, playerId: playerId, roomId: roomId, data: data}));
|
ws.send(JSON.stringify({type: type, playerId: playerId, roomId: roomId, data: data}));
|
||||||
@@ -386,44 +431,81 @@ function getValueCounts(cards) {
|
|||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isConsecutive(values) {
|
||||||
|
if (values.length < 2) return true;
|
||||||
|
values = values.slice().sort(function(a, b) { return a - b; });
|
||||||
|
for (var i = 1; i < values.length; i++) {
|
||||||
|
if (values[i] !== values[i - 1] + 1 || values[i] >= 15) return false;
|
||||||
|
}
|
||||||
|
return values[0] < 15;
|
||||||
|
}
|
||||||
|
|
||||||
function getCardType(cards) {
|
function getCardType(cards) {
|
||||||
var n = cards.length;
|
var n = cards.length;
|
||||||
if (n === 0) return 0;
|
if (n === 0) return 0;
|
||||||
|
|
||||||
var isRocket = n === 2 && cards.some(function(c) { return c.suit === 4 && c.value === 16; })
|
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; });
|
&& cards.some(function(c) { return c.suit === 4 && c.value === 17; });
|
||||||
if (isRocket) return 9;
|
if (isRocket) return 9;
|
||||||
|
|
||||||
var counts = getValueCounts(cards);
|
var counts = getValueCounts(cards);
|
||||||
var keys = Object.keys(counts);
|
var keys = Object.keys(counts);
|
||||||
|
|
||||||
|
var singles = [], pairs = [], triples = [], quads = [];
|
||||||
|
for (var v in counts) {
|
||||||
|
var c = counts[v];
|
||||||
|
var val = parseInt(v);
|
||||||
|
if (c === 1) singles.push(val);
|
||||||
|
else if (c === 2) pairs.push(val);
|
||||||
|
else if (c === 3) triples.push(val);
|
||||||
|
else if (c === 4) quads.push(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.length === 1 && n >= 3) return 8;
|
||||||
|
|
||||||
|
if (quads.length === 1 && singles.length === 2 && pairs.length === 0 && triples.length === 0 && n === 6) return 13;
|
||||||
|
if (quads.length === 1 && pairs.length === 2 && singles.length === 0 && triples.length === 0 && n === 8) return 14;
|
||||||
|
|
||||||
|
if (triples.length >= 2 && isConsecutive(triples)) {
|
||||||
|
var planeCount = triples.length;
|
||||||
|
if (n === planeCount * 3) return 10;
|
||||||
|
if (n === planeCount * 3 + planeCount && singles.length === planeCount) return 11;
|
||||||
|
if (n === planeCount * 3 + planeCount * 2 && pairs.length === planeCount) return 12;
|
||||||
|
}
|
||||||
|
|
||||||
if (n === 1) return 1;
|
if (n === 1) return 1;
|
||||||
if (n === 2 && keys.length === 1) return 2;
|
if (n === 2 && keys.length === 1) return 2;
|
||||||
if (n === 3 && keys.length === 1) return 8;
|
if (n === 4 && triples.length === 1 && singles.length === 1) return 4;
|
||||||
if (n === 4 && keys.length === 1) return 8;
|
if (n === 5 && triples.length === 1 && pairs.length === 1) return 5;
|
||||||
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 sorted = cards.slice().sort(function(a, b) { return a.value - b.value; });
|
||||||
var isSeq = true;
|
var isSeq = true;
|
||||||
for (var i = 1; i < sorted.length; i++) {
|
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 (sorted[i].value !== sorted[i - 1].value + 1 || sorted[i].value >= 15) { isSeq = false; break; }
|
||||||
}
|
}
|
||||||
if (n >= 3 && keys.length === n && isSeq) return 6;
|
if (n >= 3 && keys.length === n && isSeq) return 6;
|
||||||
|
|
||||||
var allPairs = true;
|
var allPairs = true;
|
||||||
for (var k in counts) { if (counts[k] !== 2 || parseInt(k) >= 15) allPairs = false; }
|
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 pairVals = keys.map(function(k) { return parseInt(k); }).sort(function(a, b) { return a - b; });
|
||||||
var seqPairs = true;
|
var seqPairs = true;
|
||||||
for (var i = 1; i < pairVals.length; i++) {
|
for (var i = 1; i < pairVals.length; i++) {
|
||||||
if (pairVals[i] !== pairVals[i-1] + 1) seqPairs = false;
|
if (pairVals[i] !== pairVals[i - 1] + 1) seqPairs = false;
|
||||||
}
|
}
|
||||||
if (n >= 4 && n % 2 === 0 && allPairs && seqPairs) return 7;
|
if (n >= 4 && n % 2 === 0 && allPairs && seqPairs) return 7;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTripleValues(cards) {
|
||||||
|
var counts = getValueCounts(cards);
|
||||||
|
var triples = [];
|
||||||
|
for (var v in counts) {
|
||||||
|
if (counts[v] >= 3) triples.push(parseInt(v));
|
||||||
|
}
|
||||||
|
return triples;
|
||||||
|
}
|
||||||
|
|
||||||
function canPlay(cards, lastPlay) {
|
function canPlay(cards, lastPlay) {
|
||||||
var cardType = getCardType(cards);
|
var cardType = getCardType(cards);
|
||||||
if (cardType === 0) return false;
|
if (cardType === 0) return false;
|
||||||
@@ -444,6 +526,13 @@ function canPlay(cards, lastPlay) {
|
|||||||
if (lastPlay.cardType === 8 || lastPlay.cardType === 9) return false;
|
if (lastPlay.cardType === 8 || lastPlay.cardType === 9) return false;
|
||||||
if (cardType !== lastPlay.cardType || cards.length !== lastPlay.cards.length) return false;
|
if (cardType !== lastPlay.cardType || cards.length !== lastPlay.cards.length) return false;
|
||||||
|
|
||||||
|
if (cardType === 10 || cardType === 11 || cardType === 12) {
|
||||||
|
var myTriples = getTripleValues(cards).sort(function(a, b) { return a - b; });
|
||||||
|
var lastTriples = getTripleValues(lastPlay.cards).sort(function(a, b) { return a - b; });
|
||||||
|
if (myTriples.length !== lastTriples.length) return false;
|
||||||
|
return myTriples[myTriples.length - 1] > lastTriples[lastTriples.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
var myMain = 0, lastMain = 0;
|
var myMain = 0, lastMain = 0;
|
||||||
var myCounts = getValueCounts(cards);
|
var myCounts = getValueCounts(cards);
|
||||||
var lastCounts = getValueCounts(lastPlay.cards);
|
var lastCounts = getValueCounts(lastPlay.cards);
|
||||||
@@ -514,8 +603,10 @@ function showGameOver(d) {
|
|||||||
if (state.players[i].id === d.winnerId) { w = state.players[i]; break; }
|
if (state.players[i].id === d.winnerId) { w = state.players[i]; break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$('winnerText').textContent = w ? (w.id === playerId ? '你赢了!' : w.name + ' 获胜') : '游戏结束';
|
setTimeout(function() {
|
||||||
$('gameOver').classList.remove('hidden');
|
$('winnerText').textContent = w ? (w.id === playerId ? '你赢了!' : w.name + ' 获胜') : '游戏结束';
|
||||||
|
$('gameOver').classList.remove('hidden');
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function again() {
|
function again() {
|
||||||
|
|||||||
Reference in New Issue
Block a user