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