clone

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

refactor/root-tui-model
puzzletea / games / lightsout / style.go
style.go 3.7K
1package lightsout23import (4 "strings"56 "charm.land/lipgloss/v2"7 "github.com/FelineStateMachine/puzzletea/game"8 "github.com/FelineStateMachine/puzzletea/theme"9)1011// screenToGrid converts terminal screen coordinates to grid cell coordinates.12// Returns (col, row, ok) where ok is false if the click landed outside the grid.13func (m *Model) screenToGrid(screenX, screenY int) (col, row int, ok bool) {14 gameView := m.View()15 viewWidth := lipgloss.Width(gameView)16 viewHeight := lipgloss.Height(gameView)1718 if viewWidth > m.termWidth {19 viewWidth = m.termWidth20 }2122 centerX := max((m.termWidth-viewWidth)/2, 0)23 centerY := max((m.termHeight-viewHeight)/2, 0)2425 // Title bar is always 2 lines (title + blank/subtitle line).26 titleHeight := strings.Count(game.TitleBarView("Lights Out", m.modeTitle, m.IsSolved()), "\n") + 12728 // The grid border adds 1 character on each side.29 const borderSize = 13031 // Grid origin within the centered view.32 gridInnerWidth := m.width * cellWidth33 gridOuterWidth := gridInnerWidth + 2*borderSize34 gridPadLeft := max((viewWidth-gridOuterWidth)/2, 0)3536 gridX := centerX + gridPadLeft + borderSize37 gridY := centerY + titleHeight + borderSize3839 lx := screenX - gridX40 ly := screenY - gridY41 if lx < 0 || ly < 0 {42 return 0, 0, false43 }4445 col = lx / cellWidth46 row = ly / cellHeight47 if col >= m.width || row >= m.height {48 return 0, 0, false49 }5051 return col, row, true52}5354const (55 cellWidth = 456 cellHeight = 257)5859func lightOnStyle() lipgloss.Style {60 p := theme.Current()61 return lipgloss.NewStyle().62 Background(theme.Blend(p.BG, p.Accent, 0.85))63}6465func lightOffStyle() lipgloss.Style {66 return lipgloss.NewStyle().67 Background(theme.Current().BG)68}6970func solvedCellStyle() lipgloss.Style {71 return lipgloss.NewStyle().72 Background(theme.Current().SuccessBG)73}7475func gridBorderStyle() lipgloss.Style {76 p := theme.Current()77 return lipgloss.NewStyle().78 Border(lipgloss.RoundedBorder()).79 BorderForeground(p.Border).80 BorderBackground(p.BG)81}8283func gridBorderSolvedStyle() lipgloss.Style {84 p := theme.Current()85 return lipgloss.NewStyle().86 Border(lipgloss.RoundedBorder()).87 BorderForeground(p.SuccessBorder).88 BorderBackground(p.SuccessBG)89}9091func cursorOnStyle() lipgloss.Style {92 p := theme.Current()93 return lipgloss.NewStyle().94 Background(theme.Blend(p.Accent, p.AccentText, 0.25))95}9697func cursorOffStyle() lipgloss.Style {98 p := theme.Current()99 return lipgloss.NewStyle().100 Background(theme.Blend(p.BG, p.Accent, 0.45))101}102103func cellView(isOn, isCursor, solved bool) string {104 s := lightOffStyle()105 if isOn {106 s = lightOnStyle()107 }108109 if isCursor && solved {110 s = game.CursorSolvedStyle()111 } else if solved {112 s = solvedCellStyle()113 } else if isCursor && isOn {114 s = cursorOnStyle()115 } else if isCursor {116 s = cursorOffStyle()117 }118119 content := " · "120 if isOn {121 content = " ● "122 }123 if isCursor && isOn {124 content = "▸●◂"125 } else if isCursor {126 content = "▸·◂"127 }128129 return s.Width(cellWidth).Height(cellHeight).AlignHorizontal(lipgloss.Center).Render(content)130}131132func gridView(g [][]bool, c game.Cursor, solved bool) string {133 var rows []string134 for y, row := range g {135 var rowCells []string136 for x, cell := range row {137 isCursor := x == c.X && y == c.Y138 rowCells = append(rowCells, cellView(cell, isCursor, solved))139 }140 rows = append(rows, lipgloss.JoinHorizontal(lipgloss.Top, rowCells...))141 }142 grid := lipgloss.JoinVertical(lipgloss.Left, rows...)143144 if solved {145 return gridBorderSolvedStyle().Render(grid)146 }147 return gridBorderStyle().Render(grid)148}149150func statusBarView(showFullHelp bool) string {151 if showFullHelp {152 return game.StatusBarStyle().Render("arrows/wasd: move click/enter: toggle esc: menu ctrl+r: reset ctrl+h: help")153 }154 return game.StatusBarStyle().Render("click/enter: toggle")155}