|
|
@ -41,7 +41,7 @@ |
|
|
<view class="game-card"> |
|
|
<view class="game-card"> |
|
|
<view class="section-head"> |
|
|
<view class="section-head"> |
|
|
<text>今日关卡</text> |
|
|
<text>今日关卡</text> |
|
|
<text>{{cleared ? '已通关' : '约2分钟'}}</text> |
|
|
<text>{{cleared ? '已通关' : timerText}}</text> |
|
|
</view> |
|
|
</view> |
|
|
<view class="start-strip" v-if="!playing && !cleared" @tap="startLevel"> |
|
|
<view class="start-strip" v-if="!playing && !cleared" @tap="startLevel"> |
|
|
<text>开始助推</text> |
|
|
<text>开始助推</text> |
|
|
@ -54,7 +54,7 @@ |
|
|
|
|
|
|
|
|
<view class="tips"> |
|
|
<view class="tips"> |
|
|
<text>规则:点选未被压住的卡牌,底部槽内3张相同自动消除,宇宙垃圾2张会触发清理。</text> |
|
|
<text>规则:点选未被压住的卡牌,底部槽内3张相同自动消除,宇宙垃圾2张会触发清理。</text> |
|
|
<text>陷阱难度 · 剩余 {{remainingCount}} 张 · 当前可点 {{availableCount}} 张 · {{riskText}}</text> |
|
|
<text>陷阱难度 · 剩余 {{remainingCount}} 张 · 当前可点 {{availableCount}} 张 · 倒计时 {{timeLeft}} 秒 · {{riskText}}</text> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
<view class="debug-panel"> |
|
|
<view class="debug-panel"> |
|
|
@ -147,6 +147,9 @@ |
|
|
cards: [], |
|
|
cards: [], |
|
|
slots: [], |
|
|
slots: [], |
|
|
slotLimit: 7, |
|
|
slotLimit: 7, |
|
|
|
|
|
timeLimit: 150, |
|
|
|
|
|
timeLeft: 150, |
|
|
|
|
|
timer: null, |
|
|
moveCount: 0, |
|
|
moveCount: 0, |
|
|
modal: { show: false, title: '', sub: '' } |
|
|
modal: { show: false, title: '', sub: '' } |
|
|
} |
|
|
} |
|
|
@ -205,6 +208,9 @@ |
|
|
} |
|
|
} |
|
|
return cells |
|
|
return cells |
|
|
}, |
|
|
}, |
|
|
|
|
|
timerText() { |
|
|
|
|
|
return this.playing ? `剩余 ${this.timeLeft} 秒` : '限时150秒' |
|
|
|
|
|
}, |
|
|
riskText() { |
|
|
riskText() { |
|
|
if (this.slots.length >= this.slotLimit - 1) return '危险:差1格就满' |
|
|
if (this.slots.length >= this.slotLimit - 1) return '危险:差1格就满' |
|
|
if (this.slots.length >= this.slotLimit - 2) return '注意槽位' |
|
|
if (this.slots.length >= this.slotLimit - 2) return '注意槽位' |
|
|
@ -226,6 +232,22 @@ |
|
|
this.loadHome() |
|
|
this.loadHome() |
|
|
this.buildLevel() |
|
|
this.buildLevel() |
|
|
}, |
|
|
}, |
|
|
|
|
|
onUnload() { |
|
|
|
|
|
this.clearTimer() |
|
|
|
|
|
}, |
|
|
|
|
|
onHide() { |
|
|
|
|
|
this.clearTimer() |
|
|
|
|
|
}, |
|
|
|
|
|
onShow() { |
|
|
|
|
|
if (!this.playing || !this.startTs) return |
|
|
|
|
|
const elapsed = Math.floor((Date.now() - this.startTs) / 1000) |
|
|
|
|
|
this.timeLeft = Math.max(0, this.timeLimit - elapsed) |
|
|
|
|
|
if (this.timeLeft <= 0) { |
|
|
|
|
|
this.failLevel('时间到了', '超过150秒,本关失败。不消耗次数,调整顺序再来一次。') |
|
|
|
|
|
} else { |
|
|
|
|
|
this.startTimer(false) |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
loadColleges() { |
|
|
loadColleges() { |
|
|
this.tui.request('/app/planet/college/list', 'POST', { |
|
|
this.tui.request('/app/planet/college/list', 'POST', { |
|
|
@ -278,6 +300,7 @@ |
|
|
this.startTs = Date.now() |
|
|
this.startTs = Date.now() |
|
|
this.buildLevel() |
|
|
this.buildLevel() |
|
|
if (!this.cards.length) this.buildLevel() |
|
|
if (!this.cards.length) this.buildLevel() |
|
|
|
|
|
this.startTimer() |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
buildLevel() { |
|
|
buildLevel() { |
|
|
@ -297,6 +320,9 @@ |
|
|
if (!cards.length) { |
|
|
if (!cards.length) { |
|
|
cards = this.assignEmergencyIcons(this.buildCardPositions(this.levelSeed() + '_emergency'), icons, garbageIcons) |
|
|
cards = this.assignEmergencyIcons(this.buildCardPositions(this.levelSeed() + '_emergency'), icons, garbageIcons) |
|
|
} |
|
|
} |
|
|
|
|
|
if (this.shouldBuildUnwinnableLevel()) { |
|
|
|
|
|
this.applyUnwinnableTwist(cards, icons, garbageIcons) |
|
|
|
|
|
} |
|
|
this.cards = cards |
|
|
this.cards = cards |
|
|
this.slots = [] |
|
|
this.slots = [] |
|
|
this.moveCount = 0 |
|
|
this.moveCount = 0 |
|
|
@ -448,6 +474,47 @@ |
|
|
}) |
|
|
}) |
|
|
return positions |
|
|
return positions |
|
|
}, |
|
|
}, |
|
|
|
|
|
shouldBuildUnwinnableLevel() { |
|
|
|
|
|
const rand = this.seededRandom(this.levelSeed() + '_win_rate') |
|
|
|
|
|
return rand() < 0.5 |
|
|
|
|
|
}, |
|
|
|
|
|
applyUnwinnableTwist(cards, icons, garbageIcons) { |
|
|
|
|
|
const iconMap = {} |
|
|
|
|
|
cards.forEach(card => { |
|
|
|
|
|
if (this.isGarbageIcon(card.icon)) return |
|
|
|
|
|
if (!iconMap[card.icon]) iconMap[card.icon] = [] |
|
|
|
|
|
iconMap[card.icon].push(card) |
|
|
|
|
|
}) |
|
|
|
|
|
const candidates = Object.keys(iconMap).filter(icon => iconMap[icon].length >= 3) |
|
|
|
|
|
if (!candidates.length) return |
|
|
|
|
|
const rand = this.seededRandom(this.levelSeed() + '_unwinnable') |
|
|
|
|
|
this.shuffleWithRandom(candidates, rand) |
|
|
|
|
|
const trapIcons = candidates.slice(0, 4) |
|
|
|
|
|
trapIcons.forEach((targetIcon, trapIndex) => { |
|
|
|
|
|
const group = iconMap[targetIcon].slice().sort((a, b) => { |
|
|
|
|
|
const scoreA = (a.locked ? 10 : 0) + a.layer * 3 + (this.isCenterCard(a) ? 2 : 0) |
|
|
|
|
|
const scoreB = (b.locked ? 10 : 0) + b.layer * 3 + (this.isCenterCard(b) ? 2 : 0) |
|
|
|
|
|
return scoreB - scoreA |
|
|
|
|
|
}) |
|
|
|
|
|
const key = group[0] |
|
|
|
|
|
const replacement = garbageIcons[trapIndex % garbageIcons.length] |
|
|
|
|
|
if (!key || !replacement) return |
|
|
|
|
|
key.icon = replacement |
|
|
|
|
|
key.displayIcon = this.displayIcon(key.icon) |
|
|
|
|
|
key.unwinnableKey = true |
|
|
|
|
|
key.trap = true |
|
|
|
|
|
this.exposePairEarly(iconMap[targetIcon].filter(card => card.id !== key.id).slice(0, 2), trapIndex) |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
exposePairEarly(pair, index) { |
|
|
|
|
|
const baseX = 74 + index * 88 |
|
|
|
|
|
pair.forEach((card, i) => { |
|
|
|
|
|
card.layer = 2 |
|
|
|
|
|
card.x = baseX + i * 8 |
|
|
|
|
|
card.y = 26 + i * 78 |
|
|
|
|
|
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};` |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
repositionKeyCovers(cards, seed) { |
|
|
repositionKeyCovers(cards, seed) { |
|
|
const rand = this.seededRandom(seed + '_cover') |
|
|
const rand = this.seededRandom(seed + '_cover') |
|
|
const keys = cards.filter(card => card.keyCard && card.layer === 0 && this.isCenterCard(card)).slice(0, 10) |
|
|
const keys = cards.filter(card => card.keyCard && card.layer === 0 && this.isCenterCard(card)).slice(0, 10) |
|
|
@ -572,19 +639,42 @@ |
|
|
this.moveCount++ |
|
|
this.moveCount++ |
|
|
this.tryClear(card.icon) |
|
|
this.tryClear(card.icon) |
|
|
this.refreshCardState() |
|
|
this.refreshCardState() |
|
|
if (this.cards.every(item => item.removed || item.selected)) { |
|
|
if (this.cards.every(item => item.removed) && this.slots.length === 0) { |
|
|
this.finishClear() |
|
|
this.finishClear() |
|
|
|
|
|
} else if (this.cards.every(item => item.removed || item.selected) && this.slots.length > 0) { |
|
|
|
|
|
this.failLevel() |
|
|
} else if (this.slots.length >= this.slotLimit) { |
|
|
} else if (this.slots.length >= this.slotLimit) { |
|
|
this.failLevel() |
|
|
this.failLevel() |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
startTimer(reset) { |
|
|
|
|
|
this.clearTimer() |
|
|
|
|
|
if (reset !== false) this.timeLeft = this.timeLimit |
|
|
|
|
|
this.timer = setInterval(() => { |
|
|
|
|
|
if (!this.playing) { |
|
|
|
|
|
this.clearTimer() |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
const elapsed = Math.floor((Date.now() - this.startTs) / 1000) |
|
|
|
|
|
this.timeLeft = Math.max(0, this.timeLimit - elapsed) |
|
|
|
|
|
if (this.timeLeft <= 0) { |
|
|
|
|
|
this.failLevel('时间到了', '超过150秒,本关失败。不消耗次数,调整顺序再来一次。') |
|
|
|
|
|
} |
|
|
|
|
|
}, 1000) |
|
|
|
|
|
}, |
|
|
|
|
|
clearTimer() { |
|
|
|
|
|
if (this.timer) { |
|
|
|
|
|
clearInterval(this.timer) |
|
|
|
|
|
this.timer = null |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
tryClear(icon) { |
|
|
tryClear(icon) { |
|
|
if (this.isGarbageIcon(icon)) { |
|
|
if (this.isGarbageIcon(icon)) { |
|
|
const garbage = this.slots.filter(item => item.icon === icon) |
|
|
const garbage = this.slots.filter(item => item.icon === icon && !item.unwinnableKey) |
|
|
if (garbage.length < 2) return |
|
|
if (garbage.length < 2) return |
|
|
let removedGarbage = 0 |
|
|
let removedGarbage = 0 |
|
|
this.slots = this.slots.filter(item => { |
|
|
this.slots = this.slots.filter(item => { |
|
|
if (item.icon === icon && removedGarbage < 2) { |
|
|
if (item.icon === icon && !item.unwinnableKey && removedGarbage < 2) { |
|
|
item.removed = true |
|
|
item.removed = true |
|
|
removedGarbage++ |
|
|
removedGarbage++ |
|
|
return false |
|
|
return false |
|
|
@ -615,6 +705,7 @@ |
|
|
const duration = Math.max(20, Math.floor((Date.now() - this.startTs) / 1000)) |
|
|
const duration = Math.max(20, Math.floor((Date.now() - this.startTs) / 1000)) |
|
|
const progress = Math.max(100, 260 - duration + Math.max(0, 80 - this.moveCount)) |
|
|
const progress = Math.max(100, 260 - duration + Math.max(0, 80 - this.moveCount)) |
|
|
this.playing = false |
|
|
this.playing = false |
|
|
|
|
|
this.clearTimer() |
|
|
this.tui.request('/app/planet/adventure/submit', 'POST', { |
|
|
this.tui.request('/app/planet/adventure/submit', 'POST', { |
|
|
userId: this.userId, |
|
|
userId: this.userId, |
|
|
regionId: this.regionId, |
|
|
regionId: this.regionId, |
|
|
@ -638,13 +729,20 @@ |
|
|
} |
|
|
} |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
failLevel() { |
|
|
failLevel(title, sub) { |
|
|
this.playing = false |
|
|
this.playing = false |
|
|
this.modal = { show: true, title: '槽位满了', sub: '本关失败,不消耗次数。换个顺序再推一次。' } |
|
|
this.clearTimer() |
|
|
|
|
|
this.modal = { |
|
|
|
|
|
show: true, |
|
|
|
|
|
title: title || '槽位满了', |
|
|
|
|
|
sub: sub || '本关失败,不消耗次数。换个顺序再推一次。' |
|
|
|
|
|
} |
|
|
}, |
|
|
}, |
|
|
resetLevel() { |
|
|
resetLevel() { |
|
|
if (this.cleared) return |
|
|
if (this.cleared) return |
|
|
this.playing = false |
|
|
this.playing = false |
|
|
|
|
|
this.clearTimer() |
|
|
|
|
|
this.timeLeft = this.timeLimit |
|
|
this.buildLevel() |
|
|
this.buildLevel() |
|
|
if (!this.cards.length) this.buildLevel() |
|
|
if (!this.cards.length) this.buildLevel() |
|
|
}, |
|
|
}, |
|
|
|