|
|
@ -88,6 +88,7 @@ |
|
|
:style="card.style" |
|
|
:style="card.style" |
|
|
@tap="pickCard(card)"> |
|
|
@tap="pickCard(card)"> |
|
|
<text>{{card.displayIcon}}</text> |
|
|
<text>{{card.displayIcon}}</text> |
|
|
|
|
|
<text class="debug-layer">{{card.layer}}</text> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
@ -162,8 +163,8 @@ |
|
|
cards: [], |
|
|
cards: [], |
|
|
slots: [], |
|
|
slots: [], |
|
|
slotLimit: 7, |
|
|
slotLimit: 7, |
|
|
timeLimit: 150, |
|
|
timeLimit: 300, |
|
|
timeLeft: 150, |
|
|
timeLeft: 300, |
|
|
timer: null, |
|
|
timer: null, |
|
|
roundSeed: '', |
|
|
roundSeed: '', |
|
|
moveCount: 0, |
|
|
moveCount: 0, |
|
|
@ -199,7 +200,9 @@ |
|
|
return Math.max(16, Math.min(100, Math.round((mine.score || 0) * 100 / top.score))) |
|
|
return Math.max(16, Math.min(100, Math.round((mine.score || 0) * 100 / top.score))) |
|
|
}, |
|
|
}, |
|
|
visibleCards() { |
|
|
visibleCards() { |
|
|
return this.cards.filter(item => !item.removed && !item.selected) |
|
|
return this.cards |
|
|
|
|
|
.filter(item => !item.removed && !item.selected) |
|
|
|
|
|
.sort((a,b)=>this.cardZIndex(a)-this.cardZIndex(b)) |
|
|
}, |
|
|
}, |
|
|
remainingCount() { |
|
|
remainingCount() { |
|
|
return this.cards.filter(item => !item.removed && !item.selected).length |
|
|
return this.cards.filter(item => !item.removed && !item.selected).length |
|
|
@ -225,7 +228,7 @@ |
|
|
return cells |
|
|
return cells |
|
|
}, |
|
|
}, |
|
|
timerText() { |
|
|
timerText() { |
|
|
return this.playing ? `${this.timeLeft}s` : '150s限时' |
|
|
return this.playing ? `${this.timeLeft}s` : '300s限时' |
|
|
}, |
|
|
}, |
|
|
timerPercent() { |
|
|
timerPercent() { |
|
|
return Math.max(0, Math.min(100, Math.round(this.timeLeft * 100 / this.timeLimit))) |
|
|
return Math.max(0, Math.min(100, Math.round(this.timeLeft * 100 / this.timeLimit))) |
|
|
@ -240,7 +243,7 @@ |
|
|
if (!this.playing) return '开始后进入倒计时' |
|
|
if (!this.playing) return '开始后进入倒计时' |
|
|
if (this.timeLeft <= 30) return '最后冲刺,槽位别满' |
|
|
if (this.timeLeft <= 30) return '最后冲刺,槽位别满' |
|
|
if (this.timeLeft <= 60) return '时间紧张,加快决策' |
|
|
if (this.timeLeft <= 60) return '时间紧张,加快决策' |
|
|
return '150秒内完成助推' |
|
|
return '300秒内完成助推' |
|
|
}, |
|
|
}, |
|
|
riskText() { |
|
|
riskText() { |
|
|
if (this.slots.length >= this.slotLimit - 1) return '危险:差1格就满' |
|
|
if (this.slots.length >= this.slotLimit - 1) return '危险:差1格就满' |
|
|
@ -274,7 +277,7 @@ |
|
|
const elapsed = Math.floor((Date.now() - this.startTs) / 1000) |
|
|
const elapsed = Math.floor((Date.now() - this.startTs) / 1000) |
|
|
this.timeLeft = Math.max(0, this.timeLimit - elapsed) |
|
|
this.timeLeft = Math.max(0, this.timeLimit - elapsed) |
|
|
if (this.timeLeft <= 0) { |
|
|
if (this.timeLeft <= 0) { |
|
|
this.failLevel('时间到了', '超过150秒,本关失败。不消耗次数,调整顺序再来一次。') |
|
|
this.failLevel('时间到了', '超过300秒,本关失败。不消耗次数,调整顺序再来一次。') |
|
|
} else { |
|
|
} else { |
|
|
this.startTimer(false) |
|
|
this.startTimer(false) |
|
|
} |
|
|
} |
|
|
@ -356,6 +359,9 @@ |
|
|
if (this.shouldBuildTrapLayout()) { |
|
|
if (this.shouldBuildTrapLayout()) { |
|
|
this.applyTrapLayout(cards) |
|
|
this.applyTrapLayout(cards) |
|
|
} |
|
|
} |
|
|
|
|
|
if (!this.validateTileCounts(cards)) { |
|
|
|
|
|
this.repairTileCounts(cards, icons, garbageIcons) |
|
|
|
|
|
} |
|
|
this.cards = cards |
|
|
this.cards = cards |
|
|
this.slots = [] |
|
|
this.slots = [] |
|
|
this.moveCount = 0 |
|
|
this.moveCount = 0 |
|
|
@ -481,6 +487,7 @@ |
|
|
assigned[card.id] = true |
|
|
assigned[card.id] = true |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
this.cards = cards |
|
|
this.repositionKeyCovers(cards, seed) |
|
|
this.repositionKeyCovers(cards, seed) |
|
|
const freeCards = cards.filter(card => !assigned[card.id]) |
|
|
const freeCards = cards.filter(card => !assigned[card.id]) |
|
|
const garbageCards = this.seededShuffle(freeCards.slice(), seed + '_garbage').slice(0, 6) |
|
|
const garbageCards = this.seededShuffle(freeCards.slice(), seed + '_garbage').slice(0, 6) |
|
|
@ -511,11 +518,36 @@ |
|
|
}) |
|
|
}) |
|
|
return positions |
|
|
return positions |
|
|
}, |
|
|
}, |
|
|
|
|
|
validateTileCounts(cards) { |
|
|
|
|
|
const counts = {} |
|
|
|
|
|
cards.forEach(card => { |
|
|
|
|
|
if (!card.icon) return |
|
|
|
|
|
counts[card.icon] = (counts[card.icon] || 0) + 1 |
|
|
|
|
|
}) |
|
|
|
|
|
return Object.keys(counts).every(icon => { |
|
|
|
|
|
const unit = this.isGarbageIcon(icon) ? 2 : 3 |
|
|
|
|
|
return counts[icon] % unit === 0 |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
repairTileCounts(cards, icons, garbageIcons) { |
|
|
|
|
|
const sorted = cards.slice().sort((a, b) => this.cardZIndex(a) - this.cardZIndex(b)) |
|
|
|
|
|
sorted.forEach((card, index) => { |
|
|
|
|
|
if (index < 6) { |
|
|
|
|
|
card.icon = garbageIcons[Math.floor(index / 2) % garbageIcons.length] |
|
|
|
|
|
card.garbage = true |
|
|
|
|
|
} else { |
|
|
|
|
|
card.icon = icons[Math.floor((index - 6) / 3) % icons.length] |
|
|
|
|
|
card.garbage = false |
|
|
|
|
|
} |
|
|
|
|
|
card.displayIcon = this.displayIcon(card.icon) |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
shouldBuildTrapLayout() { |
|
|
shouldBuildTrapLayout() { |
|
|
const rand = this.seededRandom((this.roundSeed || this.levelSeed()) + '_win_rate') |
|
|
const rand = this.seededRandom((this.roundSeed || this.levelSeed()) + '_win_rate') |
|
|
return rand() < 0.5 |
|
|
return rand() < 0.5 |
|
|
}, |
|
|
}, |
|
|
applyTrapLayout(cards) { |
|
|
applyTrapLayout(cards) { |
|
|
|
|
|
this.cards = cards |
|
|
const iconMap = {} |
|
|
const iconMap = {} |
|
|
cards.forEach(card => { |
|
|
cards.forEach(card => { |
|
|
if (this.isGarbageIcon(card.icon)) return |
|
|
if (this.isGarbageIcon(card.icon)) return |
|
|
@ -544,6 +576,7 @@ |
|
|
card.x = 180 + Math.floor(rand() * 180) |
|
|
card.x = 180 + Math.floor(rand() * 180) |
|
|
card.y = 140 + Math.floor(rand() * 260) |
|
|
card.y = 140 + Math.floor(rand() * 260) |
|
|
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};` |
|
|
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};` |
|
|
|
|
|
this.refreshCardState() |
|
|
}, |
|
|
}, |
|
|
exposePairEarly(pair, index) { |
|
|
exposePairEarly(pair, index) { |
|
|
const baseX = 66 + (index % 4) * 100 |
|
|
const baseX = 66 + (index % 4) * 100 |
|
|
@ -555,6 +588,7 @@ |
|
|
card.y = baseY + i * 58 |
|
|
card.y = baseY + i * 58 |
|
|
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};` |
|
|
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};` |
|
|
}) |
|
|
}) |
|
|
|
|
|
this.refreshCardState() |
|
|
}, |
|
|
}, |
|
|
buildCoverChain(key, cards, index, rand) { |
|
|
buildCoverChain(key, cards, index, rand) { |
|
|
const covers = cards.filter(card => { |
|
|
const covers = cards.filter(card => { |
|
|
@ -569,6 +603,7 @@ |
|
|
cover.y = key.y + (i < 2 ? 16 : -16) + Math.floor(rand() * 10) |
|
|
cover.y = key.y + (i < 2 ? 16 : -16) + Math.floor(rand() * 10) |
|
|
cover.style = `left:${cover.x}rpx;top:${cover.y}rpx;z-index:${this.cardZIndex(cover)};` |
|
|
cover.style = `left:${cover.x}rpx;top:${cover.y}rpx;z-index:${this.cardZIndex(cover)};` |
|
|
} |
|
|
} |
|
|
|
|
|
this.refreshCardState() |
|
|
}, |
|
|
}, |
|
|
repositionKeyCovers(cards, seed) { |
|
|
repositionKeyCovers(cards, seed) { |
|
|
const rand = this.seededRandom(seed + '_cover') |
|
|
const rand = this.seededRandom(seed + '_cover') |
|
|
@ -583,6 +618,7 @@ |
|
|
cover.style = `left:${cover.x}rpx;top:${cover.y}rpx;z-index:${this.cardZIndex(cover)};` |
|
|
cover.style = `left:${cover.x}rpx;top:${cover.y}rpx;z-index:${this.cardZIndex(cover)};` |
|
|
} |
|
|
} |
|
|
}) |
|
|
}) |
|
|
|
|
|
this.refreshCardState() |
|
|
}, |
|
|
}, |
|
|
takeNext(pool, assigned) { |
|
|
takeNext(pool, assigned) { |
|
|
while (pool.length) { |
|
|
while (pool.length) { |
|
|
@ -627,7 +663,7 @@ |
|
|
}, |
|
|
}, |
|
|
coverCount(card, cards) { |
|
|
coverCount(card, cards) { |
|
|
return cards.filter(other => { |
|
|
return cards.filter(other => { |
|
|
if (other.id === card.id || other.layer <= card.layer) return false |
|
|
if (other.id === card.id || this.getRealZ(other) <= this.getRealZ(card)) return false |
|
|
return this.isOverlap(card, other) |
|
|
return this.isOverlap(card, other) |
|
|
}).length |
|
|
}).length |
|
|
}, |
|
|
}, |
|
|
@ -639,15 +675,24 @@ |
|
|
}, |
|
|
}, |
|
|
isBlockedIn(card, list) { |
|
|
isBlockedIn(card, list) { |
|
|
return list.some(other => { |
|
|
return list.some(other => { |
|
|
if (other.id === card.id || other.layer <= card.layer) return false |
|
|
if (other.id === card.id || this.getRealZ(other) <= this.getRealZ(card)) return false |
|
|
return this.isOverlap(card, other) |
|
|
return this.isOverlap(card, other) |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
isOverlap(a, b) { |
|
|
isOverlap(a, b) { |
|
|
return Math.abs(a.x - b.x) < 82 && Math.abs(a.y - b.y) < 82 |
|
|
const size = 76 |
|
|
|
|
|
return !( |
|
|
|
|
|
a.x + size <= b.x || |
|
|
|
|
|
b.x + size <= a.x || |
|
|
|
|
|
a.y + size <= b.y || |
|
|
|
|
|
b.y + size <= a.y |
|
|
|
|
|
) |
|
|
|
|
|
}, |
|
|
|
|
|
getRealZ(card) { |
|
|
|
|
|
return (card.layer || 0) * 1000 + (card.order || 0) |
|
|
}, |
|
|
}, |
|
|
cardZIndex(card) { |
|
|
cardZIndex(card) { |
|
|
return (card.layer || 0) * 100 + (card.order || 0) + 1 |
|
|
return this.getRealZ(card) + 1 |
|
|
}, |
|
|
}, |
|
|
normalizeCards(icons) { |
|
|
normalizeCards(icons) { |
|
|
this.cards.forEach((card, index) => { |
|
|
this.cards.forEach((card, index) => { |
|
|
@ -683,8 +728,9 @@ |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
this.refreshCardState() |
|
|
this.refreshCardState() |
|
|
if (card.removed || card.selected || card.locked || this.isLocked(card)) { |
|
|
const lockedNow = this.isLocked(card) |
|
|
this.tui.toast('这张还被压住,先消上面的牌') |
|
|
if (card.removed || card.selected || lockedNow) { |
|
|
|
|
|
this.tui.toast('这张卡被压住了') |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
if (this.slots.length >= this.slotLimit) return |
|
|
if (this.slots.length >= this.slotLimit) return |
|
|
@ -713,7 +759,7 @@ |
|
|
const elapsed = Math.floor((Date.now() - this.startTs) / 1000) |
|
|
const elapsed = Math.floor((Date.now() - this.startTs) / 1000) |
|
|
this.timeLeft = Math.max(0, this.timeLimit - elapsed) |
|
|
this.timeLeft = Math.max(0, this.timeLimit - elapsed) |
|
|
if (this.timeLeft <= 0) { |
|
|
if (this.timeLeft <= 0) { |
|
|
this.failLevel('时间到了', '超过150秒,本关失败。不消耗次数,调整顺序再来一次。') |
|
|
this.failLevel('时间到了', '超过300秒,本关失败。不消耗次数,调整顺序再来一次。') |
|
|
} |
|
|
} |
|
|
}, 1000) |
|
|
}, 1000) |
|
|
}, |
|
|
}, |
|
|
@ -752,8 +798,9 @@ |
|
|
}, |
|
|
}, |
|
|
isLocked(card) { |
|
|
isLocked(card) { |
|
|
return this.cards.some(other => { |
|
|
return this.cards.some(other => { |
|
|
if (other.removed || other.selected || other.layer <= card.layer) return false |
|
|
if (other.id === card.id) return false |
|
|
return this.isOverlap(card, other) |
|
|
if (other.removed || other.selected) return false |
|
|
|
|
|
return this.getRealZ(other) > this.getRealZ(card) && this.isOverlap(card, other) |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
finishClear() { |
|
|
finishClear() { |
|
|
@ -943,6 +990,7 @@ |
|
|
.board { margin-top: 22rpx; height: 620rpx; border-radius: 36rpx; background: linear-gradient(155deg, rgba(234,248,255,0.9), rgba(255,248,222,0.72)); position: relative; overflow: hidden; z-index: 1; } |
|
|
.board { margin-top: 22rpx; height: 620rpx; border-radius: 36rpx; background: linear-gradient(155deg, rgba(234,248,255,0.9), rgba(255,248,222,0.72)); position: relative; overflow: hidden; z-index: 1; } |
|
|
.tile { position: absolute; width: 76rpx; height: 76rpx; border-radius: 22rpx; background: #fff; display: flex; align-items: center; justify-content: center; box-shadow: 0 10rpx 22rpx rgba(66,99,94,0.12); border: 2rpx solid rgba(255,255,255,0.9); transition: transform .12s ease, opacity .12s ease; } |
|
|
.tile { position: absolute; width: 76rpx; height: 76rpx; border-radius: 22rpx; background: #fff; display: flex; align-items: center; justify-content: center; box-shadow: 0 10rpx 22rpx rgba(66,99,94,0.12); border: 2rpx solid rgba(255,255,255,0.9); transition: transform .12s ease, opacity .12s ease; } |
|
|
.tile text { font-size: 42rpx; } |
|
|
.tile text { font-size: 42rpx; } |
|
|
|
|
|
.tile .debug-layer { position: absolute; right: 6rpx; bottom: 4rpx; font-size: 16rpx; color: #8AA09C; line-height: 1; } |
|
|
.tile.locked { |
|
|
.tile.locked { |
|
|
opacity: .42; |
|
|
opacity: .42; |
|
|
pointer-events: none; |
|
|
pointer-events: none; |
|
|
|