fix(tongji): stabilize food transitions and mixed weekly rank

This commit is contained in:
大哥大哥的大哥哥
2026-07-22 15:21:51 +08:00
parent 68b5b12b59
commit 1626b6f498
12 changed files with 970 additions and 107 deletions
+223 -36
View File
@@ -19,9 +19,20 @@
<TongjiIcon name="chevron-left" size="md" color="#155e4b" />
</view>
</view>
<view class="eg-title-wrap">
<view class="eg-title-wrap" :class="{ 'is-noticing': centerNoticeVisible }">
<text class="eg-title">识糖小课堂</text>
<text class="eg-subtitle">第{{ themeIndex + 1 }}站 · {{ currentTheme.title }}</text>
<view
v-if="centerNoticeVisible"
:key="centerNoticeKey"
class="eg-nav-notice"
role="status"
aria-live="polite"
aria-atomic="true"
>
<text class="eg-nav-notice-title">{{ centerNoticeTitle }}</text>
<text class="eg-nav-notice-detail">{{ centerNoticeDetail }}</text>
</view>
</view>
<view class="eg-nav-actions">
<view class="eg-nav-btn eg-nav-btn--small" @tap="toggleSfx">
@@ -126,7 +137,12 @@
<view class="eg-fire-spark is-two" />
<view class="eg-fire-spark is-three" />
</view>
<image class="eg-food-image" :src="foodOf(tile.key).img" mode="aspectFit" />
<image
class="eg-food-image"
:class="{ 'is-large-square': foodOf(tile.key).largeIcon }"
:src="foodOf(tile.key).img"
mode="aspectFit"
/>
<text class="eg-food-name">{{ foodOf(tile.key).short }}</text>
<view class="eg-sugar-tag" :class="`is-${foodOf(tile.key).level || 'low'}`">
{{ foodOf(tile.key).levelLabel || '低糖' }}
@@ -271,13 +287,13 @@
<TongjiIcon name="refresh" size="lg" color="#ffffff" />
</view>
<text class="eg-reshuffle-title">{{ reshuffleDone ? '洗牌完成' : '没有可消除组合' }}</text>
<text class="eg-reshuffle-desc">{{ reshuffleDone ? '新的消除机会已经出现' : '正在为您重新洗牌,请稍候' }}</text>
<text class="eg-reshuffle-desc">{{ reshuffleDone ? (reshuffleImportedFoods ? '洗牌完成,新食物已经加入' : '新的消除机会已经出现') : '正在为您重新洗牌,请稍候' }}</text>
</view>
</view>
<view v-if="stageVisible" class="eg-stage-overlay" @tap.stop>
<view class="eg-stage-card">
<text class="eg-stage-kicker">新菜单已解锁</text>
<text class="eg-stage-kicker">新食物排队加入</text>
<text class="eg-stage-number">第 {{ stageMeta.number }} 站</text>
<text class="eg-stage-title">{{ stageMeta.title }}</text>
<view class="eg-stage-foods">
@@ -286,7 +302,7 @@
<text>{{ food.short }}</text>
</view>
</view>
<text class="eg-stage-tip">本组橙框食物可用驼乳粉替换</text>
<text class="eg-stage-tip">橙框食物已更新,普通食物会随掉落逐步加入</text>
</view>
</view>
@@ -320,6 +336,14 @@ import { useGamePlatform } from './composables/useGamePlatform.js'
import { useGameAuth } from './composables/useGameAuth.js'
const FOOD_IMAGE_BASE_URL = 'https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/games/food/'
const LOCAL_FOOD_IMAGE_BASE_URL = '/tongji/endless-game/assets/food/'
const LOCAL_FOOD_IMAGES = {
tofu: `${LOCAL_FOOD_IMAGE_BASE_URL}tofu.jpg`,
spinach: `${LOCAL_FOOD_IMAGE_BASE_URL}spinach.jpg`,
carrot: `${LOCAL_FOOD_IMAGE_BASE_URL}carrot.jpg`,
fish: `${LOCAL_FOOD_IMAGE_BASE_URL}fish.jpg`,
redBeanCongee: `${LOCAL_FOOD_IMAGE_BASE_URL}redBeanCongee.jpg`
}
const ROWS = 5
const COLS = 4
@@ -395,9 +419,20 @@ const FOODS = {
riceNoodleSoup: { name: '汤粉', short: '汤粉', emoji: '🍜', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '汤粉以精制米粉为主,实际影响与分量和配菜有关,应注意搭配。' },
noodleSoup: { name: '汤面', short: '汤面', emoji: '🍜', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '汤面以精细面食为主,汤汁和分量需要注意,具体建议以医生审核为准。' },
milkTea: { name: '含糖奶茶', short: '奶茶', emoji: '🧋', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '含糖奶茶可能同时含有较多添加糖和能量,应关注配方与饮用量。' },
tofu: { name: '豆腐', short: '豆腐', emoji: '◻️', color: 'cream', category: '豆制品', level: 'low', levelLabel: '低糖', largeIcon: true },
spinach: { name: '菠菜', short: '菠菜', emoji: '🥬', color: 'yellow', category: '蔬菜', level: 'low', levelLabel: '低糖', largeIcon: true },
carrot: { name: '胡萝卜', short: '胡萝卜', emoji: '🥕', color: 'red', category: '蔬菜', level: 'low', levelLabel: '低糖', largeIcon: true },
fish: { name: '鱼', short: '鱼', emoji: '🐟', color: 'purple', category: '鱼肉蛋类', level: 'low', levelLabel: '低糖', largeIcon: true },
redBeanCongee: {
name: '软烂红豆粥', short: '红豆粥', emoji: '🥣', color: 'orange', category: '粥类',
level: 'high', levelLabel: '高糖', high: true, replaceable: true, largeIcon: true,
tip: '本局图标表示长时间熬煮、豆粒开花的红豆粥。食物对血糖的影响与熬煮程度、食用量和搭配有关,糖尿病患者应结合个人情况合理选择。'
},
camel: { name: '驼乳', short: '驼乳', emoji: '🥛', color: 'milk', level: 'prop', levelLabel: '道具', product: true }
}
Object.keys(FOODS).forEach((key) => { FOODS[key].img = `${FOOD_IMAGE_BASE_URL}${key}.jpg` })
Object.keys(FOODS).forEach((key) => {
FOODS[key].img = LOCAL_FOOD_IMAGES[key] || `${FOOD_IMAGE_BASE_URL}${key}.jpg`
})
const FOOD_GROUPS = [
{ title: '中式早餐', safe: ['egg', 'cucumber', 'corn', 'grainMantou'], danger: 'congee' },
{ title: '家常粥餐', safe: ['celery', 'mushroom', 'chicken', 'taro'], danger: 'centuryCongee' },
@@ -406,6 +441,7 @@ const FOOD_GROUPS = [
{ title: '日常饮品', safe: ['milk', 'tea', 'blackCoffee', 'coffee'], danger: 'soda' },
{ title: '南方汤粉', safe: ['pepper', 'greenBean', 'shrimp', 'udon'], danger: 'riceNoodleSoup' },
{ title: '北方面食', safe: ['onion', 'celery', 'corn', 'grainMantou'], danger: 'noodleSoup' },
{ title: '家常餐桌', safe: ['tofu', 'spinach', 'carrot', 'fish'], danger: 'redBeanCongee' },
]
const {
@@ -451,12 +487,19 @@ const camelMode = ref(false)
const busy = ref(false)
const gameOver = ref(false)
const themeIndex = ref(0)
const activeSafeKeys = ref([...FOOD_GROUPS[0].safe])
const foodTransition = ref(null)
const queuedSafeGroups = ref([])
const endedFood = ref(FOODS.congee)
const taskIndex = ref(0)
const taskProgress = ref(0)
const comboDryTurns = ref(0)
const taskRewardVisible = ref(false)
const taskRewardKey = ref(0)
const centerNoticeVisible = ref(false)
const centerNoticeKey = ref(0)
const centerNoticeTitle = ref('')
const centerNoticeDetail = ref('')
const statusText = ref('交换相邻食物,三个相同即可消除')
const navStyle = ref({ paddingTop: '20px' })
const touch = { x: 0, y: 0, index: -1 }
@@ -474,6 +517,7 @@ const stageMeta = ref({ number: 1, title: '', foods: [] })
const reshuffleVisible = ref(false)
const reshuffleDone = ref(false)
const reshuffling = ref(false)
const reshuffleImportedFoods = ref(false)
const confettiVisible = ref(false)
const weeklyRankVisible = ref(false)
const restoredGame = ref(false)
@@ -498,6 +542,7 @@ let jackpotTimer = null
let confettiTimer = null
let stageTimer = null
let taskRewardTimer = null
let centerNoticeTimer = null
let flowTimer = null
let flowPauseTimer = null
let flowPausedRemainingMs = 0
@@ -511,7 +556,7 @@ let incomingInviteCode = ''
const currentTheme = computed(() => FOOD_GROUPS[themeIndex.value % FOOD_GROUPS.length])
const currentTask = computed(() => {
const safe = currentTheme.value.safe
const safe = activeSafeKeys.value.length === 4 ? activeSafeKeys.value : currentTheme.value.safe
const templates = [
{ key: safe[0], label: `消除 7 个${foodOf(safe[0]).short}`, target: 7, kind: 'food' },
{ key: 'special', label: '制造 1 个惊喜棋子', target: 1, kind: 'special' },
@@ -586,7 +631,8 @@ const weeklyRangeLabel = computed(() => {
if (!start || !end) return getWeekRangeLabel(new Date())
return `${formatWeekDate(start)}—${formatWeekDate(end)}`
})
const weeklySexLabel = computed(() => platformLeaderboard.value.sex_label || '同行')
// 前期人数较少,七人榜统一混排,不向玩家展示男女分组。
const weeklySexLabel = computed(() => '同行')
const weeklyMemberCount = computed(() => {
const reported = Number(platformLeaderboard.value.member_count || 0)
const remoteCount = Array.isArray(platformLeaderboard.value.players)
@@ -686,6 +732,7 @@ onUnload(() => {
if (confettiTimer) clearTimeout(confettiTimer)
if (stageTimer) clearTimeout(stageTimer)
if (taskRewardTimer) clearTimeout(taskRewardTimer)
if (centerNoticeTimer) clearTimeout(centerNoticeTimer)
clearFlowTimers()
})
@@ -697,6 +744,101 @@ function foodOf(key) {
return FOODS[key] || FOODS.corn
}
function isValidSafeGroup(keys) {
return Array.isArray(keys)
&& keys.length === 4
&& new Set(keys).size === 4
&& keys.every(key => FOODS[key] && !foodOf(key).high && !foodOf(key).product)
}
function resetFoodTransitions(keys = currentTheme.value.safe) {
activeSafeKeys.value = isValidSafeGroup(keys) ? [...keys] : [...currentTheme.value.safe]
foodTransition.value = null
queuedSafeGroups.value = []
}
function queueSafeGroupTransition(keys) {
if (!isValidSafeGroup(keys)) return false
const target = [...keys]
const lastQueued = queuedSafeGroups.value[queuedSafeGroups.value.length - 1]
if (lastQueued?.every((key, index) => key === target[index])) return false
queuedSafeGroups.value = [...queuedSafeGroups.value, target]
startNextFoodTransition()
return true
}
function startNextFoodTransition() {
if (foodTransition.value) return false
while (queuedSafeGroups.value.length) {
const target = queuedSafeGroups.value[0]
const slot = target.findIndex((key, index) => activeSafeKeys.value[index] !== key)
if (slot < 0) {
queuedSafeGroups.value = queuedSafeGroups.value.slice(1)
continue
}
const beforeTask = currentTask.value
const from = activeSafeKeys.value[slot]
const to = target[slot]
const nextActive = [...activeSafeKeys.value]
nextActive[slot] = to
activeSafeKeys.value = nextActive
foodTransition.value = { from, to, slot }
if (beforeTask.kind === 'food' && beforeTask.key === from) taskProgress.value = 0
return true
}
return false
}
function finishFoodTransitionIfReady(silent = false) {
let changed = false
let lastAdded = ''
while (foodTransition.value) {
const transition = foodTransition.value
const remaining = board.value.filter(tile => tile?.key === transition.from).length
// 旧食物停止掉落后,最后 1~2 个已经无法再自然三消。
// 直接把这点“尾巴”换成新食物,避免下一组里长期孤零零留着一杯茶等旧图标。
if (remaining > 2) break
if (remaining > 0) {
board.value = board.value.map(tile => (
tile?.key === transition.from ? { ...tile, key: transition.to } : tile
))
}
lastAdded = transition.to
foodTransition.value = null
changed = true
startNextFoodTransition()
}
if (changed && !silent) statusText.value = `${foodOf(lastAdded).short}已加入,下一种食物正在排队`
return changed
}
function prepareFoodTransitionForReshuffle(source) {
const active = activeSafeKeys.value
const activeSet = new Set(active)
const counts = Object.fromEntries(active.map(key => [key, 0]))
source.forEach(tile => {
if (tile && activeSet.has(tile.key)) counts[tile.key] += 1
})
let imported = 0
const transition = foodTransition.value
const next = source.map(tile => {
if (!tile) return tile
const food = foodOf(tile.key)
if (food.high || food.product) return { ...tile }
if (transition && tile.key === transition.from) {
imported += 1
counts[transition.to] += 1
return { ...tile, key: transition.to }
}
if (activeSet.has(tile.key)) return { ...tile }
const key = [...active].sort((a, b) => counts[a] - counts[b])[0]
counts[key] += 1
imported += 1
return { ...tile, key }
})
return { board: next, imported }
}
function getWeekRangeLabel(now) {
const date = new Date(now)
const day = date.getDay() || 7
@@ -929,6 +1071,9 @@ function saveActiveGame() {
confirmed_session_learned: Math.max(0, Number(confirmedSessionLearned.value) || 0),
saved_at: Date.now(),
theme_index: themeIndex.value,
active_safe_keys: [...activeSafeKeys.value],
food_transition: foodTransition.value ? { ...foodTransition.value } : null,
queued_safe_groups: queuedSafeGroups.value.map(keys => [...keys]),
board: board.value.map(tile => ({ id: tile.id, key: tile.key, special: tile.special || '' })),
score: score.value,
learned: learned.value,
@@ -966,6 +1111,28 @@ function restoreActiveGame() {
}
themeIndex.value = Math.max(0, Math.min(FOOD_GROUPS.length - 1, Number(saved.theme_index) || 0))
activeSafeKeys.value = isValidSafeGroup(saved.active_safe_keys)
? [...saved.active_safe_keys]
: [...currentTheme.value.safe]
const savedTransition = saved.food_transition
foodTransition.value = savedTransition
&& FOODS[savedTransition.from]
&& FOODS[savedTransition.to]
&& !foodOf(savedTransition.from).high
&& !foodOf(savedTransition.from).product
&& !foodOf(savedTransition.to).high
&& !foodOf(savedTransition.to).product
&& Number.isInteger(Number(savedTransition.slot))
&& activeSafeKeys.value[Math.max(0, Math.min(3, Number(savedTransition.slot)))] === savedTransition.to
? {
from: savedTransition.from,
to: savedTransition.to,
slot: Math.max(0, Math.min(3, Number(savedTransition.slot)))
}
: null
queuedSafeGroups.value = Array.isArray(saved.queued_safe_groups)
? saved.queued_safe_groups.filter(isValidSafeGroup).map(keys => [...keys])
: []
board.value = saved.board.map(tile => ({
id: Math.max(1, Number(tile.id) || tileId++),
key: tile.key,
@@ -988,6 +1155,7 @@ function restoreActiveGame() {
busy.value = false
gameOver.value = false
taskRewardVisible.value = false
centerNoticeVisible.value = false
clearingIndices.value = []
fireClearingIndices.value = []
impactText.value = ''
@@ -996,12 +1164,14 @@ function restoreActiveGame() {
reshuffleVisible.value = false
reshuffleDone.value = false
reshuffling.value = false
reshuffleImportedFoods.value = false
confettiVisible.value = false
pendingThemeMatchCount = 0
taskRewardStatusUntil = 0
endedFood.value = foodOf(currentTheme.value.danger)
restoredGame.value = true
resetDrag()
finishFoodTransitionIfReady(true)
statusText.value = '已恢复上次未完成的游戏和得分'
scheduleHint()
return true
@@ -1012,6 +1182,7 @@ function startGame() {
clearActiveGame()
beginPlatformSession()
themeIndex.value = 0
resetFoodTransitions(currentTheme.value.safe)
board.value = buildPlayableBoard(openingKeysForTheme(currentTheme.value))
selected.value = -1
score.value = 0
@@ -1027,6 +1198,7 @@ function startGame() {
taskProgress.value = 0
comboDryTurns.value = 0
taskRewardVisible.value = false
centerNoticeVisible.value = false
clearingIndices.value = []
fireClearingIndices.value = []
impactText.value = ''
@@ -1035,6 +1207,7 @@ function startGame() {
reshuffleVisible.value = false
reshuffleDone.value = false
reshuffling.value = false
reshuffleImportedFoods.value = false
confettiVisible.value = false
pendingThemeMatchCount = 0
taskRewardStatusUntil = 0
@@ -1060,7 +1233,7 @@ function goBack() {
function showRules() {
uni.showModal({
title: '玩法说明',
content: `当前难度:${GAME_DIFFICULTY}/10\n\n交换相邻食物,三个相同即可消除。四连、直线五连会生成火焰惊喜棋子;横向三个与竖向三个相交的 L/T 形五消,会生成范围爆破棋子,再次消除可清除周围九格。连消×2会计入连消小目标,连续普通消除后会出现一次连消机会;连消×3可获得额外惊喜奖励。完成小目标直接获得 1 罐驼乳粉;驼乳粉可替换本组指定稀食或饮品,三个驼乳连消后进入下一组食品。主动三消“高糖”食物代表吃掉,会结束本局;火焰特效波及高糖食物代表将它移出餐盘,不算吃掉。没有可走步时会自动洗牌。`,
content: `当前难度:${GAME_DIFFICULTY}/10\n\n交换相邻食物,三个相同即可消除。四连、直线五连会生成火焰惊喜棋子;横向三个与竖向三个相交的 L/T 形五消,会生成范围爆破棋子,再次消除可清除周围九格。连消×2会计入连消小目标,连续普通消除后会出现一次连消机会;连消×3可获得额外惊喜奖励。完成小目标直接获得 1 罐驼乳粉;驼乳粉可替换本组指定稀食或饮品,三个驼乳连消后,高糖食物立即更新,普通食物会随掉落逐步换入。主动三消“高糖”食物代表吃掉,会结束本局;火焰特效波及高糖食物代表将它移出餐盘,不算吃掉。没有可走步时会自动洗牌,并加快当前新食物换入。`,
showCancel: false,
confirmText: '知道了'
})
@@ -1371,6 +1544,7 @@ async function resolveMatches(initialGroups, movedIndex, options = {}) {
&& comboTaskAtStart
&& comboDryTurns.value >= COMBO_PITY_TURNS - 1
collapseAndFill(shouldGuaranteeCombo)
finishFoodTransitionIfReady()
playDropSfx(remove.size)
await wait(190)
groups = findMatches(board.value)
@@ -1430,6 +1604,18 @@ function grantCamelReward(requested) {
return reward
}
function showCamelOverflowNotice(overflowScore) {
if (centerNoticeTimer) clearTimeout(centerNoticeTimer)
centerNoticeKey.value += 1
centerNoticeTitle.value = '驼乳粉已满'
centerNoticeDetail.value = `多余奖励换 ${overflowScore} 分`
centerNoticeVisible.value = true
centerNoticeTimer = setTimeout(() => {
centerNoticeVisible.value = false
centerNoticeTimer = null
}, 1050)
}
function rewardMegaCombo(wave) {
score.value += MEGA_COMBO_SCORE
const camelReward = grantCamelReward(1)
@@ -1437,6 +1623,7 @@ function rewardMegaCombo(wave) {
statusText.value = `${wave} 连消惊喜:+${MEGA_COMBO_SCORE} 分${rewardText ? ` · ${rewardText}` : ''}`
taskRewardStatusUntil = Date.now() + 1800
if (camelReward.granted > 0) showTaskCamelReward()
if (camelReward.overflow > 0) showCamelOverflowNotice(camelReward.overflowScore)
startConfetti()
playSfxLayer('comboMega', 'fanfare', 100, .82)
vibrate('medium')
@@ -1449,7 +1636,6 @@ function rewardMegaCombo(wave) {
function camelRewardText(reward) {
const parts = []
if (reward.granted > 0) parts.push(`驼乳粉 ×${reward.granted}`)
if (reward.overflow > 0) parts.push(`满仓多余奖励换 ${reward.overflowScore} 分`)
return parts.join(' · ')
}
@@ -1488,17 +1674,10 @@ function advanceFoodTheme() {
const previous = currentTheme.value
themeIndex.value = (themeIndex.value + 1) % FOOD_GROUPS.length
const nextTheme = currentTheme.value
const mapping = {}
previous.safe.forEach((key, index) => { mapping[key] = nextTheme.safe[index] })
mapping[previous.danger] = nextTheme.danger
const mappedBoard = board.value.map(tile => {
if (!tile || !mapping[tile.key]) return tile
return { ...tile, key: mapping[tile.key] }
})
board.value = mappedBoard
if (findMatches(mappedBoard).length || !hasSafeMoveIn(mappedBoard)) {
board.value = buildPlayableBoard(openingKeysForTheme(nextTheme))
}
board.value = board.value.map(tile => (
tile?.key === previous.danger ? { ...tile, key: nextTheme.danger } : tile
))
queueSafeGroupTransition(nextTheme.safe)
taskIndex.value = 0
taskProgress.value = 0
comboDryTurns.value = 0
@@ -1511,7 +1690,7 @@ function advanceFoodTheme() {
foods: [...nextTheme.safe, nextTheme.danger].map(key => ({ key, ...foodOf(key) }))
}
stageVisible.value = true
statusText.value = `已进入${nextTheme.title},橙框${foodOf(nextTheme.danger).short}可替换`
statusText.value = `已进入${nextTheme.title},新食物会随掉落逐步加入`
playSfxLayer('fanfare', 'sparkle', 100, .65)
if (stageTimer) clearTimeout(stageTimer)
stageTimer = setTimeout(() => { stageVisible.value = false }, 1800)
@@ -1652,14 +1831,14 @@ function findForcedDropMatch(snapshot) {
}
function pickForcedDropKey(snapshot, indices) {
return currentTheme.value.safe
return activeSafeKeys.value
.map(key => ({
key,
nearby: snapshot.reduce((count, tile, index) => (
tile?.key === key && indices.some(target => isAdjacent(target, index)) ? count + 1 : count
), 0)
}))
.sort((a, b) => a.nearby - b.nearby)[0]?.key || currentTheme.value.safe[0]
.sort((a, b) => a.nearby - b.nearby)[0]?.key || activeSafeKeys.value[0]
}
function pickDropFoodKey(snapshot, index, col) {
@@ -1667,9 +1846,9 @@ function pickDropFoodKey(snapshot, index, col) {
const key = randomFoodKey(snapshot, col)
if (!wouldCreateMatchAt(snapshot, index, key)) return key
}
const fallback = [...currentTheme.value.safe, currentTheme.value.danger]
const fallback = [...activeSafeKeys.value, currentTheme.value.danger]
.find(key => !wouldCreateMatchAt(snapshot, index, key))
return fallback || currentTheme.value.safe[(index + col) % currentTheme.value.safe.length]
return fallback || activeSafeKeys.value[(index + col) % activeSafeKeys.value.length]
}
function wouldCreateMatchAt(source, index, key) {
@@ -1689,7 +1868,7 @@ function wouldCreateMatchAt(source, index, key) {
function randomFoodKey(snapshot, col = -1) {
const highs = snapshot.filter(tile => tile && foodOf(tile.key).high).length
if (highs < HIGH_CAP && Math.random() < DANGER_DROP_CHANCE) return currentTheme.value.danger
const keys = currentTheme.value.safe
const keys = activeSafeKeys.value
if (col >= 0 && Math.random() < COLUMN_GROUP_BIAS) return keys[col % keys.length]
return keys[Math.floor(Math.random() * keys.length)]
}
@@ -1747,13 +1926,11 @@ function advanceTask(amount) {
if (camelReward.granted > 0) {
showTaskCamelReward()
statusText.value = '小目标完成,驼乳粉已放入道具栏'
uni.showToast({ title: '小目标完成:驼乳粉 +1', icon: 'none' })
} else {
statusText.value = `驼乳粉已满,多余奖励换 ${camelReward.overflowScore} 分`
statusText.value = '小目标完成,继续挑战'
showCamelOverflowNotice(camelReward.overflowScore)
}
uni.showToast({
title: camelReward.granted > 0 ? '小目标完成:驼乳粉 +1' : `驼乳粉已满:奖励换 ${camelReward.overflowScore} 分`,
icon: 'none'
})
taskIndex.value = (taskIndex.value + 1) % 4
taskProgress.value = 0
comboDryTurns.value = 0
@@ -1823,6 +2000,7 @@ async function performReshuffle() {
reshuffling.value = true
busy.value = true
reshuffleDone.value = false
reshuffleImportedFoods.value = false
reshuffleVisible.value = true
selected.value = -1
camelMode.value = false
@@ -1831,7 +2009,9 @@ async function performReshuffle() {
vibrate('medium')
await wait(760)
const source = board.value.map(tile => ({ ...tile }))
const prepared = prepareFoodTransitionForReshuffle(board.value)
const source = prepared.board
reshuffleImportedFoods.value = prepared.imported > 0
let candidate = null
for (let attempt = 0; attempt < 240; attempt += 1) {
const shuffled = shuffleTiles(source)
@@ -1841,8 +2021,11 @@ async function performReshuffle() {
break
}
board.value = candidate || buildGuaranteedBoard()
finishFoodTransitionIfReady(true)
reshuffleDone.value = true
statusText.value = '洗牌完成,新的消除机会已经出现'
statusText.value = reshuffleImportedFoods.value
? '洗牌完成,新食物已经加入'
: '洗牌完成,新的消除机会已经出现'
playSfxLayer('drop', 'sparkle', 70, .55)
vibrate('light')
await wait(520)
@@ -1867,7 +2050,9 @@ function buildGuaranteedBoard() {
}
function openingKeysForTheme(theme) {
const [a, b, c, d] = theme.safe
const [a, b, c, d] = isValidSafeGroup(activeSafeKeys.value)
? activeSafeKeys.value
: theme.safe
const danger = theme.danger
return [
a, b, c, d,
@@ -1888,7 +2073,9 @@ function buildPlayableBoard(keys) {
}
function forcedPlayableKeysForTheme(theme) {
const [a, b, c, d] = theme.safe
const [a, b, c, d] = isValidSafeGroup(activeSafeKeys.value)
? activeSafeKeys.value
: theme.safe
const danger = theme.danger
return [
d, a, danger, c,