- commit
- e839698e0f0d84649f4d99a1c9d7987511a8ed6d
- parent
- ba1e1bc24aa43294eecaf7ab756e811b0b8c7af5
- author
- Dami <dami.etoile@gmail.com>
| M | hashiwokakero/ | 20 | ++++++- |
| M | model.go | 2 | +- |
| M | nonogram/ | 3 | +- |
| M | nonogram/ | 90 | +++++++++++++++++++++++++++++- |
| M | takuzu/ | 63 | ++++++++++++++++----- |
| M | wordsearch/ | 53 | +++++++++++++++--- |
@@ -81,6 +81,16 @@ var (
8181 statusBarStyle = lipgloss.NewStyle().8282 Foreground(colorInfoText).8383 MarginTop(1)84+85+ gridBorderStyle = lipgloss.NewStyle().86+ Border(lipgloss.RoundedBorder()).87+ Padding(1).88+ BorderForeground(colorEmptyDot)89+90+ gridBorderSolvedStyle = lipgloss.NewStyle().91+ Border(lipgloss.RoundedBorder()).92+ Padding(1).93+ BorderForeground(colorBridgeSolved)8494 )8595 8696 // isHighlightedNeighbor returns true if islandID is directly connectable@@ -120,15 +130,19 @@ func gridView(m Model, solved bool) string {
120130 dispH := 2*m.puzzle.Height - 1121131 122132 var rows []string123- for dy := 0; dy < dispH; dy++ {133+ for dy := range dispH {124134 var cells []string125- for dx := 0; dx < dispW; dx++ {135+ for dx := range dispW {126136 cells = append(cells, displayCellView(m, dx, dy, solved))127137 }128138 rows = append(rows, lipgloss.JoinHorizontal(lipgloss.Top, cells...))129139 }140+ grid := lipgloss.JoinVertical(lipgloss.Left, rows...)130141 131- return lipgloss.JoinVertical(lipgloss.Left, rows...)142+ if solved {143+ return gridBorderSolvedStyle.Render(grid)144+ }145+ return gridBorderStyle.Render(grid)132146 }133147 134148 // displayCellView renders a single cell in the display grid.@@ -419,7 +419,7 @@ func (m *model) initContinueTable() {
419419 table.WithColumns(columns),420420 table.WithRows(rows),421421 table.WithFocused(true),422- table.WithHeight(24),422+ table.WithHeight(max(m.height-2, 1)),423423 )424424 425425 s := table.DefaultStyles()@@ -74,9 +74,8 @@ func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {
7474 m.updateTile(markedTile)7575 case key.Matches(msg, m.keys.ClearTile):7676 m.updateTile(emptyTile)77- default:78- m.cursor.Move(m.keys.CursorKeyMap, msg, m.width-1, m.height-1)7977 }78+ m.cursor.Move(m.keys.CursorKeyMap, msg, m.width-1, m.height-1)8079 }8180 m.updateKeyBindings()8281 return m, nil@@ -40,9 +40,14 @@ var (
4040 nonoStatusBarStyle = lipgloss.NewStyle().4141 Foreground(lipgloss.AdaptiveColor{Light: "244", Dark: "244"}).4242 MarginTop(1)43+44+ separatorFG = lipgloss.AdaptiveColor{Light: "250", Dark: "240"}4345 )4446 45-const cellWidth = 347+const (48+ cellWidth = 349+ spacerEvery = 550+)4651 4752 var (4853 hintCellStyle = baseStyle.Width(cellWidth)@@ -60,6 +65,34 @@ var (
6065 }6166 )6267 68+// needsSpacer reports whether a separator should be inserted after index i69+// in a dimension of size n (i.e. after every spacerEvery cells, except the last).70+func needsSpacer(i, n int) bool {71+ return n > spacerEvery && (i+1)%spacerEvery == 0 && i < n-172+}73+74+// hSeparator builds a horizontal separator row using box-drawing characters.75+// w is the number of grid columns. cursorX is the cursor column (-1 to disable crosshair).76+// bg is the default background, crossBG is the crosshair-highlighted background.77+func hSeparator(w, cursorX int, bg, crossBG lipgloss.TerminalColor) string {78+ defStyle := baseStyle.Foreground(separatorFG).Background(bg)79+ highStyle := baseStyle.Foreground(separatorFG).Background(crossBG)80+ segment := strings.Repeat("─", cellWidth)81+82+ var parts []string83+ for x := range w {84+ style := defStyle85+ if x == cursorX {86+ style = highStyle87+ }88+ parts = append(parts, style.Render(segment))89+ if needsSpacer(x, w) {90+ parts = append(parts, defStyle.Render("┼"))91+ }92+ }93+ return lipgloss.JoinHorizontal(lipgloss.Top, parts...)94+}95+6396 func colHintView(c TomographyDefinition, height int, current ...TomographyDefinition) string {6497 var hasCurrent bool6598 var curr TomographyDefinition@@ -68,12 +101,13 @@ func colHintView(c TomographyDefinition, height int, current ...TomographyDefini
68101 curr = current[0]69102 }70103 104+ n := len(c)71105 var renderedCols []string72106 for i, hints := range c {73107 var colHints []string74108 for range height - len(hints) {75- spacer := hintCellStyle.Render(" ")76- colHints = append(colHints, spacer)109+ pad := hintCellStyle.Render(" ")110+ colHints = append(colHints, pad)77111 }78112 79113 satisfied := hasCurrent && i < len(curr) && intSliceEqual(hints, curr[i])@@ -90,6 +124,14 @@ func colHintView(c TomographyDefinition, height int, current ...TomographyDefini
90124 }91125 renderedCol := lipgloss.JoinVertical(lipgloss.Left, colHints...)92126 renderedCols = append(renderedCols, renderedCol)127+128+ if needsSpacer(i, n) {129+ sepStyle := baseStyle.Foreground(separatorFG)130+ col := lipgloss.JoinVertical(lipgloss.Left,131+ strings.Split(strings.Repeat("│\n", height), "\n")[:height]...,132+ )133+ renderedCols = append(renderedCols, sepStyle.Render(col))134+ }93135 }94136 return lipgloss.JoinHorizontal(lipgloss.Top, renderedCols...)95137 }@@ -102,6 +144,7 @@ func rowHintView(r TomographyDefinition, width int, current ...TomographyDefinit
102144 curr = current[0]103145 }104146 147+ n := len(r)105148 var renderedRows []string106149 for i, hints := range r {107150 satisfied := hasCurrent && i < len(curr) && intSliceEqual(hints, curr[i])@@ -120,24 +163,63 @@ func rowHintView(r TomographyDefinition, width int, current ...TomographyDefinit
120163 Align(lipgloss.Right).121164 Render(strings.Join(rowHints, " "))122165 renderedRows = append(renderedRows, renderedRow)166+167+ if needsSpacer(i, n) {168+ sep := baseStyle.Foreground(separatorFG).169+ Width(width).170+ Align(lipgloss.Right).171+ Render(strings.Repeat("─", width))172+ renderedRows = append(renderedRows, sep)173+ }123174 }124175 s := lipgloss.JoinVertical(lipgloss.Left, renderedRows...)125176 return s126177 }127178 128179 func gridView(g grid, c game.Cursor, solved bool) string {180+ h := len(g)181+ w := 0182+ if h > 0 {183+ w = len(g[0])184+ }185+186+ sepStyle := baseStyle.Foreground(separatorFG)187+188+ // Determine background for horizontal separators (matches grid background).189+ gridBG := emptyStyle.GetBackground()190+ if solved {191+ gridBG = solvedBG192+ }193+129194 var rows []string130195 for y, row := range g {196+ inCursorRow := y == c.Y197+131198 var rowBuilder []string132199 for x, cell := range row {133200 isCursor := x == c.X && y == c.Y134- inCursorRow := y == c.Y135201 inCursorCol := x == c.X136202 cell := tileView(cell, isCursor, inCursorRow, inCursorCol, solved)137203 rowBuilder = append(rowBuilder, cell)204+205+ if needsSpacer(x, w) {206+ bg := gridBG207+ if !solved && inCursorRow {208+ bg = crosshairBG209+ }210+ rowBuilder = append(rowBuilder, sepStyle.Background(bg).Render("│"))211+ }138212 }139213 row := lipgloss.JoinHorizontal(lipgloss.Top, rowBuilder...)140214 rows = append(rows, row)215+216+ if needsSpacer(y, h) {217+ cursorX := -1218+ if !solved {219+ cursorX = c.X220+ }221+ rows = append(rows, hSeparator(w, cursorX, gridBG, crosshairBG))222+ }141223 }142224 grid := lipgloss.JoinVertical(lipgloss.Left, rows...)143225 return grid@@ -28,15 +28,7 @@ var (
2828 crosshairBG = lipgloss.AdaptiveColor{Light: "254", Dark: "237"}2929 solvedBG = lipgloss.AdaptiveColor{Light: "151", Dark: "22"}3030 31- gridBorderStyle = lipgloss.NewStyle().32- Border(lipgloss.RoundedBorder()).33- BorderForeground(lipgloss.AdaptiveColor{Light: "250", Dark: "240"}).34- BorderBackground(backgroundColor)35-36- gridBorderSolvedStyle = lipgloss.NewStyle().37- Border(lipgloss.RoundedBorder()).38- BorderForeground(lipgloss.AdaptiveColor{Light: "22", Dark: "149"}).39- BorderBackground(solvedBG)31+ borderFG = lipgloss.AdaptiveColor{Light: "250", Dark: "240"}4032 4133 statusBarStyle = lipgloss.NewStyle().4234 Foreground(lipgloss.AdaptiveColor{Light: "244", Dark: "244"}).@@ -70,7 +62,7 @@ func cellView(val rune, isProvided, isCursor, inCursorRow, inCursorCol, solved b
7062 }7163 7264 if isCursor && !solved {73- s = cursorStyle65+ s = s.Background(cursorStyle.GetBackground()).Bold(true)7466 } else if !solved && (inCursorRow || inCursorCol) {7567 s = s.Background(crosshairBG)7668 }@@ -87,24 +79,67 @@ func cellView(val rune, isProvided, isCursor, inCursorRow, inCursorCol, solved b
8779 }8880 8981 func gridView(g grid, provided [][]bool, c game.Cursor, solved bool) string {82+ w := 083+ if len(g) > 0 {84+ w = len(g[0])85+ }86+9087 var rows []string9188 for y, row := range g {9289 var rowCells []string90+ inCursorRow := y == c.Y91+92+ // Left border segment for this row.93+ rowCells = append(rowCells, borderChar("│", solved, !solved && inCursorRow))94+9395 for x, val := range row {9496 isCursor := x == c.X && y == c.Y95- inCursorRow := y == c.Y9697 inCursorCol := x == c.X9798 cell := cellView(val, provided[y][x], isCursor, inCursorRow, inCursorCol, solved)9899 rowCells = append(rowCells, cell)99100 }101+102+ // Right border segment for this row.103+ rowCells = append(rowCells, borderChar("│", solved, !solved && inCursorRow))104+100105 rows = append(rows, lipgloss.JoinHorizontal(lipgloss.Top, rowCells...))101106 }102- content := lipgloss.JoinVertical(lipgloss.Left, rows...)103107 108+ // Build top and bottom border rows with per-column crosshair highlighting.109+ topRow := hBorderRow(w, c.X, "╭", "╮", solved)110+ botRow := hBorderRow(w, c.X, "╰", "╯", solved)111+112+ return lipgloss.JoinVertical(lipgloss.Left, topRow, lipgloss.JoinVertical(lipgloss.Left, rows...), botRow)113+}114+115+// borderChar renders a single border character with optional crosshair highlighting.116+func borderChar(ch string, solved, highlight bool) string {117+ s := baseStyle.Foreground(borderFG).Background(backgroundColor)104118 if solved {105- return gridBorderSolvedStyle.Render(content)119+ s = baseStyle.Foreground(lipgloss.AdaptiveColor{Light: "22", Dark: "149"}).Background(solvedBG)120+ } else if highlight {121+ s = s.Background(crosshairBG)122+ }123+ return s.Render(ch)124+}125+126+// hBorderRow builds a horizontal border row (top or bottom) with per-column crosshair.127+func hBorderRow(w, cursorX int, left, right string, solved bool) string {128+ var parts []string129+ parts = append(parts, borderChar(left, solved, false))130+ segment := "───" // cellWidth = 3131+ for x := range w {132+ highlight := !solved && x == cursorX133+ s := baseStyle.Foreground(borderFG).Background(backgroundColor)134+ if solved {135+ s = baseStyle.Foreground(lipgloss.AdaptiveColor{Light: "22", Dark: "149"}).Background(solvedBG)136+ } else if highlight {137+ s = s.Background(crosshairBG)138+ }139+ parts = append(parts, s.Render(segment))106140 }107- return gridBorderStyle.Render(content)141+ parts = append(parts, borderChar(right, solved, false))142+ return lipgloss.JoinHorizontal(lipgloss.Top, parts...)108143 }109144 110145 func statusBarView(showFullHelp bool) string {@@ -40,15 +40,51 @@ var (
4040 statusBarStyle = lipgloss.NewStyle().4141 Foreground(lipgloss.AdaptiveColor{Light: "137", Dark: "137"}).4242 MarginTop(1)43+44+ borderFG = lipgloss.AdaptiveColor{Light: "250", Dark: "240"}45+ gridBG = lipgloss.AdaptiveColor{Light: "254", Dark: "236"}46+ solvedBdrFG = lipgloss.AdaptiveColor{Light: "22", Dark: "149"}47+48+ gridBorderStyle = lipgloss.NewStyle().49+ Border(lipgloss.RoundedBorder()).50+ BorderForeground(borderFG).51+ BorderBackground(gridBG)52+53+ gridBorderSolvedStyle = lipgloss.NewStyle().54+ Border(lipgloss.RoundedBorder()).55+ BorderForeground(solvedBdrFG).56+ BorderBackground(gridBG)57+58+ wordListBorderStyle = lipgloss.NewStyle().59+ Border(lipgloss.RoundedBorder()).60+ BorderForeground(borderFG).61+ Padding(0, 1)62+63+ wordListBorderSolvedStyle = lipgloss.NewStyle().64+ Border(lipgloss.RoundedBorder()).65+ BorderForeground(solvedBdrFG).66+ Padding(0, 1)4367 )4468 4569 func renderView(m Model) string {4670 title := game.TitleBarView("Word Search", m.modeTitle, m.solved)47- gridView := renderGrid(m)48- wordListView := renderWordList(m)71+ gridContent := renderGrid(m)72+ wordListContent := renderWordList(m)73+74+ gBorder := gridBorderStyle75+ wBorder := wordListBorderStyle76+ if m.solved {77+ gBorder = gridBorderSolvedStyle78+ wBorder = wordListBorderSolvedStyle79+ }80+81+ gridView := gBorder.Render(gridContent)82+ gridHeight := lipgloss.Height(gridView)83+ // Match word list height to grid height, subtracting border lines (top+bottom).84+ wBorder = wBorder.Height(gridHeight - 2)85+ wordListView := wBorder.Render(wordListContent)4986 50- // Join grid and word list horizontally with spacing51- spacer := strings.Repeat(" ", 4)87+ spacer := strings.Repeat(" ", 2)5288 mainView := lipgloss.JoinHorizontal(lipgloss.Top, gridView, spacer, wordListView)5389 5490 status := statusBarView(m.showFullHelp)@@ -64,18 +100,19 @@ func statusBarView(showFullHelp bool) string {
64100 }65101 66102 func renderGrid(m Model) string {67- var sb strings.Builder103+ var rows []string68104 69105 for y := 0; y < m.height; y++ {106+ var cells []string70107 for x := 0; x < m.width; x++ {71108 letter := m.grid.Get(x, y)72109 style := getCellStyle(m, x, y)73- sb.WriteString(style.Render(fmt.Sprintf(" %c ", letter)))110+ cells = append(cells, style.Render(fmt.Sprintf(" %c ", letter)))74111 }75- sb.WriteString("\n")112+ rows = append(rows, lipgloss.JoinHorizontal(lipgloss.Top, cells...))76113 }77114 78- return sb.String()115+ return lipgloss.JoinVertical(lipgloss.Left, rows...)79116 }80117 81118 func getCellStyle(m Model, x, y int) lipgloss.Style {