package main import ( "bytes" "fmt" tea "github.com/charmbracelet/bubbletea" "math/rand" "os" "time" ) type point struct { x, y int } type direction int const ( up = iota right down left ) func (d direction) toByte() byte { return []byte{'^', '>', 'v', '<'}[d] } type tickMsg time.Time type model struct { dx, dy int head point tail []point maxLen int dir, newDir direction food point points int } func (m model) toOffset(p point) int { return p.y*(m.dx+1) + p.x } func (m model) isEmpty(p point) bool { // Check collision with head if p == m.head { return false } // Check collision with borders if p.x < 0 || p.x >= m.dx || p.y < 0 || p.y >= m.dy { return false } // Check collision with tail for _, t := range m.tail { if p == t { return false } } return true } func (m model) randFood() point { candidate := point{x: rand.Intn(m.dx), y: rand.Intn(m.dy)} for !m.isEmpty(candidate) { candidate = point{x: rand.Intn(m.dx), y: rand.Intn(m.dy)} } return candidate } func initialModel() model { m := model{ dx: 20, dy: 10, head: point{1, 1}, tail: make([]point, 0, 10), maxLen: 5, dir: right, newDir: right, points: 0, } m.food = m.randFood() return m } func tickCmd(points int) func() tea.Msg { base := 300 scaled := base - (10 * points) return tea.Tick(time.Millisecond*time.Duration(scaled), func(t time.Time) tea.Msg { return tickMsg(t) }) } func (m model) Init() tea.Cmd { return tickCmd(m.points) } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: var newDir direction switch msg.String() { case "ctrl+c": return m, tea.Quit case "up", "k": newDir = up case "right", "l": newDir = right case "down", "j": newDir = down case "left", "h": newDir = left } // Prevent from going in the reverse direction if (newDir+2)%4 != m.dir { m.newDir = newDir } case tickMsg: // Calculate new head position var newHead point switch m.newDir { case up: newHead = point{m.head.x, m.head.y - 1} case right: newHead = point{m.head.x + 1, m.head.y} case down: newHead = point{m.head.x, m.head.y + 1} case left: newHead = point{m.head.x - 1, m.head.y} } m.dir = m.newDir if !m.isEmpty(newHead) { fmt.Println("DEP, siempre saludaba.") return m, tea.Quit } // Good to go; update head and tail m.tail = append(m.tail, m.head) if len(m.tail) >= m.maxLen { m.tail = m.tail[1:] } m.head = newHead // Finally, when the snake eats the food: if m.head == m.food { m.food = m.randFood() m.maxLen += 1 m.points += 1 } return m, tickCmd(m.points) } return m, nil } func (m model) View() string { // Generate board row := append(bytes.Repeat([]byte{'-'}, m.dx), '\n') board := bytes.Repeat(row, m.dy) // Draw head board[m.toOffset(m.head)] = m.dir.toByte() // Draw tail for _, p := range m.tail { board[m.toOffset(p)] = '#' } // Draw food board[m.toOffset(m.food)] = 'o' return fmt.Sprintf("%s\nPoints: %d\n", string(board), m.points) } func main() { rand.Seed(time.Now().UnixNano()) p := tea.NewProgram(initialModel()) if err := p.Start(); err != nil { fmt.Println(err) os.Exit(1) } }