1package shikaku23import (4 "fmt"5 "image/color"6 "strings"78 "charm.land/lipgloss/v2"9 "github.com/FelineStateMachine/puzzletea/game"10 "github.com/FelineStateMachine/puzzletea/theme"11)1213const (14 cellWidth = game.DynamicGridCellWidth15 previewRegionID = -216)1718func rectColors() []color.Color { return theme.Current().ThemeColors() }1920func gridView(m Model, solved bool) string {21 preview, previewClue, previewValid := activePreview(m)22 return game.RenderDynamicGrid(game.DynamicGridSpec{23 Width: m.puzzle.Width,24 Height: m.puzzle.Height,25 Solved: solved,26 Cell: func(x, y int) string {27 return cellView(m, x, y, preview, previewClue, previewValid, solved)28 },29 ZoneAt: func(x, y int) int {30 return regionTokenAt(m, preview, x, y)31 },32 ZoneFill: func(zone int) color.Color {33 return regionBackground(zone, previewClue, previewValid)34 },35 })36}3738func cellView(m Model, x, y int, preview *Rectangle, previewClue *Clue, previewValid, solved bool) string {39 p := theme.Current()40 isCursor := m.cursor.X == x && m.cursor.Y == y41 inPreview := preview != nil && preview.Contains(x, y)42 clue := m.puzzle.FindClueAt(x, y)43 token := regionTokenAt(m, preview, x, y)4445 display := " ยท "46 if clue != nil {47 display = fmt.Sprintf("%2d ", clue.Value)48 if clue.Value < 10 {49 display = fmt.Sprintf(" %d ", clue.Value)50 }51 }5253 style := lipgloss.NewStyle().54 Width(cellWidth).55 AlignHorizontal(lipgloss.Center)5657 fg := p.TextDim58 bg := p.BG59 if clue != nil {60 fg = p.Given61 style = style.Bold(true)62 }6364 if regionBG := regionBackground(token, previewClue, previewValid); regionBG != nil {65 bg = regionBG66 fg = theme.TextOnBG(regionBG)67 }6869 if m.selectedClue != nil && clue != nil && clue.ID == *m.selectedClue && !inPreview {70 bg = p.SelectionBG71 fg = theme.TextOnBG(bg)72 style = style.Bold(true)73 }7475 if solved && token == unownedRegionID {76 bg = p.SuccessBG77 fg = p.SolvedFG78 }7980 if isCursor {81 display = cursorDisplay(display)82 }8384 return style.Foreground(fg).Background(bg).Render(display)85}8687func cursorDisplay(display string) string {88 runes := []rune(display)89 if len(runes) != cellWidth {90 return display91 }9293 switch {94 case runes[0] == ' ' && runes[cellWidth-1] == ' ':95 return game.CursorLeft + string(runes[1]) + game.CursorRight96 case runes[cellWidth-1] == ' ':97 return game.CursorLeft + string(runes[:cellWidth-1])98 case runes[0] == ' ':99 return string(runes[1:]) + game.CursorRight100 default:101 return display102 }103}104105func activePreview(m Model) (*Rectangle, *Clue, bool) {106 var preview *Rectangle107 var previewClue *Clue108109 if m.mousePreview != nil {110 preview = m.mousePreview111 clues := m.puzzle.CluesInRect(*preview)112 if len(clues) == 1 {113 previewClue = clues[0]114 }115 } else if m.selectedClue != nil {116 previewClue = m.puzzle.FindClueByID(*m.selectedClue)117 if previewClue != nil {118 r := m.expansion.rect(previewClue)119 preview = &r120 }121 }122123 return preview, previewClue, previewClue != nil && m.puzzle.ValidRectangleForClue(*preview, previewClue.ID)124}125126func regionTokenAt(m Model, preview *Rectangle, x, y int) int {127 if preview != nil && preview.Contains(x, y) {128 return previewRegionID129 }130131 owner := m.puzzle.CellOwner(x, y)132 if owner >= 0 {133 return owner134 }135 return unownedRegionID136}137138func regionBackground(token int, previewClue *Clue, previewValid bool) color.Color {139 p := theme.Current()140 switch token {141 case previewRegionID:142 if previewClue == nil {143 return p.ErrorBG144 }145 if previewValid {146 return p.SuccessBG147 }148 return p.ErrorBG149 case unownedRegionID:150 return nil151 default:152 colors := rectColors()153 if len(colors) == 0 {154 return nil155 }156 return colors[token%len(colors)]157 }158}159160func infoView(puzzle *Puzzle) string {161 p := theme.Current()162163 placed := len(puzzle.Rectangles)164 total := len(puzzle.Clues)165 correct := 0166 for _, r := range puzzle.Rectangles {167 clue := puzzle.FindClueByID(r.ClueID)168 if clue != nil && r.Area() == clue.Value {169 correct++170 }171 }172173 satisfiedStyle := lipgloss.NewStyle().Foreground(p.SuccessBorder)174 infoStyle := lipgloss.NewStyle().Foreground(p.Info)175176 var sb strings.Builder177 sb.WriteString(infoStyle.Render("Rectangles: "))178 sb.WriteString(satisfiedStyle.Render(fmt.Sprintf("%d", correct)))179 sb.WriteString(infoStyle.Render(fmt.Sprintf("/%d correct Placed: %d", total, placed)))180 return sb.String()181}182183func statusBarView(selected, showFullHelp bool) string {184 if selected {185 if showFullHelp {186 return game.StatusBarStyle().Render("arrows: expand shift+arrows: shrink enter: confirm bkspc: cancel mouse: drag rect esc: menu ctrl+r: reset ctrl+h: help")187 }188 return game.StatusBarStyle().Render("arrows: expand shift+arrows: shrink enter: confirm bkspc: cancel mouse: drag")189 }190 if showFullHelp {191 return game.StatusBarStyle().Render("arrows/wasd: move enter/space: select clue bkspc: cancel/delete mouse: click+drag clue esc: menu ctrl+r: reset ctrl+h: help")192 }193 return game.StatusBarStyle().Render("enter/space: select clue bkspc: cancel/delete mouse: click+drag clue")194}195196func statusBarVariants() []string {197 return []string{198 statusBarView(false, false),199 statusBarView(false, true),200 statusBarView(true, false),201 statusBarView(true, true),202 }203}