- commit
- d260f2e00012bf3d2207281fc8b6b2c3d1ca48ed
- parent
- 04734451ee053244591c0de01e258e0a9fff5e1e
- author
- Dami <dami.etoile@gmail.com>
| M | nurikabe/ | 38 | +++++++++++++++++++++++++++--- |
| M | nurikabe/ | 19 | +++++++++++++-- |
@@ -14,6 +14,7 @@ import (
1414 const (1515 generationHardTimeout = 10 * time.Second1616 uniquenessMinRemainingBudget = 250 * time.Millisecond17+ frontierBiasSampleCount = 71718 )1819 1920 type generationBudget struct {@@ -677,12 +678,41 @@ func (s *candidateState) popFrontier(rng *rand.Rand) (point, bool) {
677678 if len(s.frontier) == 0 {678679 return point{}, false679680 }680- idx := rng.IntN(len(s.frontier))681- picked := s.frontier[idx]681+682+ bestIdx := -1683+ bestScore := -1684+ samples := min(frontierBiasSampleCount, len(s.frontier))685+ for range samples {686+ idx := rng.IntN(len(s.frontier))687+ score := s.frontierPriorityScore(s.frontier[idx])688+ if score > bestScore || (score == bestScore && rng.IntN(2) == 0) {689+ bestScore = score690+ bestIdx = idx691+ }692+ }693+694+ if bestIdx < 0 {695+ bestIdx = rng.IntN(len(s.frontier))696+ }697+698+ picked := s.frontier[bestIdx]682699 s.removeFrontier(picked)683700 return picked, true684701 }685702 703+func (s *candidateState) frontierPriorityScore(p point) int {704+ if !s.inBounds(p) || s.sea[p.y][p.x] {705+ return 0706+ }707+708+ label := s.labels[p.y][p.x]709+ componentSize := s.componentSize[label]710+ sameLabelNeighbors := len(s.landNeighborsWithLabel(p, label))711+712+ // Favor carving from larger components; local degree nudges toward useful splits.713+ return componentSize*16 + sameLabelNeighbors*3714+}715+686716 func (s *candidateState) removeComponent(label int) {687717 size, ok := s.componentSize[label]688718 if !ok {@@ -1048,7 +1078,7 @@ func modeIslandProfile(mode NurikabeMode) islandProfile {
10481078 minAverageSize: 1.8,10491079 maxSingletonRatio: 0.72,10501080 minLargestIsland: 4,1051- maxLargestIsland: 20,1081+ maxLargestIsland: 15,10521082 }10531083 case "Expert":10541084 return islandProfile{@@ -1059,7 +1089,7 @@ func modeIslandProfile(mode NurikabeMode) islandProfile {
10591089 minAverageSize: 1.7,10601090 maxSingletonRatio: 0.78,10611091 minLargestIsland: 4,1062- maxLargestIsland: 22,1092+ maxLargestIsland: 17,10631093 }10641094 default:10651095 return islandProfile{@@ -590,10 +590,23 @@ func TestConstructedCandidateValidCompletedBoard(t *testing.T) {
590590 clueTarget = 2591591 }592592 593- rng := rand.New(rand.NewPCG(901, 1777))594- candidate, err := buildCandidateByCarving(mode, clueTarget, profile, rng, 0)593+ var (594+ candidate candidateResult595+ err error596+ )597+598+ // Keep this deterministic while avoiding reliance on a single RNG path.599+ for attempt := range 24 {600+ seedA := uint64(901 + attempt*17)601+ seedB := uint64(1777 + attempt*29)602+ rng := rand.New(rand.NewPCG(seedA, seedB))603+ candidate, err = buildCandidateByCarving(mode, clueTarget, profile, rng, attempt)604+ if err == nil {605+ break606+ }607+ }595608 if err != nil {596- t.Fatalf("buildCandidateByCarving error: %v", err)609+ t.Fatalf("buildCandidateByCarving failed across deterministic attempts: %v", err)597610 }598611 599612 if err := validateClues(candidate.puzzle.Clues, candidate.puzzle.Width, candidate.puzzle.Height); err != nil {