diff --git a/scripts/prototype-d5.mjs b/scripts/prototype-d5.mjs index 7775dcc..8032007 100644 --- a/scripts/prototype-d5.mjs +++ b/scripts/prototype-d5.mjs @@ -112,6 +112,8 @@ const MIN_CONTRAST = Number(args.minContrast ?? 12); // levels that were fine. See openingShowFloor in deep-lock. const MIN_OPENING_SHOW = openingShowFloor(Number(args.minOpeningShow ?? 800)); const MIN_OPENING_CHOICES = Number(args.minOpeningChoices ?? 3); +// The cascade alternative to a single long show, in the same calibrated units. +const MIN_OPENING_RUN = openingShowFloor(Number(args.minOpeningRun ?? 2600)); const MIN_CRUISES = Number(args.minCruises ?? 3); // Rotation count is deliberately not capped: turning is free and it is the // point of the game. What has to hold is that every turn pays out (minRelease), @@ -123,7 +125,9 @@ const playable = grownEnough.filter(r => r.audit.unsolvable === 0 && r.stats.fir // The show is the payout. An opening that offers two rim pieces and a 0.3s // hop gives a casual player nothing to enjoy before the thinking starts. && r.show.openingChoices >= MIN_OPENING_CHOICES - && r.show.openingBestMs >= MIN_OPENING_SHOW + // One long departure, or a cascade of short ones — a packed board pays the + // other way round and the single-best test cannot see it. + && (r.show.openingBestMs >= MIN_OPENING_SHOW || r.show.openingRunMs >= MIN_OPENING_RUN) && r.show.cruises >= MIN_CRUISES && r.shape.crossPerCell <= MAX_CROSS); // Locked-share used to sort first and highest, which is backwards for a casual @@ -142,9 +146,9 @@ const g5 = g4.filter(r => r.stats.minRelease >= MIN_RELEASE); console.log(`\n【门槛】长成 ${grownEnough.length} → 首转双向 ${g1.length} → 锁住≤90% ${g2.length} → 无不可解态 ${g3.length} → 终局紧局≤${(MAX_TIGHT_SHARE*100).toFixed(1)}% ${g4.length} → 每转释放≥${MIN_RELEASE} ${g5.length} → 反差≥${MIN_CONTRAST} ${playable.length}`); const g6 = g5.filter(r => r.contrast.contrast >= MIN_CONTRAST); const g7 = g6.filter(r => r.show.openingChoices >= MIN_OPENING_CHOICES); -const g8 = g7.filter(r => r.show.openingBestMs >= MIN_OPENING_SHOW); +const g8 = g7.filter(r => r.show.openingBestMs >= MIN_OPENING_SHOW || r.show.openingRunMs >= MIN_OPENING_RUN); const g9 = g8.filter(r => r.show.cruises >= MIN_CRUISES); -console.log(` → 开局可点≥${MIN_OPENING_CHOICES} ${g7.length} → 开局演出≥${(MIN_OPENING_SHOW / 1000).toFixed(2)}s ${g8.length} → 长巡游≥${MIN_CRUISES} ${g9.length} → 交叉/格≤${MAX_CROSS} ${playable.length}`); +console.log(` → 开局可点≥${MIN_OPENING_CHOICES} ${g7.length} → 开局演出≥${(MIN_OPENING_SHOW/1000).toFixed(2)}s 或连锁≥${(MIN_OPENING_RUN/1000).toFixed(1)}s ${g8.length} → 长巡游≥${MIN_CRUISES} ${g9.length} → 交叉/格≤${MAX_CROSS} ${playable.length}`); console.log(`【候选池】旋转深度 ${JSON.stringify(playable.reduce((m, r) => (m[r.stats.rotations] = (m[r.stats.rotations] || 0) + 1, m), {}))}`); console.log(` 交叉/格 中位 ${med(playable.map(r => r.shape.crossPerCell)).toFixed(2)} · 直线占比 中位 ${(med(playable.map(r => r.shape.straightShare))*100).toFixed(0)}% · 密度 中位 ${(med(playable.map(r => r.level.tiles.length/r.level.cells.length))*100).toFixed(0)}%`); console.log(` 反差 中位 ${med(playable.map(r => r.contrast.contrast)).toFixed(1)} · 家族 ${JSON.stringify(playable.reduce((m, r) => (m[r.family] = (m[r.family] || 0) + 1, m), {}))}`); diff --git a/src/deep-lock.js b/src/deep-lock.js index c5dc965..d2a843e 100644 --- a/src/deep-lock.js +++ b/src/deep-lock.js @@ -304,6 +304,13 @@ export function showStats(level) { exits: shows.length, openingChoices: openingShows.length, openingBestMs: openingShows.length ? Math.round(Math.max(...openingShows)) : 0, + // What the opening actually pays out, counting every piece that can leave + // before a turn is needed. A sparse board pays with one long journey; a + // packed board pays with a cascade of short ones, which is the thing the + // brief calls 多个棋子连环出去,非常赏心悦目. Judging both by the length of + // their single best departure fails every packed board — 29 of 29 in a + // sweep — for lacking a payoff they deliver a different way. + openingRunMs: Math.round(openingShows.reduce((sum, ms) => sum + ms, 0)), totalMs: Math.round(shows.reduce((a, b) => a + b, 0)), medianMs: Math.round(sorted[Math.floor(sorted.length / 2)]), longestMs: Math.round(sorted[sorted.length - 1]),