|
|
|
@ -53,7 +53,7 @@ |
|
|
|
</view> |
|
|
|
|
|
|
|
<view class="tips"> |
|
|
|
<text>规则:点选未被压住的卡牌,底部槽内3张相同自动消除,槽满则失败。</text> |
|
|
|
<text>规则:点选未被压住的卡牌,底部槽内3张相同自动消除,宇宙垃圾2张会触发清理。</text> |
|
|
|
<text>陷阱难度 · 剩余 {{remainingCount}} 张 · 当前可点 {{availableCount}} 张 · {{riskText}}</text> |
|
|
|
</view> |
|
|
|
|
|
|
|
@ -69,7 +69,7 @@ |
|
|
|
class="tile" |
|
|
|
v-for="card in visibleCards" |
|
|
|
:key="card.id" |
|
|
|
:class="{locked: isLocked(card), selected: card.selected}" |
|
|
|
:class="card.className" |
|
|
|
:style="card.style" |
|
|
|
@tap="pickCard(card)"> |
|
|
|
<text>{{card.icon}}</text> |
|
|
|
@ -180,29 +180,19 @@ |
|
|
|
return Math.max(16, Math.min(100, Math.round((mine.score || 0) * 100 / top.score))) |
|
|
|
}, |
|
|
|
visibleCards() { |
|
|
|
return this.cards.filter(item => !item.removed) |
|
|
|
return this.cards.filter(item => !item.removed && !item.selected) |
|
|
|
}, |
|
|
|
remainingCount() { |
|
|
|
return this.cards.filter(item => !item.removed && !item.selected).length |
|
|
|
}, |
|
|
|
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() { |
|
|
|
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 |
|
|
|
return this.collectDifficultyStats(this.cards.filter(item => !item.removed && !item.selected)).directComboCount |
|
|
|
}, |
|
|
|
trapGroupCount() { |
|
|
|
const map = {} |
|
|
|
this.cards.forEach(item => { |
|
|
|
if (item.trap) map[item.icon] = true |
|
|
|
}) |
|
|
|
return Object.keys(map).length |
|
|
|
return this.collectDifficultyStats(this.cards).trapGroupCount |
|
|
|
}, |
|
|
|
riskText() { |
|
|
|
if (this.slots.length >= this.slotLimit - 1) return '危险:差1格就满' |
|
|
|
@ -276,23 +266,31 @@ |
|
|
|
this.playing = true |
|
|
|
this.startTs = Date.now() |
|
|
|
this.buildLevel() |
|
|
|
if (!this.cards.length) this.buildLevel() |
|
|
|
}) |
|
|
|
}, |
|
|
|
buildLevel() { |
|
|
|
const icons = ['🍔', '🥤', '📚', '🏀', '🎧', '🚲', '🍜', '📦', '☕', '🧋'] |
|
|
|
const icons = ['🍔', '🥤', '🥟', '🏀', '🎧', '🚲', '🍜', '🔥', '☕', '🍢'] |
|
|
|
const garbageIcons = ['🚀', '🪐', '🛸'] |
|
|
|
let cards = [] |
|
|
|
for (let attempt = 0; attempt < 8; attempt++) { |
|
|
|
for (let attempt = 0; attempt < 100; attempt++) { |
|
|
|
const seed = this.levelSeed() + '_try_' + attempt |
|
|
|
const positions = this.buildCardPositions(seed) |
|
|
|
cards = this.assignTrapLevelIcons(positions, icons, seed) |
|
|
|
if (cards.length) break |
|
|
|
cards = this.assignTrapLayoutIcons(positions, icons, garbageIcons, seed) |
|
|
|
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) { |
|
|
|
cards = this.assignSolvableIcons(this.buildFallbackPositions(), icons, this.levelSeed() + '_fallback') |
|
|
|
cards = this.assignEmergencyIcons(this.buildCardPositions(this.levelSeed() + '_emergency'), icons, garbageIcons) |
|
|
|
} |
|
|
|
this.cards = cards |
|
|
|
this.slots = [] |
|
|
|
this.moveCount = 0 |
|
|
|
this.normalizeCards(icons) |
|
|
|
this.refreshCardState() |
|
|
|
}, |
|
|
|
buildCardPositions(seed) { |
|
|
|
const cards = [] |
|
|
|
@ -305,10 +303,11 @@ |
|
|
|
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 dx = Math.floor(rand() * 6) |
|
|
|
const dy = Math.floor(rand() * 6) |
|
|
|
const card = { |
|
|
|
id: 'c' + idx, |
|
|
|
order: idx, |
|
|
|
icon: '', |
|
|
|
layer, |
|
|
|
x: zone.x + col * zone.gapX + (layer * zone.offsetX) + ((row % 2) * zone.stagger) + dx, |
|
|
|
@ -316,11 +315,12 @@ |
|
|
|
style: '', |
|
|
|
removed: 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) |
|
|
|
idx++ |
|
|
|
} |
|
|
|
@ -356,173 +356,151 @@ |
|
|
|
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 } |
|
|
|
{ x: 38, y: 18, cols: 6, gapX: 86, gapY: 82, offsetX: 0, offsetY: 0, stagger: 4 }, |
|
|
|
{ x: 82, y: 58, cols: 5, gapX: 86, gapY: 82, offsetX: 0, offsetY: 0, stagger: 4 }, |
|
|
|
{ x: 124, y: 98, cols: 4, gapX: 86, gapY: 82, offsetX: 0, offsetY: 0, stagger: 4 } |
|
|
|
] |
|
|
|
} |
|
|
|
} |
|
|
|
return templates[type] || templates.hell |
|
|
|
}, |
|
|
|
assignSolvableIcons(positions, icons, seed) { |
|
|
|
// 上线关卡不能随机发牌。这里先模拟一条真实可点击的消除路径, |
|
|
|
// 再按路径给三张牌分配同一图案,保证每日关卡至少存在一条解法。 |
|
|
|
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') |
|
|
|
assignTrapLayoutIcons(positions, icons, garbageIcons, seed, forceBuild) { |
|
|
|
const rand = this.seededRandom(seed + '_layout') |
|
|
|
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) |
|
|
|
cards.forEach(card => { |
|
|
|
card.icon = '' |
|
|
|
card.trap = false |
|
|
|
card.keyCard = false |
|
|
|
card.garbage = false |
|
|
|
}) |
|
|
|
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 => { |
|
|
|
const bottom = this.seededShuffle(cards.filter(card => card.layer === 0), seed + '_bottom') |
|
|
|
const middle = this.seededShuffle(cards.filter(card => card.layer === 1), seed + '_middle') |
|
|
|
const top = this.seededShuffle(cards.filter(card => card.layer === 2), seed + '_top') |
|
|
|
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.solutionStep = step + 1 |
|
|
|
if (group.trapIcon) { |
|
|
|
card.trap = true |
|
|
|
} |
|
|
|
card.trap = group < trapCount |
|
|
|
card.keyCard = group < trapCount && index === 2 |
|
|
|
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 => { |
|
|
|
card.reservedTrap = false |
|
|
|
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${card.layer + 1};` |
|
|
|
cards.forEach((card, index) => { |
|
|
|
if (!card.icon) { |
|
|
|
card.icon = icons[(index + Math.floor(rand() * icons.length)) % icons.length] |
|
|
|
} |
|
|
|
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};` |
|
|
|
}) |
|
|
|
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 |
|
|
|
}) |
|
|
|
assignEmergencyIcons(positions, icons, garbageIcons) { |
|
|
|
positions.forEach((card, index) => { |
|
|
|
card.icon = index < 6 ? garbageIcons[Math.floor(index / 2) % garbageIcons.length] : icons[Math.floor((index - 6) / 3) % icons.length] |
|
|
|
card.trap = index >= 6 && index < 42 |
|
|
|
card.keyCard = card.trap && card.layer === 0 && this.isCenterCard(card) |
|
|
|
card.garbage = index < 6 |
|
|
|
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};` |
|
|
|
}) |
|
|
|
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] |
|
|
|
return positions |
|
|
|
}, |
|
|
|
repositionKeyCovers(cards, seed) { |
|
|
|
const rand = this.seededRandom(seed + '_cover') |
|
|
|
const keys = cards.filter(card => card.keyCard && card.layer === 0 && this.isCenterCard(card)).slice(0, 10) |
|
|
|
const covers = this.seededShuffle(cards.filter(card => !card.keyCard && card.layer > 0), seed + '_cover_cards') |
|
|
|
keys.forEach(key => { |
|
|
|
for (let i = 0; i < 2; i++) { |
|
|
|
const cover = covers.shift() |
|
|
|
if (!cover) return |
|
|
|
cover.x = key.x + (i === 0 ? 18 : -10) + Math.floor(rand() * 8) |
|
|
|
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)};` |
|
|
|
} |
|
|
|
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) { |
|
|
|
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 null |
|
|
|
}, |
|
|
|
takeAny(cards, assigned) { |
|
|
|
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) { |
|
|
|
return card.x >= 160 && card.x <= 400 && card.y >= 120 && card.y <= 440 |
|
|
|
coverCount(card, cards) { |
|
|
|
return cards.filter(other => { |
|
|
|
if (other.id === card.id || other.layer <= card.layer) return false |
|
|
|
return this.isOverlap(card, other) |
|
|
|
}).length |
|
|
|
}, |
|
|
|
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)) |
|
|
|
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] |
|
|
|
isGarbageIcon(icon) { |
|
|
|
return icon === '🚀' || icon === '🪐' || icon === '🛸' |
|
|
|
}, |
|
|
|
cardDistanceScore(a, b) { |
|
|
|
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y) + Math.abs(a.layer - b.layer) * 60 |
|
|
|
isCenterCard(card) { |
|
|
|
return card.x >= 160 && card.x <= 400 && card.y >= 120 && card.y <= 440 |
|
|
|
}, |
|
|
|
isBlockedIn(card, list) { |
|
|
|
return list.some(other => { |
|
|
|
@ -531,7 +509,28 @@ |
|
|
|
}) |
|
|
|
}, |
|
|
|
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) { |
|
|
|
if (this.cleared) { |
|
|
|
@ -542,12 +541,17 @@ |
|
|
|
this.tui.toast('先点击开始助推') |
|
|
|
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 |
|
|
|
card.selected = true |
|
|
|
this.slots.push(card) |
|
|
|
this.moveCount++ |
|
|
|
this.tryClear(card.icon) |
|
|
|
this.refreshCardState() |
|
|
|
if (this.cards.every(item => item.removed || item.selected)) { |
|
|
|
this.finishClear() |
|
|
|
} else if (this.slots.length >= this.slotLimit) { |
|
|
|
@ -555,6 +559,20 @@ |
|
|
|
} |
|
|
|
}, |
|
|
|
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) |
|
|
|
if (same.length < 3) return |
|
|
|
let removed = 0 |
|
|
|
@ -608,6 +626,7 @@ |
|
|
|
if (this.cleared) return |
|
|
|
this.playing = false |
|
|
|
this.buildLevel() |
|
|
|
if (!this.cards.length) this.buildLevel() |
|
|
|
}, |
|
|
|
closeModal() { |
|
|
|
this.modal.show = false |
|
|
|
@ -668,6 +687,7 @@ |
|
|
|
const row = Math.floor(i / 9) |
|
|
|
const card = { |
|
|
|
id: 'f' + idx, |
|
|
|
order: idx, |
|
|
|
icon: '', |
|
|
|
layer, |
|
|
|
x: 10 + col * 68 + (layer * 6), |
|
|
|
@ -675,9 +695,12 @@ |
|
|
|
style: '', |
|
|
|
removed: 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) |
|
|
|
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; } |
|
|
|
.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.locked { opacity: .42; background: #F2F6F5; } |
|
|
|
.tile.locked{ |
|
|
|
opacity:.82; |
|
|
|
filter:grayscale(.15); |
|
|
|
} |
|
|
|
.tile.selected { opacity: 0; transform: scale(.5); pointer-events: none; } |
|
|
|
.slot-wrap { margin-top: 24rpx; } |
|
|
|
.slot-title { color: #6B817D; font-size: 24rpx; margin-bottom: 14rpx; } |
|
|
|
|