更新
This commit is contained in:
@@ -52,14 +52,14 @@
|
|||||||
"mp-weixin" : {
|
"mp-weixin" : {
|
||||||
"appid" : "wx79b9a0bfbfe7cbcd",
|
"appid" : "wx79b9a0bfbfe7cbcd",
|
||||||
"setting" : {
|
"setting" : {
|
||||||
"urlCheck" : false
|
"urlCheck" : false,
|
||||||
|
"minified" : true
|
||||||
},
|
},
|
||||||
"optimization" : {
|
"optimization" : {
|
||||||
"subPackages" : true
|
"subPackages" : true
|
||||||
},
|
},
|
||||||
"usingComponents" : true,
|
"usingComponents" : true,
|
||||||
"requiredBackgroundModes" : ["audio"],
|
"requiredBackgroundModes" : [ "audio" ],
|
||||||
|
|
||||||
"plugins" : {
|
"plugins" : {
|
||||||
"WechatSI" : {
|
"WechatSI" : {
|
||||||
"version" : "0.3.5",
|
"version" : "0.3.5",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 45 KiB |
@@ -923,6 +923,9 @@ function initBoard() {
|
|||||||
commitBoard(next)
|
commitBoard(next)
|
||||||
// 注入本关唯一高糖(解锁前固定 2 个,无法三连)
|
// 注入本关唯一高糖(解锁前固定 2 个,无法三连)
|
||||||
ensureHighPresence()
|
ensureHighPresence()
|
||||||
|
// 注入高糖可能破坏“有解”性:开局静默兜底,避免出现整盘无法消除的死局
|
||||||
|
// (仅打乱类型、保持高糖数量不变)
|
||||||
|
if (!boardHasSolution()) shuffleTypesUntilPlayable()
|
||||||
startHintTimer()
|
startHintTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1423,6 +1426,38 @@ function startHintTimer() {
|
|||||||
hintTimer = setTimeout(showOperationHint, HINT_DELAY_MS)
|
hintTimer = setTimeout(showOperationHint, HINT_DELAY_MS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 就地打乱棋盘上所有方块的“食物类型”(位置/对象不变),直到棋盘“有解”:
|
||||||
|
* 无现成消除组 + 至少存在一个可用移动。仅交换 type,故高糖数量等多重集不变。
|
||||||
|
* 不带任何 UI 反馈,供初始化等静默场景使用。返回是否成功。
|
||||||
|
*/
|
||||||
|
function shuffleTypesUntilPlayable(maxRetry = 80) {
|
||||||
|
const tiles = []
|
||||||
|
for (let r = 0; r < ROWS; r++) {
|
||||||
|
for (let c = 0; c < COLS; c++) {
|
||||||
|
const t = board.value[r]?.[c]
|
||||||
|
if (t && !t.isRemoved) tiles.push(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tiles.length < 3) return false
|
||||||
|
for (let attempt = 0; attempt < maxRetry; attempt++) {
|
||||||
|
for (let i = tiles.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[tiles[i].type, tiles[j].type] = [tiles[j].type, tiles[i].type]
|
||||||
|
}
|
||||||
|
if (findMatches().length === 0 && findPossibleMoves().length > 0) {
|
||||||
|
bumpLayout()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前棋盘是否“有解”(无现成消除组且至少有一个可用移动) */
|
||||||
|
function boardHasSolution() {
|
||||||
|
return findMatches().length === 0 && findPossibleMoves().length > 0
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 棋盘死锁自动被测与修复。
|
* 棋盘死锁自动被测与修复。
|
||||||
* 使用 Fisher-Yates 打乱食物类型,最多重试 maxRetry 次。
|
* 使用 Fisher-Yates 打乱食物类型,最多重试 maxRetry 次。
|
||||||
@@ -1431,7 +1466,7 @@ function startHintTimer() {
|
|||||||
async function reshuffleIfStuck() {
|
async function reshuffleIfStuck() {
|
||||||
if (gamePhase.value !== 'playing') return
|
if (gamePhase.value !== 'playing') return
|
||||||
// 已有解(无现成消除组 + 存在可用移动)则无需重排
|
// 已有解(无现成消除组 + 存在可用移动)则无需重排
|
||||||
if (findMatches().length === 0 && findPossibleMoves().length > 0) return
|
if (boardHasSolution()) return
|
||||||
|
|
||||||
// 给玩家反馈
|
// 给玩家反馈
|
||||||
playSfx('reshuffle', 0.75)
|
playSfx('reshuffle', 0.75)
|
||||||
@@ -1444,32 +1479,8 @@ async function reshuffleIfStuck() {
|
|||||||
setTimeout(() => { floatingVals.value = floatingVals.value.filter(v => v.id !== id) }, 1400)
|
setTimeout(() => { floatingVals.value = floatingVals.value.filter(v => v.id !== id) }, 1400)
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
// 收集棋盘上所有有效方块(保持 tile 对象与位置不变,仅打乱其食物类型)
|
// 就地打乱类型直到有解;失败则回退到全棋盘重新生成
|
||||||
const tiles = []
|
if (!shuffleTypesUntilPlayable(60)) {
|
||||||
for (let r = 0; r < ROWS; r++) {
|
|
||||||
for (let c = 0; c < COLS; c++) {
|
|
||||||
const t = board.value[r]?.[c]
|
|
||||||
if (t && !t.isRemoved) tiles.push(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 反复打乱类型并就地试写,直到“有解且无现成三连”;
|
|
||||||
// tiles 已是 board.value 中的同一批对象,故 findMatches/findPossibleMoves 可直接复用
|
|
||||||
const MAX_RETRY = 60
|
|
||||||
let ok = false
|
|
||||||
for (let attempt = 0; attempt < MAX_RETRY; attempt++) {
|
|
||||||
for (let i = tiles.length - 1; i > 0; i--) {
|
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
|
||||||
[tiles[i].type, tiles[j].type] = [tiles[j].type, tiles[i].type]
|
|
||||||
}
|
|
||||||
if (findMatches().length === 0 && findPossibleMoves().length > 0) {
|
|
||||||
ok = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
// 就地洗牌无法洗出可玩布局,回退到全棋盘重新生成
|
|
||||||
initBoard()
|
initBoard()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1754,10 +1765,8 @@ async function processSwap(t1, t2, opts = {}) {
|
|||||||
isProcessing.value = false
|
isProcessing.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 按解锁进度维持高糖数量(到第 7 步起补到可消除的 3 个)
|
// 落子算法已保证“下落即有可消除走法”;此处仅作极端兜底(静默、无横幅)
|
||||||
ensureHighPresence()
|
if (!boardHasSolution()) shuffleTypesUntilPlayable()
|
||||||
// 所有连消结束后,检查棋盘是否死锁并自动重排
|
|
||||||
await reshuffleIfStuck()
|
|
||||||
} else {
|
} else {
|
||||||
await animateSwapOffset(t1, t2)
|
await animateSwapOffset(t1, t2)
|
||||||
swapTiles(t1, t2)
|
swapTiles(t1, t2)
|
||||||
@@ -1831,9 +1840,134 @@ async function clearAndRefill() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ——— 落子前在 next 网格上做的辅助判定(不触动可见的 board.value) ——— */
|
||||||
|
function gridIsHigh(grid, r, c) {
|
||||||
|
const t = grid[r]?.[c]
|
||||||
|
return t && !t.isRemoved && t.type.gi === 'high'
|
||||||
|
}
|
||||||
|
|
||||||
|
function gridWouldFormHighRun(grid, r, c) {
|
||||||
|
let h = 1
|
||||||
|
for (let cc = c - 1; cc >= 0 && gridIsHigh(grid, r, cc); cc--) h++
|
||||||
|
for (let cc = c + 1; cc < COLS && gridIsHigh(grid, r, cc); cc++) h++
|
||||||
|
if (h >= 3) return true
|
||||||
|
let v = 1
|
||||||
|
for (let rr = r - 1; rr >= 0 && gridIsHigh(grid, rr, c); rr--) v++
|
||||||
|
for (let rr = r + 1; rr < ROWS && gridIsHigh(grid, rr, c); rr++) v++
|
||||||
|
return v >= 3
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 在 grid 上是否存在“现成可消除组” */
|
||||||
|
function gridHasCurrentMatch(grid) {
|
||||||
|
const saved = board.value
|
||||||
|
board.value = grid
|
||||||
|
const has = findMatches().length > 0
|
||||||
|
board.value = saved
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 在 grid 上是否存在“可交换出消除的走法” */
|
||||||
|
function gridHasMove(grid) {
|
||||||
|
const saved = board.value
|
||||||
|
board.value = grid
|
||||||
|
const ok = findPossibleMoves().length > 0
|
||||||
|
board.value = saved
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅在 next 网格上调整本局高糖数量(只动“新生成的方块”,玩家尚未看到,故无可见突变):
|
||||||
|
* 解锁前固定 2 个、解锁后 3~HIGH_COUNT_MAX 个;注入时避免直接连成三连。
|
||||||
|
*/
|
||||||
|
function balanceHighInRefill(next, newTiles) {
|
||||||
|
const highFood = sessionHighFood.value
|
||||||
|
if (!highFood) return
|
||||||
|
const before = playerMoves.value < HIGH_UNLOCK_MOVES
|
||||||
|
const minTarget = before ? HIGH_COUNT_LOCKED : HIGH_COUNT_UNLOCKED
|
||||||
|
const maxCap = before ? HIGH_COUNT_LOCKED : HIGH_COUNT_MAX
|
||||||
|
|
||||||
|
let high = 0
|
||||||
|
for (let r = 0; r < ROWS; r++) {
|
||||||
|
for (let c = 0; c < COLS; c++) if (gridIsHigh(next, r, c)) high++
|
||||||
|
}
|
||||||
|
const lowMidPool = activeFoods.value.filter((f) => f.gi !== 'high')
|
||||||
|
|
||||||
|
if (high > maxCap) {
|
||||||
|
for (const t of shuffleInPlace([...newTiles])) {
|
||||||
|
if (high <= maxCap) break
|
||||||
|
if (t.type.gi !== 'high') continue
|
||||||
|
const repl = lowMidPool.length ? lowMidPool[Math.floor(Math.random() * lowMidPool.length)] : null
|
||||||
|
if (repl) { t.type = cloneFoodType(repl); high-- }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (high < minTarget) {
|
||||||
|
const cands = shuffleInPlace([...newTiles])
|
||||||
|
for (const t of cands) {
|
||||||
|
if (high >= minTarget) break
|
||||||
|
if (t.type.gi === 'high') continue
|
||||||
|
if (gridWouldFormHighRun(next, t.r, t.c)) continue
|
||||||
|
t.type = cloneFoodType(highFood)
|
||||||
|
high++
|
||||||
|
}
|
||||||
|
for (const t of cands) {
|
||||||
|
if (high >= minTarget) break
|
||||||
|
if (t.type.gi === 'high') continue
|
||||||
|
t.type = cloneFoodType(highFood)
|
||||||
|
high++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保证“落下后必有可消除的走法”:只重 roll 新方块里的非高糖类型(落子前完成,无可见重排)。
|
||||||
|
* 若 next 已有现成消除组(即将触发连锁),则交给连锁处理,最终落子时再保证。
|
||||||
|
*/
|
||||||
|
function ensureRefillPlayable(next, newTiles) {
|
||||||
|
if (gridHasCurrentMatch(next)) return
|
||||||
|
if (gridHasMove(next)) return
|
||||||
|
|
||||||
|
const nonHighPool = activeFoods.value.filter((f) => f.gi !== 'high')
|
||||||
|
const rerollable = newTiles.filter((t) => t.type.gi !== 'high')
|
||||||
|
if (nonHighPool.length && rerollable.length) {
|
||||||
|
for (let attempt = 0; attempt < 100; attempt++) {
|
||||||
|
rerollable.forEach((t) => {
|
||||||
|
t.type = cloneFoodType(nonHighPool[Math.floor(Math.random() * nonHighPool.length)])
|
||||||
|
})
|
||||||
|
if (gridHasMove(next)) return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极端兜底:静默打乱整盘类型(保持多重集/高糖数量),无任何横幅提示
|
||||||
|
silentShuffleGrid(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 在 next 网格上静默打乱类型直至“无现成消除组且有可用走法”,仅作极端兜底 */
|
||||||
|
function silentShuffleGrid(next) {
|
||||||
|
const tiles = []
|
||||||
|
for (let r = 0; r < ROWS; r++) {
|
||||||
|
for (let c = 0; c < COLS; c++) {
|
||||||
|
const t = next[r]?.[c]
|
||||||
|
if (t && !t.isRemoved) tiles.push(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tiles.length < 3) return
|
||||||
|
const saved = board.value
|
||||||
|
board.value = next
|
||||||
|
for (let attempt = 0; attempt < 80; attempt++) {
|
||||||
|
for (let i = tiles.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[tiles[i].type, tiles[j].type] = [tiles[j].type, tiles[i].type]
|
||||||
|
}
|
||||||
|
if (findMatches().length === 0 && findPossibleMoves().length > 0) break
|
||||||
|
}
|
||||||
|
board.value = saved
|
||||||
|
}
|
||||||
|
|
||||||
async function applyGravity() {
|
async function applyGravity() {
|
||||||
const next = Array.from({ length: ROWS }, () => Array(COLS).fill(null))
|
const next = Array.from({ length: ROWS }, () => Array(COLS).fill(null))
|
||||||
const dropTiles = []
|
const dropTiles = []
|
||||||
|
const newTiles = []
|
||||||
|
|
||||||
for (let c = 0; c < COLS; c++) {
|
for (let c = 0; c < COLS; c++) {
|
||||||
const surviving = []
|
const surviving = []
|
||||||
@@ -1861,10 +1995,15 @@ async function applyGravity() {
|
|||||||
tile.isDropAnimate = false
|
tile.isDropAnimate = false
|
||||||
next[r][c] = tile
|
next[r][c] = tile
|
||||||
dropTiles.push(tile)
|
dropTiles.push(tile)
|
||||||
|
newTiles.push(tile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureBoardFull(next)
|
ensureBoardFull(next)
|
||||||
|
// 落子前只调整“新方块”:①按解锁进度维持高糖数量 ②保证落下后必有可消除走法
|
||||||
|
// 这样棋盘随下落即更新,无需“无解时整盘自动重排”。
|
||||||
|
balanceHighInRefill(next, newTiles)
|
||||||
|
ensureRefillPlayable(next, newTiles)
|
||||||
commitBoard(next)
|
commitBoard(next)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user