clone

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

bug/architecture-test-trim-path
puzzletea / games / hitori / style.go
style.go 4.8K
1package hitori23import (4 "fmt"5 "image/color"67 "charm.land/lipgloss/v2"8 "github.com/FelineStateMachine/puzzletea/game"9 "github.com/FelineStateMachine/puzzletea/theme"10)1112const cellWidth = game.DynamicGridCellWidth1314const shadedCellDisplay = " █ "1516type cellVisualState int1718const (19 cellVisualStateBase cellVisualState = iota20 cellVisualStateCrosshair21 cellVisualStateCursor22 cellVisualStateSolved23 cellVisualStateConflict24)2526type cellVisual struct {27 text string28 fg color.Color29 bg color.Color30 bridgeBG color.Color31 bold bool32 state cellVisualState33}3435func cellView(num rune, mark cellMark, isCursor, inCursorRow, inCursorCol, solved, conflict bool) string {36 return renderCellVisual(resolveCellVisual(num, mark, isCursor, inCursorRow, inCursorCol, solved, conflict))37}3839func resolveCellVisual(40 num rune,41 mark cellMark,42 isCursor, inCursorRow, inCursorCol, solved, conflict bool,43) cellVisual {44 p := theme.Current()45 visual := hitoriBaseVisual(mark, num)4647 // Priority: cursor+solved > cursor+conflict > cursor > solved > conflict > crosshair > base.48 switch {49 case isCursor:50 return hitoriCursorVisual(num, mark, solved, conflict)51 case solved:52 visual.fg = p.SolvedFG53 visual.bg = p.SuccessBG54 visual.bridgeBG = p.SuccessBG55 visual.state = cellVisualStateSolved56 case conflict:57 visual.text = conflictDisplay(mark, num)58 visual.fg = game.ConflictFG()59 visual.bg = game.ConflictBG()60 visual.bridgeBG = game.ConflictBG()61 visual.state = cellVisualStateConflict62 case inCursorRow || inCursorCol:63 visual.bg = p.Surface64 visual.bridgeBG = p.Surface65 visual.state = cellVisualStateCrosshair66 }6768 return visual69}7071func hitoriBaseVisual(mark cellMark, num rune) cellVisual {72 p := theme.Current()7374 switch mark {75 case shaded:76 return cellVisual{77 text: shadedCellDisplay,78 fg: p.Surface,79 bg: p.Surface,80 bridgeBG: p.Surface,81 state: cellVisualStateBase,82 }83 case circled:84 return cellVisual{85 text: fmt.Sprintf("(%c)", num),86 fg: p.Info,87 bg: p.BG,88 bridgeBG: p.BG,89 state: cellVisualStateBase,90 }91 default:92 return cellVisual{93 text: fmt.Sprintf(" %c ", num),94 fg: p.FG,95 bg: p.BG,96 bridgeBG: p.BG,97 state: cellVisualStateBase,98 }99 }100}101102func hitoriCursorVisual(num rune, mark cellMark, solved, conflict bool) cellVisual {103 p := theme.Current()104 visual := hitoriBaseVisual(mark, num)105 visual.bold = true106107 switch mark {108 case circled:109 visual.fg = p.Info110 case shaded:111 visual.fg = game.CursorFG()112 default:113 visual.fg = game.CursorFG()114 }115116 switch {117 case solved:118 visual.bg = p.SuccessBG119 visual.bridgeBG = p.SuccessBG120 visual.state = cellVisualStateSolved121 if mark == shaded {122 visual.fg = p.SolvedFG123 }124 case conflict:125 visual.text = conflictDisplay(mark, num)126 visual.bg = game.ConflictBG()127 visual.bridgeBG = game.ConflictBG()128 visual.state = cellVisualStateConflict129 default:130 visual.bg = game.CursorBG()131 visual.bridgeBG = p.Surface132 visual.state = cellVisualStateCursor133 }134135 return visual136}137138func conflictDisplay(mark cellMark, num rune) string {139 switch mark {140 case shaded:141 return "!█!"142 default:143 return fmt.Sprintf("!%c!", num)144 }145}146147func renderCellVisual(visual cellVisual) string {148 style := lipgloss.NewStyle().149 Width(cellWidth).150 AlignHorizontal(lipgloss.Center).151 Foreground(visual.fg).152 Background(visual.bg)153 if visual.bold {154 style = style.Bold(true)155 }156 return style.Render(visual.text)157}158159func bridgeFill(numbers grid, marks [][]cellMark, c game.Cursor, solved bool, conflicts [][]bool, bridge game.DynamicGridBridge) color.Color {160 _, _, _ = numbers, marks, conflicts161 if solved {162 return nil163 }164165 if game.DynamicGridBridgeOnCrosshairAxis(c, bridge) {166 return theme.Current().Surface167 }168169 return nil170}171172func gridView(numbers grid, marks [][]cellMark, c game.Cursor, solved bool, conflicts [][]bool) string {173 height := len(numbers)174 width := 0175 if height > 0 {176 width = len(numbers[0])177 }178179 return game.RenderDynamicGrid(game.DynamicGridSpec{180 Width: width,181 Height: height,182 CellWidth: cellWidth,183 Solved: solved,184 Cell: func(x, y int) string {185 return renderCellVisual(resolveCellVisual(186 numbers[y][x],187 marks[y][x],188 x == c.X && y == c.Y,189 y == c.Y,190 x == c.X,191 solved,192 conflicts[y][x],193 ))194 },195 HasVerticalEdge: func(x, _ int) bool {196 return x <= 0 || x >= width197 },198 HasHorizontalEdge: func(_, y int) bool {199 return y <= 0 || y >= height200 },201 BridgeFill: func(bridge game.DynamicGridBridge) color.Color {202 return bridgeFill(numbers, marks, c, solved, conflicts, bridge)203 },204 })205}206207func statusBarView(showFullHelp bool) string {208 if showFullHelp {209 return game.StatusBarStyle().Render("arrows/wasd: move mouse: click move x: shade z: circle bkspc: clear esc: menu ctrl+r: reset ctrl+h: help")210 }211 return game.StatusBarStyle().Render("mouse: click move x: shade z: circle bkspc: clear")212}