wangfukang 1 week ago
parent
commit
c01a45a826
  1. 380
      package1/planet/adventure.vue

380
package1/planet/adventure.vue

@ -53,7 +53,7 @@
</view> </view>
<view class="tips"> <view class="tips">
<text>规则点选未被压住的卡牌底部槽内3张相同自动消除槽满则失败</text> <text>规则点选未被压住的卡牌底部槽内3张相同自动消除宇宙垃圾2张会触发清理</text>
<text>陷阱难度 · 剩余 {{remainingCount}} · 当前可点 {{availableCount}} · {{riskText}}</text> <text>陷阱难度 · 剩余 {{remainingCount}} · 当前可点 {{availableCount}} · {{riskText}}</text>
</view> </view>
@ -69,7 +69,7 @@
class="tile" class="tile"
v-for="card in visibleCards" v-for="card in visibleCards"
:key="card.id" :key="card.id"
:class="{locked: isLocked(card), selected: card.selected}" :class="card.className"
:style="card.style" :style="card.style"
@tap="pickCard(card)"> @tap="pickCard(card)">
<text>{{card.icon}}</text> <text>{{card.icon}}</text>
@ -180,29 +180,19 @@
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) return this.cards.filter(item => !item.removed && !item.selected)
}, },
remainingCount() { remainingCount() {
return this.cards.filter(item => !item.removed && !item.selected).length return this.cards.filter(item => !item.removed && !item.selected).length
}, },
availableCount() { availableCount() {
return this.cards.filter(item => !item.removed && !item.selected && !this.isLocked(item)).length return this.cards.filter(item => !item.removed && !item.selected && !item.locked).length
}, },
directComboCount() { directComboCount() {
const map = {} return this.collectDifficultyStats(this.cards.filter(item => !item.removed && !item.selected)).directComboCount
this.cards.forEach(item => {
if (!item.removed && !item.selected && !this.isLocked(item)) {
map[item.icon] = (map[item.icon] || 0) + 1
}
})
return Object.keys(map).filter(k => map[k] >= 3).length
}, },
trapGroupCount() { trapGroupCount() {
const map = {} return this.collectDifficultyStats(this.cards).trapGroupCount
this.cards.forEach(item => {
if (item.trap) map[item.icon] = true
})
return Object.keys(map).length
}, },
riskText() { riskText() {
if (this.slots.length >= this.slotLimit - 1) return '危险:差1格就满' if (this.slots.length >= this.slotLimit - 1) return '危险:差1格就满'
@ -276,23 +266,31 @@
this.playing = true this.playing = true
this.startTs = Date.now() this.startTs = Date.now()
this.buildLevel() this.buildLevel()
if (!this.cards.length) this.buildLevel()
}) })
}, },
buildLevel() { buildLevel() {
const icons = ['🍔', '🥤', '📚', '🏀', '🎧', '🚲', '🍜', '📦', '☕', '🧋'] const icons = ['🍔', '🥤', '🥟', '🏀', '🎧', '🚲', '🍜', '🔥', '☕', '🍢']
const garbageIcons = ['🚀', '🪐', '🛸']
let cards = [] let cards = []
for (let attempt = 0; attempt < 8; attempt++) { for (let attempt = 0; attempt < 100; attempt++) {
const seed = this.levelSeed() + '_try_' + attempt const seed = this.levelSeed() + '_try_' + attempt
const positions = this.buildCardPositions(seed) const positions = this.buildCardPositions(seed)
cards = this.assignTrapLevelIcons(positions, icons, seed) cards = this.assignTrapLayoutIcons(positions, icons, garbageIcons, seed)
if (cards.length) break if (cards.length && this.checkDifficultyMetrics(cards)) break
cards = []
}
if (!cards.length) {
cards = this.assignTrapLayoutIcons(this.buildCardPositions(this.levelSeed() + '_fallback'), icons, garbageIcons, this.levelSeed() + '_fallback', true)
} }
if (!cards.length) { if (!cards.length) {
cards = this.assignSolvableIcons(this.buildFallbackPositions(), icons, this.levelSeed() + '_fallback') cards = this.assignEmergencyIcons(this.buildCardPositions(this.levelSeed() + '_emergency'), icons, garbageIcons)
} }
this.cards = cards this.cards = cards
this.slots = [] this.slots = []
this.moveCount = 0 this.moveCount = 0
this.normalizeCards(icons)
this.refreshCardState()
}, },
buildCardPositions(seed) { buildCardPositions(seed) {
const cards = [] const cards = []
@ -305,10 +303,11 @@
const zone = template.zones[layer] const zone = template.zones[layer]
const col = i % zone.cols const col = i % zone.cols
const row = Math.floor(i / zone.cols) const row = Math.floor(i / zone.cols)
const dx = Math.floor(rand() * 18) const dx = Math.floor(rand() * 6)
const dy = Math.floor(rand() * 14) const dy = Math.floor(rand() * 6)
const card = { const card = {
id: 'c' + idx, id: 'c' + idx,
order: idx,
icon: '', icon: '',
layer, layer,
x: zone.x + col * zone.gapX + (layer * zone.offsetX) + ((row % 2) * zone.stagger) + dx, x: zone.x + col * zone.gapX + (layer * zone.offsetX) + ((row % 2) * zone.stagger) + dx,
@ -316,11 +315,12 @@
style: '', style: '',
removed: false, removed: false,
selected: false, selected: false,
solutionStep: 0, locked: false,
className: '',
trap: false, trap: false,
keyCard: false keyCard: false
} }
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${card.layer + 1};` card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
cards.push(card) cards.push(card)
idx++ idx++
} }
@ -356,173 +356,151 @@
hell: { hell: {
layerCounts: [36, 30, 24], layerCounts: [36, 30, 24],
zones: [ zones: [
{ x: 12, y: 16, cols: 6, gapX: 82, gapY: 72, offsetX: 10, offsetY: 22, stagger: 10 }, { x: 38, y: 18, cols: 6, gapX: 86, gapY: 82, offsetX: 0, offsetY: 0, stagger: 4 },
{ x: 86, y: 72, cols: 5, gapX: 70, gapY: 60, offsetX: 18, offsetY: 34, stagger: 16 }, { x: 82, y: 58, cols: 5, gapX: 86, gapY: 82, offsetX: 0, offsetY: 0, stagger: 4 },
{ x: 126, y: 112, cols: 4, gapX: 64, gapY: 54, offsetX: 22, offsetY: 38, stagger: 16 } { x: 124, y: 98, cols: 4, gapX: 86, gapY: 82, offsetX: 0, offsetY: 0, stagger: 4 }
] ]
} }
} }
return templates[type] || templates.hell return templates[type] || templates.hell
}, },
assignSolvableIcons(positions, icons, seed) { assignTrapLayoutIcons(positions, icons, garbageIcons, seed, forceBuild) {
// 线 const rand = this.seededRandom(seed + '_layout')
//
const rand = this.seededRandom(seed + '_solution')
const remaining = positions.slice()
const solutionGroups = []
while (remaining.length) {
const available = remaining.filter(card => !this.isBlockedIn(card, remaining))
if (available.length < 3) return []
const picked = this.pickSolutionTriple(available, rand)
solutionGroups.push(picked)
picked.forEach(card => {
const idx = remaining.indexOf(card)
if (idx >= 0) remaining.splice(idx, 1)
})
}
const tripleIcons = []
icons.forEach(icon => {
for (let i = 0; i < 3; i++) tripleIcons.push(icon)
})
this.seededShuffle(tripleIcons, seed + '_icons')
solutionGroups.forEach((group, step) => {
const icon = tripleIcons[step]
group.forEach(card => {
card.icon = icon
card.solutionStep = step + 1
})
})
return positions
},
assignTrapLevelIcons(positions, icons, seed) {
const rand = this.seededRandom(seed + '_trap')
const cards = positions.slice() const cards = positions.slice()
const trapIcons = this.seededShuffle(icons.slice(), seed + '_trap_icons').slice(0, 4) cards.forEach(card => {
const remaining = cards.slice() card.icon = ''
const groups = [] card.trap = false
const trapPlans = this.buildTrapPlans(cards, trapIcons, seed) card.keyCard = false
if (trapPlans.length < 4) return [] card.garbage = false
while (remaining.length) {
const available = remaining.filter(card => !this.isBlockedIn(card, remaining))
if (available.length < 3) return []
const readyTrap = trapPlans.find(plan => {
return !plan.used && plan.cards.every(card => remaining.indexOf(card) >= 0) && !this.isBlockedIn(plan.key, remaining)
})
const normalAvailable = available.filter(card => !card.reservedTrap)
const picked = readyTrap ? readyTrap.cards : this.pickSolutionTriple(normalAvailable, rand)
if (!picked || picked.length < 3) return []
groups.push({
cards: picked,
trapIcon: readyTrap ? readyTrap.icon : ''
})
if (readyTrap) readyTrap.used = true
picked.forEach(card => {
const idx = remaining.indexOf(card)
if (idx >= 0) remaining.splice(idx, 1)
})
}
if (trapPlans.some(plan => !plan.used)) return []
const trapIndexes = []
groups.forEach((group, index) => {
if (group.trapIcon) trapIndexes.push(index)
}) })
const groupIcons = this.buildPressureIconOrder(groups.length, icons, trapIcons, trapIndexes, seed) const bottom = this.seededShuffle(cards.filter(card => card.layer === 0), seed + '_bottom')
groups.forEach((group, step) => { const middle = this.seededShuffle(cards.filter(card => card.layer === 1), seed + '_middle')
const icon = group.trapIcon || groupIcons[step] || icons[step % icons.length] const top = this.seededShuffle(cards.filter(card => card.layer === 2), seed + '_top')
group.cards.forEach(card => { const bottomCenter = this.seededShuffle(bottom.filter(card => this.isCenterCard(card)), seed + '_bottom_center')
const topOuter = this.seededShuffle(top.filter(card => !this.isCenterCard(card)), seed + '_top_outer')
const midOuter = this.seededShuffle(middle.filter(card => !this.isCenterCard(card)), seed + '_mid_outer')
const assigned = {}
const groupCount = Math.floor((cards.length - 6) / 3)
const trapCount = Math.max(10, Math.ceil(groupCount * 0.5))
for (let group = 0; group < groupCount; group++) {
const icon = icons[group % icons.length]
let chosen = []
if (group < trapCount) {
chosen = [
this.takeNext(topOuter, assigned) || this.takeNext(top, assigned) || this.takeAny(cards, assigned),
this.takeNext(midOuter, assigned) || this.takeNext(middle, assigned) || this.takeAny(cards, assigned),
this.takeNext(bottomCenter, assigned) || this.takeNext(bottom, assigned) || this.takeAny(cards, assigned)
]
} else {
chosen = [
this.takeNext(top, assigned) || this.takeAny(cards, assigned),
this.takeNext(middle, assigned) || this.takeAny(cards, assigned),
this.takeNext(bottom, assigned) || this.takeAny(cards, assigned)
]
}
if (chosen.some(card => !card)) {
if (!forceBuild) return []
break
}
chosen.forEach((card, index) => {
card.icon = icon card.icon = icon
card.solutionStep = step + 1 card.trap = group < trapCount
if (group.trapIcon) { card.keyCard = group < trapCount && index === 2
card.trap = true assigned[card.id] = true
}
}) })
}
this.repositionKeyCovers(cards, seed)
const freeCards = cards.filter(card => !assigned[card.id])
const garbageCards = this.seededShuffle(freeCards.slice(), seed + '_garbage').slice(0, 6)
garbageCards.forEach((card, index) => {
card.icon = garbageIcons[Math.floor(index / 2) % garbageIcons.length]
card.garbage = true
assigned[card.id] = true
}) })
cards.forEach((card, index) => {
cards.forEach(card => { if (!card.icon) {
card.reservedTrap = false card.icon = icons[(index + Math.floor(rand() * icons.length)) % icons.length]
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${card.layer + 1};` }
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
}) })
return cards return cards
}, },
buildTrapPlans(cards, trapIcons, seed) { assignEmergencyIcons(positions, icons, garbageIcons) {
const startAvailable = cards.filter(card => !this.isBlockedIn(card, cards)) positions.forEach((card, index) => {
const baitPool = this.seededShuffle(startAvailable.filter(card => !this.isCenterCard(card)), seed + '_bait') card.icon = index < 6 ? garbageIcons[Math.floor(index / 2) % garbageIcons.length] : icons[Math.floor((index - 6) / 3) % icons.length]
const keyPool = this.seededShuffle(cards.filter(card => card.layer === 0 && this.isCenterCard(card)), seed + '_key') card.trap = index >= 6 && index < 42
const plans = [] card.keyCard = card.trap && card.layer === 0 && this.isCenterCard(card)
trapIcons.forEach(icon => { card.garbage = index < 6
const baitA = baitPool.shift() card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
const baitB = baitPool.shift()
const key = keyPool.shift()
if (!baitA || !baitB || !key) return
;[baitA, baitB, key].forEach(card => {
card.reservedTrap = true
card.trap = true
})
key.keyCard = true
plans.push({
icon,
cards: [baitA, baitB, key],
key,
used: false
})
}) })
return plans return positions
}, },
buildPressureIconOrder(count, icons, trapIcons, trapIndexes, seed) { repositionKeyCovers(cards, seed) {
const rand = this.seededRandom(seed + '_pressure') const rand = this.seededRandom(seed + '_cover')
const safeIcons = icons.filter(icon => trapIcons.indexOf(icon) < 0) const keys = cards.filter(card => card.keyCard && card.layer === 0 && this.isCenterCard(card)).slice(0, 10)
const order = new Array(count) const covers = this.seededShuffle(cards.filter(card => !card.keyCard && card.layer > 0), seed + '_cover_cards')
trapIndexes.forEach((idx, i) => { keys.forEach(key => {
order[idx] = trapIcons[i % trapIcons.length] for (let i = 0; i < 2; i++) {
}) const cover = covers.shift()
const usage = {} if (!cover) return
trapIcons.forEach(icon => { cover.x = key.x + (i === 0 ? 18 : -10) + Math.floor(rand() * 8)
usage[icon] = 1 cover.y = key.y + (i === 0 ? 18 : -10) + Math.floor(rand() * 8)
}) cover.style = `left:${cover.x}rpx;top:${cover.y}rpx;z-index:${this.cardZIndex(cover)};`
for (let i = 0; i < count; i++) {
if (order[i]) continue
const pool = i < 12 ? icons : safeIcons
let icon = pool[i % pool.length]
let guard = 0
while ((usage[icon] || 0) >= 3 && guard < pool.length + icons.length) {
icon = pool[(i + guard + 1) % pool.length]
guard++
}
if ((usage[icon] || 0) >= 3) {
icon = icons.find(item => (usage[item] || 0) < 3) || icons[i % icons.length]
} }
order[i] = icon })
usage[icon] = (usage[icon] || 0) + 1 },
takeNext(pool, assigned) {
while (pool.length) {
const card = pool.shift()
if (!assigned[card.id]) return card
} }
for (let i = 0; i < order.length; i += 6) { return null
const part = order.slice(i, i + 6) },
this.shuffleWithRandom(part, rand) takeAny(cards, assigned) {
for (let j = 0; j < part.length; j++) order[i + j] = part[j] for (let i = 0; i < cards.length; i++) {
if (!assigned[cards[i].id]) return cards[i]
}
return null
},
checkDifficultyMetrics(cards) {
const stats = this.collectDifficultyStats(cards)
return stats.visibleTypeCount >= 8 &&
stats.directComboCount <= 2 &&
stats.trapGroupCount >= 10 &&
stats.deepKeyCount >= 10
},
collectDifficultyStats(cards) {
const visible = cards.filter(card => !this.isBlockedIn(card, cards))
const typeMap = {}
const countMap = {}
visible.forEach(card => {
typeMap[card.icon] = true
countMap[card.icon] = (countMap[card.icon] || 0) + 1
})
const trapMap = {}
let deepKeyCount = 0
cards.forEach(card => {
if (card.trap && card.icon) trapMap[card.icon] = true
if (card.keyCard && card.layer === 0 && this.coverCount(card, cards) >= 2) deepKeyCount++
})
return {
visibleTypeCount: Object.keys(typeMap).length,
directComboCount: Object.keys(countMap).filter(icon => countMap[icon] >= 3 && !this.isGarbageIcon(icon)).length,
trapGroupCount: Object.keys(trapMap).length,
deepKeyCount
} }
return order
}, },
isCenterCard(card) { coverCount(card, cards) {
return card.x >= 160 && card.x <= 400 && card.y >= 120 && card.y <= 440 return cards.filter(other => {
if (other.id === card.id || other.layer <= card.layer) return false
return this.isOverlap(card, other)
}).length
}, },
pickSolutionTriple(available, rand) { isGarbageIcon(icon) {
const pool = available.slice() return icon === '🚀' || icon === '🪐' || icon === '🛸'
if (pool.length < 3) return []
this.shuffleWithRandom(pool, rand)
const first = pool.shift()
pool.sort((a, b) => this.cardDistanceScore(b, first) - this.cardDistanceScore(a, first))
const second = pool.shift()
pool.sort((a, b) => {
const bd = this.cardDistanceScore(b, first) + this.cardDistanceScore(b, second)
const ad = this.cardDistanceScore(a, first) + this.cardDistanceScore(a, second)
return bd - ad
})
const third = pool.shift()
return [first, second, third]
}, },
cardDistanceScore(a, b) { isCenterCard(card) {
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y) + Math.abs(a.layer - b.layer) * 60 return card.x >= 160 && card.x <= 400 && card.y >= 120 && card.y <= 440
}, },
isBlockedIn(card, list) { isBlockedIn(card, list) {
return list.some(other => { return list.some(other => {
@ -531,7 +509,28 @@
}) })
}, },
isOverlap(a, b) { isOverlap(a, b) {
return Math.abs(a.x - b.x) < 66 && Math.abs(a.y - b.y) < 58 return Math.abs(a.x - b.x) < 74 && Math.abs(a.y - b.y) < 74
},
cardZIndex(card) {
return (card.layer || 0) * 1000 + (card.order || 0) + 1
},
normalizeCards(icons) {
this.cards.forEach((card, index) => {
if (!card.icon) card.icon = icons[index % icons.length]
card.locked = false
card.className = ''
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
})
},
refreshCardState() {
this.cards.forEach(card => {
const locked = !card.removed && !card.selected && this.isLocked(card)
card.locked = locked
card.className = [
locked ? 'locked' : '',
card.selected ? 'selected' : ''
].filter(Boolean).join(' ')
})
}, },
pickCard(card) { pickCard(card) {
if (this.cleared) { if (this.cleared) {
@ -542,12 +541,17 @@
this.tui.toast('先点击开始助推') this.tui.toast('先点击开始助推')
return return
} }
if (card.removed || card.selected || this.isLocked(card)) return this.refreshCardState()
if (card.removed || card.selected || card.locked) {
this.tui.toast('这张还被压住,先消上面的牌')
return
}
if (this.slots.length >= this.slotLimit) return if (this.slots.length >= this.slotLimit) return
card.selected = true card.selected = true
this.slots.push(card) this.slots.push(card)
this.moveCount++ this.moveCount++
this.tryClear(card.icon) this.tryClear(card.icon)
this.refreshCardState()
if (this.cards.every(item => item.removed || item.selected)) { if (this.cards.every(item => item.removed || item.selected)) {
this.finishClear() this.finishClear()
} else if (this.slots.length >= this.slotLimit) { } else if (this.slots.length >= this.slotLimit) {
@ -555,6 +559,20 @@
} }
}, },
tryClear(icon) { tryClear(icon) {
if (this.isGarbageIcon(icon)) {
const garbage = this.slots.filter(item => item.icon === icon)
if (garbage.length < 2) return
let removedGarbage = 0
this.slots = this.slots.filter(item => {
if (item.icon === icon && removedGarbage < 2) {
item.removed = true
removedGarbage++
return false
}
return true
})
return
}
const same = this.slots.filter(item => item.icon === icon) const same = this.slots.filter(item => item.icon === icon)
if (same.length < 3) return if (same.length < 3) return
let removed = 0 let removed = 0
@ -608,6 +626,7 @@
if (this.cleared) return if (this.cleared) return
this.playing = false this.playing = false
this.buildLevel() this.buildLevel()
if (!this.cards.length) this.buildLevel()
}, },
closeModal() { closeModal() {
this.modal.show = false this.modal.show = false
@ -668,6 +687,7 @@
const row = Math.floor(i / 9) const row = Math.floor(i / 9)
const card = { const card = {
id: 'f' + idx, id: 'f' + idx,
order: idx,
icon: '', icon: '',
layer, layer,
x: 10 + col * 68 + (layer * 6), x: 10 + col * 68 + (layer * 6),
@ -675,9 +695,12 @@
style: '', style: '',
removed: false, removed: false,
selected: false, selected: false,
solutionStep: 0 locked: false,
className: '',
trap: false,
keyCard: false
} }
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${card.layer + 1};` card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
cards.push(card) cards.push(card)
idx++ idx++
} }
@ -729,7 +752,10 @@
.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; } .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; }
.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.locked { opacity: .42; background: #F2F6F5; } .tile.locked{
opacity:.82;
filter:grayscale(.15);
}
.tile.selected { opacity: 0; transform: scale(.5); pointer-events: none; } .tile.selected { opacity: 0; transform: scale(.5); pointer-events: none; }
.slot-wrap { margin-top: 24rpx; } .slot-wrap { margin-top: 24rpx; }
.slot-title { color: #6B817D; font-size: 24rpx; margin-bottom: 14rpx; } .slot-title { color: #6B817D; font-size: 24rpx; margin-bottom: 14rpx; }

Loading…
Cancel
Save