clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

stylen

Dami committed e839698
commit
e839698e0f0d84649f4d99a1c9d7987511a8ed6d
parent
ba1e1bc24aa43294eecaf7ab756e811b0b8c7af5
author
Dami <dami.etoile@gmail.com>
6 files changed, +199 -32
Mhashiwokakero/style.go20++++++-
Mmodel.go2+-
Mnonogram/Model.go3+-
Mnonogram/style.go90+++++++++++++++++++++++++++++-
Mtakuzu/style.go63++++++++++++++++-----
Mwordsearch/style.go53+++++++++++++++---
M hashiwokakero/style.go +17 -3
M model.go +1 -1
M nonogram/Model.go +1 -2
M nonogram/style.go +86 -4
@@ -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
M takuzu/style.go +49 -14
@@ -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 {
M wordsearch/style.go +45 -8
@@ -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 {