-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsnake.cpp
287 lines (236 loc) · 7.54 KB
/
snake.cpp
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#include "snake.h"
uint32_t snake_colors[] = {
COLOR_HSV(0, 255, 255),
COLOR_HSV(4096, 255, 255),
COLOR_HSV(8192, 255, 255),
COLOR_HSV(12288, 255, 255),
COLOR_HSV(16384, 255, 255),
COLOR_HSV(20480, 255, 255),
COLOR_HSV(24576, 255, 255),
COLOR_HSV(28672, 255, 255),
COLOR_HSV(32768, 255, 255),
COLOR_HSV(36864, 255, 255),
COLOR_HSV(40960, 255, 255),
COLOR_HSV(45056, 255, 255),
COLOR_HSV(49152, 255, 255),
COLOR_HSV(53248, 255, 255),
COLOR_HSV(57344, 255, 255),
COLOR_HSV(61440, 255, 255),
};
Snake::Snake() {
head_x = 0;
head_y = 0;
prev_dx = 0;
prev_dy = 0;
init();
}
void Snake::init() {
printf("Snake init! tickdiv=%u\n", this->tickdiv);
// Reset grid
memset(this->grid, 0, sizeof(this->grid));
this->score = 0;
this->length = 1;
this->game_over = false;
// Initialize snake
this->head_x = DISPLAY_SIZE/2;
this->head_y = DISPLAY_SIZE/2;
this->prev_dx = 0;
this->prev_dy = 0;
this->grid[head_x][head_y].flags = SNAKE_FLAG_BODY | SNAKE_FLAG_TAIL;
// Set head color based on speed
uint8_t headcolor;
if (tickdiv >= SNAKE_TICKDIV_SLOW) {
headcolor = SNAKE_HEADCOLOR_SLOW;
} else if (tickdiv >= SNAKE_TICKDIV_MEDIUM) {
headcolor = SNAKE_HEADCOLOR_MEDIUM;
} else {
headcolor = SNAKE_HEADCOLOR_FAST;
}
this->grid[head_x][head_y].color = headcolor;
// Spawn first fruit
if (wall_collision) {
spawn_fruit(7);
} else {
spawn_fruit(1);
}
}
void Snake::set_wall_collision(bool collide) {
this->wall_collision = collide;
}
bool Snake::tick(float ax, float ay, float az) {
if (game_over) {
return false;
}
this->tickcounter--;
if (this->tickcounter <= 0) {
this->tickcounter = this->tickdiv;
this->update(ax, ay, az);
return true;
}
return false;
}
void Snake::update(float ax, float ay, float az) {
int dx = 0;
int dy = 0;
// Decide in which direction the user wants to move
if (abs(ax) > abs(ay)) {
// X acceleration is greater, tilt on x axis if higher than threshold
if (ax >= SNAKE_TILT_THRESHOLD) {
dx = 1;
} else if (-ax >= SNAKE_TILT_THRESHOLD) {
dx = -1;
}
} else {
// Y acceleration is greater, tilt on y axis if higher than threshold
if (ay >= SNAKE_TILT_THRESHOLD) {
dy = 1;
} else if (-ay >= SNAKE_TILT_THRESHOLD) {
dy = -1;
}
}
// If user does not indicate direction, either use previous or wait if unknown
if (dx == 0 && dy == 0) {
if (prev_dx == 0 && prev_dy == 0) {
return;
}
dx = prev_dx;
dy = prev_dy;
}
// Prevent direction change if user wants to reverse onto itself
if (!(grid[head_x][head_y].flags & SNAKE_FLAG_TAIL)) {
if ((head_x+dx+DISPLAY_SIZE)%DISPLAY_SIZE == grid[head_x][head_y].prev_x
&& (head_y+dy+DISPLAY_SIZE)%DISPLAY_SIZE == grid[head_x][head_y].prev_y) {
// Continue in same direction as before
dx = prev_dx;
dy = prev_dy;
}
}
this->prev_dx = dx;
this->prev_dy = dy;
int new_x = head_x+dx;
int new_y = head_y+dy;
// Check if we collide with a wall
if (wall_collision) {
// Game over on collision
if (new_x < 0 || new_y < 0 || new_x >= DISPLAY_SIZE || new_y >= DISPLAY_SIZE) {
game_over = true;
return;
}
} else {
// Wrap around
new_x = (new_x+DISPLAY_SIZE) % DISPLAY_SIZE;
new_y = (new_y+DISPLAY_SIZE) % DISPLAY_SIZE;
}
snake_node_t* next = &grid[new_x][new_y];
if (next->flags & SNAKE_FLAG_BODY || next->flags & SNAKE_FLAG_TAIL) {
// Collision with itself, game over
game_over = true;
return;
} else if (next->flags & SNAKE_FLAG_FRUIT) {
// Found fruit
// Remove it and increase length, color is kept from fruit color
next->flags = 0;
uint8_t color = next->color;
// Advance snake and attach fruit to tail
snake_node_t* tail = advance_snake(next);
tail->flags = SNAKE_FLAG_BODY;
snake_node_t* newtail = &grid[tail->prev_x][tail->prev_y];
newtail->color = color;
newtail->flags = SNAKE_FLAG_BODY|SNAKE_FLAG_TAIL;
length++;
// Spawn new fruit
spawn_fruit();
} else {
// Empty field, move snake forwards
advance_snake(next);
}
head_x = new_x;
head_y = new_y;
}
snake_node_t *Snake::advance_snake(snake_node_t *newhead) {
snake_node_t* next = newhead;
next->flags = SNAKE_FLAG_BODY;
if (length == 1) {
// Special case, length == 1
next->flags |= SNAKE_FLAG_TAIL;
next->color = grid[head_x][head_y].color;
next->prev_x = head_x;
next->prev_y = head_y;
grid[head_x][head_y].flags = 0;
return next;
} else {
// Standard case, length >= 2
next->prev_x = head_x;
next->prev_y = head_y;
snake_node_t *cur = next;
while (true) {
// Travel along the snake from head to tail
next = &grid[cur->prev_x][cur->prev_y];
// Shift colors from back to front
cur->color = next->color;
// If we find the tail, remove it and stop
if (next->flags & SNAKE_FLAG_TAIL) {
next->flags = 0;
cur->flags |= SNAKE_FLAG_TAIL;
break;
}
cur = next;
}
return cur;
}
}
void Snake::spawn_fruit(uint8_t color) {
printf("Spawning new fruit...\n");
if (this->length >= DISPLAY_SIZE*DISPLAY_SIZE) {
// No more room for fruits, don't spawn any
// TODO: maybe add some kind of game won screen here
return;
}
uint8_t pos_x=0, pos_y=0;
// Find an empty spot by picking an index of remaining empty tiles
// This way, we don't have to retry many times near the end of the game
int idx = rand()%(DISPLAY_SIZE*DISPLAY_SIZE-this->length);
int i = 0;
for (int x = 0; x < DISPLAY_SIZE; ++x) {
for (int y = 0; y < DISPLAY_SIZE; ++y) {
if (this->grid[x][y].flags == 0) {
// Empty spot, check if the index is right
if (i == idx) {
// Found it!
pos_x = x;
pos_y = y;
break;
}
i++;
}
}
if (i == idx) {
break;
}
}
printf("Found fruit location: x=%u y=%u (idx=%u, max=%u)\n", pos_x, pos_y, idx, DISPLAY_SIZE*DISPLAY_SIZE-this->length);
this->grid[pos_x][pos_y].flags |= SNAKE_FLAG_FRUIT;
if (color == 0xFF) {
// Generate random color if we aren't given one
color = rand()%SNAKE_COLORS_COUNT;
}
this->grid[pos_x][pos_y].color = color;
}
void Snake::draw() {
for (int x = 0; x < DISPLAY_SIZE; ++x) {
for (int y = 0; y < DISPLAY_SIZE; ++y) {
snake_node_t* node = &grid[x][y];
// Draw node if flags are non-empty
// Since fruits are currently visually identical to body parts,
// we don't need to differentiate between them
if (node->flags != 0) {
gl_pixel(x, y, snake_colors[node->color]);
} else {
gl_pixel(x, y, COLOR(0, 0, 0));
}
}
}
}
void Snake::set_tickdiv(int div) {
this->tickdiv = div;
}