feat(tongji): add milk tea afternoon challenge
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -16,6 +16,7 @@ const FOOD_GROUPS = [
|
|||||||
{ title: '粗粮早餐', safe: ['broccoli', 'milk', 'brownRice', 'potato'], danger: 'milletCongee' },
|
{ title: '粗粮早餐', safe: ['broccoli', 'milk', 'brownRice', 'potato'], danger: 'milletCongee' },
|
||||||
{ title: '水果饮品', safe: ['apple', 'strawberry', 'banana', 'mango'], danger: 'orangeJuice' },
|
{ title: '水果饮品', safe: ['apple', 'strawberry', 'banana', 'mango'], danger: 'orangeJuice' },
|
||||||
{ title: '日常饮品', safe: ['milk', 'tea', 'blackCoffee', 'coffee'], danger: 'soda' },
|
{ title: '日常饮品', safe: ['milk', 'tea', 'blackCoffee', 'coffee'], danger: 'soda' },
|
||||||
|
{ title: '下午茶甜饮', safe: ['milk', 'tea', 'apple', 'strawberry'], danger: 'milkTea' },
|
||||||
{ title: '南方汤粉', safe: ['pepper', 'greenBean', 'shrimp', 'udon'], danger: 'riceNoodleSoup' },
|
{ title: '南方汤粉', safe: ['pepper', 'greenBean', 'shrimp', 'udon'], danger: 'riceNoodleSoup' },
|
||||||
{ title: '北方面食', safe: ['onion', 'celery', 'corn', 'grainMantou'], danger: 'noodleSoup' },
|
{ title: '北方面食', safe: ['onion', 'celery', 'corn', 'grainMantou'], danger: 'noodleSoup' },
|
||||||
{ title: '家常餐桌', safe: ['tofu', 'spinach', 'carrot', 'fish'], danger: 'redBeanCongee' }
|
{ title: '家常餐桌', safe: ['tofu', 'spinach', 'carrot', 'fish'], danger: 'redBeanCongee' }
|
||||||
@@ -423,6 +424,12 @@ function createMetrics() {
|
|||||||
function runTargetedTailTests(metrics) {
|
function runTargetedTailTests(metrics) {
|
||||||
for (let groupIndex = 0; groupIndex < FOOD_GROUPS.length; groupIndex += 1) {
|
for (let groupIndex = 0; groupIndex < FOOD_GROUPS.length; groupIndex += 1) {
|
||||||
const nextIndex = (groupIndex + 1) % FOOD_GROUPS.length
|
const nextIndex = (groupIndex + 1) % FOOD_GROUPS.length
|
||||||
|
const previousGroup = FOOD_GROUPS[groupIndex]
|
||||||
|
const nextGroup = FOOD_GROUPS[nextIndex]
|
||||||
|
// 相邻主题允许在相同槽位保留同一种食物。定向测试应选择真正退出下一组的食物,
|
||||||
|
// 不能固定测试第 0 槽,否则“牛奶→牛奶”会被误报为尾巴未转换。
|
||||||
|
const slot = previousGroup.safe.findIndex(key => !nextGroup.safe.includes(key))
|
||||||
|
if (slot < 0) continue
|
||||||
for (const remaining of [0, 1, 2]) {
|
for (const remaining of [0, 1, 2]) {
|
||||||
const state = {
|
const state = {
|
||||||
run: -1,
|
run: -1,
|
||||||
@@ -430,21 +437,21 @@ function runTargetedTailTests(metrics) {
|
|||||||
rng: createRng(BASE_SEED + groupIndex * 17 + remaining),
|
rng: createRng(BASE_SEED + groupIndex * 17 + remaining),
|
||||||
metrics,
|
metrics,
|
||||||
themeIndex: nextIndex,
|
themeIndex: nextIndex,
|
||||||
activeSafeKeys: [...FOOD_GROUPS[nextIndex].safe],
|
activeSafeKeys: [...nextGroup.safe],
|
||||||
transition: {
|
transition: {
|
||||||
from: FOOD_GROUPS[groupIndex].safe[0],
|
from: previousGroup.safe[slot],
|
||||||
to: FOOD_GROUPS[nextIndex].safe[0],
|
to: nextGroup.safe[slot],
|
||||||
slot: 0,
|
slot,
|
||||||
age: 0
|
age: 0
|
||||||
},
|
},
|
||||||
queuedSafeGroups: [[...FOOD_GROUPS[nextIndex].safe]],
|
queuedSafeGroups: [[...nextGroup.safe]],
|
||||||
board: []
|
board: []
|
||||||
}
|
}
|
||||||
const fill = FOOD_GROUPS[nextIndex].safe
|
const fill = nextGroup.safe
|
||||||
state.board = Array.from({ length: ROWS * COLS }, (_, index) => tile(fill[index % fill.length]))
|
state.board = Array.from({ length: ROWS * COLS }, (_, index) => tile(fill[index % fill.length]))
|
||||||
for (let index = 0; index < remaining; index += 1) state.board[index] = tile(state.transition.from)
|
for (let index = 0; index < remaining; index += 1) state.board[index] = tile(state.transition.from)
|
||||||
finishTransitionIfReady(state)
|
finishTransitionIfReady(state)
|
||||||
if (state.board.some(item => item.key === FOOD_GROUPS[groupIndex].safe[0])) {
|
if (state.board.some(item => item.key === state.transition?.from || item.key === previousGroup.safe[slot])) {
|
||||||
recordFailure(state, 'targeted_tail_not_converted', `${groupIndex}:${remaining}`)
|
recordFailure(state, 'targeted_tail_not_converted', `${groupIndex}:${remaining}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ const FOOD_ICON_FILES = [
|
|||||||
'spinach.jpg',
|
'spinach.jpg',
|
||||||
'carrot.jpg',
|
'carrot.jpg',
|
||||||
'fish.jpg',
|
'fish.jpg',
|
||||||
'redBeanCongee.jpg'
|
'redBeanCongee.jpg',
|
||||||
|
'milkTea.jpg'
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -212,6 +212,13 @@
|
|||||||
.eg-cell.is-level-high { background: #fff0e4; }
|
.eg-cell.is-level-high { background: #fff0e4; }
|
||||||
.eg-cell.is-milk { background: #edf7ff; }
|
.eg-cell.is-milk { background: #edf7ff; }
|
||||||
.eg-cell.is-high { border-color: #ef7b45; }
|
.eg-cell.is-high { border-color: #ef7b45; }
|
||||||
|
.eg-cell.is-villain {
|
||||||
|
background: radial-gradient(circle at 50% 38%, #fff7e8 0%, #ffe9d8 70%, #ffddc6 100%);
|
||||||
|
box-shadow: inset 0 -5rpx 0 rgba(154,57,25,.08), 0 6rpx 15rpx rgba(191,72,31,.16);
|
||||||
|
}
|
||||||
|
.eg-cell.is-villain .eg-food-image { animation: egVillainPeek 2.2s ease-in-out infinite; }
|
||||||
|
.eg-cell.is-villain.is-clearing .eg-food-image,
|
||||||
|
.eg-cell.is-villain.is-dragging .eg-food-image { animation: none; }
|
||||||
.eg-food-image { width: 132rpx; height: 92rpx; margin-top: 7rpx; border-radius: 17rpx; filter: saturate(1.06) contrast(1.03); }
|
.eg-food-image { width: 132rpx; height: 92rpx; margin-top: 7rpx; border-radius: 17rpx; filter: saturate(1.06) contrast(1.03); }
|
||||||
.eg-food-image.is-large-square { width: 136rpx; height: 102rpx; margin-top: 2rpx; filter: saturate(1.1) contrast(1.06); }
|
.eg-food-image.is-large-square { width: 136rpx; height: 102rpx; margin-top: 2rpx; filter: saturate(1.1) contrast(1.06); }
|
||||||
.eg-food-name { margin-top: 8rpx; color: #203d34; font-size: 29rpx; font-weight: 900; letter-spacing: 1rpx; line-height: 1.1; }
|
.eg-food-name { margin-top: 8rpx; color: #203d34; font-size: 29rpx; font-weight: 900; letter-spacing: 1rpx; line-height: 1.1; }
|
||||||
@@ -469,6 +476,13 @@
|
|||||||
100% { opacity: 0; transform: scale(.32) rotate(7deg); }
|
100% { opacity: 0; transform: scale(.32) rotate(7deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes egVillainPeek {
|
||||||
|
0%, 72%, 100% { transform: translateY(0) rotate(0); filter: saturate(1.08) contrast(1.05); }
|
||||||
|
80% { transform: translateY(-3rpx) rotate(-1.5deg); filter: saturate(1.15) contrast(1.08); }
|
||||||
|
88% { transform: translateY(0) rotate(1.5deg); }
|
||||||
|
95% { transform: translateY(-2rpx) rotate(0); }
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes egTaskRewardFlight {
|
@keyframes egTaskRewardFlight {
|
||||||
0% { opacity: 0; transform: translate3d(-20rpx, 0, 0) scale(.62) rotate(-8deg); }
|
0% { opacity: 0; transform: translate3d(-20rpx, 0, 0) scale(.62) rotate(-8deg); }
|
||||||
16% { opacity: 1; transform: translate3d(0, -24rpx, 0) scale(1.1) rotate(3deg); }
|
16% { opacity: 1; transform: translate3d(0, -24rpx, 0) scale(1.1) rotate(3deg); }
|
||||||
|
|||||||
@@ -302,7 +302,7 @@
|
|||||||
<text>{{ food.short }}</text>
|
<text>{{ food.short }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text class="eg-stage-tip">橙框食物已更新,普通食物会随掉落逐步加入</text>
|
<text class="eg-stage-tip">{{ stageMeta.tip || '橙框食物已更新,普通食物会随掉落逐步加入' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -342,7 +342,8 @@ const LOCAL_FOOD_IMAGES = {
|
|||||||
spinach: `${LOCAL_FOOD_IMAGE_BASE_URL}spinach.jpg`,
|
spinach: `${LOCAL_FOOD_IMAGE_BASE_URL}spinach.jpg`,
|
||||||
carrot: `${LOCAL_FOOD_IMAGE_BASE_URL}carrot.jpg`,
|
carrot: `${LOCAL_FOOD_IMAGE_BASE_URL}carrot.jpg`,
|
||||||
fish: `${LOCAL_FOOD_IMAGE_BASE_URL}fish.jpg`,
|
fish: `${LOCAL_FOOD_IMAGE_BASE_URL}fish.jpg`,
|
||||||
redBeanCongee: `${LOCAL_FOOD_IMAGE_BASE_URL}redBeanCongee.jpg`
|
redBeanCongee: `${LOCAL_FOOD_IMAGE_BASE_URL}redBeanCongee.jpg`,
|
||||||
|
milkTea: `${LOCAL_FOOD_IMAGE_BASE_URL}milkTea.jpg`
|
||||||
}
|
}
|
||||||
|
|
||||||
const ROWS = 5
|
const ROWS = 5
|
||||||
@@ -365,7 +366,8 @@ const FLOW_TICK_MS = 100
|
|||||||
const ACTIVE_GAME_KEY = 'tongji_endless_active_game_v2'
|
const ACTIVE_GAME_KEY = 'tongji_endless_active_game_v2'
|
||||||
const LEGACY_ACTIVE_GAME_KEY = 'tongji_endless_active_game_v1'
|
const LEGACY_ACTIVE_GAME_KEY = 'tongji_endless_active_game_v1'
|
||||||
const ENDED_GAME_KEY = 'tongji_endless_ended_game_v1'
|
const ENDED_GAME_KEY = 'tongji_endless_ended_game_v1'
|
||||||
const ACTIVE_GAME_VERSION = 2
|
// 新增“下午茶甜饮”后关卡序号发生变化;旧局不能按新序号恢复,否则高糖目标会错位。
|
||||||
|
const ACTIVE_GAME_VERSION = 3
|
||||||
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 = [
|
||||||
@@ -418,7 +420,11 @@ const FOODS = {
|
|||||||
soda: { name: '含糖汽水', short: '汽水', emoji: '🥤', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '含糖饮料中的糖吸收较快,日常应优先选择不加糖的饮品。' },
|
soda: { name: '含糖汽水', short: '汽水', emoji: '🥤', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '含糖饮料中的糖吸收较快,日常应优先选择不加糖的饮品。' },
|
||||||
riceNoodleSoup: { name: '汤粉', short: '汤粉', emoji: '🍜', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '汤粉以精制米粉为主,实际影响与分量和配菜有关,应注意搭配。' },
|
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: '汤面以精细面食为主,汤汁和分量需要注意,具体建议以医生审核为准。' },
|
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: '含糖奶茶可能同时含有较多添加糖和能量,应关注配方与饮用量。' },
|
milkTea: {
|
||||||
|
name: '含糖奶茶', short: '奶茶', emoji: '🧋', color: 'orange', level: 'high', levelLabel: '高糖',
|
||||||
|
high: true, replaceable: true, largeIcon: true, villain: true,
|
||||||
|
tip: '含糖奶茶可能同时含有较多添加糖和能量,应关注配方、杯量和饮用频率。游戏中的食物等级需由甄养堂医生最终审核。'
|
||||||
|
},
|
||||||
tofu: { name: '豆腐', short: '豆腐', emoji: '◻️', color: 'cream', category: '豆制品', level: 'low', levelLabel: '低糖', largeIcon: true },
|
tofu: { name: '豆腐', short: '豆腐', emoji: '◻️', color: 'cream', category: '豆制品', level: 'low', levelLabel: '低糖', largeIcon: true },
|
||||||
spinach: { name: '菠菜', short: '菠菜', emoji: '🥬', color: 'yellow', 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 },
|
carrot: { name: '胡萝卜', short: '胡萝卜', emoji: '🥕', color: 'red', category: '蔬菜', level: 'low', levelLabel: '低糖', largeIcon: true },
|
||||||
@@ -439,6 +445,12 @@ const FOOD_GROUPS = [
|
|||||||
{ title: '粗粮早餐', safe: ['broccoli', 'milk', 'brownRice', 'potato'], danger: 'milletCongee' },
|
{ title: '粗粮早餐', safe: ['broccoli', 'milk', 'brownRice', 'potato'], danger: 'milletCongee' },
|
||||||
{ title: '水果饮品', safe: ['apple', 'strawberry', 'banana', 'mango'], danger: 'orangeJuice' },
|
{ title: '水果饮品', safe: ['apple', 'strawberry', 'banana', 'mango'], danger: 'orangeJuice' },
|
||||||
{ title: '日常饮品', safe: ['milk', 'tea', 'blackCoffee', 'coffee'], danger: 'soda' },
|
{ title: '日常饮品', safe: ['milk', 'tea', 'blackCoffee', 'coffee'], danger: 'soda' },
|
||||||
|
{
|
||||||
|
title: '下午茶甜饮',
|
||||||
|
safe: ['milk', 'tea', 'apple', 'strawberry'],
|
||||||
|
danger: 'milkTea',
|
||||||
|
hint: '奶茶混进下午茶了,别把它三连消除!'
|
||||||
|
},
|
||||||
{ title: '南方汤粉', safe: ['pepper', 'greenBean', 'shrimp', 'udon'], danger: 'riceNoodleSoup' },
|
{ title: '南方汤粉', safe: ['pepper', 'greenBean', 'shrimp', 'udon'], danger: 'riceNoodleSoup' },
|
||||||
{ title: '北方面食', safe: ['onion', 'celery', 'corn', 'grainMantou'], danger: 'noodleSoup' },
|
{ title: '北方面食', safe: ['onion', 'celery', 'corn', 'grainMantou'], danger: 'noodleSoup' },
|
||||||
{ title: '家常餐桌', safe: ['tofu', 'spinach', 'carrot', 'fish'], danger: 'redBeanCongee' },
|
{ title: '家常餐桌', safe: ['tofu', 'spinach', 'carrot', 'fish'], danger: 'redBeanCongee' },
|
||||||
@@ -513,7 +525,7 @@ const impactText = ref('')
|
|||||||
const impactLevel = ref(1)
|
const impactLevel = ref(1)
|
||||||
const camelJackpot = ref({ visible: false, count: 0, kicker: '', title: '', reward: '' })
|
const camelJackpot = ref({ visible: false, count: 0, kicker: '', title: '', reward: '' })
|
||||||
const stageVisible = ref(false)
|
const stageVisible = ref(false)
|
||||||
const stageMeta = ref({ number: 1, title: '', foods: [] })
|
const stageMeta = ref({ number: 1, title: '', foods: [], tip: '' })
|
||||||
const reshuffleVisible = ref(false)
|
const reshuffleVisible = ref(false)
|
||||||
const reshuffleDone = ref(false)
|
const reshuffleDone = ref(false)
|
||||||
const reshuffling = ref(false)
|
const reshuffling = ref(false)
|
||||||
@@ -1263,6 +1275,7 @@ function cellClasses(tile) {
|
|||||||
{
|
{
|
||||||
'is-selected': selected.value === tile.index,
|
'is-selected': selected.value === tile.index,
|
||||||
'is-high': food.high,
|
'is-high': food.high,
|
||||||
|
'is-villain': food.villain,
|
||||||
'is-camel-target': camelMode.value && food.replaceable,
|
'is-camel-target': camelMode.value && food.replaceable,
|
||||||
'is-clearing': clearingIndices.value.includes(tile.index),
|
'is-clearing': clearingIndices.value.includes(tile.index),
|
||||||
'is-fire-clearing': fireClearingIndices.value.includes(tile.index),
|
'is-fire-clearing': fireClearingIndices.value.includes(tile.index),
|
||||||
@@ -1687,10 +1700,11 @@ function advanceFoodTheme() {
|
|||||||
stageMeta.value = {
|
stageMeta.value = {
|
||||||
number: themeIndex.value + 1,
|
number: themeIndex.value + 1,
|
||||||
title: nextTheme.title,
|
title: nextTheme.title,
|
||||||
foods: [...nextTheme.safe, nextTheme.danger].map(key => ({ key, ...foodOf(key) }))
|
foods: [...nextTheme.safe, nextTheme.danger].map(key => ({ key, ...foodOf(key) })),
|
||||||
|
tip: nextTheme.hint || '橙框食物已更新,普通食物会随掉落逐步加入'
|
||||||
}
|
}
|
||||||
stageVisible.value = true
|
stageVisible.value = true
|
||||||
statusText.value = `已进入${nextTheme.title},新食物会随掉落逐步加入`
|
statusText.value = nextTheme.hint || `已进入${nextTheme.title},新食物会随掉落逐步加入`
|
||||||
playSfxLayer('fanfare', 'sparkle', 100, .65)
|
playSfxLayer('fanfare', 'sparkle', 100, .65)
|
||||||
if (stageTimer) clearTimeout(stageTimer)
|
if (stageTimer) clearTimeout(stageTimer)
|
||||||
stageTimer = setTimeout(() => { stageVisible.value = false }, 1800)
|
stageTimer = setTimeout(() => { stageVisible.value = false }, 1800)
|
||||||
|
|||||||
Reference in New Issue
Block a user