Files
2026-08-11 17:41:36 +08:00

1631 lines
67 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
const common_vendor = require("../../common/vendor.js");
const tongji_endlessGame_composables_useGameSfx = require("./composables/useGameSfx.js");
const tongji_endlessGame_composables_useGamePlatform = require("./composables/useGamePlatform.js");
const tongji_endlessGame_composables_useGameAuth = require("./composables/useGameAuth.js");
if (!Math) {
TongjiIcon();
}
const TongjiIcon = () => "../components/TongjiIcon.js";
const FOOD_IMAGE_BASE_URL = "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/games/food/";
const ROWS = 5;
const COLS = 4;
const GAME_DIFFICULTY = 5;
const HIGH_CAP = 3;
const MAX_CAMEL_COUNT = 5;
const OVERFLOW_CAMEL_SCORE = 100;
const MAX_CASCADE_WAVES = 3;
const COMBO_TASK_WAVE = 2;
const COMBO_TASK_TARGET = 2;
const COMBO_PITY_TURNS = 6;
const MEGA_COMBO_SCORE = 300;
const _sfc_main = {
__name: "index",
setup(__props) {
const DANGER_DROP_CHANCE = 0.05 + GAME_DIFFICULTY * 0.01;
const COLUMN_GROUP_BIAS = 0.86 - GAME_DIFFICULTY * 0.028;
const CHEER_MESSAGES = [
{ source: "同行鼓励", text: "你需要加油哦!再向前一点,就能超过上一名。", tone: "warm" },
{ source: "同行鼓励", text: "你太棒了!今天也在认真认识食物。", tone: "bright" },
{ source: "开心鼓励", text: "你肯定是个大美女(大帅哥)!保持好心情。", tone: "bright" },
{ source: "亲友留言示例", text: "游戏虽好,可不要贪吃哦!", tone: "family" },
{ source: "亲友留言示例", text: "差不多行了,赶快洗洗睡吧!", tone: "family" },
{ source: "亲友留言示例", text: "你太努力了,你是我的榜样!", tone: "warm" },
{ source: "亲友留言示例", text: "好样的!", tone: "bright" },
{ source: "亲友自定义留言示例", text: "我血糖已经正常了,你们玩吧。", tone: "family", userGenerated: true }
];
const FOODS = {
corn: { name: "玉米", short: "玉米", emoji: "🌽", color: "yellow", level: "mid", levelLabel: "中糖" },
egg: { name: "鸡蛋", short: "鸡蛋", emoji: "🥚", color: "cream", level: "low", levelLabel: "低糖" },
tomato: { name: "番茄", short: "番茄", emoji: "🍅", color: "red", level: "low", levelLabel: "低糖" },
potato: { name: "红薯", short: "红薯", emoji: "🍠", color: "purple", level: "mid", levelLabel: "中糖" },
cucumber: { name: "黄瓜", short: "黄瓜", emoji: "🥒", color: "yellow", level: "low", levelLabel: "低糖" },
lettuce: { name: "生菜", short: "生菜", emoji: "🥬", color: "cream", level: "low", levelLabel: "低糖" },
grainMantou: { name: "杂粮馒头", short: "杂粮馒头", emoji: "🫓", color: "red", level: "mid", levelLabel: "中糖" },
wonton: { name: "馄饨", short: "馄饨", emoji: "🥟", color: "purple", level: "mid", levelLabel: "中糖" },
celery: { name: "芹菜", short: "芹菜", emoji: "🌿", color: "yellow", level: "low", levelLabel: "低糖" },
mushroom: { name: "香菇", short: "香菇", emoji: "🍄", color: "cream", level: "low", levelLabel: "低糖" },
chicken: { name: "鸡肉", short: "鸡肉", emoji: "🍗", color: "red", level: "mid", levelLabel: "中糖" },
taro: { name: "芋头", short: "芋头", emoji: "🥔", color: "purple", level: "mid", levelLabel: "中糖" },
broccoli: { name: "西兰花", short: "西兰花", emoji: "🥦", color: "yellow", level: "low", levelLabel: "低糖" },
milk: { name: "牛奶", short: "牛奶", emoji: "🥛", color: "cream", level: "low", levelLabel: "低糖" },
brownRice: { name: "糙米饭", short: "糙米饭", emoji: "🍙", color: "red", level: "mid", levelLabel: "中糖" },
apple: { name: "苹果", short: "苹果", emoji: "🍎", color: "yellow", level: "low", levelLabel: "低糖" },
strawberry: { name: "草莓", short: "草莓", emoji: "🍓", color: "cream", level: "low", levelLabel: "低糖" },
banana: { name: "香蕉", short: "香蕉", emoji: "🍌", color: "red", level: "mid", levelLabel: "中糖" },
mango: { name: "芒果", short: "芒果", emoji: "🥭", color: "purple", level: "mid", levelLabel: "中糖" },
tea: { name: "红茶", short: "红茶", emoji: "🍵", color: "yellow", level: "low", levelLabel: "低糖" },
blackCoffee: { name: "黑咖啡", short: "黑咖啡", emoji: "☕", color: "cream", level: "low", levelLabel: "低糖" },
coffee: { name: "咖啡", short: "咖啡", emoji: "☕", color: "red", level: "mid", levelLabel: "中糖" },
pineapple: { name: "菠萝", short: "菠萝", emoji: "🍍", color: "purple", level: "mid", levelLabel: "中糖" },
pepper: { name: "青椒", short: "青椒", emoji: "🫑", color: "yellow", level: "low", levelLabel: "低糖" },
greenBean: { name: "四季豆", short: "四季豆", emoji: "🫛", color: "cream", level: "low", levelLabel: "低糖" },
shrimp: { name: "虾仁", short: "虾仁", emoji: "🦐", color: "red", level: "mid", levelLabel: "中糖" },
udon: { name: "乌冬面", short: "乌冬面", emoji: "🍲", color: "purple", level: "mid", levelLabel: "中糖" },
onion: { name: "洋葱", short: "洋葱", emoji: "🧅", color: "yellow", level: "low", levelLabel: "低糖" },
congee: {
name: "白米粥",
short: "白粥",
emoji: "🥣",
color: "orange",
level: "high",
levelLabel: "高糖",
high: true,
replaceable: true,
tip: "白米粥煮得软烂,消化吸收通常较快。糖尿病患者应结合食用量、搭配和个人血糖情况,在医生或营养师指导下合理选择。"
},
eightCongee: { name: "八宝粥", short: "八宝粥", emoji: "🥣", color: "orange", level: "high", levelLabel: "高糖", high: true, replaceable: true, tip: "八宝粥常含糯米、豆类或添加糖,实际影响与配方和食用量有关,请留意营养成分。" },
centuryCongee: { name: "皮蛋瘦肉粥", short: "瘦肉粥", emoji: "🥣", color: "orange", level: "high", levelLabel: "高糖", high: true, replaceable: true, tip: "粥类煮得软烂后通常吸收较快,搭配和食用量需要结合个人情况。" },
milletCongee: { name: "小米粥", short: "小米粥", emoji: "🥣", color: "orange", level: "high", levelLabel: "高糖", high: true, replaceable: true, tip: "小米粥的升糖影响与浓稠度和食用量有关,游戏等级需由医生最终审核。" },
orangeJuice: { 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: "汤粉以精制米粉为主,实际影响与分量和配菜有关,应注意搭配。" },
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: "含糖奶茶可能同时含有较多添加糖和能量,应关注配方与饮用量。" },
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`;
});
const FOOD_GROUPS = [
{ title: "中式早餐", safe: ["egg", "cucumber", "corn", "grainMantou"], danger: "congee" },
{ title: "家常粥餐", safe: ["celery", "mushroom", "chicken", "taro"], danger: "centuryCongee" },
{ title: "粗粮早餐", safe: ["broccoli", "milk", "brownRice", "potato"], danger: "milletCongee" },
{ title: "水果饮品", safe: ["apple", "strawberry", "banana", "mango"], danger: "orangeJuice" },
{ title: "日常饮品", safe: ["milk", "tea", "blackCoffee", "coffee"], danger: "soda" },
{ title: "南方汤粉", safe: ["pepper", "greenBean", "shrimp", "udon"], danger: "riceNoodleSoup" },
{ title: "北方面食", safe: ["onion", "celery", "corn", "grainMantou"], danger: "noodleSoup" }
];
const {
enabled: sfxEnabled,
play: playSfx,
playLayer: playSfxLayer,
playDrop: playDropSfx,
warmUp: warmUpSfx
} = tongji_endlessGame_composables_useGameSfx.useGameSfx();
const { proxy } = common_vendor.getCurrentInstance();
const { ensureLoggedIn } = tongji_endlessGame_composables_useGameAuth.useGameAuth(proxy);
const {
connected: platformConnected,
loading: platformLoading,
syncStatus: platformSyncStatus,
leaderboard: platformLeaderboard,
confirmedSessionLearned,
connect: connectPlatform,
beginSession: beginPlatformSession,
queueProgress: queuePlatformProgress,
flushProgress: flushPlatformProgress,
syncAndRefresh: syncAndRefreshPlatform,
recordShare: recordPlatformShare,
dispose: disposePlatform
} = tongji_endlessGame_composables_useGamePlatform.useGamePlatform(proxy, ensureLoggedIn);
const board = common_vendor.ref([]);
const selected = common_vendor.ref(-1);
const score = common_vendor.ref(0);
const bestScore = common_vendor.ref(0);
const learned = common_vendor.ref(0);
const combo = common_vendor.ref(0);
const feastMoves = common_vendor.ref(0);
const camelCount = common_vendor.ref(3);
const camelMode = common_vendor.ref(false);
const busy = common_vendor.ref(false);
const gameOver = common_vendor.ref(false);
const themeIndex = common_vendor.ref(0);
const endedFood = common_vendor.ref(FOODS.congee);
const taskIndex = common_vendor.ref(0);
const taskProgress = common_vendor.ref(0);
const comboDryTurns = common_vendor.ref(0);
const taskRewardVisible = common_vendor.ref(false);
const taskRewardKey = common_vendor.ref(0);
const statusText = common_vendor.ref("交换相邻食物,三个相同即可消除");
const navStyle = common_vendor.ref({ paddingTop: "20px" });
const touch = { x: 0, y: 0, index: -1 };
const dragIndex = common_vendor.ref(-1);
const dragTarget = common_vendor.ref(-1);
const dragX = common_vendor.ref(0);
const dragY = common_vendor.ref(0);
const clearingIndices = common_vendor.ref([]);
const fireClearingIndices = common_vendor.ref([]);
const impactText = common_vendor.ref("");
const impactLevel = common_vendor.ref(1);
const camelJackpot = common_vendor.ref({ visible: false, count: 0, kicker: "", title: "", reward: "" });
const stageVisible = common_vendor.ref(false);
const stageMeta = common_vendor.ref({ number: 1, title: "", foods: [] });
const reshuffleVisible = common_vendor.ref(false);
const reshuffleDone = common_vendor.ref(false);
const reshuffling = common_vendor.ref(false);
const confettiVisible = common_vendor.ref(false);
const weeklyRankVisible = common_vendor.ref(false);
const cheerIndex = common_vendor.ref(0);
const CONFETTI_COLORS = ["#ff3154", "#ffd928", "#28c76f", "#4d8dff", "#a855f7", "#ff7a1a"];
const confettiPieces = Array.from({ length: 34 }, (_, index) => ({
id: index,
emoji: index % 8 === 0 ? "🎉" : index % 11 === 0 ? "★" : "",
style: {
left: `${(index * 37 + 7) % 100}%`,
backgroundColor: index % 8 === 0 ? "transparent" : CONFETTI_COLORS[index % CONFETTI_COLORS.length],
animationDelay: `${index % 9 * 0.065}s`,
animationDuration: `${3.7 + index % 5 * 0.36}s`,
width: `${12 + index % 3 * 5}rpx`,
height: `${22 + index % 4 * 6}rpx`
}
}));
let suppressTapUntil = 0;
let tileId = 1;
let hintTimer = null;
let jackpotTimer = null;
let confettiTimer = null;
let stageTimer = null;
let taskRewardTimer = null;
let pendingThemeMatchCount = 0;
let taskRewardStatusUntil = 0;
let sfxGesturePrimed = false;
let incomingInviteCode = "";
const currentTheme = common_vendor.computed(() => FOOD_GROUPS[themeIndex.value % FOOD_GROUPS.length]);
const currentTask = common_vendor.computed(() => {
const safe = 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" },
{ key: "combo", label: "完成连消 ×2", target: COMBO_TASK_TARGET, kind: "combo" },
{ key: safe[1], label: `消除 8 个${foodOf(safe[1]).short}`, target: 8, kind: "food" }
];
return templates[taskIndex.value % templates.length];
});
const taskPercent = common_vendor.computed(() => Math.min(100, taskProgress.value / currentTask.value.target * 100));
const highCount = common_vendor.computed(() => board.value.filter((tile) => tile && foodOf(tile.key).high).length);
const boardRows = common_vendor.computed(() => Array.from({ length: ROWS }, (_, row) => board.value.slice(row * COLS, row * COLS + COLS).map((tile, col) => ({ ...tile, index: row * COLS + col }))));
const pendingWeeklyLearned = common_vendor.computed(() => Math.max(0, learned.value - confirmedSessionLearned.value));
const weeklyMyCount = common_vendor.computed(() => {
var _a;
return Number(((_a = platformLeaderboard.value.me) == null ? void 0 : _a.count) || 0) + pendingWeeklyLearned.value;
});
const weeklyRankRows = common_vendor.computed(() => {
const remote = Array.isArray(platformLeaderboard.value.players) ? platformLeaderboard.value.players : [];
const userData = common_vendor.index.getStorageSync("userData") || {};
const fallbackName = String(userData.nickname || "我");
const source = remote.length ? remote.map((player, index) => ({
...player,
isMe: !!(player.is_me || player.isMe),
avatarUrl: player.avatar || "",
avatar: String(player.name || "同行").slice(0, 1),
tone: index % 7 + 1,
count: player.is_me || player.isMe ? weeklyMyCount.value : Number(player.count || 0)
})) : [{
id: "local-me",
name: fallbackName,
avatar: fallbackName.slice(0, 1) || "我",
avatarUrl: userData.avatar || "",
count: weeklyMyCount.value,
tone: 1,
isMe: true
}];
return source.sort((a, b) => b.count - a.count || Number(b.best_score || 0) - Number(a.best_score || 0)).map((player, index) => ({ ...player, rank: index + 1 }));
});
const weeklyMyRank = common_vendor.computed(() => {
var _a;
return ((_a = weeklyRankRows.value.find((player) => player.isMe)) == null ? void 0 : _a.rank) || 1;
});
const weeklyRankRise = common_vendor.computed(() => 0);
const weeklyIsFirst = common_vendor.computed(() => weeklyMyRank.value === 1);
const weeklyDistance = common_vendor.computed(() => {
const mineIndex = weeklyRankRows.value.findIndex((player) => player.isMe);
if (mineIndex <= 0)
return 0;
return Math.max(1, weeklyRankRows.value[mineIndex - 1].count - weeklyMyCount.value + 1);
});
const currentCheer = common_vendor.computed(() => CHEER_MESSAGES[cheerIndex.value % CHEER_MESSAGES.length]);
const weeklyRangeLabel = common_vendor.computed(() => {
const start = platformLeaderboard.value.week_start;
const end = platformLeaderboard.value.week_end;
if (!start || !end)
return getWeekRangeLabel(/* @__PURE__ */ new Date());
return `${formatWeekDate(start)}—${formatWeekDate(end)}`;
});
const weeklySexLabel = common_vendor.computed(() => platformLeaderboard.value.sex_label || "同行");
const weeklyMemberCount = common_vendor.computed(() => Math.max(1, Number(platformLeaderboard.value.member_count || weeklyRankRows.value.length)));
const weeklyStatusLabel = common_vendor.computed(() => {
if (platformLoading.value)
return "连接中";
if (!platformConnected.value)
return "离线记录中";
if (platformSyncStatus.value === "pending" || platformSyncStatus.value === "syncing")
return "保存中";
return "真实周榜";
});
common_vendor.onShareAppMessage(() => {
recordPlatformShare();
return {
title: gameShareTitle(),
path: gameSharePath()
};
});
common_vendor.onShareTimeline(() => {
recordPlatformShare();
const inviteCode = String(platformLeaderboard.value.invite_code || "");
return {
title: gameShareTitle(),
query: inviteCode ? `from=share&invite_code=${encodeURIComponent(inviteCode)}` : "from=share"
};
});
function initNavLayout() {
var _a;
let paddingTop = 20;
let paddingRight = common_vendor.index.upx2px(28);
try {
const info = common_vendor.index.getSystemInfoSync();
paddingTop = Number(((_a = info.safeAreaInsets) == null ? void 0 : _a.top) || info.statusBarHeight) || 20;
const menu = typeof common_vendor.index.getMenuButtonBoundingClientRect === "function" ? common_vendor.index.getMenuButtonBoundingClientRect() : null;
if (menu && menu.width > 0) {
paddingTop = Math.max(paddingTop, Number(menu.top) || 0);
paddingRight = Math.max(
paddingRight,
(Number(info.windowWidth) || 0) - Number(menu.left) + common_vendor.index.upx2px(8)
);
}
} catch (_) {
}
navStyle.value = {
paddingTop: `${paddingTop}px`,
paddingRight: `${paddingRight}px`
};
}
common_vendor.onLoad((options = {}) => {
initNavLayout();
bestScore.value = Number(common_vendor.index.getStorageSync("tongji_endless_best") || 0);
incomingInviteCode = String(options.invite_code || "").trim();
startGame();
weeklyRankVisible.value = true;
connectPlatform(incomingInviteCode);
});
common_vendor.onShow(() => {
connectPlatform(incomingInviteCode).then((ok) => {
if (ok)
flushPlatformProgress();
});
});
common_vendor.onUnload(() => {
if (learned.value > 0 || gameOver.value) {
queuePlatformProgress(learned.value, score.value, gameOver.value);
flushPlatformProgress();
}
disposePlatform();
if (hintTimer)
clearTimeout(hintTimer);
if (jackpotTimer)
clearTimeout(jackpotTimer);
if (confettiTimer)
clearTimeout(confettiTimer);
if (stageTimer)
clearTimeout(stageTimer);
if (taskRewardTimer)
clearTimeout(taskRewardTimer);
});
function makeTile(key, special = "") {
return { id: tileId++, key, special };
}
function foodOf(key) {
return FOODS[key] || FOODS.corn;
}
function getWeekRangeLabel(now) {
const date = new Date(now);
const day = date.getDay() || 7;
const monday = new Date(date);
monday.setDate(date.getDate() - day + 1);
const sunday = new Date(monday);
sunday.setDate(monday.getDate() + 6);
const label = (value) => `${value.getMonth() + 1}月${value.getDate()}日`;
return `${label(monday)}—${label(sunday)}`;
}
function formatWeekDate(value) {
const parts = String(value).split("-").map(Number);
if (parts.length !== 3)
return String(value);
return `${parts[1]}月${parts[2]}日`;
}
function gameShareTitle() {
if (learned.value > 0) {
return `我在识糖小课堂认识了${learned.value}份食物,一起来挑战吧`;
}
return "识糖小课堂:边玩边认识常见食物";
}
function gameSharePath() {
const inviteCode = String(platformLeaderboard.value.invite_code || "");
const query = inviteCode ? `?from=share&invite_code=${encodeURIComponent(inviteCode)}` : "?from=share";
return `/tongji/endless-game/index${query}`;
}
function openWeeklyRank() {
primeSfxFromGesture();
if (gameOver.value)
return;
if (busy.value || reshuffling.value || stageVisible.value || camelJackpot.value.visible) {
statusText.value = "本次消除完成后即可查看同行榜";
return;
}
weeklyRankVisible.value = true;
syncAndRefreshPlatform(learned.value, score.value);
playSfx("select", 0.72);
}
function closeWeeklyRank() {
primeSfxFromGesture();
weeklyRankVisible.value = false;
playSfx("select", 0.86);
scheduleHint();
}
function cycleCheer() {
primeSfxFromGesture();
cheerIndex.value = (cheerIndex.value + 1) % CHEER_MESSAGES.length;
playSfx("select", 0.68);
}
function startGame() {
if (hintTimer)
clearTimeout(hintTimer);
beginPlatformSession();
themeIndex.value = 0;
board.value = buildPlayableBoard(openingKeysForTheme(currentTheme.value));
selected.value = -1;
score.value = 0;
learned.value = 0;
combo.value = 0;
feastMoves.value = 0;
camelCount.value = 3;
camelMode.value = false;
busy.value = false;
gameOver.value = false;
taskIndex.value = 0;
taskProgress.value = 0;
comboDryTurns.value = 0;
taskRewardVisible.value = false;
clearingIndices.value = [];
fireClearingIndices.value = [];
impactText.value = "";
camelJackpot.value.visible = false;
stageVisible.value = false;
reshuffleVisible.value = false;
reshuffleDone.value = false;
reshuffling.value = false;
confettiVisible.value = false;
pendingThemeMatchCount = 0;
taskRewardStatusUntil = 0;
resetDrag();
statusText.value = "交换相邻食物,三个相同即可消除";
scheduleHint();
}
function goBack() {
const pages = getCurrentPages();
if (pages.length > 1) {
common_vendor.index.navigateBack({
delta: 1,
fail: () => common_vendor.index.redirectTo({ url: "/tongji/pages/weekly" })
});
return;
}
common_vendor.index.redirectTo({ url: "/tongji/pages/weekly" });
}
function showRules() {
common_vendor.index.showModal({
title: "玩法说明",
content: `当前难度:${GAME_DIFFICULTY}/10
交换相邻食物,三个相同即可消除。四连、直线五连会生成火焰惊喜棋子;横向三个与竖向三个相交的 L/T 形五消,会生成范围爆破棋子,再次消除可清除周围九格。连消×2会计入连消小目标,连续普通消除后会出现一次连消机会;连消×3可获得额外惊喜奖励。完成小目标直接获得 1 罐驼乳粉;驼乳粉可替换本组指定稀食或饮品,三个驼乳连消后进入下一组食品。主动三消“高糖”食物代表吃掉,会结束本局;火焰特效波及高糖食物代表将它移出餐盘,不算吃掉。没有可走步时会自动洗牌。`,
showCancel: false,
confirmText: "知道了"
});
}
function toggleSfx() {
sfxEnabled.value = !sfxEnabled.value;
try {
common_vendor.index.setStorageSync("tongji_game_sfx_enabled", sfxEnabled.value ? "1" : "0");
} catch (_) {
}
if (sfxEnabled.value) {
primeSfxFromGesture();
playSfx("select", 0.8);
}
common_vendor.index.showToast({ title: sfxEnabled.value ? "音效已开启" : "音效已关闭", icon: "none" });
}
function primeSfxFromGesture() {
if (sfxGesturePrimed || !sfxEnabled.value)
return;
sfxGesturePrimed = true;
warmUpSfx();
}
function cellClasses(tile) {
const food = foodOf(tile.key);
return [
`is-${food.color}`,
`is-level-${food.level}`,
{
"is-selected": selected.value === tile.index,
"is-high": food.high,
"is-camel-target": camelMode.value && food.replaceable,
"is-clearing": clearingIndices.value.includes(tile.index),
"is-fire-clearing": fireClearingIndices.value.includes(tile.index),
"is-dragging": dragIndex.value === tile.index || dragTarget.value === tile.index
}
];
}
function cellMotionStyle(index) {
if (dragIndex.value === index)
return { transform: `translate(${dragX.value}px, ${dragY.value}px) scale(1.05)`, zIndex: 6 };
if (dragTarget.value === index)
return { transform: `translate(${-dragX.value * 0.72}px, ${-dragY.value * 0.72}px) scale(.96)`, zIndex: 4 };
return {};
}
function touchStart(event, index) {
var _a;
primeSfxFromGesture();
const point = (_a = event.touches) == null ? void 0 : _a[0];
if (!point)
return;
touch.x = point.clientX;
touch.y = point.clientY;
touch.index = index;
dragIndex.value = index;
dragTarget.value = -1;
dragX.value = 0;
dragY.value = 0;
}
function touchMove(event, index) {
var _a;
const point = (_a = event.touches) == null ? void 0 : _a[0];
if (!point || touch.index !== index || busy.value)
return;
const rawX = point.clientX - touch.x;
const rawY = point.clientY - touch.y;
const horizontal = Math.abs(rawX) > Math.abs(rawY);
dragX.value = horizontal ? Math.max(-82, Math.min(82, rawX)) : 0;
dragY.value = horizontal ? 0 : Math.max(-82, Math.min(82, rawY));
let target = index;
if (horizontal && Math.abs(rawX) > 10)
target += rawX > 0 ? 1 : -1;
if (!horizontal && Math.abs(rawY) > 10)
target += rawY > 0 ? COLS : -COLS;
dragTarget.value = target >= 0 && target < ROWS * COLS && isAdjacent(index, target) ? target : -1;
}
async function touchEnd(event, index) {
var _a;
const point = (_a = event.changedTouches) == null ? void 0 : _a[0];
if (!point || touch.index !== index) {
resetDrag();
return;
}
const dx = point.clientX - touch.x;
const dy = point.clientY - touch.y;
if (Math.max(Math.abs(dx), Math.abs(dy)) < 32) {
resetDrag();
return;
}
let target = index;
if (Math.abs(dx) > Math.abs(dy))
target += dx > 0 ? 1 : -1;
else
target += dy > 0 ? COLS : -COLS;
suppressTapUntil = Date.now() + 350;
if (target >= 0 && target < ROWS * COLS && isAdjacent(index, target)) {
const horizontal = Math.abs(dx) > Math.abs(dy);
dragX.value = horizontal ? dx > 0 ? 78 : -78 : 0;
dragY.value = horizontal ? 0 : dy > 0 ? 78 : -78;
dragTarget.value = target;
playSfx("swap", 0.85);
await wait(85);
resetDrag();
trySwap(index, target, true);
} else {
resetDrag();
playSfx("swapFail", 0.65);
}
}
function resetDrag() {
dragIndex.value = -1;
dragTarget.value = -1;
dragX.value = 0;
dragY.value = 0;
}
async function tapTile(index) {
primeSfxFromGesture();
if (Date.now() < suppressTapUntil)
return;
if (busy.value || gameOver.value)
return;
const tile = board.value[index];
if (camelMode.value) {
if (!foodOf(tile.key).replaceable) {
statusText.value = `本组驼乳粉用于替换${foodOf(currentTheme.value.danger).short}`;
return;
}
board.value[index] = makeTile("camel");
board.value = [...board.value];
camelCount.value -= 1;
camelMode.value = false;
selected.value = -1;
playSfxLayer("insulin", "sparkle", 55, 0.5);
vibrate("light");
const groups = findMatches(board.value);
if (groups.length) {
busy.value = true;
statusText.value = "驼乳棋子连起来了!";
await resolveMatches(groups, index);
busy.value = false;
finishTurn();
} else {
statusText.value = `已将${foodOf(currentTheme.value.danger).short}移出餐盘,凑齐三个驼乳可换主题`;
if (!checkBoardHealth())
scheduleHint();
}
return;
}
if (selected.value < 0) {
selected.value = index;
playSfx("select", 0.75);
statusText.value = "再选择一个相邻食物进行交换";
return;
}
if (selected.value === index) {
selected.value = -1;
playSfx("deselect", 0.65);
return;
}
if (!isAdjacent(selected.value, index)) {
selected.value = index;
statusText.value = "只能交换上下左右相邻的食物";
return;
}
const from = selected.value;
selected.value = -1;
trySwap(from, index);
}
function toggleCamel() {
primeSfxFromGesture();
if (busy.value || camelCount.value <= 0 || gameOver.value)
return;
camelMode.value = !camelMode.value;
selected.value = -1;
statusText.value = camelMode.value ? `请选择橙色边框的${foodOf(currentTheme.value.danger).short}进行替换` : "已取消使用驼乳粉";
}
function isAdjacent(a, b) {
const ar = Math.floor(a / COLS);
const ac = a % COLS;
const br = Math.floor(b / COLS);
const bc = b % COLS;
return Math.abs(ar - br) + Math.abs(ac - bc) === 1;
}
async function trySwap(a, b, soundPlayed = false) {
if (busy.value || gameOver.value)
return;
busy.value = true;
if (!soundPlayed)
playSfx("swap", 0.85);
swap(a, b);
await wait(130);
const groups = findMatches(board.value);
if (!groups.length) {
swap(a, b);
playSfx("swapFail", 0.8);
vibrate("light");
statusText.value = "这一步没有形成三连,再试一次";
busy.value = false;
if (!checkBoardHealth())
scheduleHint();
return;
}
const directIndices = new Set(groups.flatMap((group) => group.indices));
const highTile = [...directIndices].map((i) => board.value[i]).find((tile) => foodOf(tile.key).high);
if (highTile) {
playSfx("lose", 0.9);
vibrate("heavy");
endedFood.value = foodOf(highTile.key);
saveBest();
gameOver.value = true;
queuePlatformProgress(learned.value, score.value, true);
busy.value = false;
return;
}
await resolveMatches(groups, b);
busy.value = false;
if (!gameOver.value)
finishTurn();
}
function swap(a, b) {
const next = [...board.value];
[next[a], next[b]] = [next[b], next[a]];
board.value = next;
}
async function resolveMatches(initialGroups, movedIndex) {
const comboTaskAtStart = currentTask.value.kind === "combo";
let groups = initialGroups;
let wave = 0;
let turnConsumed = 0;
let madeSpecial = 0;
let firstWave = true;
while (groups.length && wave < MAX_CASCADE_WAVES) {
wave += 1;
combo.value = wave;
const remove = new Set(groups.flatMap((group) => group.indices));
let keeper = -1;
let keeperSpecial = "";
const bentMatch = firstWave ? findBentFiveMatch(groups) : null;
if (firstWave) {
const large = groups.find((group) => group.indices.length >= 4);
if (bentMatch) {
keeper = bentMatch.intersection;
keeperSpecial = "burst";
remove.delete(keeper);
madeSpecial = 1;
} else if (large) {
keeper = large.indices.includes(movedIndex) ? movedIndex : large.indices[0];
keeperSpecial = large.indices.length >= 5 ? "all" : large.direction;
remove.delete(keeper);
madeSpecial = 1;
}
}
fireClearingIndices.value = [];
const specialEffects = expandSpecials(remove);
const camelGroup = groups.filter((group) => group.indices.every((index) => {
var _a;
return ((_a = board.value[index]) == null ? void 0 : _a.key) === "camel";
})).sort((a, b) => b.indices.length - a.indices.length)[0];
const bentIndices = bentMatch ? [.../* @__PURE__ */ new Set([...bentMatch.row.indices, ...bentMatch.col.indices])] : [];
const bentCamelCount = bentIndices.length === 5 && bentIndices.every((index) => {
var _a;
return ((_a = board.value[index]) == null ? void 0 : _a.key) === "camel";
}) ? 5 : 0;
const camelMatchCount = Math.max(bentCamelCount, (camelGroup == null ? void 0 : camelGroup.indices.length) || 0);
if (camelMatchCount >= 3)
applyCamelJackpot(camelMatchCount, remove);
const removedTiles = [...remove].map((i) => board.value[i]).filter(Boolean);
const removedNormal = removedTiles.filter((tile) => {
const food = foodOf(tile.key);
return !food.high && !food.product;
});
const fireRemovedHigh = fireClearingIndices.value.some((index) => {
var _a;
return foodOf((_a = board.value[index]) == null ? void 0 : _a.key).high;
});
const largestMatch = Math.max(
...groups.map((group) => group.indices.length),
keeperSpecial === "burst" || specialEffects.hasBurst ? 5 : 3
);
clearingIndices.value = [...remove];
const triggeredSpecial = fireRemovedHigh ? "fire-high" : specialEffects.hasBurst ? "burst-triggered" : specialEffects.hasFire ? "fire-triggered" : keeperSpecial;
showImpact(largestMatch, wave, triggeredSpecial);
playMatchImpact(largestMatch, wave);
vibrate(largestMatch >= 5 || wave >= 3 ? "medium" : "light");
turnConsumed += removedNormal.length;
learned.value += removedNormal.length;
updateFoodTask(removedNormal);
const multiplier = feastMoves.value > 0 ? 2 : 1;
score.value += (removedNormal.length * 12 + Math.max(0, wave - 1) * 20 + madeSpecial * 35) * multiplier;
queuePlatformProgress(learned.value, score.value, false);
await wait(specialEffects.hasFire ? 330 : 210);
const next = [...board.value];
remove.forEach((index) => {
next[index] = null;
});
if (keeper >= 0 && next[keeper])
next[keeper] = { ...next[keeper], special: keeperSpecial };
board.value = next;
clearingIndices.value = [];
fireClearingIndices.value = [];
if (madeSpecial && currentTask.value.kind === "special")
advanceTask(madeSpecial);
await wait(70);
const shouldGuaranteeCombo = firstWave && comboTaskAtStart && comboDryTurns.value >= COMBO_PITY_TURNS - 1;
collapseAndFill(shouldGuaranteeCombo);
playDropSfx(remove.size);
await wait(190);
groups = findMatches(board.value);
const automaticHighMatch = groups.some((group) => group.indices.some((index) => {
var _a;
return foodOf((_a = board.value[index]) == null ? void 0 : _a.key).high;
}));
if (automaticHighMatch) {
board.value = makeStablePlayableBoard(board.value);
groups = [];
statusText.value = "自动掉落避开了高糖连消,棋盘已整理";
} else if (wave >= MAX_CASCADE_WAVES && groups.length) {
board.value = makeStablePlayableBoard(board.value);
groups = [];
statusText.value = "连消奖励已结算,棋盘已稳定";
}
firstWave = false;
madeSpecial = 0;
}
if (wave >= 3)
rewardMegaCombo(wave);
if (comboTaskAtStart) {
if (wave >= COMBO_TASK_WAVE) {
comboDryTurns.value = 0;
advanceTask(1);
} else {
comboDryTurns.value += 1;
}
}
if (turnConsumed > 0 && feastMoves.value > 0)
feastMoves.value -= 1;
if (pendingThemeMatchCount >= 3) {
const matchedCount = pendingThemeMatchCount;
pendingThemeMatchCount = 0;
const celebrationDelay = matchedCount >= 5 ? 2300 : matchedCount === 4 ? 2e3 : 1250;
await wait(celebrationDelay);
advanceFoodTheme();
}
}
function playMatchImpact(matchSize, wave) {
if (wave >= 3)
playSfxLayer("comboMega", "blast", 70, 0.55);
else if (wave === 2)
playSfxLayer("combo3", "pop", 60, 0.5);
else if (matchSize >= 5)
playSfxLayer("match5", "blast", 65, 0.55);
else if (matchSize >= 4)
playSfxLayer("match4", "sparkle", 55, 0.5);
else
playSfxLayer("match3", "pop", 45, 0.45);
}
function planCamelReward(requested) {
const available = Math.max(0, MAX_CAMEL_COUNT - camelCount.value);
const granted = Math.min(requested, available);
const overflow = requested - granted;
return { granted, overflow, overflowScore: overflow * OVERFLOW_CAMEL_SCORE };
}
function grantCamelReward(requested) {
const reward = planCamelReward(requested);
camelCount.value += reward.granted;
score.value += reward.overflowScore;
return reward;
}
function rewardMegaCombo(wave) {
score.value += MEGA_COMBO_SCORE;
const camelReward = grantCamelReward(1);
const rewardText = camelRewardText(camelReward);
statusText.value = `${wave} 连消惊喜:+${MEGA_COMBO_SCORE} 分${rewardText ? ` · ${rewardText}` : ""}`;
taskRewardStatusUntil = Date.now() + 1800;
if (camelReward.granted > 0)
showTaskCamelReward();
startConfetti();
playSfxLayer("comboMega", "fanfare", 100, 0.82);
vibrate("medium");
common_vendor.index.showToast({
title: `${wave} 连消惊喜:+${MEGA_COMBO_SCORE} 分${camelReward.granted > 0 ? "、驼乳粉 +1" : ""}`,
icon: "none"
});
}
function camelRewardText(reward) {
const parts = [];
if (reward.granted > 0)
parts.push(`驼乳粉 ×${reward.granted}`);
if (reward.overflow > 0)
parts.push(`满仓多余奖励换 ${reward.overflowScore} 分`);
return parts.join(" · ");
}
function applyCamelJackpot(count, remove) {
pendingThemeMatchCount = Math.max(pendingThemeMatchCount, count);
if (count >= 5) {
score.value += 1554;
const camelReward = grantCamelReward(4);
feastMoves.value = Math.max(feastMoves.value, 6);
board.value.forEach((tile, index) => {
if (foodOf(tile == null ? void 0 : tile.key).high)
remove.add(index);
});
showCamelJackpot(5, "驼乳五连双倍大奖", `+1554 分 · ${camelRewardText(camelReward)} · 双倍得分 ×6`);
startConfetti();
playJackpotSequence();
return;
}
if (count === 4) {
score.value += 288;
const camelReward = grantCamelReward(1);
const highIndex = board.value.findIndex((tile, index) => foodOf(tile == null ? void 0 : tile.key).high && !remove.has(index));
if (highIndex >= 0)
remove.add(highIndex);
showCamelJackpot(4, "驼乳超级四连", `+288 分 · ${camelRewardText(camelReward)} · 移出一份高糖食物`);
playSfxLayer("match5", "fanfare", 90, 0.7);
setTimeout(() => playSfx("tap", 1.16, 0.58), 260);
vibrate("medium");
return;
}
score.value += 77;
showCamelJackpot(3, "驼乳幸运三连", "+77 分 · 轻量连消奖励");
playSfxLayer("match3", "sparkle", 44, 0.38);
vibrate("light");
}
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));
}
taskIndex.value = 0;
taskProgress.value = 0;
comboDryTurns.value = 0;
selected.value = -1;
camelMode.value = false;
endedFood.value = foodOf(nextTheme.danger);
stageMeta.value = {
number: themeIndex.value + 1,
title: nextTheme.title,
foods: [...nextTheme.safe, nextTheme.danger].map((key) => ({ key, ...foodOf(key) }))
};
stageVisible.value = true;
statusText.value = `已进入${nextTheme.title},橙框${foodOf(nextTheme.danger).short}可替换`;
playSfxLayer("fanfare", "sparkle", 100, 0.65);
if (stageTimer)
clearTimeout(stageTimer);
stageTimer = setTimeout(() => {
stageVisible.value = false;
}, 1800);
}
function showCamelJackpot(count, title, reward) {
if (jackpotTimer)
clearTimeout(jackpotTimer);
camelJackpot.value = {
visible: true,
count,
kicker: count >= 5 ? "JACKPOT · 777" : count === 4 ? "ONE MORE · 再来一罐" : "LUCKY · 小惊喜",
title,
reward
};
const duration = count >= 5 ? 2200 : count === 4 ? 1900 : 1150;
jackpotTimer = setTimeout(() => {
camelJackpot.value.visible = false;
}, duration);
}
function startConfetti() {
if (confettiTimer)
clearTimeout(confettiTimer);
confettiVisible.value = false;
setTimeout(() => {
confettiVisible.value = true;
}, 20);
confettiTimer = setTimeout(() => {
confettiVisible.value = false;
}, 6e3);
}
function playJackpotSequence() {
playSfx("tap", 0.9, 0.82);
setTimeout(() => playSfx("tap", 1, 1.05), 150);
setTimeout(() => playSfx("tap", 1.08, 1.35), 300);
setTimeout(() => playSfxLayer("comboMega", "fanfare", 100, 0.85), 430);
setTimeout(() => playSfxLayer("blast", "sparkle", 90, 0.75), 690);
vibrate("medium");
setTimeout(() => vibrate("medium"), 170);
setTimeout(() => vibrate("heavy"), 430);
}
function showImpact(matchSize, wave, specialType = "") {
impactLevel.value = Math.min(3, wave >= 3 ? 3 : matchSize >= 5 ? 3 : matchSize >= 4 ? 2 : 1);
if (specialType === "fire-high")
impactText.value = "火焰移出高糖!";
else if (wave >= 3)
impactText.value = `${wave} 连消!`;
else if (specialType === "fire-triggered")
impactText.value = "火焰清除!";
else if (specialType === "burst-triggered")
impactText.value = "范围爆破!";
else if (specialType === "burst")
impactText.value = "L形惊喜!";
else if (matchSize >= 5)
impactText.value = "五连惊喜!";
else if (matchSize >= 4)
impactText.value = "四连!";
else
impactText.value = "消除!";
setTimeout(() => {
impactText.value = "";
}, 520);
}
function vibrate(type = "light") {
try {
common_vendor.index.vibrateShort({ type });
} catch (_) {
}
}
function expandSpecials(remove) {
const queue = [...remove];
const seen = /* @__PURE__ */ new Set();
const fire = /* @__PURE__ */ new Set();
let hasBurst = false;
let hasFire = false;
while (queue.length) {
const index = queue.shift();
if (seen.has(index))
continue;
seen.add(index);
const tile = board.value[index];
if (!(tile == null ? void 0 : tile.special))
continue;
hasFire = true;
const extra = [];
if (tile.special === "row") {
const row = Math.floor(index / COLS);
for (let col = 0; col < COLS; col += 1)
extra.push(row * COLS + col);
} else if (tile.special === "col") {
const col = index % COLS;
for (let row = 0; row < ROWS; row += 1)
extra.push(row * COLS + col);
} else if (tile.special === "burst") {
hasBurst = true;
const centerRow = Math.floor(index / COLS);
const centerCol = index % COLS;
for (let row = Math.max(0, centerRow - 1); row <= Math.min(ROWS - 1, centerRow + 1); row += 1) {
for (let col = Math.max(0, centerCol - 1); col <= Math.min(COLS - 1, centerCol + 1); col += 1) {
extra.push(row * COLS + col);
}
}
} else {
board.value.forEach((item, i) => {
if ((item == null ? void 0 : item.key) === tile.key)
extra.push(i);
});
}
extra.forEach((i) => fire.add(i));
extra.forEach((i) => {
if (!remove.has(i)) {
remove.add(i);
queue.push(i);
}
});
}
fireClearingIndices.value = [...fire];
return { hasBurst, hasFire };
}
function collapseAndFill(forceSafeMatch = false) {
const next = Array(ROWS * COLS).fill(null);
for (let col = 0; col < COLS; col += 1) {
const existing = [];
for (let row = ROWS - 1; row >= 0; row -= 1) {
const tile = board.value[row * COLS + col];
if (tile)
existing.push(tile);
}
existing.forEach((tile, offset) => {
next[(ROWS - 1 - offset) * COLS + col] = tile;
});
}
const forcedIndices = forceSafeMatch ? findForcedDropMatch(next) : [];
const forcedSet = new Set(forcedIndices);
const forcedKey = forcedIndices.length ? pickForcedDropKey(next, forcedIndices) : "";
for (let row = 0; row < ROWS; row += 1) {
for (let col = 0; col < COLS; col += 1) {
const index = row * COLS + col;
if (!next[index]) {
const key = forcedSet.has(index) ? forcedKey : pickDropFoodKey(next, index, col);
next[index] = makeTile(key);
}
}
}
board.value = next;
return forcedIndices.length === 3;
}
function findForcedDropMatch(snapshot) {
const empty = new Set(snapshot.map((tile, index) => tile ? -1 : index).filter((index) => index >= 0));
for (let row = 0; row < ROWS; row += 1) {
for (let col = 0; col <= COLS - 3; col += 1) {
const indices = [0, 1, 2].map((offset) => row * COLS + col + offset);
if (indices.every((index) => empty.has(index)))
return indices;
}
}
for (let col = 0; col < COLS; col += 1) {
for (let row = 0; row <= ROWS - 3; row += 1) {
const indices = [0, 1, 2].map((offset) => (row + offset) * COLS + col);
if (indices.every((index) => empty.has(index)))
return indices;
}
}
return [];
}
function pickForcedDropKey(snapshot, indices) {
var _a;
return ((_a = currentTheme.value.safe.map((key) => ({
key,
nearby: snapshot.reduce((count, tile, index) => (tile == null ? void 0 : tile.key) === key && indices.some((target) => isAdjacent(target, index)) ? count + 1 : count, 0)
})).sort((a, b) => a.nearby - b.nearby)[0]) == null ? void 0 : _a.key) || currentTheme.value.safe[0];
}
function pickDropFoodKey(snapshot, index, col) {
for (let attempt = 0; attempt < 16; attempt += 1) {
const key = randomFoodKey(snapshot, col);
if (!wouldCreateMatchAt(snapshot, index, key))
return key;
}
const fallback = [...currentTheme.value.safe, currentTheme.value.danger].find((key) => !wouldCreateMatchAt(snapshot, index, key));
return fallback || currentTheme.value.safe[(index + col) % currentTheme.value.safe.length];
}
function wouldCreateMatchAt(source, index, key) {
const row = Math.floor(index / COLS);
const col = index % COLS;
const keyAt = (target) => {
var _a;
return target === index ? key : (_a = source[target]) == null ? void 0 : _a.key;
};
let horizontal = 1;
for (let c = col - 1; c >= 0 && keyAt(row * COLS + c) === key; c -= 1)
horizontal += 1;
for (let c = col + 1; c < COLS && keyAt(row * COLS + c) === key; c += 1)
horizontal += 1;
if (horizontal >= 3)
return true;
let vertical = 1;
for (let r = row - 1; r >= 0 && keyAt(r * COLS + col) === key; r -= 1)
vertical += 1;
for (let r = row + 1; r < ROWS && keyAt(r * COLS + col) === key; r += 1)
vertical += 1;
return vertical >= 3;
}
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;
if (col >= 0 && Math.random() < COLUMN_GROUP_BIAS)
return keys[col % keys.length];
return keys[Math.floor(Math.random() * keys.length)];
}
function findBentFiveMatch(groups) {
const rows = groups.filter((group) => group.direction === "row" && group.indices.length === 3);
const cols = groups.filter((group) => group.direction === "col" && group.indices.length === 3);
for (const row of rows) {
for (const col of cols) {
const intersection = row.indices.find((index) => col.indices.includes(index));
if (intersection !== void 0)
return { intersection, row, col };
}
}
return null;
}
function findMatches(source) {
var _a, _b, _c, _d;
const groups = [];
for (let row = 0; row < ROWS; row += 1) {
let start = 0;
while (start < COLS) {
let end = start + 1;
while (end < COLS && ((_a = source[row * COLS + end]) == null ? void 0 : _a.key) === ((_b = source[row * COLS + start]) == null ? void 0 : _b.key))
end += 1;
if (source[row * COLS + start] && end - start >= 3) {
groups.push({ direction: "row", indices: Array.from({ length: end - start }, (_, n) => row * COLS + start + n) });
}
start = end;
}
}
for (let col = 0; col < COLS; col += 1) {
let start = 0;
while (start < ROWS) {
let end = start + 1;
while (end < ROWS && ((_c = source[end * COLS + col]) == null ? void 0 : _c.key) === ((_d = source[start * COLS + col]) == null ? void 0 : _d.key))
end += 1;
if (source[start * COLS + col] && end - start >= 3) {
groups.push({ direction: "col", indices: Array.from({ length: end - start }, (_, n) => (start + n) * COLS + col) });
}
start = end;
}
}
return groups;
}
function updateFoodTask(tiles) {
if (currentTask.value.kind !== "food")
return;
advanceTask(tiles.filter((tile) => tile.key === currentTask.value.key).length);
}
function advanceTask(amount) {
if (!amount)
return;
taskProgress.value += amount;
if (taskProgress.value < currentTask.value.target)
return;
score.value += 120;
const camelReward = grantCamelReward(1);
if (camelReward.granted > 0) {
showTaskCamelReward();
statusText.value = "小目标完成,驼乳粉已放入道具栏";
} else {
statusText.value = `驼乳粉已满,多余奖励换 ${camelReward.overflowScore} 分`;
}
common_vendor.index.showToast({
title: camelReward.granted > 0 ? "小目标完成:驼乳粉 +1" : `驼乳粉已满:奖励换 ${camelReward.overflowScore} 分`,
icon: "none"
});
taskIndex.value = (taskIndex.value + 1) % 4;
taskProgress.value = 0;
comboDryTurns.value = 0;
}
function showTaskCamelReward() {
if (taskRewardTimer)
clearTimeout(taskRewardTimer);
taskRewardKey.value += 1;
taskRewardVisible.value = true;
taskRewardStatusUntil = Date.now() + 1600;
playSfxLayer("boost", "sparkle", 75, 0.55);
vibrate("light");
taskRewardTimer = setTimeout(() => {
taskRewardVisible.value = false;
}, 1150);
}
function finishTurn() {
saveBest();
if (checkBoardHealth())
return;
if (stageVisible.value) {
scheduleHint();
return;
}
if (Date.now() < taskRewardStatusUntil) {
scheduleHint();
return;
}
statusText.value = combo.value >= 2 ? `漂亮!完成 ${combo.value} 连消` : "食物已从上方补充,继续挑战";
scheduleHint();
}
function checkBoardHealth() {
if (hasSafeMove() || reshuffling.value)
return false;
performReshuffle();
return true;
}
function hasSafeMove() {
return hasSafeMoveIn(board.value);
}
function hasSafeMoveIn(source) {
return Boolean(findSafeMoveIn(source));
}
function findSafeMoveIn(source) {
for (let i = 0; i < source.length; i += 1) {
const candidates = [];
if (i % COLS < COLS - 1)
candidates.push(i + 1);
if (i + COLS < source.length)
candidates.push(i + COLS);
for (const j of candidates) {
const copy = source.map((tile) => ({ ...tile }));
[copy[i], copy[j]] = [copy[j], copy[i]];
const groups = findMatches(copy);
if (groups.length && !groups.flatMap((group) => group.indices).some((index) => foodOf(copy[index].key).high)) {
return { from: i, to: j };
}
}
}
return null;
}
async function performReshuffle() {
if (reshuffling.value || gameOver.value)
return;
reshuffling.value = true;
busy.value = true;
reshuffleDone.value = false;
reshuffleVisible.value = true;
selected.value = -1;
camelMode.value = false;
statusText.value = "没有可消除组合,正在重新洗牌";
playSfx("reshuffle", 0.9);
vibrate("medium");
await wait(760);
const source = board.value.map((tile) => ({ ...tile }));
let candidate = null;
for (let attempt = 0; attempt < 240; attempt += 1) {
const shuffled = shuffleTiles(source);
if (findMatches(shuffled).length)
continue;
if (!hasSafeMoveIn(shuffled))
continue;
candidate = shuffled;
break;
}
board.value = candidate || buildGuaranteedBoard();
reshuffleDone.value = true;
statusText.value = "洗牌完成,新的消除机会已经出现";
playSfxLayer("drop", "sparkle", 70, 0.55);
vibrate("light");
await wait(520);
reshuffleVisible.value = false;
reshuffling.value = false;
busy.value = false;
scheduleHint();
}
function shuffleTiles(source) {
const result = source.map((tile) => ({ ...tile }));
for (let i = result.length - 1; i > 0; i -= 1) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}
function buildGuaranteedBoard() {
return buildPlayableBoard(openingKeysForTheme(currentTheme.value));
}
function openingKeysForTheme(theme) {
const [a, b, c, d] = theme.safe;
const danger = theme.danger;
return [
a,
b,
c,
d,
b,
c,
d,
a,
c,
d,
a,
danger,
d,
a,
danger,
b,
a,
b,
c,
d
];
}
function buildPlayableBoard(keys) {
let candidate = keys.map((key) => makeTile(key));
for (let attempt = 0; attempt < 240; attempt += 1) {
if (!findMatches(candidate).length && hasSafeMoveIn(candidate))
return candidate;
candidate = shuffleTiles(candidate);
}
return forcedPlayableKeysForTheme(currentTheme.value).map((key) => makeTile(key));
}
function forcedPlayableKeysForTheme(theme) {
const [a, b, c, d] = theme.safe;
const danger = theme.danger;
return [
d,
a,
danger,
c,
d,
c,
b,
danger,
c,
a,
b,
a,
d,
d,
a,
a,
b,
d,
c,
b
];
}
function makeStablePlayableBoard(source) {
const tiles = source.filter(Boolean).map((tile) => ({ ...tile }));
if (tiles.length === ROWS * COLS) {
for (let attempt = 0; attempt < 300; attempt += 1) {
const candidate = shuffleTiles(tiles);
if (!findMatches(candidate).length && hasSafeMoveIn(candidate))
return candidate;
}
}
return buildGuaranteedBoard();
}
function safeMoveHint(move) {
var _a, _b;
if (!move)
return "";
const from = foodOf((_a = board.value[move.from]) == null ? void 0 : _a.key).short;
const to = foodOf((_b = board.value[move.to]) == null ? void 0 : _b.key).short;
const direction = move.to === move.from + 1 ? "向右" : "向下";
return `提示:把${from}${direction}与${to}交换`;
}
function scheduleHint() {
if (hintTimer)
clearTimeout(hintTimer);
hintTimer = setTimeout(() => {
if (busy.value || gameOver.value || stageVisible.value)
return;
const move = findSafeMoveIn(board.value);
if (!move) {
performReshuffle();
return;
}
statusText.value = safeMoveHint(move);
}, 5e3);
}
function saveBest() {
if (score.value <= bestScore.value)
return;
bestScore.value = score.value;
common_vendor.index.setStorageSync("tongji_endless_best", bestScore.value);
}
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
return (_ctx, _cache) => {
return common_vendor.e({
a: confettiVisible.value
}, confettiVisible.value ? {
b: common_vendor.f(common_vendor.unref(confettiPieces), (piece, k0, i0) => {
return {
a: common_vendor.t(piece.emoji),
b: piece.id,
c: piece.emoji ? 1 : "",
d: common_vendor.s(piece.style)
};
})
} : {}, {
c: common_vendor.p({
name: "chevron-left",
size: "md",
color: "#155e4b"
}),
d: common_vendor.o(goBack, "cd"),
e: common_vendor.t(themeIndex.value + 1),
f: common_vendor.t(currentTheme.value.title),
g: common_vendor.p({
name: "volume",
size: "sm",
color: common_vendor.unref(sfxEnabled) ? "#155e4b" : "#94a3b8"
}),
h: common_vendor.o(toggleSfx, "35"),
i: common_vendor.p({
name: "info",
size: "sm",
color: "#155e4b"
}),
j: common_vendor.o(showRules, "e4"),
k: common_vendor.s(navStyle.value),
l: common_vendor.t(learned.value),
m: common_vendor.t(score.value),
n: common_vendor.t(weeklyMyRank.value),
o: busy.value || reshuffling.value || stageVisible.value || camelJackpot.value.visible ? 1 : "",
p: common_vendor.o(openWeeklyRank, "7c"),
q: common_vendor.p({
name: "trophy",
size: "sm",
color: "#8a5a10"
}),
r: common_vendor.t(currentTask.value.label),
s: common_vendor.t(Math.min(taskProgress.value, currentTask.value.target)),
t: common_vendor.t(currentTask.value.target),
v: taskPercent.value + "%",
w: taskRewardVisible.value
}, taskRewardVisible.value ? {
x: foodOf("camel").img,
y: taskRewardKey.value
} : {}, {
z: common_vendor.t(Math.max(combo.value, 1)),
A: combo.value >= 3 ? 1 : "",
B: feastMoves.value > 0
}, feastMoves.value > 0 ? {
C: common_vendor.t(feastMoves.value)
} : {}, {
D: common_vendor.t(highCount.value),
E: common_vendor.f(3, (n, k0, i0) => {
return {
a: n,
b: n <= highCount.value ? 1 : ""
};
}),
F: common_vendor.f(boardRows.value, (row, rowIndex, i0) => {
return {
a: common_vendor.f(row, (tile, k1, i1) => {
return common_vendor.e({
a: tile.special
}, tile.special ? common_vendor.e({
b: tile.special === "row" || tile.special === "col"
}, tile.special === "row" || tile.special === "col" ? {} : tile.special === "burst" ? {} : {}, {
c: tile.special === "burst",
d: common_vendor.n(`is-${tile.special}`)
}) : {}, {
e: fireClearingIndices.value.includes(tile.index)
}, fireClearingIndices.value.includes(tile.index) ? {} : {}, {
f: foodOf(tile.key).img,
g: common_vendor.t(foodOf(tile.key).short),
h: common_vendor.t(foodOf(tile.key).levelLabel || "低糖"),
i: common_vendor.n(`is-${foodOf(tile.key).level || "low"}`),
j: camelMode.value && foodOf(tile.key).replaceable
}, camelMode.value && foodOf(tile.key).replaceable ? {} : {}, {
k: tile.id,
l: common_vendor.n(cellClasses(tile)),
m: common_vendor.s(cellMotionStyle(tile.index)),
n: common_vendor.o(($event) => tapTile(tile.index), tile.id),
o: common_vendor.o(($event) => touchStart($event, tile.index), tile.id),
p: common_vendor.o(($event) => touchMove($event, tile.index), tile.id),
q: common_vendor.o(($event) => touchEnd($event, tile.index), tile.id)
});
}),
b: rowIndex
};
}),
G: busy.value ? 1 : "",
H: impactText.value
}, impactText.value ? {
I: common_vendor.t(impactText.value),
J: common_vendor.n(`is-level-${impactLevel.value}`)
} : {}, {
K: camelJackpot.value.visible
}, camelJackpot.value.visible ? common_vendor.e({
L: common_vendor.t(camelJackpot.value.kicker),
M: camelJackpot.value.count === 3
}, camelJackpot.value.count === 3 ? {
N: common_vendor.f(3, (n, k0, i0) => {
return {
a: n
};
}),
O: foodOf("camel").img
} : camelJackpot.value.count === 4 ? {
Q: common_vendor.f(5, (n, k0, i0) => {
return common_vendor.e({
a: n < 5
}, n < 5 ? {
b: foodOf("camel").img
} : {}, {
c: n,
d: n === 5 ? 1 : ""
});
})
} : {
R: common_vendor.f(5, (n, k0, i0) => {
return {
a: n
};
}),
S: foodOf("camel").img,
T: common_vendor.f(3, (n, k0, i0) => {
return {
a: n
};
})
}, {
P: camelJackpot.value.count === 4,
U: common_vendor.t(camelJackpot.value.title),
V: camelJackpot.value.count >= 5
}, camelJackpot.value.count >= 5 ? {} : {}, {
W: common_vendor.t(camelJackpot.value.reward),
X: common_vendor.n(`is-match-${camelJackpot.value.count}`)
}) : {}, {
Y: common_vendor.p({
name: "bulb",
size: "sm",
color: "#9a6718"
}),
Z: common_vendor.t(statusText.value),
aa: foodOf("camel").img,
ab: common_vendor.p({
name: "refresh",
size: "sm",
color: "#ffffff"
}),
ac: common_vendor.t(foodOf(currentTheme.value.danger).short),
ad: common_vendor.t(camelCount.value),
ae: camelMode.value ? 1 : "",
af: camelCount.value <= 0 || busy.value ? 1 : "",
ag: common_vendor.o(toggleCamel, "1c"),
ah: weeklyRankVisible.value
}, weeklyRankVisible.value ? common_vendor.e({
ai: common_vendor.t(weeklyStatusLabel.value),
aj: !common_vendor.unref(platformConnected) ? 1 : "",
ak: common_vendor.t(weeklyRangeLabel.value),
al: common_vendor.t(weeklySexLabel.value),
am: common_vendor.t(weeklyMemberCount.value),
an: common_vendor.t(weeklyMyRank.value),
ao: common_vendor.t(weeklyMyCount.value),
ap: weeklyRankRise.value > 0
}, weeklyRankRise.value > 0 ? {
aq: common_vendor.t(weeklyMyRank.value),
ar: common_vendor.t(weeklyRankRise.value)
} : {
as: common_vendor.t(weeklyMyRank.value)
}, {
at: weeklyIsFirst.value
}, weeklyIsFirst.value ? {} : {
av: common_vendor.t(weeklyDistance.value)
}, {
aw: common_vendor.f(weeklyRankRows.value, (player, k0, i0) => {
return common_vendor.e({
a: common_vendor.t(player.rank),
b: player.avatarUrl
}, player.avatarUrl ? {
c: player.avatarUrl
} : {
d: common_vendor.t(player.avatar)
}, {
e: common_vendor.n(`is-tone-${player.tone}`),
f: common_vendor.t(player.name),
g: player.isMe
}, player.isMe ? {} : {}, {
h: common_vendor.t(player.count),
i: player.id,
j: player.isMe ? 1 : ""
});
}),
ax: common_vendor.t(currentCheer.value.source),
ay: common_vendor.o(cycleCheer, "02"),
az: common_vendor.t(currentCheer.value.text),
aA: currentCheer.value.userGenerated
}, currentCheer.value.userGenerated ? {} : {}, {
aB: common_vendor.n(`is-${currentCheer.value.tone}`),
aC: common_vendor.o(closeWeeklyRank, "f2"),
aD: common_vendor.o(() => {
}, "9c")
}) : {}, {
aE: reshuffleVisible.value
}, reshuffleVisible.value ? {
aF: common_vendor.p({
name: "refresh",
size: "lg",
color: "#ffffff"
}),
aG: common_vendor.t(reshuffleDone.value ? "洗牌完成" : "没有可消除组合"),
aH: common_vendor.t(reshuffleDone.value ? "新的消除机会已经出现" : "正在为您重新洗牌,请稍候"),
aI: common_vendor.o(() => {
}, "40")
} : {}, {
aJ: stageVisible.value
}, stageVisible.value ? {
aK: common_vendor.t(stageMeta.value.number),
aL: common_vendor.t(stageMeta.value.title),
aM: common_vendor.f(stageMeta.value.foods, (food, k0, i0) => {
return {
a: food.img,
b: common_vendor.t(food.short),
c: food.key,
d: food.high ? 1 : ""
};
}),
aN: common_vendor.o(() => {
}, "01")
} : {}, {
aO: gameOver.value
}, gameOver.value ? {
aP: common_vendor.t(endedFood.value.name),
aQ: common_vendor.t(endedFood.value.tip),
aR: common_vendor.t(score.value),
aS: common_vendor.t(bestScore.value),
aT: common_vendor.o(startGame, "33"),
aU: common_vendor.o(goBack, "98"),
aV: common_vendor.o(() => {
}, "72")
} : {}, {
aW: common_vendor.o(primeSfxFromGesture, "77"),
aX: common_vendor.o(primeSfxFromGesture, "a4")
});
};
}
};
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-ace1d107"]]);
_sfc_main.__runtimeHooks = 6;
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/endless-game/index.js.map