rendered paste body#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 20typedef 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 snaketypedef 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<y1;++y) { cursor_to(x0,y); putch(WALL_VERTICAL); for(int x=x0;x<x1-1;++x) putch(' '); cursor_to(x1,y); putch(WALL_VERTICAL); } for(int x=x0;x<x1;++x) { cursor_to(x,y0); putch(WALL_HORIZ); cursor_to(x,y1); putch(WALL_HORIZ); } cursor_to(x0,y0); putch(WALL_TOPLEFT); cursor_to(x1,y0); putch(WALL_TOPRIGHT); cursor_to(x0,y1); putch(WALL_BOTTOMLEFT); cursor_to(x1,y1); putch(WALL_BOTTOMRIGHT);}void draw_title() { draw_box(20,5,60,20); cursor_to(23,7); printf("rNibbles - (C) Richard Whitty 2009"); cursor_to(28,9); printf("Controls: Up: W"); cursor_to(28,10); printf(" Down: S"); cursor_to(28,11); printf(" Left: A"); cursor_to(28,12); printf(" Right: D"); cursor_to(28,13); printf(" Quit: Q"); cursor_to(25,19); printf("Press any key to start..."); flush(); getch();}void update_score(int c,int s) { int BGC=BLACK; int TC=LIGHTGREY; cursor_to(WIDTH+2,4); colour(GOAL_COLOUR,BLACK); putch(GOAL_CHAR); colour(TC,BGC); printf(" : %d",c); cursor_to(WIDTH+2,6); printf("Score : %d",s); cursor_to(WIDTH+2,7); printf("Length: %d",curr_segment); cursor_to(WIDTH+2,8); printf("Level : %d",current_level);}void draw_score(int c,int s) { int BGC=BLACK; int TC=LIGHTGREY; colour(TC,BGC); draw_box(WIDTH,0,79,HEIGHT-1); cursor_to(WIDTH+2,2); print("rNibbles - Score"); update_score(c,s);}int cherries=0;int draw_end(int s) { while(!sys_poll(stdin,P_READ)) { getch(); } colour(WHITE,BLACK); draw_box(20,5,60,20); colour(LIGHTRED,BLACK); cursor_to(28,7); printf("GAME OVER!"); colour(LIGHTGREY,BLACK); cursor_to(28,9); switch(s) { case GAME_RUNNING: print("Loop somehow exited while game was running..."); break; case GAME_WON: print("Congratulations, you win!");break; case GAME_HITSELF: print(" Stop hitting yourself!!"); break; case GAME_HITWALL: print(" Oops..."); break; case GAME_QUIT: print(" Well thanks anyway...");break; } cursor_to(25,12); printf("You got %d cherries",cherries); cursor_to(25,13); printf("The snake was %d feet long!",curr_segment); cursor_to(25,18); print("Play again? (Y/N[N])"); flush(); sys_sleep(DISP_DELAY); for(;;) { switch(getch()) { case 'Y': case 'y': return 0; case 'n': case 'N': case '\n': case ' ': return 1; } }}void init_map() { memset(_map,0,sizeof(_map)); for(int y=1;y<HEIGHT-1;++y) { set_map(0,y,WALL_VERTICAL); set_map(WIDTH-1,y,WALL_VERTICAL); } for(int x=1;x<WIDTH-1;++x) { set_map(x,0,WALL_HORIZ); set_map(x,HEIGHT-1,WALL_HORIZ); } set_map(0,0,WALL_TOPLEFT); set_map(WIDTH-1,0,WALL_TOPRIGHT); set_map(0,HEIGHT-1,WALL_BOTTOMLEFT); set_map(WIDTH-1,HEIGHT-1,WALL_BOTTOMRIGHT);}void loadlevel(int i) { init_map(); if(i==0) { // bare map } else if(i==1) {// single line down middle int x1=WIDTH/2; int y1=HEIGHT/4; int y2=y1*3; while(y1<y2) { set_map(x1,y1,WALL_VERTICAL); ++y1; } }else if(i==2) { // two lines down middle int x1=WIDTH/3; int x2=x1*2; int y1=HEIGHT/4; int y2=y1*3; while(y1<y2) { set_map(x1,y1,WALL_VERTICAL); set_map(x2,y1,WALL_VERTICAL); ++y1; } }else if(i==3) { // upside-down U in centre int x1=WIDTH/3; int x2=x1*2; int y1=HEIGHT/4; int y2=y1*3; set_map(x1,y1,WALL_TOPLEFT); set_map(x2,y1,WALL_TOPRIGHT); for(int y=y1+1;y<y2;++y) { set_map(x1,y,WALL_VERTICAL); set_map(x2,y,WALL_VERTICAL); } for(int x=x1+1;x<x2;++x) { set_map(x,y1,WALL_HORIZ); } } else if(i==4) { // like a cross empty in the middle int x1=WIDTH/3; for(int x=1;x<x1;++x) { set_map(x,HEIGHT/2,WALL_HORIZ); set_map(WIDTH-x,HEIGHT/2,WALL_HORIZ); } int y1=HEIGHT/3; for(int y=1;y<y1;++y) { set_map(WIDTH/2,y,WALL_VERTICAL); set_map(WIDTH/2,HEIGHT-y,WALL_VERTICAL); } }}void draw_map() { for(int x=0;x<WIDTH;++x) { for(int y=0;y<HEIGHT;++y) { if(_map[x][y]) { cursor_to(x,y); putch(_map[x][y]); } } }}// allocates a segment.snake_seg* get_seg(u32 x,u32 y) { if(curr_segment>=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<N_LEVELS) { continue; } poll_char(0); // show the game over screen. If the user wants to quit, let him. if(draw_end(state)) { break; } cherries=0;current_level=0; score=0; } // reset the screen to something sane. colour(LIGHTGREY,BLACK); cls(); cursor_to(0,0); printf("\nThank you for playing rNibbles :p\n" "You got %d Cherries and scored %d points!\n",cherries,score); return 0;}