diff options
author | Guillermo Ramos | 2021-04-04 21:23:44 +0200 |
---|---|---|
committer | Guillermo Ramos | 2021-04-04 21:23:44 +0200 |
commit | aaffd2ad23293a99ea85c4a67b62058c71d9746a (patch) | |
tree | 92135f36d29d629f83bb552a6d5318fc53e6625e | |
download | gosnake-aaffd2ad23293a99ea85c4a67b62058c71d9746a.tar.gz |
Initial commit
-rw-r--r-- | cmd/gosnake.go | 188 | ||||
-rw-r--r-- | go.mod | 5 | ||||
-rw-r--r-- | go.sum | 32 |
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) + } +} @@ -0,0 +1,5 @@ +module git.gramos.me/gosnake.git + +go 1.16 + +require github.com/charmbracelet/bubbletea v0.13.1 @@ -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= |