Nonogram: add density parameter to control fill percentage, validate puzzles to reject trivial (all-empty/all-filled) rows/columns, expand modes to 12 difficulty levels across 4 grid sizes (5x5 through 20x20). Distribution: add Homebrew tap config to goreleaser, add Winget releaser job to release workflow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- commit
- 24af6f105e8e2cd7685c007afdf650ed05b709d8
- parent
- 15527784f1d2e15c9ecf2b4e4da1c001fff67a66
- author
- Dami <dami.etoile@gmail.com>
| M | .github/ | 12 | ++++++++++ |
| M | .goreleaser.yml | 18 | +++++++++++++++ |
| M | nonogram/ | 36 | ++++++++++++++++++++++++------ |
| M | nonogram/ | 30 | ++++++++++++++++++------- |
@@ -25,3 +25,15 @@ jobs:
2525 args: release --clean2626 env:2727 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}28+ HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}29+30+ winget:31+ name: Publish to WinGet32+ needs: release33+ runs-on: windows-latest34+ steps:35+ - uses: vedantmgoyal9/winget-releaser@v236+ with:37+ identifier: FelineStateMachine.puzzletea38+ installers-regex: '\.zip$'39+ token: ${{ secrets.WINGET_TOKEN }}@@ -25,6 +25,24 @@ archives:
2525 - zip2626 name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"2727 28+brews:29+ - name: puzzletea30+ directory: Formula31+ homepage: "https://github.com/FelineStateMachine/puzzletea"32+ description: "Terminal-based puzzle game collection built with Bubble Tea"33+ license: "MIT"34+ repository:35+ owner: FelineStateMachine36+ name: homebrew-tap37+ branch: main38+ token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"39+ commit_author:40+ name: goreleaserbot41+ email: bot@goreleaser.com42+ commit_msg_template: "Update {{ .ProjectName }} to {{ .Tag }}"43+ test: |44+ system "#{bin}/puzzletea", "--version"45+2846 changelog:2947 sort: asc3048 filters:@@ -3,7 +3,6 @@ package nonogram
33 import (44 "encoding/json"55 "fmt"6- "math"76 "math/rand"87 "strings"98 )@@ -28,7 +27,7 @@ func (m Model) GetSave() ([]byte, error) {
2827 }2928 jsonData, err := json.Marshal(save)3029 if err != nil {31- return nil, fmt.Errorf("unable to marshal save data:\n%v", save)30+ return nil, fmt.Errorf("unable to marshal save data: %w", err)3231 }3332 return jsonData, nil3433 }@@ -57,22 +56,31 @@ func (m Model) GetTomography() Hints {
5756 }5857 5958 func GenerateRandomTomography(mode NonogramMode) Hints {60- return generateTomography(newGrid(generateRandomState(mode.Height, mode.Width)))59+ for {60+ s := generateRandomState(mode.Height, mode.Width, mode.Density)61+ g := newGrid(s)62+ hints := generateTomography(g)63+ if isValidPuzzle(hints, mode.Height, mode.Width) {64+ return hints65+ }66+ }6167 }6268 63-func generateRandomState(h, w int) state {69+func generateRandomState(h, w int, density float64) state {6470 if h <= 0 || w <= 0 {6571 return ""6672 }6773 74+ density = max(0.1, min(0.9, density))75+6876 var b strings.Builder6977 b.Grow((w + 1) * h)7078 7179 for i := range h {72- rowWeight := float32(math.Pow(rand.Float64(), 3))80+ rowDensity := density + (rand.Float64()-0.5)*0.381+ rowDensity = max(0.05, min(0.95, rowDensity))7382 for range w {74- filled := rand.Float32() > rowWeight // Percentage chance to fill.75- if filled {83+ if rand.Float64() < rowDensity {7684 b.WriteRune(filledTile)7785 } else {7886 b.WriteRune(emptyTile)@@ -85,3 +93,17 @@ func generateRandomState(h, w int) state {
8593 8694 return state(b.String())8795 }96+97+func isValidPuzzle(hints Hints, height, width int) bool {98+ for _, rh := range hints.rows {99+ if len(rh) == 1 && (rh[0] == 0 || rh[0] == width) {100+ return false101+ }102+ }103+ for _, ch := range hints.cols {104+ if len(ch) == 1 && (ch[0] == 0 || ch[0] == height) {105+ return false106+ }107+ }108+ return true109+}@@ -13,7 +13,8 @@ func init() {
1313 1414 type NonogramMode struct {1515 game.BaseMode16- Height, Width int16+ Width, Height int17+ Density float64 // target fill percentage, 0.0–1.01718 }1819 1920 var (@@ -21,11 +22,12 @@ var (
2122 _ game.Spawner = NonogramMode{} // compile-time interface check2223 )2324 24-func NewMode(title, description string, Height, Width int) NonogramMode {25+func NewMode(title, description string, width, height int, density float64) NonogramMode {2526 return NonogramMode{2627 BaseMode: game.NewBaseMode(title, description),27- Height: Height,28- Width: Width,28+ Width: width,29+ Height: height,30+ Density: density,2931 }3032 }3133 @@ -35,8 +37,20 @@ func (n NonogramMode) Spawn() (game.Gamer, error) {
3537 }3638 3739 var Modes = []list.Item{38- NewMode("Easy - 5x5", "A random nonogram on a five by five board.", 5, 5),39- NewMode("Medium - 10x10", "A random nonogram on a ten by ten board.", 10, 10),40- NewMode("Hard - 15x15", "A random nonogram on a fifteen by fifteen board.", 15, 15),41- NewMode("Extra - 5x10", "A random nonogram on a five by ten board.", 5, 10),40+ // 5x541+ NewMode("Easy 5x5", "5x5 grid, ~35% filled. Simple hints.", 5, 5, 0.35),42+ NewMode("Medium 5x5", "5x5 grid, ~50% filled. Balanced challenge.", 5, 5, 0.50),43+ NewMode("Hard 5x5", "5x5 grid, ~65% filled. Dense hints.", 5, 5, 0.65),44+ // 10x1045+ NewMode("Easy 10x10", "10x10 grid, ~35% filled. Simple hints.", 10, 10, 0.35),46+ NewMode("Medium 10x10", "10x10 grid, ~50% filled. Balanced challenge.", 10, 10, 0.50),47+ NewMode("Hard 10x10", "10x10 grid, ~65% filled. Dense hints.", 10, 10, 0.65),48+ // 15x1549+ NewMode("Easy 15x15", "15x15 grid, ~35% filled. Simple hints.", 15, 15, 0.35),50+ NewMode("Medium 15x15", "15x15 grid, ~50% filled. Balanced challenge.", 15, 15, 0.50),51+ NewMode("Hard 15x15", "15x15 grid, ~65% filled. Dense hints.", 15, 15, 0.65),52+ // 20x2053+ NewMode("Easy 20x20", "20x20 grid, ~35% filled. Simple hints.", 20, 20, 0.35),54+ NewMode("Medium 20x20", "20x20 grid, ~50% filled. Balanced challenge.", 20, 20, 0.50),55+ NewMode("Hard 20x20", "20x20 grid, ~65% filled. Dense hints.", 20, 20, 0.65),4256 }