snake.c (8753B)
1 /* Snake 2 * 3 * Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free 4 * software under the GNU GPL v3 license or any later version. See 5 * COPYING in the root directory for details. 6 */ 7 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <conio.h> 11 #include "../cc65_eris/os.h" 12 13 #define FOOD_DENSITY (0x100) // Food on every nth field 14 #define SNAKE_LENGTH_MAX (0x1000) 15 #define DELAY (0x800) // Delay between moves 16 17 18 #define FALSE 0 19 #define TRUE !FALSE 20 21 /* Generic data structures */ 22 23 typedef struct screen_pos { 24 int x; 25 int y; 26 } screen_pos_t; 27 28 /* Data structures and functions for snake */ 29 30 enum direction {north, south, west, east}; 31 32 typedef struct snake { 33 // List of snake elements. start_element points to the tail of the 34 // snake and end_element to the head. When the snake moves, 35 // end_element is increased, elements[end_element] becomes the new 36 // head. Unless the snake grows in size, start_element is increased 37 // as well, removing the previous tail element. 38 screen_pos_t elements[SNAKE_LENGTH_MAX]; 39 unsigned start_element; 40 unsigned end_element; 41 // snake_length is the target length of the snake. On every move, 42 // the snake grows by one element until it has reached length 43 // snake_length 44 unsigned snake_length; 45 unsigned score; 46 enum direction direction; 47 unsigned char screen_w; 48 unsigned char screen_h; 49 int (*move)(struct snake *self, enum direction direction); 50 int (*check_collision)(struct snake *self); 51 int (*occupied)(struct snake *self, screen_pos_t *p); 52 } snake_t; 53 54 static int snake_move(snake_t *self, enum direction direction) { 55 screen_pos_t *prev, *new; 56 unsigned current_snake_length; 57 // Check if the snake grows. If it does not grow, we have to 58 // overwrite the current tail element with a space. 59 if (self->start_element <= self->end_element) 60 current_snake_length = self->end_element - self->start_element + 1; 61 else 62 current_snake_length = (self->end_element + 63 (SNAKE_LENGTH_MAX - self->start_element)) + 1; 64 // Snake can not grow beyond its maximal length. 65 current_snake_length = (current_snake_length > SNAKE_LENGTH_MAX ? 66 SNAKE_LENGTH_MAX : current_snake_length); 67 // Snake did not grow, we have to clear the previous start element 68 if (current_snake_length == self->snake_length) { 69 gotoxy(self->elements[self->start_element].x, 70 self->elements[self->start_element].y); 71 cputc(' '); 72 self->start_element = ((self->start_element) + 1) % SNAKE_LENGTH_MAX; 73 } 74 // The new element is added after the previous end_element. 75 // We copy the position of the previous end_element over 76 // and adjust the position according to the direction of the 77 // move. 78 prev = &(self->elements[self->end_element]); 79 self->end_element = ((self->end_element) + 1) % SNAKE_LENGTH_MAX; 80 new = &(self->elements[self->end_element]); 81 (new->y) = (prev->y); 82 (new->x) = (prev->x); 83 switch (direction) { 84 case north: 85 (new->y)--; 86 if (new->y == -1) 87 return -1; 88 break; 89 case south: 90 (new->y)++; 91 if (new->y == self->screen_h) 92 return -1; 93 break; 94 case west: 95 (new->x)--; 96 if (new->x == -1) 97 return -1; 98 break; 99 case east: 100 (new->x)++; 101 if (new->x == self->screen_w) 102 return -1; 103 break; 104 } 105 // Draw new end element 106 gotoxy(self->elements[self->end_element].x, 107 self->elements[self->end_element].y); 108 cputc('#'); 109 // Move cursor to neutral position 110 gotoxy(0, 0); 111 return 0; 112 } 113 114 static int snake_check_collision(snake_t *self) { 115 unsigned current; 116 int collision = FALSE; 117 if (self->start_element != self->end_element) { 118 for (current = self->start_element; 119 (collision == FALSE) && (current != self->end_element); 120 current = (current + 1) % SNAKE_LENGTH_MAX) { 121 if ((self->elements[self->end_element].x == 122 self->elements[current].x) && 123 (self->elements[self->end_element].y == 124 self->elements[current].y)) 125 collision = TRUE; 126 } 127 } 128 return collision; 129 }; 130 131 static int snake_occupied(snake_t *self, screen_pos_t *p) { 132 unsigned current = self->start_element; 133 int occupied = FALSE; 134 for (current = self->start_element; 135 (occupied == FALSE) && (current != self->end_element); 136 current = (current + 1) % SNAKE_LENGTH_MAX) { 137 if ((self->elements[current].x == p->x) && 138 (self->elements[current].y == p->y)) 139 occupied = TRUE; 140 } 141 if ((self->elements[self->end_element].x == p->x) && 142 (self->elements[self->end_element].y == p->y)) 143 occupied = TRUE; 144 return occupied; 145 }; 146 147 148 snake_t *snake_new(unsigned char screen_w, unsigned char screen_h, 149 unsigned start_x, unsigned start_y) { 150 snake_t *snake = malloc(sizeof(snake_t)); 151 snake->move = &snake_move; 152 snake->check_collision = &snake_check_collision; 153 snake->occupied = &snake_occupied; 154 snake->direction = east; 155 snake->start_element = 0; 156 snake->end_element = 0; 157 snake->elements[0].x = start_x; 158 snake->elements[0].y = start_y; 159 snake->snake_length = 1; 160 snake->score = 0; 161 snake->direction = east; 162 snake->screen_w = screen_w; 163 snake->screen_h = screen_h; 164 return snake; 165 }; 166 167 /* Data structures and functions for food */ 168 169 typedef struct food { 170 unsigned char screen_w; 171 unsigned char screen_h; 172 unsigned food_items; // Number of food items 173 screen_pos_t *elements; 174 snake_t *snake; 175 void (*check_eat)(struct food *self); 176 } food_t; 177 178 static void check_eat(struct food *self) { 179 unsigned i; 180 for (i = 0; i < self->food_items; i++) { 181 // Check if current element has been eaten 182 if ((self->snake->elements[self->snake->end_element].x == 183 self->elements[i].x) && 184 (self->snake->elements[self->snake->end_element].y == 185 self->elements[i].y)) { 186 self->snake->score++; 187 gotoxy(7, self->screen_h+1); 188 cprintf("%d", self->snake->score); 189 // Food item has been eaten. 190 self->elements[i].x = 0xFF; 191 self->elements[i].y = 0xFF; 192 // Grow snake 193 if (self->snake->snake_length < SNAKE_LENGTH_MAX) { 194 self->snake->snake_length += 1; 195 } 196 } 197 // Check if current element is empty 198 if ((self->elements[i].x == 0xFF) && 199 (self->elements[i].y == 0xFF)) { 200 // Current element is not placed. Try to place it. 201 // Biased way to draw random numbers 202 self->elements[i].x = rand() % self->screen_w; 203 self->elements[i].y = rand() % self->screen_h; 204 // Make sure we did not place the new food element on a 205 // snake element 206 if (snake_occupied(self->snake, &(self->elements[i]))) { 207 self->elements[i].x = 0xFF; 208 self->elements[i].y = 0xFF; 209 } 210 else { 211 // Draw new food element 212 gotoxy(self->elements[i].x, 213 self->elements[i].y); 214 cputc('*'); 215 gotoxy(0, 0); 216 } 217 } 218 } 219 } 220 221 food_t *food_new(unsigned char screen_w, unsigned char screen_h, 222 unsigned food_items, struct snake *snake) { 223 unsigned i; 224 food_t *food = malloc(sizeof(food_t)); 225 food->screen_w = screen_w; 226 food->screen_h = screen_h; 227 food->food_items = food_items; 228 food->elements = calloc(food_items, sizeof(screen_pos_t)); 229 food->snake = snake; 230 food->check_eat = &check_eat; 231 for (i = 0; i < food->food_items; i++) { 232 // Initial placement of food items 233 // Biased way to draw random numbers 234 food->elements[i].x = rand() % food->screen_w; 235 food->elements[i].y = rand() % food->screen_h; 236 // Draw new food element 237 gotoxy(food->elements[i].x, 238 food->elements[i].y); 239 cputc('*'); 240 } 241 return food; 242 }; 243 244 245 void main(void) { 246 unsigned char screen_w, screen_h; 247 snake_t *snake; 248 food_t *food; 249 unsigned delay; 250 unsigned char key; 251 enum direction heading; 252 unsigned char counter; 253 254 // TODO: Initialize RNG 255 256 do { // Loop forever 257 cprintf("q to quit, any other key to play."); 258 if (cgetc() == 'q') { 259 return; 260 } 261 clrscr(); 262 screensize(&screen_w, &screen_h); 263 screen_h--; // Last line reserved for status information 264 snake = snake_new(screen_w, screen_h, screen_w/2, screen_h/2); 265 food = food_new(screen_w, screen_h, screen_w * screen_h / FOOD_DENSITY + 5, 266 snake); 267 gotoxy(0, screen_h+1); 268 cputs("Score: 0 Move: wasd Quit: q"); 269 heading = east; 270 counter = 0; 271 while (!snake->check_collision(snake)) { 272 if (kbhit()) { 273 key = cgetc(); 274 switch (key) { 275 case 'a': 276 heading = west; 277 break; 278 case 's': 279 heading = south; 280 break; 281 case 'd': 282 heading = east; 283 break; 284 case 'w': 285 heading = north; 286 break; 287 case 'q': 288 return; 289 } 290 } 291 if (snake->move(snake, heading) == -1) { 292 // crashed into border of screen 293 break; 294 }; 295 food->check_eat(food); 296 for (delay = 0; delay < DELAY; delay++) { 297 }; 298 }; 299 free(snake); 300 gotoxy(0,0); 301 cprintf("CRASH!\r\n"); 302 // Delay loop after crash 303 for (delay = 0; delay < 0xFFFF; delay++) { 304 }; 305 } while (TRUE); 306 }