diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/gosnake.go | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/cmd/gosnake.go b/cmd/gosnake.go new file mode 100644 index 0000000..3d77877 --- /dev/null +++ b/cmd/gosnake.go @@ -0,0 +1,188 @@ +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) + } +} |