feat(tongji): add hidden flow scoring mode
This commit is contained in:
@@ -117,12 +117,20 @@
|
|||||||
background: rgba(255,255,255,.92);
|
background: rgba(255,255,255,.92);
|
||||||
box-shadow: 0 18rpx 44rpx rgba(29, 78, 59, .12);
|
box-shadow: 0 18rpx 44rpx rgba(29, 78, 59, .12);
|
||||||
}
|
}
|
||||||
|
.eg-board-shell.is-flowing {
|
||||||
|
border-color: rgba(245, 158, 11, .72);
|
||||||
|
box-shadow: 0 0 0 5rpx rgba(255, 221, 89, .2), 0 18rpx 46rpx rgba(234, 88, 12, .2);
|
||||||
|
animation: egFlowShell 1.05s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
.eg-board-top { display: flex; align-items: center; justify-content: space-between; margin: 0 4rpx 14rpx; }
|
.eg-board-top { display: flex; align-items: center; justify-content: space-between; margin: 0 4rpx 14rpx; }
|
||||||
.eg-board-bonuses { display: flex; align-items: center; gap: 8rpx; }
|
.eg-board-bonuses { display: flex; align-items: center; gap: 8rpx; }
|
||||||
.eg-combo { padding: 8rpx 16rpx; border-radius: 999rpx; color: #45685d; background: #e8f3ee; font-size: 25rpx; font-weight: 700; }
|
.eg-combo { padding: 8rpx 16rpx; border-radius: 999rpx; color: #45685d; background: #e8f3ee; font-size: 25rpx; font-weight: 700; }
|
||||||
.eg-combo.is-hot { color: #fff; background: linear-gradient(135deg, #f59e0b, #ea580c); }
|
.eg-combo.is-hot { color: #fff; background: linear-gradient(135deg, #f59e0b, #ea580c); }
|
||||||
.eg-double-state { padding: 8rpx 14rpx; border: 2rpx solid rgba(255,255,255,.9); border-radius: 999rpx; color: #fff; background: linear-gradient(135deg, #7c3aed, #d946ef); box-shadow: 0 4rpx 12rpx rgba(124,58,237,.24); font-size: 23rpx; font-weight: 900; white-space: nowrap; }
|
.eg-double-state { padding: 8rpx 14rpx; border: 2rpx solid rgba(255,255,255,.9); border-radius: 999rpx; color: #fff; background: linear-gradient(135deg, #7c3aed, #d946ef); box-shadow: 0 4rpx 12rpx rgba(124,58,237,.24); font-size: 23rpx; font-weight: 900; white-space: nowrap; }
|
||||||
|
.eg-flow-state { display: flex; align-items: center; gap: 8rpx; padding: 8rpx 14rpx; border: 3rpx solid #fff3a3; border-radius: 999rpx; color: #fff; background: linear-gradient(135deg, #f97316, #dc2626); box-shadow: 0 5rpx 16rpx rgba(220,38,38,.3); font-size: 25rpx; font-weight: 1000; white-space: nowrap; animation: egFlowBadge .62s ease-in-out infinite alternate; }
|
||||||
|
.eg-flow-state > text:last-child { min-width: 62rpx; color: #fff7a8; text-align: right; }
|
||||||
|
.eg-flow-state.is-paused { border-color: #d9f6ff; background: linear-gradient(135deg, #1687b4, #3155a6); box-shadow: 0 5rpx 16rpx rgba(34,109,166,.28); animation: none; }
|
||||||
.eg-risk { display: flex; align-items: center; gap: 10rpx; color: #8d512c; font-size: 24rpx; }
|
.eg-risk { display: flex; align-items: center; gap: 10rpx; color: #8d512c; font-size: 24rpx; }
|
||||||
.eg-risk-dots { display: flex; gap: 5rpx; }
|
.eg-risk-dots { display: flex; gap: 5rpx; }
|
||||||
.eg-risk-dot { width: 12rpx; height: 12rpx; border-radius: 50%; background: #ead8c9; }
|
.eg-risk-dot { width: 12rpx; height: 12rpx; border-radius: 50%; background: #ead8c9; }
|
||||||
@@ -559,6 +567,16 @@
|
|||||||
100% { opacity: 1; transform: scale(1); }
|
100% { opacity: 1; transform: scale(1); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes egFlowShell {
|
||||||
|
from { filter: saturate(1); }
|
||||||
|
to { filter: saturate(1.08); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes egFlowBadge {
|
||||||
|
from { transform: scale(.98); filter: brightness(.96); }
|
||||||
|
to { transform: scale(1.035); filter: brightness(1.12); }
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes egStageIn {
|
@keyframes egStageIn {
|
||||||
0% { opacity: 0; transform: scale(.7) translateY(34rpx); }
|
0% { opacity: 0; transform: scale(.7) translateY(34rpx); }
|
||||||
18% { opacity: 1; transform: scale(1.06) translateY(0); }
|
18% { opacity: 1; transform: scale(1.06) translateY(0); }
|
||||||
|
|||||||
@@ -70,11 +70,19 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="eg-board-shell">
|
<view class="eg-board-shell" :class="{ 'is-flowing': flowActive }">
|
||||||
<view class="eg-board-top">
|
<view class="eg-board-top">
|
||||||
<view class="eg-board-bonuses">
|
<view class="eg-board-bonuses">
|
||||||
<view class="eg-combo" :class="{ 'is-hot': combo >= 3 }">连消 ×{{ Math.max(combo, 1) }}</view>
|
<view class="eg-combo" :class="{ 'is-hot': combo >= 3 }">连消 ×{{ Math.max(combo, 1) }}</view>
|
||||||
<view v-if="feastMoves > 0" class="eg-double-state">双倍得分 ×{{ feastMoves }}</view>
|
<view
|
||||||
|
v-if="flowActive"
|
||||||
|
class="eg-flow-state"
|
||||||
|
:class="{ 'is-paused': flowPaused }"
|
||||||
|
>
|
||||||
|
<text>{{ flowPaused ? '选择暂停' : '心流 ×2' }}</text>
|
||||||
|
<text>{{ flowCountdownLabel }}秒</text>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="feastMoves > 0" class="eg-double-state">双倍得分 ×{{ feastMoves }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="eg-risk">
|
<view class="eg-risk">
|
||||||
<text>高糖食物 {{ highCount }}/3</text>
|
<text>高糖食物 {{ highCount }}/3</text>
|
||||||
@@ -324,8 +332,16 @@ const COMBO_TASK_WAVE = 2
|
|||||||
const COMBO_TASK_TARGET = 2
|
const COMBO_TASK_TARGET = 2
|
||||||
const COMBO_PITY_TURNS = 6
|
const COMBO_PITY_TURNS = 6
|
||||||
const MEGA_COMBO_SCORE = 300
|
const MEGA_COMBO_SCORE = 300
|
||||||
const ACTIVE_GAME_KEY = 'tongji_endless_active_game_v1'
|
const FLOW_TRIGGER_GROUPS = 10
|
||||||
const ACTIVE_GAME_VERSION = 1
|
const FLOW_WINDOW_MS = 3000
|
||||||
|
const FLOW_DURATION_MS = 3000
|
||||||
|
const FLOW_CAMEL_PAUSE_MS = 3000
|
||||||
|
const FLOW_JACKPOT_DURATION_MS = 6000
|
||||||
|
const FLOW_TICK_MS = 100
|
||||||
|
const ACTIVE_GAME_KEY = 'tongji_endless_active_game_v2'
|
||||||
|
const LEGACY_ACTIVE_GAME_KEY = 'tongji_endless_active_game_v1'
|
||||||
|
const ENDED_GAME_KEY = 'tongji_endless_ended_game_v1'
|
||||||
|
const ACTIVE_GAME_VERSION = 2
|
||||||
const DANGER_DROP_CHANCE = 0.05 + GAME_DIFFICULTY * 0.01
|
const DANGER_DROP_CHANCE = 0.05 + GAME_DIFFICULTY * 0.01
|
||||||
const COLUMN_GROUP_BIAS = 0.86 - GAME_DIFFICULTY * 0.028
|
const COLUMN_GROUP_BIAS = 0.86 - GAME_DIFFICULTY * 0.028
|
||||||
const CHEER_MESSAGES = [
|
const CHEER_MESSAGES = [
|
||||||
@@ -426,6 +442,10 @@ const bestScore = ref(0)
|
|||||||
const learned = ref(0)
|
const learned = ref(0)
|
||||||
const combo = ref(0)
|
const combo = ref(0)
|
||||||
const feastMoves = ref(0)
|
const feastMoves = ref(0)
|
||||||
|
const flowActive = ref(false)
|
||||||
|
const flowPaused = ref(false)
|
||||||
|
const flowRemainingMs = ref(0)
|
||||||
|
const flowChainCount = ref(0)
|
||||||
const camelCount = ref(3)
|
const camelCount = ref(3)
|
||||||
const camelMode = ref(false)
|
const camelMode = ref(false)
|
||||||
const busy = ref(false)
|
const busy = ref(false)
|
||||||
@@ -478,6 +498,12 @@ let jackpotTimer = null
|
|||||||
let confettiTimer = null
|
let confettiTimer = null
|
||||||
let stageTimer = null
|
let stageTimer = null
|
||||||
let taskRewardTimer = null
|
let taskRewardTimer = null
|
||||||
|
let flowTimer = null
|
||||||
|
let flowPauseTimer = null
|
||||||
|
let flowPausedRemainingMs = 0
|
||||||
|
let lastFlowGroupAt = 0
|
||||||
|
let flowLastTickAt = 0
|
||||||
|
let flowPageHidden = false
|
||||||
let pendingThemeMatchCount = 0
|
let pendingThemeMatchCount = 0
|
||||||
let taskRewardStatusUntil = 0
|
let taskRewardStatusUntil = 0
|
||||||
let sfxGesturePrimed = false
|
let sfxGesturePrimed = false
|
||||||
@@ -495,6 +521,7 @@ const currentTask = computed(() => {
|
|||||||
return templates[taskIndex.value % templates.length]
|
return templates[taskIndex.value % templates.length]
|
||||||
})
|
})
|
||||||
const taskPercent = computed(() => Math.min(100, taskProgress.value / currentTask.value.target * 100))
|
const taskPercent = computed(() => Math.min(100, taskProgress.value / currentTask.value.target * 100))
|
||||||
|
const flowCountdownLabel = computed(() => Math.max(0, flowRemainingMs.value / 1000).toFixed(1))
|
||||||
const highCount = computed(() => board.value.filter(tile => tile && foodOf(tile.key).high).length)
|
const highCount = computed(() => board.value.filter(tile => tile && foodOf(tile.key).high).length)
|
||||||
const boardRows = computed(() => Array.from({ length: ROWS }, (_, row) => (
|
const boardRows = computed(() => Array.from({ length: ROWS }, (_, row) => (
|
||||||
board.value.slice(row * COLS, row * COLS + COLS).map((tile, col) => ({ ...tile, index: row * COLS + col }))
|
board.value.slice(row * COLS, row * COLS + COLS).map((tile, col) => ({ ...tile, index: row * COLS + col }))
|
||||||
@@ -634,12 +661,16 @@ onLoad((options = {}) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
|
flowPageHidden = false
|
||||||
|
syncFlowClock()
|
||||||
connectPlatform(incomingInviteCode).then((ok) => {
|
connectPlatform(incomingInviteCode).then((ok) => {
|
||||||
if (ok) flushPlatformProgress()
|
if (ok) flushPlatformProgress()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onHide(() => {
|
onHide(() => {
|
||||||
|
flowPageHidden = true
|
||||||
|
if (flowPaused.value) resumeFlowFromCamel()
|
||||||
saveActiveGame()
|
saveActiveGame()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -655,6 +686,7 @@ onUnload(() => {
|
|||||||
if (confettiTimer) clearTimeout(confettiTimer)
|
if (confettiTimer) clearTimeout(confettiTimer)
|
||||||
if (stageTimer) clearTimeout(stageTimer)
|
if (stageTimer) clearTimeout(stageTimer)
|
||||||
if (taskRewardTimer) clearTimeout(taskRewardTimer)
|
if (taskRewardTimer) clearTimeout(taskRewardTimer)
|
||||||
|
clearFlowTimers()
|
||||||
})
|
})
|
||||||
|
|
||||||
function makeTile(key, special = '') {
|
function makeTile(key, special = '') {
|
||||||
@@ -742,6 +774,142 @@ function currentWeekStartKey(now = new Date()) {
|
|||||||
|
|
||||||
function clearActiveGame() {
|
function clearActiveGame() {
|
||||||
try { uni.removeStorageSync(ACTIVE_GAME_KEY) } catch (_) {}
|
try { uni.removeStorageSync(ACTIVE_GAME_KEY) } catch (_) {}
|
||||||
|
try { uni.removeStorageSync(LEGACY_ACTIVE_GAME_KEY) } catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readEndedGame() {
|
||||||
|
try { return uni.getStorageSync(ENDED_GAME_KEY) || null } catch (_) { return null }
|
||||||
|
}
|
||||||
|
|
||||||
|
function markGameEnded() {
|
||||||
|
try {
|
||||||
|
uni.setStorageSync(ENDED_GAME_KEY, {
|
||||||
|
session_key: getPlatformSessionKey(),
|
||||||
|
ended_at: Date.now()
|
||||||
|
})
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFlowTimers() {
|
||||||
|
if (flowTimer) clearInterval(flowTimer)
|
||||||
|
if (flowPauseTimer) clearTimeout(flowPauseTimer)
|
||||||
|
flowTimer = null
|
||||||
|
flowPauseTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFlowState() {
|
||||||
|
clearFlowTimers()
|
||||||
|
flowActive.value = false
|
||||||
|
flowPaused.value = false
|
||||||
|
flowRemainingMs.value = 0
|
||||||
|
flowChainCount.value = 0
|
||||||
|
flowPausedRemainingMs = 0
|
||||||
|
lastFlowGroupAt = 0
|
||||||
|
flowLastTickAt = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function flowClockBlocked() {
|
||||||
|
return flowPageHidden
|
||||||
|
|| flowPaused.value
|
||||||
|
|| busy.value
|
||||||
|
|| reshuffling.value
|
||||||
|
|| stageVisible.value
|
||||||
|
|| camelJackpot.value.visible
|
||||||
|
|| weeklyRankVisible.value
|
||||||
|
|| gameOver.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncFlowClock() {
|
||||||
|
if (!flowActive.value) return
|
||||||
|
const now = Date.now()
|
||||||
|
if (!flowLastTickAt) flowLastTickAt = now
|
||||||
|
const elapsed = Math.max(0, now - flowLastTickAt)
|
||||||
|
flowLastTickAt = now
|
||||||
|
if (flowClockBlocked()) return
|
||||||
|
flowRemainingMs.value = Math.max(0, flowRemainingMs.value - elapsed)
|
||||||
|
if (flowRemainingMs.value <= 0) endFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureFlowTimer() {
|
||||||
|
if (flowTimer) clearInterval(flowTimer)
|
||||||
|
flowLastTickAt = Date.now()
|
||||||
|
flowTimer = setInterval(syncFlowClock, FLOW_TICK_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateFlow(duration = FLOW_DURATION_MS, source = 'streak') {
|
||||||
|
const wasActive = flowActive.value
|
||||||
|
flowActive.value = true
|
||||||
|
flowPaused.value = false
|
||||||
|
flowPausedRemainingMs = 0
|
||||||
|
flowRemainingMs.value = Math.max(wasActive ? flowRemainingMs.value : 0, duration)
|
||||||
|
flowChainCount.value = Math.max(flowChainCount.value, FLOW_TRIGGER_GROUPS)
|
||||||
|
ensureFlowTimer()
|
||||||
|
if (wasActive) return
|
||||||
|
statusText.value = source === 'camel-five'
|
||||||
|
? '五罐驼乳开启 6 秒心流,继续消除得分翻倍'
|
||||||
|
: '手感来了!3 秒内继续消除,得分翻倍'
|
||||||
|
playSfxLayer('comboMega', 'sparkle', 90, .72)
|
||||||
|
vibrate('medium')
|
||||||
|
uni.showToast({
|
||||||
|
title: source === 'camel-five' ? '五连大奖:心流双倍 6 秒' : '隐藏心流已触发:得分 ×2',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function endFlow() {
|
||||||
|
if (flowTimer) clearInterval(flowTimer)
|
||||||
|
if (flowPauseTimer) clearTimeout(flowPauseTimer)
|
||||||
|
flowTimer = null
|
||||||
|
flowPauseTimer = null
|
||||||
|
flowActive.value = false
|
||||||
|
flowPaused.value = false
|
||||||
|
flowRemainingMs.value = 0
|
||||||
|
flowChainCount.value = 0
|
||||||
|
flowPausedRemainingMs = 0
|
||||||
|
lastFlowGroupAt = 0
|
||||||
|
flowLastTickAt = 0
|
||||||
|
if (!busy.value && !gameOver.value) statusText.value = '心流结束,继续寻找下一次手感'
|
||||||
|
}
|
||||||
|
|
||||||
|
function pauseFlowForCamel() {
|
||||||
|
if (!flowActive.value || flowPaused.value) return
|
||||||
|
syncFlowClock()
|
||||||
|
if (!flowActive.value) return
|
||||||
|
flowPaused.value = true
|
||||||
|
flowPausedRemainingMs = flowRemainingMs.value
|
||||||
|
if (flowPauseTimer) clearTimeout(flowPauseTimer)
|
||||||
|
flowPauseTimer = setTimeout(() => {
|
||||||
|
resumeFlowFromCamel()
|
||||||
|
if (camelMode.value && !busy.value) statusText.value = '3 秒选择暂停已结束,请尽快选择替换食物'
|
||||||
|
}, FLOW_CAMEL_PAUSE_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resumeFlowFromCamel() {
|
||||||
|
if (!flowPaused.value) return
|
||||||
|
if (flowPauseTimer) clearTimeout(flowPauseTimer)
|
||||||
|
flowPauseTimer = null
|
||||||
|
flowPaused.value = false
|
||||||
|
flowRemainingMs.value = Math.max(0, flowPausedRemainingMs)
|
||||||
|
flowPausedRemainingMs = 0
|
||||||
|
flowLastTickAt = Date.now()
|
||||||
|
if (flowRemainingMs.value <= 0) endFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerFlowGroups(groupCount, countsTowardTrigger = true) {
|
||||||
|
const count = Math.max(0, Number(groupCount) || 0)
|
||||||
|
if (!count) return
|
||||||
|
const now = Date.now()
|
||||||
|
if (flowActive.value) {
|
||||||
|
flowRemainingMs.value = Math.max(flowRemainingMs.value, FLOW_DURATION_MS)
|
||||||
|
flowLastTickAt = now
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!countsTowardTrigger) return
|
||||||
|
flowChainCount.value = now - lastFlowGroupAt <= FLOW_WINDOW_MS
|
||||||
|
? flowChainCount.value + count
|
||||||
|
: count
|
||||||
|
lastFlowGroupAt = now
|
||||||
|
if (flowChainCount.value >= FLOW_TRIGGER_GROUPS) activateFlow(FLOW_DURATION_MS)
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveActiveGame() {
|
function saveActiveGame() {
|
||||||
@@ -755,6 +923,7 @@ function saveActiveGame() {
|
|||||||
try {
|
try {
|
||||||
uni.setStorageSync(ACTIVE_GAME_KEY, {
|
uni.setStorageSync(ACTIVE_GAME_KEY, {
|
||||||
version: ACTIVE_GAME_VERSION,
|
version: ACTIVE_GAME_VERSION,
|
||||||
|
status: 'active',
|
||||||
week_start: currentWeekStartKey(),
|
week_start: currentWeekStartKey(),
|
||||||
session_key: getPlatformSessionKey(),
|
session_key: getPlatformSessionKey(),
|
||||||
confirmed_session_learned: Math.max(0, Number(confirmedSessionLearned.value) || 0),
|
confirmed_session_learned: Math.max(0, Number(confirmedSessionLearned.value) || 0),
|
||||||
@@ -782,7 +951,16 @@ function restoreActiveGame() {
|
|||||||
const validBoard = Array.isArray(saved?.board)
|
const validBoard = Array.isArray(saved?.board)
|
||||||
&& saved.board.length === ROWS * COLS
|
&& saved.board.length === ROWS * COLS
|
||||||
&& saved.board.every(tile => tile && FOODS[tile.key])
|
&& saved.board.every(tile => tile && FOODS[tile.key])
|
||||||
if (saved?.version !== ACTIVE_GAME_VERSION || saved?.week_start !== currentWeekStartKey() || !validBoard) {
|
const ended = readEndedGame()
|
||||||
|
const savedSessionKey = String(saved?.session_key || '')
|
||||||
|
const endedSessionKey = String(ended?.session_key || '')
|
||||||
|
if (
|
||||||
|
saved?.version !== ACTIVE_GAME_VERSION
|
||||||
|
|| saved?.status !== 'active'
|
||||||
|
|| saved?.week_start !== currentWeekStartKey()
|
||||||
|
|| !validBoard
|
||||||
|
|| (savedSessionKey && savedSessionKey === endedSessionKey)
|
||||||
|
) {
|
||||||
clearActiveGame()
|
clearActiveGame()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -798,6 +976,7 @@ function restoreActiveGame() {
|
|||||||
learned.value = Math.max(0, Number(saved.learned) || 0)
|
learned.value = Math.max(0, Number(saved.learned) || 0)
|
||||||
combo.value = Math.max(0, Number(saved.combo) || 0)
|
combo.value = Math.max(0, Number(saved.combo) || 0)
|
||||||
feastMoves.value = Math.max(0, Number(saved.feast_moves) || 0)
|
feastMoves.value = Math.max(0, Number(saved.feast_moves) || 0)
|
||||||
|
resetFlowState()
|
||||||
camelCount.value = Math.max(0, Math.min(MAX_CAMEL_COUNT, Number(saved.camel_count) || 0))
|
camelCount.value = Math.max(0, Math.min(MAX_CAMEL_COUNT, Number(saved.camel_count) || 0))
|
||||||
taskIndex.value = Math.max(0, Number(saved.task_index) || 0) % 4
|
taskIndex.value = Math.max(0, Number(saved.task_index) || 0) % 4
|
||||||
taskProgress.value = Math.max(0, Number(saved.task_progress) || 0)
|
taskProgress.value = Math.max(0, Number(saved.task_progress) || 0)
|
||||||
@@ -839,6 +1018,7 @@ function startGame() {
|
|||||||
learned.value = 0
|
learned.value = 0
|
||||||
combo.value = 0
|
combo.value = 0
|
||||||
feastMoves.value = 0
|
feastMoves.value = 0
|
||||||
|
resetFlowState()
|
||||||
camelCount.value = 3
|
camelCount.value = 3
|
||||||
camelMode.value = false
|
camelMode.value = false
|
||||||
busy.value = false
|
busy.value = false
|
||||||
@@ -1003,6 +1183,7 @@ async function tapTile(index) {
|
|||||||
board.value = [...board.value]
|
board.value = [...board.value]
|
||||||
camelCount.value -= 1
|
camelCount.value -= 1
|
||||||
camelMode.value = false
|
camelMode.value = false
|
||||||
|
resumeFlowFromCamel()
|
||||||
selected.value = -1
|
selected.value = -1
|
||||||
playSfxLayer('insulin', 'sparkle', 55, .5)
|
playSfxLayer('insulin', 'sparkle', 55, .5)
|
||||||
vibrate('light')
|
vibrate('light')
|
||||||
@@ -1010,7 +1191,7 @@ async function tapTile(index) {
|
|||||||
if (groups.length) {
|
if (groups.length) {
|
||||||
busy.value = true
|
busy.value = true
|
||||||
statusText.value = '驼乳棋子连起来了!'
|
statusText.value = '驼乳棋子连起来了!'
|
||||||
await resolveMatches(groups, index)
|
await resolveMatches(groups, index, { toolTriggered: true })
|
||||||
busy.value = false
|
busy.value = false
|
||||||
finishTurn()
|
finishTurn()
|
||||||
} else {
|
} else {
|
||||||
@@ -1046,7 +1227,15 @@ function toggleCamel() {
|
|||||||
if (busy.value || camelCount.value <= 0 || gameOver.value) return
|
if (busy.value || camelCount.value <= 0 || gameOver.value) return
|
||||||
camelMode.value = !camelMode.value
|
camelMode.value = !camelMode.value
|
||||||
selected.value = -1
|
selected.value = -1
|
||||||
statusText.value = camelMode.value ? `请选择橙色边框的${foodOf(currentTheme.value.danger).short}进行替换` : '已取消使用驼乳粉'
|
if (camelMode.value) {
|
||||||
|
pauseFlowForCamel()
|
||||||
|
statusText.value = flowPaused.value
|
||||||
|
? `心流暂停 3 秒,请选择橙色边框的${foodOf(currentTheme.value.danger).short}`
|
||||||
|
: `请选择橙色边框的${foodOf(currentTheme.value.danger).short}进行替换`
|
||||||
|
} else {
|
||||||
|
resumeFlowFromCamel()
|
||||||
|
statusText.value = '已取消使用驼乳粉'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAdjacent(a, b) {
|
function isAdjacent(a, b) {
|
||||||
@@ -1081,6 +1270,8 @@ async function trySwap(a, b, soundPlayed = false) {
|
|||||||
endedFood.value = foodOf(highTile.key)
|
endedFood.value = foodOf(highTile.key)
|
||||||
saveBest()
|
saveBest()
|
||||||
gameOver.value = true
|
gameOver.value = true
|
||||||
|
resetFlowState()
|
||||||
|
markGameEnded()
|
||||||
clearActiveGame()
|
clearActiveGame()
|
||||||
queuePlatformProgress(learned.value, score.value, true)
|
queuePlatformProgress(learned.value, score.value, true)
|
||||||
busy.value = false
|
busy.value = false
|
||||||
@@ -1097,7 +1288,8 @@ function swap(a, b) {
|
|||||||
board.value = next
|
board.value = next
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveMatches(initialGroups, movedIndex) {
|
async function resolveMatches(initialGroups, movedIndex, options = {}) {
|
||||||
|
const toolTriggered = !!options.toolTriggered
|
||||||
const comboTaskAtStart = currentTask.value.kind === 'combo'
|
const comboTaskAtStart = currentTask.value.kind === 'combo'
|
||||||
let groups = initialGroups
|
let groups = initialGroups
|
||||||
let wave = 0
|
let wave = 0
|
||||||
@@ -1136,6 +1328,7 @@ async function resolveMatches(initialGroups, movedIndex) {
|
|||||||
? 5
|
? 5
|
||||||
: 0
|
: 0
|
||||||
const camelMatchCount = Math.max(bentCamelCount, camelGroup?.indices.length || 0)
|
const camelMatchCount = Math.max(bentCamelCount, camelGroup?.indices.length || 0)
|
||||||
|
registerFlowGroups(groups.length, !toolTriggered)
|
||||||
if (camelMatchCount >= 3) applyCamelJackpot(camelMatchCount, remove)
|
if (camelMatchCount >= 3) applyCamelJackpot(camelMatchCount, remove)
|
||||||
const removedTiles = [...remove].map(i => board.value[i]).filter(Boolean)
|
const removedTiles = [...remove].map(i => board.value[i]).filter(Boolean)
|
||||||
// 排名统计的是正确认识并消除的普通食物;驼乳是道具,不计入认糖数。
|
// 排名统计的是正确认识并消除的普通食物;驼乳是道具,不计入认糖数。
|
||||||
@@ -1162,7 +1355,7 @@ async function resolveMatches(initialGroups, movedIndex) {
|
|||||||
turnConsumed += removedNormal.length
|
turnConsumed += removedNormal.length
|
||||||
learned.value += removedNormal.length
|
learned.value += removedNormal.length
|
||||||
updateFoodTask(removedNormal)
|
updateFoodTask(removedNormal)
|
||||||
const multiplier = feastMoves.value > 0 ? 2 : 1
|
const multiplier = feastMoves.value > 0 || flowActive.value ? 2 : 1
|
||||||
score.value += (removedNormal.length * 12 + Math.max(0, wave - 1) * 20 + madeSpecial * 35) * multiplier
|
score.value += (removedNormal.length * 12 + Math.max(0, wave - 1) * 20 + madeSpecial * 35) * multiplier
|
||||||
queuePlatformProgress(learned.value, score.value, false)
|
queuePlatformProgress(learned.value, score.value, false)
|
||||||
await wait(specialEffects.hasFire ? 330 : 210)
|
await wait(specialEffects.hasFire ? 330 : 210)
|
||||||
@@ -1262,29 +1455,31 @@ function camelRewardText(reward) {
|
|||||||
|
|
||||||
function applyCamelJackpot(count, remove) {
|
function applyCamelJackpot(count, remove) {
|
||||||
pendingThemeMatchCount = Math.max(pendingThemeMatchCount, count)
|
pendingThemeMatchCount = Math.max(pendingThemeMatchCount, count)
|
||||||
|
const bonusMultiplier = flowActive.value ? 2 : 1
|
||||||
if (count >= 5) {
|
if (count >= 5) {
|
||||||
score.value += 1554
|
const bonus = 1554 * bonusMultiplier
|
||||||
const camelReward = grantCamelReward(4)
|
score.value += bonus
|
||||||
feastMoves.value = Math.max(feastMoves.value, 6)
|
activateFlow(FLOW_JACKPOT_DURATION_MS, 'camel-five')
|
||||||
board.value.forEach((tile, index) => { if (foodOf(tile?.key).high) remove.add(index) })
|
board.value.forEach((tile, index) => { if (foodOf(tile?.key).high) remove.add(index) })
|
||||||
showCamelJackpot(5, '驼乳五连双倍大奖', `+1554 分 · ${camelRewardText(camelReward)} · 双倍得分 ×6`)
|
showCamelJackpot(5, '驼乳五连双倍大奖', `+${bonus} 分 · 移出全部高糖 · 心流双倍 6 秒`)
|
||||||
startConfetti()
|
startConfetti()
|
||||||
playJackpotSequence()
|
playJackpotSequence()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (count === 4) {
|
if (count === 4) {
|
||||||
score.value += 288
|
const bonus = 388 * bonusMultiplier
|
||||||
const camelReward = grantCamelReward(1)
|
score.value += bonus
|
||||||
const highIndex = board.value.findIndex((tile, index) => foodOf(tile?.key).high && !remove.has(index))
|
const highIndex = board.value.findIndex((tile, index) => foodOf(tile?.key).high && !remove.has(index))
|
||||||
if (highIndex >= 0) remove.add(highIndex)
|
if (highIndex >= 0) remove.add(highIndex)
|
||||||
showCamelJackpot(4, '驼乳超级四连', `+288 分 · ${camelRewardText(camelReward)} · 移出一份高糖食物`)
|
showCamelJackpot(4, '驼乳超级四连', `+${bonus} 分 · 移出一份高糖食物 · 再来一罐冲击大奖`)
|
||||||
playSfxLayer('match5', 'fanfare', 90, .7)
|
playSfxLayer('match5', 'fanfare', 90, .7)
|
||||||
setTimeout(() => playSfx('tap', 1.16, .58), 260)
|
setTimeout(() => playSfx('tap', 1.16, .58), 260)
|
||||||
vibrate('medium')
|
vibrate('medium')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
score.value += 77
|
const bonus = 77 * bonusMultiplier
|
||||||
showCamelJackpot(3, '驼乳幸运三连', '+77 分 · 轻量连消奖励')
|
score.value += bonus
|
||||||
|
showCamelJackpot(3, '驼乳幸运三连', `+${bonus} 分 · 轻量连消奖励`)
|
||||||
playSfxLayer('match3', 'sparkle', 44, .38)
|
playSfxLayer('match3', 'sparkle', 44, .38)
|
||||||
vibrate('light')
|
vibrate('light')
|
||||||
}
|
}
|
||||||
@@ -1586,7 +1781,9 @@ function finishTurn() {
|
|||||||
scheduleHint()
|
scheduleHint()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
statusText.value = combo.value >= 2 ? `漂亮!完成 ${combo.value} 连消` : '食物已从上方补充,继续挑战'
|
statusText.value = flowActive.value
|
||||||
|
? '心流双倍进行中,3 秒内继续消除可续时'
|
||||||
|
: (combo.value >= 2 ? `漂亮!完成 ${combo.value} 连消` : '食物已从上方补充,继续挑战')
|
||||||
scheduleHint()
|
scheduleHint()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user