commit adea577decb710307f3dbabd2f398a468a93ba49 Author: Lukáš Dratva Date: Tue Jun 4 07:30:46 2019 +0200 funguje diff --git a/pvjc19cv9/Makefile b/pvjc19cv9/Makefile new file mode 100644 index 0000000..e9521fe --- /dev/null +++ b/pvjc19cv9/Makefile @@ -0,0 +1,15 @@ +CFLAGS=-std=c99 -Wall -g +CC=gcc + +all: game + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm *.o + rm game + +game: main.o game.o world.o snake.o + $(CC) main.o game.o world.o snake.o -ltermbox -lm -o game + diff --git a/pvjc19cv9/README.md b/pvjc19cv9/README.md new file mode 100644 index 0000000..0d01c7b --- /dev/null +++ b/pvjc19cv9/README.md @@ -0,0 +1,17 @@ +# Snake Master + +Implement Snake game. + +Make the game to pass the automatic tests and make the game to be nice. + +## Files + +Please do not change file names. + +- `snake.h`: you implementation should follow this header. +- `snake.c`: implemement the game according to the documentation in header file in this file to pass automatic tests. +- `Makefile`: rules to build the game. +- `game.c`: modify this file to change the appereance of the game and the initial state. +- `main.c`: trivial main function that runs the game +- `world.c`: world game loop and ASCII graphics library (do not change). +- `world.h`: world library interface (do not change). diff --git a/pvjc19cv9/game.c b/pvjc19cv9/game.c new file mode 100644 index 0000000..1305758 --- /dev/null +++ b/pvjc19cv9/game.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include "world.h" +#include "snake.h" + +// Start is called one in the beginning +void start(struct world* world,int argc, char** argv){ + // Allocate memory for the state + struct state* st = calloc(1,(sizeof(struct state))); + // Store pointer to the state to the world variable + world->state = st; + st->snake = NULL; + st->sx = 0; + st->sy = -1; + int cy = world->height/2; + int cx = world->width/2; + st->snake = add_snake(st->snake,cx + 5 ,cy); + st->snake = add_snake(st->snake,cx + 4,cy); + st->snake = add_snake(st->snake,cx + 3,cy); + st->snake = add_snake(st->snake,cx + 2,cy); + st->snake = add_snake(st->snake,cx + 1,cy); + st->snake = add_snake(st->snake,cx,cy); + + int h = world->height; + int w = world->width; + + st->foodx[0] = rand() % w; + st->foody[0] = rand() % h; + st->foodx[1] = rand() % w; + st->foody[1] = rand() % h; + st->foodx[2] = rand() % w; + st->foody[2] = rand() % h; + st->foodx[3] = rand() % w; + st->foody[3] = rand() % h; + st->foodx[4] = rand() % w; + st->foody[4] = rand() % h; +} + +// Step is called in a loop once in interval. +// It should modify the state and draw it. +int step(struct world* w,int key){ + // Get state pointer + struct state* st = w->state; + enum direction dir = DIR_NONE; + if (key == TB_KEY_ARROW_LEFT){ + dir = DIR_LEFT; + } + else if (key == TB_KEY_ARROW_RIGHT){ + dir = DIR_RIGHT; + } + else if (key == TB_KEY_ARROW_UP){ + dir = DIR_UP; + } + else if (key == TB_KEY_ARROW_DOWN){ + dir = DIR_DOWN; + } + int r = step_state(st,dir,w->width,w->height); + struct snake* sn = st->snake; + while (sn != NULL){ + set_character(w,sn->x,sn->y,'x'); + sn = sn->next; + } + for (int i = 0 ; i < FOOD_COUNT; i++){ + if (st->foodx[i] >= 0 && st->foody[i] >= 0){ + set_character(w,st->foodx[i],st->foody[i],'*'); + } + } + if (r){ + set_message(w,20,20,"Koniec"); + } + if (key == TB_KEY_ESC){ + // Non zero means finish the loop and stop the game. + return 1; + } + + return 0; +} + +// Stop is called after game loop is finished +void stop(struct world* world){ + // Free memory for game state + struct state* st = world->state; + free_snake(st->snake); + free(st); +} diff --git a/pvjc19cv9/main.c b/pvjc19cv9/main.c new file mode 100644 index 0000000..bbb24bc --- /dev/null +++ b/pvjc19cv9/main.c @@ -0,0 +1,5 @@ +#include "world.h" + +int main(int argc, char** argv){ + game(argc,argv); +}; diff --git a/pvjc19cv9/snake.c b/pvjc19cv9/snake.c new file mode 100644 index 0000000..fd23607 --- /dev/null +++ b/pvjc19cv9/snake.c @@ -0,0 +1,151 @@ +#include "snake.h" +#include + + +struct snake* add_snake(struct snake* snake,int x,int y){ + +struct snake* new=calloc(sizeof(struct snake),1); + + new->x=x; + + new->y=y; + + new->next=snake; + + return new; + + +} + + + +struct snake* remove_snake(struct snake* snake){ + + if(snake==NULL)return NULL; + + if(snake->next==NULL){ + + free(snake); + + + return NULL; + + } + + struct snake* temp=snake; + + while(temp->next->next!=NULL){ + + temp=snake->next; + + + } + + + free(temp->next); + + temp->next=NULL; + + + return snake; + +} + + + + +void free_snake(struct snake* sn){ + + if(sn==NULL)return; + + if(sn->next!=NULL)free_snake(sn->next); + + free(sn); + + sn=NULL; + +} + + + + +int step_state(struct state* st,enum direction dir,int width,int height){ + + + if(st==NULL)return 0; + + switch(dir){ + + case DIR_DOWN:st->sx=0;st->sy=1;break; + + case DIR_UP:st->sx=0;st->sy=-1;break; + + case DIR_LEFT:st->sx=-1;st->sy=0;break; + + case DIR_RIGHT:st->sx=1;st->sy=0;break; + + default:break; + + + } + + int newx=st->snake->x+st->sx; + + if(newx>width)return END_WALL; + + if(newx<0)return END_WALL; + + int newy=st->snake->y+st->sy; + + if(newy>height)return END_WALL; + + if(newy<0)return END_WALL; + +struct snake* pos=st->snake; + + while(pos!=NULL){ + + if(pos->x==newx && pos->y==newy){ + + return END_SNAKE; + + } + + pos=pos->next; + + } + + for(int i=0;ifoodx[i]==newx && st->foody[i]==newy){ + + st->foodx[i]=-1; + + st->foody[i]=-1; + + add_snake(st->snake,newx,newy); + + return END_CONTINUE; + + + } + + } + + + for(int i=0;i<=FOOD_COUNT;i++){ + + if(i==FOOD_COUNT)return END_FOOD; + + if(st->foodx>=0)break; + + } + + + st->snake=add_snake(st->snake,newx,newy); + + remove_snake(st->snake); + + return END_CONTINUE; + +} diff --git a/pvjc19cv9/snake.h b/pvjc19cv9/snake.h new file mode 100644 index 0000000..974363a --- /dev/null +++ b/pvjc19cv9/snake.h @@ -0,0 +1,128 @@ +#ifndef snake_h_INCLUDED +#define snake_h_INCLUDED + + +// Number of food items on the plane +#define FOOD_COUNT 5 + +/** + * One part of the snake; + * + * The snake is a linked list; + */ + +struct snake { + // x position of the snake part + int x; + // y position of the snake part + int y; + // Pointer to the next snake part. + // The last part of the snake has NULL pointer to the next part. + struct snake* next; +}; + +/** + * Direction of the snake + */ + +enum direction { + // No direction command was given + DIR_NONE, + // Up arrow, direction to north + DIR_UP, + // Down arrow, direction to south + DIR_DOWN, + // Left arow, direction to west + DIR_LEFT, + // Right arrow, direction east. + DIR_RIGHT +}; + +enum endgame { + // Continue the game + END_CONTINUE = 0, + // Snake hit a wall + END_WALL, + // Snake hit itself + END_SNAKE, + // No food left + END_FOOD, + // Other reason to end + END_USER +}; + +/** + * State of the game. + * + * The state consists of the snake, its speed and food on the plane. + * + * The snake is a linked list of snake parts. + * + * Speed vector is a vector added to the last head position to create a new head. + * + * Food are points on the plane. Food with negative coordinates meads food is already eaten. + */ + +struct state { + // Snake as a linked list + struct snake* snake; + // X of the speed vector + int sx; + // Y of the speed vector + int sy; + // X of the food positions + int foodx[FOOD_COUNT]; + // Y of the food positions + int foody[FOOD_COUNT]; +}; + +/** + * Add a new snake part with given position. The new snake part becomes the new head. + * @param head of the snake. + * @param x coordinate of the new head; + * @param y coordinate of the new head. + * @return new head of the snake. + */ +struct snake* add_snake(struct snake* snake,int x,int y); +/** + * Remove the last snake part. + * The last snake part should always have NULL next pointer. + * @param head of the snake. + * @return new head of the snake. + */ +struct snake* remove_snake(struct snake* snake); +/** + * Remove each snake part; + * @param head of the snake. + */ +void free_snake(struct snake* sn); +/** + * Change game state. + * + * The function should first set the speed vector according to the direction. + * If direction is: + * - DIR_NONE, speed vector does not change + * - DIR_UP, speed vector is 0,-1 + * - DIR_DOWN, speed vector is 0,1 + * - DIR_LEFT, speed vector is -1, 0 + * - DIR_RIGHT, speed vector is 1, 0 + * + * Then it calculates the new position of the head according to the old position + * of the snake head and the speed vector. + * + * - If the new position is on the snake, end the game, return END_SNAKE. + * - If the new position is on the plane, add new snake part on the new position and remove the last part of the snake, return END_CONTINUE. + * - If the new position is on the food, mark food as eaten and add new snake part on the position of the food. Return END_CONTINUE. + * - If there is no food left, end the game, return END_FOOD. + * + * @param old state of the game. + * @param direction command, one of "enum direction". + * @param width of the plane. + * @param height of the plane. + * @return reason to end the game according to enum endgame. + */ +int step_state(struct state* state,enum direction dir,int width,int height); + + +#endif // snake_h_INCLUDED + diff --git a/pvjc19cv9/world.c b/pvjc19cv9/world.c new file mode 100644 index 0000000..d0204db --- /dev/null +++ b/pvjc19cv9/world.c @@ -0,0 +1,117 @@ +#include "world.h" +#include +#include +#include +#include +#include +#include + +void destroy_world(struct world* world){ + stop(world); + tb_shutdown(); +} + +void end_message(struct world* world, const char* message){ + destroy_world(world); + puts(message); + exit(0); +} + +void assert_message(int event, struct world* world, const char* message){ + if (event == 0){ + end_message(world,message); + } +} + +void init_world(struct world* w) { + assert_message(w != NULL,w,"init_world:: world is NULL"); + w->height = tb_height(); + w->width = tb_width(); + if (w->interval <= 0){ + w->interval = 100; + } +} + +void set_color_character(struct world* w,int x,int y,int character,uint16_t foreground,uint16_t background) { + assert_message(w != NULL,w,"set_character:: world is NULL"); + if (x < 0 || x >= w->width){ + char msg[100]; + sprintf(msg,"set_character:: width %d is out of bounds (0,%d)",x,w->width); + end_message(w,msg); + } + if (y < 0 || y >= w->height){ + char msg[100]; + sprintf(msg,"set_character:: height %d is out of bounds (0,%d)",y,w->height); + end_message(w,msg); + } + tb_change_cell(x,y,character,foreground,background); +} + +void set_character(struct world* w,int x,int y,int character) { + set_color_character(w,x,y,character,TB_WHITE,TB_BLACK); +} + +void set_message(struct world* w,int x,int y,const char* message) { + int l = strlen(message); + for (int i = 0; i < l; i++){ + set_character(w,x+i,y,message[i]); + } +} + +void set_color_message(struct world* w,int x,int y,const char* message,int character,uint16_t foreground,uint16_t background) { + int l = strlen(message); + for (int i = 0; i < l; i++){ + set_color_character(w,x+i,y,message[i],foreground,background); + } +} + +int step_world(struct world* world,int eventkey){ + init_world(world); + tb_clear(); + int r = step(world,eventkey); + tb_present(); + return r; +} + +void game(int argc, char** argv){ + srand(time(NULL)); + int r = tb_init(); + if (r < 0){ + puts("Termbox Error."); + return; + } + struct tb_event event; + struct world world; + memset(&world,0,sizeof(struct world)); + init_world(&world); + start(&world,argc,argv); + r = step_world(&world,WORLD_START_EVENT); + while (!r) { + int t = tb_peek_event(&event,world.interval); + if (t == -1){ + end_message(&world,"termox poll error"); + } + int eventkey = WORLD_TIMEOUT_EVENT; + if (event.type == TB_EVENT_KEY){ + eventkey = event.key; + if (event.ch > 0){ + eventkey = event.ch; + } + } + else if (event.type == TB_EVENT_RESIZE){ + eventkey = WORLD_RESIZE_EVENT; + } + else if (event.type == TB_EVENT_MOUSE){ + // Ignore mouse events + continue; + } + r = step_world(&world,eventkey); + if (eventkey == TB_KEY_CTRL_C){ + r = 1; + } + else if (eventkey == TB_KEY_CTRL_D){ + r = 1; + } + } + destroy_world(&world); +}; diff --git a/pvjc19cv9/world.h b/pvjc19cv9/world.h new file mode 100644 index 0000000..840a7c0 --- /dev/null +++ b/pvjc19cv9/world.h @@ -0,0 +1,82 @@ +#ifndef _GAME_H_ +#define _GAME_H_ + +// Signalizes game next loop +// (abuses ctrl tilda keypress code) +#define WORLD_TIMEOUT_EVENT 0 +// Signlizes the first step of the game loop +// (abuses ctrl a keypress) +#define WORLD_START_EVENT 1 +// Signalizes resize of the screen (width and height have changed) +// (abuses ctrl b keypress) +#define WORLD_RESIZE_EVENT 2 + +#include +/** + * Game world represented as a rectangular matrix of colorful characters. + * + * Point [0,0] is displayed the upper left corner of the screen. + * + */ + +struct world { + /** + * Width of the screen. + */ + int width; + /** + * Height of the screen. + */ + int height; + /** + * State of the game managed by the programmer. + */ + void* state; + /** + * Interval to wait for next step. + */ + int interval; +}; + + +/** + * Sets cell to a state. + * @param world + * @param x coordinate of cell + * @param y coordinate of cell + * @param new state of the cell + */ +void set_character(struct world* w,int x,int y,int value); + +void set_message(struct world* w,int x,int y,const char* message); + +/** + * Runs the game loop. + * @param number of commandline arguments + * @param commandline arguments + */ +void game(int argc,char** argv); + +/** + * Initializes user state. + * @param world + * @param number of commandline arguments + * @param commandline arguments + * + */ +void start(struct world*,int argc,char** argv); + +/** + * Free user state. + * @param world + */ +void stop(struct world* world); + +/** + * Changes state of the world according to pressed keys or events. + * @param world + * @param code of the event. Event is WORLD_START_EVENT for the first step, WORLD_TIMEOUT_EVENT for regular step or Termbox key code (TB_KEY_*) for key press event. Mouse events are ignored. + */ +int step(struct world* world,int key); + +#endif