clone

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

feat/elo-difficulty
puzzletea / games / sudokurgb / model.go
model.go 3.9K
1package sudokurgb23import (4 "fmt"5 "strconv"67 "charm.land/bubbles/v2/key"8 tea "charm.land/bubbletea/v2"9 "github.com/FelineStateMachine/puzzletea/game"10)1112const (13 gridSize = 914 valueCount = 315 houseQuota = 316)1718var _ game.Gamer = Model{}1920type Model struct {21 cursor game.Cursor22 grid grid23 provided []cell24 providedGrid [gridSize][gridSize]bool25 analysis boardAnalysis26 keys KeyMap27 modeTitle string28 showFullHelp bool29 termWidth int30 termHeight int31 originX int32 originY int33 originValid bool34}3536func buildProvidedGrid(provided []cell) [gridSize][gridSize]bool {37 var pg [gridSize][gridSize]bool38 for _, c := range provided {39 pg[c.y][c.x] = true40 }41 return pg42}4344func New(mode SudokuRGBMode, provided []cell) (game.Gamer, error) {45 g := newGrid(provided)46 m := Model{47 grid: g,48 provided: provided,49 providedGrid: buildProvidedGrid(provided),50 analysis: analyzeGrid(g),51 keys: DefaultKeyMap,52 modeTitle: mode.Title(),53 }54 return m, nil55}5657func (m Model) Init() tea.Cmd {58 return nil59}6061func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {62 switch msg := msg.(type) {63 case game.HelpToggleMsg:64 m.showFullHelp = msg.Show65 m.originValid = false66 case tea.WindowSizeMsg:67 m.termWidth = msg.Width68 m.termHeight = msg.Height69 m.originValid = false70 case tea.MouseClickMsg:71 if msg.Button == tea.MouseLeft {72 if col, row, ok := m.screenToGrid(msg.X, msg.Y); ok {73 m.cursor.X = col74 m.cursor.Y = row75 }76 }77 case tea.KeyPressMsg:78 switch {79 case key.Matches(msg, m.keys.FillValue):80 value, _ := strconv.Atoi(msg.String())81 m.updateCell(value)82 case key.Matches(msg, m.keys.ClearCell):83 m.updateCell(0)84 default:85 m.cursor.Move(m.keys.CursorKeyMap, msg, gridSize-1, gridSize-1)86 }87 }8889 m.updateKeyBindings()90 return m, nil91}9293func (m Model) SetTitle(t string) game.Gamer {94 m.modeTitle = t95 return m96}9798func (m Model) IsSolved() bool {99 return m.isSolved()100}101102func (m Model) Reset() game.Gamer {103 m.grid = newGrid(m.provided)104 m.analysis = analyzeGrid(m.grid)105 m.originValid = false106 return m107}108109func (m *Model) updateCell(value int) {110 if m.providedGrid[m.cursor.Y][m.cursor.X] {111 return112 }113114 wasSolved := m.isSolved()115 m.grid[m.cursor.Y][m.cursor.X].v = value116 m.analysis = analyzeGrid(m.grid)117 if m.isSolved() != wasSolved {118 m.originValid = false119 }120}121122func (m Model) View() string {123 solved := isSolvedWith(m.grid, m.analysis)124 title := game.TitleBarView("Sudoku RGB", m.modeTitle, solved)125 board := buildBoardBlock(m, solved)126 if solved {127 return game.ComposeGameView(title, board.Block)128 }129130 status := statusBarView(m.showFullHelp)131 return game.ComposeGameViewRows(title, board.Block, game.StableRow(status, statusBarView(false), statusBarView(true)))132}133134func (m Model) GetDebugInfo() string {135 cursorCell := m.grid[m.cursor.Y][m.cursor.X]136 isProvided := m.providedGrid[m.cursor.Y][m.cursor.X]137 boxConflict := m.analysis.boxConflictCells[m.cursor.Y][m.cursor.X]138 solved := m.isSolved()139140 filledCount := 0141 conflictCount := 0142 for y := range gridSize {143 for x := range gridSize {144 if m.grid[y][x].v != 0 {145 filledCount++146 }147 if m.analysis.boxConflictCells[y][x] {148 conflictCount++149 }150 }151 }152153 status := "In Progress"154 if solved {155 status = "Solved"156 }157158 return game.DebugHeader("Sudoku RGB", [][2]string{159 {"Status", status},160 {"Cursor", fmt.Sprintf("(%d, %d)", m.cursor.X, m.cursor.Y)},161 {"Cell Value", cellDebugValue(cursorCell.v)},162 {"Is Provided", fmt.Sprintf("%v", isProvided)},163 {"Box Conflict", fmt.Sprintf("%v", boxConflict)},164 {"Row Counts", m.analysis.rowCountsString(m.cursor.Y)},165 {"Col Counts", m.analysis.colCountsString(m.cursor.X)},166 {"Cells Filled", fmt.Sprintf("%d / 81", filledCount)},167 {"Box Conflict Count", fmt.Sprintf("%d", conflictCount)},168 {"Provided Count", fmt.Sprintf("%d", len(m.provided))},169 })170}171172func cellDebugValue(value int) string {173 if value == 0 {174 return "empty"175 }176 return fmt.Sprintf("%d (%s)", value, cellContentValue(value))177}