diff --git a/package1/planet/adventure.vue b/package1/planet/adventure.vue
index cbe14bf..6e58c4b 100644
--- a/package1/planet/adventure.vue
+++ b/package1/planet/adventure.vue
@@ -54,7 +54,14 @@
规则:点选未被压住的卡牌,底部槽内3张相同自动消除,槽满则失败。
- 地狱难度 · 剩余 {{remainingCount}} 张 · 当前可点 {{availableCount}} 张 · {{riskText}}
+ 陷阱难度 · 剩余 {{remainingCount}} 张 · 当前可点 {{availableCount}} 张 · {{riskText}}
+
+
+
+ {{availableCount}}可点
+ {{slots.length}}/{{slotLimit}}槽位
+ {{directComboCount}}可消
+ {{trapGroupCount}}陷阱组
@@ -139,7 +146,7 @@
startTs: 0,
cards: [],
slots: [],
- slotLimit: 5,
+ slotLimit: 7,
moveCount: 0,
modal: { show: false, title: '', sub: '' }
}
@@ -181,6 +188,22 @@
availableCount() {
return this.cards.filter(item => !item.removed && !item.selected && !this.isLocked(item)).length
},
+ directComboCount() {
+ const map = {}
+ 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() {
+ const map = {}
+ this.cards.forEach(item => {
+ if (item.trap) map[item.icon] = true
+ })
+ return Object.keys(map).length
+ },
riskText() {
if (this.slots.length >= this.slotLimit - 1) return '危险:差1格就满'
if (this.slots.length >= this.slotLimit - 2) return '注意槽位'
@@ -256,12 +279,12 @@
})
},
buildLevel() {
- const icons = ['🍔', '🥤', '📚', '🏀', '🎧', '🚲', '🍜', '📦', '☕', '🧋', '🎮', '🌟', '🍟', '🥪']
+ const icons = ['🍔', '🥤', '📚', '🏀', '🎧', '🚲', '🍜', '📦', '☕', '🧋']
let cards = []
for (let attempt = 0; attempt < 8; attempt++) {
const seed = this.levelSeed() + '_try_' + attempt
const positions = this.buildCardPositions(seed)
- cards = this.assignSolvableIcons(positions, icons, seed)
+ cards = this.assignTrapLevelIcons(positions, icons, seed)
if (cards.length) break
}
if (!cards.length) {
@@ -273,25 +296,29 @@
},
buildCardPositions(seed) {
const cards = []
- const layerCounts = [42, 36, 30, 18]
+ const template = this.levelTemplate('hell')
+ const layerCounts = template.layerCounts
const rand = this.seededRandom(seed + '_pos')
let idx = 0
layerCounts.forEach((count, layer) => {
for (let i = 0; i < count; i++) {
- const col = i % 7
- const row = Math.floor(i / 7)
+ const zone = template.zones[layer]
+ const col = i % zone.cols
+ const row = Math.floor(i / zone.cols)
const dx = Math.floor(rand() * 18)
const dy = Math.floor(rand() * 14)
const card = {
id: 'c' + idx,
icon: '',
layer,
- x: 12 + col * 70 + (layer * 16) + ((row % 2) * 10) + dx,
- y: 16 + row * 62 + (layer * 34) + dy,
+ x: zone.x + col * zone.gapX + (layer * zone.offsetX) + ((row % 2) * zone.stagger) + dx,
+ y: zone.y + row * zone.gapY + (layer * zone.offsetY) + dy,
style: '',
removed: false,
selected: false,
- solutionStep: 0
+ solutionStep: 0,
+ trap: false,
+ keyCard: false
}
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${card.layer + 1};`
cards.push(card)
@@ -300,6 +327,43 @@
})
return cards
},
+ levelTemplate(type) {
+ const templates = {
+ simple: {
+ layerCounts: [36, 30, 24],
+ zones: [
+ { x: 20, y: 18, cols: 6, gapX: 82, gapY: 72, offsetX: 8, offsetY: 18, stagger: 8 },
+ { x: 70, y: 72, cols: 5, gapX: 78, gapY: 70, offsetX: 10, offsetY: 22, stagger: 10 },
+ { x: 96, y: 110, cols: 5, gapX: 74, gapY: 66, offsetX: 12, offsetY: 24, stagger: 10 }
+ ]
+ },
+ normal: {
+ layerCounts: [36, 30, 24],
+ zones: [
+ { x: 18, y: 18, cols: 6, gapX: 80, gapY: 70, offsetX: 12, offsetY: 24, stagger: 10 },
+ { x: 78, y: 76, cols: 5, gapX: 76, gapY: 66, offsetX: 14, offsetY: 28, stagger: 12 },
+ { x: 110, y: 116, cols: 4, gapX: 74, gapY: 62, offsetX: 16, offsetY: 30, stagger: 12 }
+ ]
+ },
+ difficult: {
+ layerCounts: [36, 30, 24],
+ zones: [
+ { x: 14, y: 16, cols: 6, gapX: 80, gapY: 70, offsetX: 14, offsetY: 28, stagger: 12 },
+ { x: 92, y: 76, cols: 5, gapX: 70, gapY: 62, offsetX: 18, offsetY: 32, stagger: 14 },
+ { x: 132, y: 122, cols: 4, gapX: 66, gapY: 58, offsetX: 20, offsetY: 34, stagger: 14 }
+ ]
+ },
+ hell: {
+ layerCounts: [36, 30, 24],
+ zones: [
+ { x: 12, y: 16, cols: 6, gapX: 82, gapY: 72, offsetX: 10, offsetY: 22, stagger: 10 },
+ { x: 86, y: 72, cols: 5, gapX: 70, gapY: 60, offsetX: 18, offsetY: 34, stagger: 16 },
+ { x: 126, y: 112, cols: 4, gapX: 64, gapY: 54, offsetX: 22, offsetY: 38, stagger: 16 }
+ ]
+ }
+ }
+ return templates[type] || templates.hell
+ },
assignSolvableIcons(positions, icons, seed) {
// 上线关卡不能随机发牌。这里先模拟一条真实可点击的消除路径,
// 再按路径给三张牌分配同一图案,保证每日关卡至少存在一条解法。
@@ -330,8 +394,121 @@
})
return positions
},
+ assignTrapLevelIcons(positions, icons, seed) {
+ const rand = this.seededRandom(seed + '_trap')
+ const cards = positions.slice()
+ const trapIcons = this.seededShuffle(icons.slice(), seed + '_trap_icons').slice(0, 4)
+ const remaining = cards.slice()
+ const groups = []
+ const trapPlans = this.buildTrapPlans(cards, trapIcons, seed)
+ if (trapPlans.length < 4) return []
+
+ 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)
+ groups.forEach((group, step) => {
+ const icon = group.trapIcon || groupIcons[step] || icons[step % icons.length]
+ group.cards.forEach(card => {
+ card.icon = icon
+ card.solutionStep = step + 1
+ if (group.trapIcon) {
+ card.trap = true
+ }
+ })
+ })
+
+ cards.forEach(card => {
+ card.reservedTrap = false
+ card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${card.layer + 1};`
+ })
+ return cards
+ },
+ buildTrapPlans(cards, trapIcons, seed) {
+ const startAvailable = cards.filter(card => !this.isBlockedIn(card, cards))
+ const baitPool = this.seededShuffle(startAvailable.filter(card => !this.isCenterCard(card)), seed + '_bait')
+ const keyPool = this.seededShuffle(cards.filter(card => card.layer === 0 && this.isCenterCard(card)), seed + '_key')
+ const plans = []
+ trapIcons.forEach(icon => {
+ const baitA = baitPool.shift()
+ 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
+ },
+ buildPressureIconOrder(count, icons, trapIcons, trapIndexes, seed) {
+ const rand = this.seededRandom(seed + '_pressure')
+ const safeIcons = icons.filter(icon => trapIcons.indexOf(icon) < 0)
+ const order = new Array(count)
+ trapIndexes.forEach((idx, i) => {
+ order[idx] = trapIcons[i % trapIcons.length]
+ })
+ const usage = {}
+ trapIcons.forEach(icon => {
+ usage[icon] = 1
+ })
+ 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
+ }
+ for (let i = 0; i < order.length; i += 6) {
+ const part = order.slice(i, i + 6)
+ this.shuffleWithRandom(part, rand)
+ for (let j = 0; j < part.length; j++) order[i + j] = part[j]
+ }
+ return order
+ },
+ isCenterCard(card) {
+ return card.x >= 160 && card.x <= 400 && card.y >= 120 && card.y <= 440
+ },
pickSolutionTriple(available, rand) {
const pool = available.slice()
+ 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))
@@ -525,6 +702,10 @@
.hero-title { margin-top: 10rpx; font-size: 42rpx; font-weight: 900; }
.hero-sub, .tips { margin-top: 16rpx; color: #6B817D; font-size: 24rpx; line-height: 1.55; }
.tips { display: flex; flex-direction: column; gap: 6rpx; }
+ .debug-panel { margin-top: 16rpx; display: flex; gap: 10rpx; }
+ .debug-panel view { flex: 1; border-radius: 20rpx; padding: 12rpx 6rpx; background: rgba(255,255,255,0.76); text-align: center; border: 1rpx solid rgba(53,214,166,0.16); }
+ .debug-panel text:first-child { display: block; color: #22B889; font-size: 28rpx; font-weight: 900; }
+ .debug-panel text:last-child { display: block; margin-top: 4rpx; color: #8AA09C; font-size: 20rpx; }
.college-picker { margin-top: 18rpx; padding: 18rpx 22rpx; border-radius: 26rpx; background: rgba(255,255,255,0.72); display: flex; align-items: center; justify-content: space-between; color: #42635E; font-size: 24rpx; }
.college-picker text:last-child { color: #22B889; font-weight: 900; }
.rocket { margin-top: 22rpx; display: flex; align-items: center; gap: 16rpx; }
@@ -553,8 +734,8 @@
.slot-wrap { margin-top: 24rpx; }
.slot-title { color: #6B817D; font-size: 24rpx; margin-bottom: 14rpx; }
.slots { display: flex; gap: 10rpx; }
- .slot { flex: 1; height: 74rpx; border-radius: 20rpx; background: rgba(255,255,255,0.8); border: 2rpx dashed rgba(53,214,166,0.28); display: flex; align-items: center; justify-content: center; }
- .slot text { font-size: 38rpx; }
+ .slot { flex: 1; height: 66rpx; border-radius: 18rpx; background: rgba(255,255,255,0.8); border: 2rpx dashed rgba(53,214,166,0.28); display: flex; align-items: center; justify-content: center; }
+ .slot text { font-size: 34rpx; }
.actions { margin-top: 28rpx; display: flex; gap: 18rpx; }
.sub-btn, .main-btn { flex: 1; height: 82rpx; line-height: 82rpx; text-align: center; border-radius: 999rpx; font-size: 28rpx; font-weight: 900; }
.sub-btn { background: rgba(255,255,255,0.78); color: #42635E; }