From 74d9392626609b3b25adcb84593af7ccc95c38ad Mon Sep 17 00:00:00 2001 From: Daniel Hladek Date: Thu, 23 Apr 2020 17:39:46 +0200 Subject: [PATCH] initial --- Makefile | 14 ++++++ README.md | 17 +++++++ game.c | 82 ++++++++++++++++++++++++++++++++ game.h | 26 ++++++++++ main.c | 9 ++++ snake.c | 23 +++++++++ snake.h | 114 ++++++++++++++++++++++++++++++++++++++++++++ world.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ world.h | 100 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 523 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 game.c create mode 100644 game.h create mode 100644 main.c create mode 100644 snake.c create mode 100644 snake.h create mode 100644 world.c create mode 100644 world.h 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