-
Notifications
You must be signed in to change notification settings - Fork 6
/
snake.go
165 lines (146 loc) · 3.03 KB
/
snake.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package main
import tl "github.com/JoelOtter/termloop"
type direction int
const (
right direction = iota
left
up
down
)
// Snake is the snake.
type Snake struct {
*tl.Entity
body []Coord
bodyLen int
direction direction
}
// NewSnake creates a new Snake with a default length and position.
func NewSnake() *Snake {
s := new(Snake)
s.Entity = tl.NewEntity(5, 5, 1, 1)
s.body = []Coord{
{3, 5},
{4, 5},
{5, 5}, // head
}
// Need to track length explicitly for the case
// where we're actively growing
s.bodyLen = len(s.body)
s.direction = right
return s
}
func (s *Snake) head() *Coord {
return &s.body[len(s.body)-1]
}
func (s *Snake) grow(amount int) {
s.bodyLen += amount
}
func (s *Snake) isGrowing() bool {
return s.bodyLen > len(s.body)
}
func (s *Snake) isCollidingWithSelf() bool {
for i := 0; i < len(s.body)-1; i++ {
if *s.head() == s.body[i] {
return true
}
}
return false
}
func (s *Snake) isCollidingWithBorder() bool {
return border.Contains(*s.head())
}
// Draw is called every frame so it calculates new positions and checks
// for collisions in addition to just drawing the Snake.
func (s *Snake) Draw(screen *tl.Screen) {
// Update position based on direction
newHead := *s.head()
switch s.direction {
case right:
newHead.x++
case left:
newHead.x--
case up:
newHead.y--
case down:
newHead.y++
}
if s.isGrowing() {
// We must be growing
s.body = append(s.body, newHead)
} else {
s.body = append(s.body[1:], newHead)
}
s.SetPosition(newHead.x, newHead.y)
if s.isCollidingWithSelf() || s.isCollidingWithBorder() {
EndGame()
}
// Draw snake
for _, c := range s.body {
screen.RenderCell(c.x, c.y, &tl.Cell{
Fg: tl.ColorGreen,
Ch: 'o',
})
}
}
// Tick handles keypress events
func (s *Snake) Tick(event tl.Event) {
// Find new direction - but you can't go
// back from where you came.
if event.Type == tl.EventKey {
switch event.Key {
case tl.KeyArrowRight:
if s.direction != left {
s.direction = right
}
case tl.KeyArrowLeft:
if s.direction != right {
s.direction = left
}
case tl.KeyArrowUp:
if s.direction != down {
s.direction = up
}
case tl.KeyArrowDown:
if s.direction != up {
s.direction = down
}
case 0:
// Vim mode!
switch event.Ch {
case 'h', 'H':
if s.direction != right {
s.direction = left
}
case 'j', 'J':
if s.direction != up {
s.direction = down
}
case 'k', 'K':
if s.direction != down {
s.direction = up
}
case 'l', 'L':
if s.direction != left {
s.direction = right
}
}
}
}
}
// Collide is called when a collision occurs, since this Snake is a
// DynamicPhysical that can handle its own collisions. Here we check what
// we're colliding with and handle it accordingly.
func (s *Snake) Collide(collision tl.Physical) {
switch collision.(type) {
case *Food:
s.handleFoodCollision()
case *Border:
s.handleBorderCollision()
}
}
func (s *Snake) handleFoodCollision() {
s.grow(5)
}
func (s *Snake) handleBorderCollision() {
EndGame()
}