eris2010

Documentation: http://frombelow.net/projects/eris2010/
Clone: git clone https://git.frombelow.net/eris2010.git
Log | Files | Refs | Submodules | README | LICENSE

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 }