commit 74d9392626609b3b25adcb84593af7ccc95c38ad Author: Daniel Hladek Date: Thu Apr 23 17:39:46 2020 +0200 initial diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4b19473 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +CFLAGS=-std=c99 -Wall -g + +all: game + +%.o: %.c + gcc $(CFLAGS) -c $< -o $@ + +clean: + rm *.o + rm game + +game: main.o game.o world.o snake.o + gcc -rdynamic -g main.o game.o world.o snake.o -lcurses -lm -o game + diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d01c7b --- /dev/null +++ b/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/game.c b/game.c new file mode 100644 index 0000000..8464a73 --- /dev/null +++ b/game.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include "world.h" +#include "game.h" +#include "snake.h" + + +// Start is called one in the beginning +void* init_game(struct world* world){ + + // Allocate memory for the state + struct state* st = calloc(1,(sizeof(struct state))); + st->snake = NULL; + st->sx = 1; + st->sy = 0; + int cy = world->height/2; + int cx = world->width/2 - 5; + for (int i = 0; i < 5; i++){ + st->snake = add_snake(st->snake,cx + i ,cy); + } + + int h = world->height; + int w = world->width; + for (int i = 0; i < 5; i++){ + st->foodx[i] = rand() % w; + st->foody[i] = rand() % h; + } + return st; + +} + +// Step is called in a loop once in interval. +// It should modify the state and draw it. +int world_event(struct world* w,void* game){ + // Get state pointer + struct state* st = game; + int key = w->key; + + if (key == KEY_RIGHT){ + st->sx = 1; + st->sy = 0; + } + else if (key == KEY_LEFT){ + st->sx = -1; + st->sy = 0; + } + else if (key == KEY_DOWN){ + st->sx = 0; + st->sy = 1; + } + else if (key == KEY_UP){ + st->sx = 0; + st->sy = -1; + } + else if (key == KEY_ENTER){ + // Non zero means finish the loop and stop the game. + return 1; + } + st->width = w->width; + st->height = w->height; + int r = step_state(st); + // Draw snake + struct snake* sn = st->snake; + while (sn != NULL){ + set_cell(w,'x',sn->x,sn->y); + sn = sn->next; + } + for (int i = 0 ; i < FOOD_COUNT; i++){ + if (st->foodx[i] >= 0 && st->foody[i] >= 0){ + set_cell(w,'*',st->foodx[i],st->foody[i]); + } + } + if (r){ + char message[] = "Koniec"; + for (int i = 0; i < 6; i++){ + set_cell(w,message[i],10+i,10); + } + } + return 0; +} + diff --git a/game.h b/game.h new file mode 100644 index 0000000..67af71e --- /dev/null +++ b/game.h @@ -0,0 +1,26 @@ +#ifndef _GAME_H_INCLUDE_ +#define _GAME_H_INCLUDE_ +#include "world.h" + +// Set of variables that expresses state of the game. +// +struct game { + // X position of the cat + int catx; + // Y opsition of the cat + int caty; + // X position of the mouse + int mousex; + // Y position of the mouse + int mousey; + // Funky message + char message[100]; +}; + +// Returns pointer to newly allocated world +void* init_game(struct world* g); + +// Changes world according to the game state (pressed key, screen size or other event) +int world_event(struct world* world,void* game); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..cacab63 --- /dev/null +++ b/main.c @@ -0,0 +1,9 @@ +#include "game.h" +#include "world.h" +#include + + +int main(int argc, char** argv){ + start_world(world_event,init_game,free); + return 0; +} diff --git a/snake.c b/snake.c new file mode 100644 index 0000000..7038cdb --- /dev/null +++ b/snake.c @@ -0,0 +1,23 @@ +#include "snake.h" +#include + +struct snake* add_snake(struct snake* snake,int x,int y){ + return NULL; +} + +struct snake* remove_snake(struct snake* snake){ + return NULL; +} + +void free_snake(struct snake* sn){ +} + +int is_snake(struct snake* snake,int x,int y){ + return 0; +} + +int step_state(struct state* st){ + int nx = (st->snake->x + st->sx); + int ny = (st->snake->y + st->sy); + return END_CONTINUE; +} diff --git a/snake.h b/snake.h new file mode 100644 index 0000000..4d1ff74 --- /dev/null +++ b/snake.h @@ -0,0 +1,114 @@ +#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; +}; + +// End game reason constants, return value of step_state +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 food positions + int foodx[FOOD_COUNT]; + // Y of the food positions + int foody[FOOD_COUNT]; + int sx; + int sy; + int width; + int height; +}; + +/** + * 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); + +/** + * Finds out if given coordinates are part of the snake. + * @param snake + * @param x coordinate to search in snake + * @param y coordinate to search in snake + * @return True, if there is a snake part with coordinates x,y. False otherwise + * + */ +int is_snake(struct snake* snake,int x, int y); +/** + * Remove and free each snake part; + * @param head of the snake. + */ +void free_snake(struct snake* sn); +/** + * Change game state. + * + * The function shoud calculate new posision of the snake head + * from the current position and speed vector. + * Then it should modify snake parst or food coordinates according to the rules: + * + * - If the new position is on the snake, end the game, return END_SNAKE. + * - If the new position is on the food, mark food as eaten + * (set its coordinates to -1) and add new snake part on the position of the food. If there is no food left, return END_FOOD. else return END_CONTINUE. + * - 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. + * + * @param current state of the game + * @return reason to end the game according to enum endgame. + */ +int step_state(struct state* state); + + +#endif // snake_h_INCLUDED + diff --git a/world.c b/world.c new file mode 100644 index 0000000..bc519e1 --- /dev/null +++ b/world.c @@ -0,0 +1,138 @@ +#include "world.h" +#include +#include +#include +#include +#include + +#include +#include + +void print_backtrace() { + //https://linux.die.net/man/3/backtrace_symbols + int j, nptrs; + void *buffer[100]; + char **strings; + nptrs = backtrace(buffer, 100); + printf("backtrace() returned %d addresses\n", nptrs); + + /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO) + would produce similar output to the following: */ + + strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { + perror("backtrace_symbols"); + exit(EXIT_FAILURE); + } + for (j = 0; j < nptrs; j++) + printf("%s\n", strings[j]); + free(strings); +} + +void abort_game(const char* message){ + endwin(); + print_backtrace(); + puts(message); + exit(1); +} + +void assert_message(int event,const char* message){ + if (event == 0){ + abort_game(message); + } +} + +void stamp_cell(struct world* w,int character,enum color_stamp pen, int x,int y) { + assert_message(w != NULL,"stamp_cell:: world is NULL"); + if (x < 0 || x >= w->width){ + char msg[100]; + sprintf(msg,"stamp_cell:: width %d is out of bounds (0,%d)",x,w->width); + abort_game(msg); + } + if (y < 0 || y >= w->height){ + char msg[100]; + sprintf(msg,"stamp_cell:: height %d is out of bounds (0,%d)",y,w->height); + abort_game(msg); + } + attron(pen); + mvaddch(y,x,character); + attroff(pen); +} + +void set_cell(struct world* w,int character,int x,int y) { + stamp_cell(w,character,0,x,y); +} + +int start_world(int (*world_event)(struct world* world,void* game),void* (*init_game)(struct world*),void (*destroy_game)(void*)){ + srand(time(NULL)); + int r = 1; + if (initscr() == NULL){ + // TODO Which Error? + puts("Curses Error."); + return -1; + } + noecho(); // Nevypisuj vstup na obrazovku + cbreak(); // Zabudni starý vstup + nodelay(stdscr,TRUE); // Nečakaj na stlačenie + keypad(stdscr,TRUE); // Aktivuje šípky + curs_set(FALSE); // Neviditeľný kurzor + if (has_colors()){ // Zistenie či terminál podporuje farby + start_color(); + // Pera na ciernom podklade + init_pair(BLACK_FRONT, COLOR_BLACK, COLOR_BLACK); + init_pair(WHITE_FRONT, COLOR_WHITE, COLOR_BLACK); + init_pair(RED_FRONT, COLOR_RED, COLOR_BLACK); + init_pair(GREEN_FRONT, COLOR_GREEN, COLOR_BLACK); + init_pair(BLUE_FRONT, COLOR_BLUE, COLOR_BLACK); + init_pair(CYAN_FRONT, COLOR_CYAN, COLOR_BLACK); + init_pair(MAGENTA_FRONT,COLOR_MAGENTA, COLOR_BLACK); + init_pair(YELLOW_FRONT, COLOR_YELLOW, COLOR_BLACK); + init_pair(BLACK_BACK, COLOR_BLACK, COLOR_BLACK); + init_pair(WHITE_BACK, COLOR_BLACK, COLOR_WHITE); + init_pair(RED_BACK, COLOR_BLACK, COLOR_RED); + init_pair(GREEN_BACK, COLOR_BLACK, COLOR_GREEN); + init_pair(BLUE_BACK, COLOR_BLACK, COLOR_BLUE); + init_pair(CYAN_BACK, COLOR_BLACK, COLOR_CYAN); + init_pair(MAGENTA_BACK, COLOR_BLACK, COLOR_MAGENTA); + init_pair(YELLOW_BACK, COLOR_BLACK, COLOR_YELLOW); + } + else { + puts("No colors!\n"); + } + struct world world; + world.height = LINES; + world.width = COLS; + world.interval = 100; + world.key = ERR; + + void* game = NULL; + if (init_game != NULL){ + game = init_game(&world); + assert_message(game != NULL,"init_game should return non null pointer"); + } + timeout(world.interval); + // Initial step + r = world_event(&world,game); + refresh(); + while (!r) { + world.height = LINES; + world.width = COLS; + world.key = getch(); + // Clear screen + mvaddch(0,0,' '); + int screenchars = LINES*COLS; + for (int j = 1; j < screenchars;j++ ){ + addch(' '); + } + // Draw new world + r = world_event(&world,game); + refresh(); + // set new timeout + timeout(world.interval); + } + if (destroy_game != NULL){ + destroy_game(game); + } + endwin(); + return r; +}; diff --git a/world.h b/world.h new file mode 100644 index 0000000..e83e126 --- /dev/null +++ b/world.h @@ -0,0 +1,100 @@ +#ifndef _WORLD_H_ +#define _WORLD_H_ + +#include +/** + * World represented as a rectangular matrix of colorful characters. + * + * Point [0,0] is displayed the upper left corner of the screen. + * + */ + +struct world { + /** + * Last width of the screen. + */ + int width; + /** + * Last height of the screen. + */ + int height; + /** + * Interval in miliseconds to wait for next step. + */ + int interval; + /** + * Last pressed key or Curses event. + * + * Special event values: + * ERR if timeout, + * KEY_RESIZE if screen resize + * KEY_EVENT, other event, + * KEY_MOUSE, mouse clicked + * + * Key values: + * + * ' ' Space + * KEY_DOWN Arrow down + * KEY_UP Arrow up + * KEY_LEFT Arrow left + * KEY_RIGHT Arrow right + * KEY_A1 Upper left of keypad + * KEY_A3 Upper right of keypad + * KEY_B2 Center of keypad + * KEY_C1 Lower left of keypad + * KEY_C3 Lower right of keypad + * + * KEY_ENTER + * KEY_BACKSPACE + */ + int key; +}; + + +enum color_stamp { + BLACK_FRONT, + WHITE_FRONT, + RED_FRONT, + GREEN_FRONT, + BLUE_FRONT, + CYAN_FRONT, + MAGENTA_FRONT, + YELLOW_FRONT, + BLACK_BACK, + WHITE_BACK, + RED_BACK, + GREEN_BACK, + BLUE_BACK, + CYAN_BACK, + MAGENTA_BACK, + YELLOW_BACK, +}; + +/** + * 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_cell(struct world* w,int character,int x,int y); + +void stamp_cell(struct world* w,int character,enum color_stamp stamp,int x,int y); + +/** + * + * @param world + * @param number of commandline arguments + * @param init_world + * @param destroy_world + * + * void init_world(struct world* w); + * Initializes user state. + * Free user state. + * @param world + */ + +int start_world(int (*world_event)(struct world* world,void* game),void* (*init_game)(struct world* world),void (*destroy_game)(void* game)); + + +#endif