#include "lib.h" // this is a simple nibbles like game for the OS. // it should actually be portable to other unix systems // because most of lib.c will be as well. #define FRAME_DELAY 500 #define SPEEDUP 20 #define CHERRIES_PER_LEVEL 10 #define POINTS_PER_CHERRY 8 #define N_LEVELS 5 #define START_LEVEL 0 #define INITIAL_LENGTH 6 #define MAX_SEGMENTS ((CHERRIES_PER_LEVEL*GROW_AMOUNT)+(CHERRIES_PER_LEVEL*N_LEVELS)+INITIAL_LENGTH) #define DISP_DELAY 500 #define GROW_AMOUNT 5 #define QUEUE_KEYPRESSES 0 #define SNAKE_CHAR 0xdb #define GOAL_CHAR 0xeb #define GOAL_COLOUR LIGHTRED #define WALL_VERTICAL 0xba #define WALL_HORIZ 0xCD #define WALL_TOPLEFT 0xc9 #define WALL_TOPRIGHT 0xbb #define WALL_BOTTOMLEFT 0xc8 #define WALL_BOTTOMRIGHT 0xbc #define WIDTH 60 #define HEIGHT 20 typedef unsigned char map_el; map_el _map[WIDTH][HEIGHT]; void set_map(int x,int y,map_el e) { if(x<0||x>=WIDTH||y<0||y>HEIGHT) { colour(LIGHTGREY,BLACK); cls(); printf("Invalid map coord: %d,%d\n",x,y); sys_exit(1); } _map[x][y]=e; } typedef enum { GAME_RUNNING,GAME_HITSELF,GAME_HITWALL,GAME_WON,GAME_QUIT } game_state; // one bit of the snake typedef struct snake_seg{ u32 x,y; struct snake_seg *next; } snake_seg; // this actually goes against conventional linked list terminology. // the snake_tail is the head of the list, whereas snake_head is the tail. snake_seg *snake_head=0; snake_seg *snake_tail=0; int current_level=START_LEVEL; // simple pool allocator. snake_seg segments[MAX_SEGMENTS]; int curr_segment=0; void draw_box(int x0,int y0,int x1,int y1) { for(int y=y0;y=MAX_SEGMENTS) { return 0; printf("\nsnake got too long, sorry :(\n"); sys_exit(1); } snake_seg *ret=segments+(curr_segment++); ret->x=x; ret->y=y; ret->next=0; return ret; } // draws the segment on the screen. map_el draw_seg(snake_seg* seg) { map_el retval=_map[seg->x][seg->y]; set_map(seg->x,seg->y,SNAKE_CHAR); colour(LIGHTGREEN,0); cursor_to(seg->x,seg->y); putch(SNAKE_CHAR); return retval; } // erases the segment from the screen. void erase_seg(snake_seg* seg) { set_map(seg->x,seg->y,0); cursor_to(seg->x,seg->y); putch(' '); } int rand(int lim) { static long a = 100001; a = (a * 125) % 2796203; return ((a % lim)); } void place_new_goal() { u32 x,y; do { x=rand(WIDTH); y=rand(HEIGHT); } while(_map[x][y]); set_map(x,y,GOAL_CHAR); cursor_to(x,y); colour(GOAL_COLOUR,BLACK); putch(GOAL_CHAR); } static inline char poll_char(int queue) { char c=0; if(queue) { if(!sys_poll(stdin,P_READ)) c=getch(); } else { while(!sys_poll(stdin,P_READ)) c=getch(); } return c; } // 'nuff said. int main(int argc,char**argv) { int score=0; cherries=0; draw_title(); current_level=START_LEVEL; for(;;){ char direction='U'; score+=cherries; int growing=score/POINTS_PER_CHERRY; cherries=0; curr_segment=0; snake_tail=snake_head=get_seg(5,5); growing+=INITIAL_LENGTH; snake_tail->next=snake_head; colour(LIGHTGREY,BLACK); cls(); loadlevel(current_level); draw_map(); draw_score(cherries,score); draw_seg(snake_head); draw_seg(snake_tail); place_new_goal(); cursor_to(0,0); flush(); poll_char(QUEUE_KEYPRESSES); getch(); // wait for keypress char c=0; game_state state=GAME_RUNNING; int dx=0,dy=-1; while(state==GAME_RUNNING) { c=poll_char(QUEUE_KEYPRESSES); if(c) { // if there's no key pressed, skip this processing. switch(c) { case 'w': if(direction!='D') direction='U'; break; case 's': if(direction!='U') direction='D'; break; case 'a': if(direction!='R') direction='L'; break; case 'd': if(direction!='L') direction='R'; break; case 'q': state=GAME_QUIT; continue; case 'p': sys_sleep(200); poll_char(QUEUE_KEYPRESSES) ; c=getch(); continue; } dx=dy=0; switch(direction) { case 'U': dy=-1;break; case 'D': dy=1;break; case 'L': dx=-1;break; case 'R': dx=1;break; default: printf("Direction set incorrectly: '%c'\n",direction); sys_exit(1); } } u32 new_x=snake_head->x+dx; u32 new_y=snake_head->y+dy; if(growing) { // grow the snake --growing; snake_head->next=get_seg(new_x,new_y); update_score(cherries,score); } else { // if it's not growing, then get rid of the last // segment. erase_seg(snake_tail); snake_seg* keep=snake_tail; snake_tail=keep->next; // and reuse the structure to be the new head. keep->next=0; keep->x=new_x;keep->y=new_y; snake_head->next=keep; } // the head moves down the list. snake_head=snake_head->next; switch(draw_seg(snake_head)) { case SNAKE_CHAR: state=GAME_HITSELF; break; case WALL_HORIZ: case WALL_BOTTOMLEFT:case WALL_BOTTOMRIGHT: case WALL_VERTICAL: case WALL_TOPLEFT:case WALL_TOPRIGHT: state=GAME_HITWALL; break; case GOAL_CHAR: ++cherries; score+=POINTS_PER_CHERRY; growing+=GROW_AMOUNT; if(cherries>=CHERRIES_PER_LEVEL) state=GAME_WON; else place_new_goal(); update_score(cherries,score); break; case 0: // empty space, continue; break; default: printf("Other char\n"); // shouldn't happen. break; } cursor_to(0,0); // local echo is still enabled.... flush(); // lib.c might be buffering this. // speed up the game as the player gets more cherries. sys_sleep(FRAME_DELAY-(cherries*SPEEDUP)); } sys_sleep(300); if(state==GAME_WON && ++current_level