aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillermo Ramos2021-04-04 21:23:44 +0200
committerGuillermo Ramos2021-04-04 21:23:44 +0200
commitaaffd2ad23293a99ea85c4a67b62058c71d9746a (patch)
tree92135f36d29d629f83bb552a6d5318fc53e6625e
downloadgosnake-aaffd2ad23293a99ea85c4a67b62058c71d9746a.tar.gz
Initial commit
-rw-r--r--cmd/gosnake.go188
-rw-r--r--go.mod5
-rw-r--r--go.sum32
3 files changed, 225 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)
+ }
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..4d575a0
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module git.gramos.me/gosnake.git
+
+go 1.16
+
+require github.com/charmbracelet/bubbletea v0.13.1
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..517bdf9
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,32 @@
+github.com/charmbracelet/bubbletea v0.13.1 h1:huvX8mPaeMZ8DLulT50iEWRF+iitY5FNEDqDVLu69nM=
+github.com/charmbracelet/bubbletea v0.13.1/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg=
+github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc=
+github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
+github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
+github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
+github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
+github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
+github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk=
+github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
+github.com/muesli/termenv v0.7.2 h1:r1raklL3uKE7rOvWgSenmEm2px+dnc33OTisZ8YR1fw=
+github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDCiKYPh8=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI=
+golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM=
+golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=