diff --git a/.gitignore b/.gitignore index 0b8cb6c..5c030b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -build/ .clangd +target/ diff --git a/compile.sh b/compile.sh index f15cf5d..1067797 100755 --- a/compile.sh +++ b/compile.sh @@ -1,7 +1,26 @@ #!/bin/sh +arg=-1 + +if [ -v 1 ]; then + arg=$1 +fi + +if [ $arg = "clean" ]; then + rm -rf ./target/* + exit +fi + +oflag="-Og" +debug_flag="-ggdb" + +if [ $arg = "release" ]; then + oflag="-O3" + debug_flag="" +fi + src="src/*.c" -flags="-std=c23 -ggdb -Og -Wall -Wextra -Werror -Wpedantic -pedantic-errors" +flags="-std=c23 $oflag $debug_flag -Wall -Wextra -Werror -Wpedantic -pedantic-errors" includes="-I src/headers" cmd="gcc $flags $includes $src -o target/sanke" @@ -9,14 +28,12 @@ cmd="gcc $flags $includes $src -o target/sanke" echo $cmd $cmd + if [ $? -gt 0 ]; then exit fi -echo - -if [ -v 1 ]; then - if [ $1 = "run" ]; then - ./target/sanke - fi +if [ $arg = "run" ]; then + echo + ./target/sanke fi diff --git a/src/board.c b/src/Board.c similarity index 94% rename from src/board.c rename to src/Board.c index 074961b..4b0733d 100644 --- a/src/board.c +++ b/src/Board.c @@ -2,8 +2,8 @@ #include #include -#include "board.h" -#include "snake.h" +#include "Board.h" +#include "Snake.h" #define MAT_INDEX(mat, w, i, j) (mat)[(j) + (w) * (i)] @@ -73,17 +73,17 @@ void board_set_square( void board_clear(Board* board) { for (size_t i = 0; i < board->height; i++) { for (size_t j = 0; j < board->width; j++) { - printf("Clearing board: i: %zu j: %zu\n", i, j); + // printf("Clearing board: i: %zu j: %zu\n", i, j); board_set_square(board, j, i, ' '); } } } void board_draw_snake(Board* board, Snake* snake) { - SnakePart part = {}; + BoardPiece part = {}; for (size_t i = 0; i < snake->length; i++) { part = snake_get_part(snake, i); - board_set_square(board, part.y, part.x, part.vis_char); + board_set_square(board, part.x, part.y, part.vis_char); } } diff --git a/src/BoardPiece.c b/src/BoardPiece.c new file mode 100644 index 0000000..7f19070 --- /dev/null +++ b/src/BoardPiece.c @@ -0,0 +1,15 @@ +#include + +#include "BoardPiece.h" + +void board_piece_print_info(BoardPiece* piece, char* name) { + printf("%s: {\n", name); + printf(" x: %d\n", piece->x); + printf(" y: %d\n", piece->y); + printf(" vis_char: %c\n", piece->vis_char); + printf("}\n"); +} + +bool pieces_collide(BoardPiece* piece1, BoardPiece* piece2) { + return piece1->x == piece2->x && piece1->y == piece2->y; +} diff --git a/src/Snake.c b/src/Snake.c new file mode 100644 index 0000000..2f4d442 --- /dev/null +++ b/src/Snake.c @@ -0,0 +1,210 @@ +#include +#include +#include + +#include "Snake.h" + +extern const char snake_vis; + +static BoardPiece* snake_get_part_ptr(const Snake* snake, const size_t index) { + if (index >= snake->max_length) { + return NULL; + } + + return snake->parts + index; +} + +BoardPiece snake_get_part(const Snake* snake, const size_t index) { + assert(index < snake->max_length); + return snake->parts[index]; +} + +Snake snake_alloc( + const int board_square_count, + const int init_x, + const int init_y, + const char init_dir + ) +{ + Snake snake = {0}; + snake.max_length = board_square_count; + snake.length = 1; + snake.dir = init_dir; + snake.parts = (BoardPiece*) malloc(sizeof(BoardPiece) * snake.max_length); + snake.parts[0] = (BoardPiece) { .x = init_x, .y = init_y, .vis_char = '&' }; + + return snake; +} + +void snake_free(Snake* snake) { + free(snake->parts); +} + +static void check_bounds(Snake* snake, const int width, const int height) { + for (size_t i = 0;i < snake->length; i++) { + BoardPiece* part = snake->parts + i; + if (part->x < 0) { + part->x = width - 2; + } else if (part->x >= width) { + part->x = 0; + } + + if (part->y < 0) { + part->y = height - 1; + } else if (part->y >= height) { + part->y = 0; + } + } +} + +void snake_move(Snake* snake, const int width, const int height) { + BoardPiece* first_part = snake_get_part_ptr(snake, 0); + int first_part_old_x = first_part->x; + int first_part_old_y = first_part->y; + + // Move first part + switch (snake->dir) { + case 'w': + first_part->y -= 1; + break; + case 'a': + first_part->x -= 2; + break; + case 's': + first_part->y += 1; + break; + case 'd': + first_part->x += 2; + break; + default: + fprintf(stderr, "ERROR: Invalid direction in snake_move: %c.\n", snake->dir); + exit(EXIT_FAILURE); + } + + // Stop here if there is only one part + BoardPiece* last_part = snake_get_part_ptr(snake, snake->length - 1); + if (last_part == first_part) { + check_bounds(snake, width, height); + return; + } + + // Move all other parts except for the second one, gets skipped if there are only 2 parts + BoardPiece* second_part = first_part + 1; + BoardPiece* prev_part = 0; + for (BoardPiece* part = last_part; part != second_part; part--) { + prev_part = part - 1; + part->x = prev_part->x; + part->y = prev_part->y; + } + + // Move second part + second_part->x = first_part_old_x; + second_part->y = first_part_old_y; + + check_bounds(snake, width, height); +} + +void snake_print_info(Snake* snake) { + printf("snake: {\n"); + printf(" parts: {\n"); + for (size_t i = 0; i < snake->length; i++) { + BoardPiece part = snake->parts[i]; + printf(" x: %d\n", part.x); + printf(" y: %d\n", part.y); + } + printf(" }\n"); + + printf(" max_length: %zu\n", snake->max_length); + printf(" length: %zu\n", snake->length); + printf(" dir: %c\n", snake->dir); + printf("}\n"); +} + +void snake_change_direction(Snake* snake, const char direction) { + snake->dir = direction; +} + +bool snake_collides(const Snake* snake, const BoardPiece* piece) { + for (size_t i = 0; i < snake->length; i++) { + BoardPiece part = snake->parts[i]; + if (part.x == piece->x && part.y == piece->y) { + return true; + } + } + + return false; +} + +bool snake_collides_with_tail(const Snake* snake) { + const BoardPiece* head = snake->parts; + for (size_t i = 1; i < snake->length; i++) { + BoardPiece part = snake->parts[i]; + if (part.x == head->x && part.y == head->y) { + return true; + } + } + + return false; +} + +void snake_add_part(Snake* snake) { + if (snake->length == snake->max_length) { + fprintf(stderr, "ERROR: Cannot add another part to snake. Would exceed max_length.\n"); + exit(EXIT_FAILURE); + } + + int x_shift; + int y_shift; + BoardPiece last_part; + char prev_part_dir; + + if (snake->length == 1) { + last_part = snake_get_part(snake, 0); + prev_part_dir = snake->dir; + } else { + last_part = snake_get_part(snake, snake->length - 1); + const BoardPiece second_to_last_part = snake_get_part(snake, snake->length - 2); + + if (second_to_last_part.y < last_part.y && second_to_last_part.x == last_part.x) { + prev_part_dir = 'w'; + } else if (second_to_last_part.x < last_part.x && second_to_last_part.y == last_part.y) { + prev_part_dir = 'a'; + } else if (second_to_last_part.y > last_part.y && second_to_last_part.x == last_part.x) { + prev_part_dir = 's'; + } else if (second_to_last_part.x > last_part.x && second_to_last_part.y == last_part.y) { + prev_part_dir = 'd'; + } else { + fprintf(stderr, "%s:%d: ERROR: Invalid direction.\n", __FILE__, __LINE__); + exit(EXIT_FAILURE); + } + } + + switch (prev_part_dir) { + case 'w': + x_shift = 0; + y_shift = 1; + break; + case 'a': + x_shift = 1; + y_shift = 0; + break; + case 's': + x_shift = 0; + y_shift = -1; + break; + case 'd': + x_shift = -1; + y_shift = 0; + break; + default: + fprintf(stderr, "%s:%d: ERROR: Invalid direction.\n", __FILE__, __LINE__); + exit(EXIT_FAILURE); + } + + snake->parts[snake->length] = (BoardPiece){ + .x = last_part.x + x_shift, + .y = last_part.y + y_shift, + .vis_char = snake_vis + }; + snake->length++; +} diff --git a/src/food.c b/src/food.c new file mode 100644 index 0000000..3fd8d7e --- /dev/null +++ b/src/food.c @@ -0,0 +1,13 @@ +#include + +#include "food.h" +#include "Board.h" +#include "BoardPiece.h" +#include "Snake.h" + +void food_new_location(Board* board, BoardPiece* food, Snake* snake) { + do { + food->x = rand() % board->width; + food->y = rand() % board->height; + } while (food->x % 2 != 0 || snake_collides(snake, food)); +} diff --git a/src/headers/board.h b/src/headers/Board.h similarity index 96% rename from src/headers/board.h rename to src/headers/Board.h index 9686d57..e08bb03 100644 --- a/src/headers/board.h +++ b/src/headers/Board.h @@ -1,7 +1,7 @@ #ifndef BOARD_H_ #define BOARD_H_ -#include "snake.h" +#include "Snake.h" typedef struct { size_t width; diff --git a/src/headers/BoardPiece.h b/src/headers/BoardPiece.h new file mode 100644 index 0000000..bae71bf --- /dev/null +++ b/src/headers/BoardPiece.h @@ -0,0 +1,13 @@ +#ifndef BOARD_PIECE_H_ +#define BOARD_PIECE_H_ + +typedef struct { + int x; + int y; + char vis_char; +} BoardPiece; + +void board_piece_print_info(BoardPiece* piece, char* name); +bool pieces_collide(BoardPiece* piece1, BoardPiece* piece2); + +#endif // BOARD_PIECE_H_ diff --git a/src/headers/Snake.h b/src/headers/Snake.h new file mode 100644 index 0000000..1e076ae --- /dev/null +++ b/src/headers/Snake.h @@ -0,0 +1,29 @@ +#ifndef SNAKE_H_ +#define SNAKE_H_ + +#include +#include "BoardPiece.h" + +typedef struct { + BoardPiece* parts; + size_t max_length; + size_t length; + char dir; +} Snake; + +BoardPiece snake_get_part(const Snake* snake, const size_t index); +Snake snake_alloc( + const int board_square_count, + const int init_x, + const int init_y, + const char init_dir + ); +void snake_free(Snake* snake); +void snake_move(Snake* snake, const int width, const int height); +void snake_print_info(Snake* snake); +void snake_change_direction(Snake* snake, const char direction); +bool snake_collides(const Snake* snake, const BoardPiece* piece); +bool snake_collides_with_tail(const Snake* snake); +void snake_add_part(Snake* snake); + +#endif // SNAKE_H_ diff --git a/src/headers/food.h b/src/headers/food.h new file mode 100644 index 0000000..0a1c839 --- /dev/null +++ b/src/headers/food.h @@ -0,0 +1,10 @@ +#ifndef FOOD_H_ +#define FOOD_H_ + +#include "BoardPiece.h" +#include "Board.h" +#include "Snake.h" + +void food_new_location(Board* board, BoardPiece* food, Snake* snake); + +#endif // FOOD_H_ diff --git a/src/headers/snake.h b/src/headers/snake.h deleted file mode 100644 index ada466c..0000000 --- a/src/headers/snake.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef SNAKE_H_ -#define SNAKE_H_ - -#include - -typedef struct { - int x; - int y; - char vis_char; -} SnakePart; - -typedef struct { - SnakePart* parts; - size_t max_length; - size_t length; - char dir; -} Snake; - -SnakePart snake_get_part(const Snake* snake, const size_t index); -Snake snake_alloc( - const int board_square_count, - const int init_x, - const int init_y, - const char init_dir - ); -void snake_free(Snake* snake); -void snake_move(Snake* snake); - -#endif // SNAKE_H_ diff --git a/src/main.c b/src/main.c index b05530d..18a78c2 100644 --- a/src/main.c +++ b/src/main.c @@ -6,55 +6,117 @@ #include #include -#include "board.h" -#include "snake.h" +#include "Board.h" +#include "Snake.h" #include "utils.h" +#include "food.h" +#include "BoardPiece.h" + +#define TERMIOS 1 +#define DEBUG 0 const char* VERSION = "1.1.9"; -const char SNAKE_VIS = '#'; +const char snake_vis = '#'; void cmd_args(int argc, char** argv); void empty_stdin_buffer(); void get_int_or_minus_one(int* dst); struct termios set_termios(); -int main() { - // cmd_args(argc, argv); - - // const clock_t initClock = clock(); - - // Set gamespeed - // int gameSpeed = 0; - // printf("Enter gamespeed (default: 0, max: 180): "); - // getIntOrMinusOne(&gameSpeed); - // if (gameSpeed == -1 || gameSpeed > 180) { - // gameSpeed = 0; - // } +int main(int argc, char** argv) { + cmd_args(argc, argv); + srand(time(NULL)); // Termios setup +#if TERMIOS const struct termios attr_orig = set_termios(); +#endif // TERMIOS // Game model init const int board_w = 15; const int board_h = 15; Board board = board_alloc(board_w, board_h); Snake snake = snake_alloc(board_w * board_h, 0, 0, 'd'); + BoardPiece food = { .vis_char = '$' }; + food_new_location(&board, &food, &snake); + unsigned int score = 0; // Screen init system("clear"); + int frame = 0; + int fps = 0; + + long second_start = time(NULL); + int frame_stamp = frame; + while (true) { + long second_check = time(NULL); + long elapsed_time = second_check - second_start; + if (elapsed_time >= 1) { + fps = frame - frame_stamp; + if (elapsed_time > 1) { + fps /= 2; + } + frame_stamp = frame; + second_start = time(NULL); + } + system("clear"); - snake_move(&snake); + + // Process input + char input = '0'; + read(STDIN_FILENO, &input, 1); + + if (isalpha(input)) { + if ( + (input == 'w' && snake.dir != 's') + || (input == 'a' && snake.dir != 'd') + || (input == 's' && snake.dir != 'w') + || (input == 'd' && snake.dir != 'a') + ) { + snake_change_direction(&snake, input); + } + } + + snake_move(&snake, board.width, board.height); board_clear(&board); board_draw_snake(&board, &snake); + board_set_square(&board, food.x, food.y, food.vis_char); + printf("FPS: %d\n", fps); print_board(&board); - sleep_ms(1000); + printf("Score: %d\n", score); + + BoardPiece snake_head = snake_get_part(&snake, 0); + + if (snake_collides_with_tail(&snake)) { + printf("GAME OVER\n"); + printf("Final score: %d\n", score); + exit(EXIT_SUCCESS); + } + + if (pieces_collide(&snake_head, &food)) { + score++; + snake_add_part(&snake); + food_new_location(&board, &food, &snake); + } + +#if DEBUG + printf("input: %c\n", input); + printf("Frame: %d\n", frame); + snake_print_info(&snake); + board_piece_print_info(&food, "Food"); +#endif // DEBUG + + frame++; + sleep_ms(150); } +#if TERMIOS tcsetattr(STDIN_FILENO, 0, &attr_orig); +#endif // TERMIOS return 0; } diff --git a/src/snake.c b/src/snake.c deleted file mode 100644 index 0325fd7..0000000 --- a/src/snake.c +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include -#include - -#include "snake.h" - -static SnakePart* snake_get_part_ptr(const Snake* snake, const size_t index) { - if (index >= snake->max_length) { - return NULL; - } - - return snake->parts + index; -} - -SnakePart snake_get_part(const Snake* snake, const size_t index) { - assert(index < snake->max_length); - return snake->parts[index]; -} - -Snake snake_alloc( - const int board_square_count, - const int init_x, - const int init_y, - const char init_dir - ) -{ - Snake snake = {0}; - snake.max_length = board_square_count; - snake.length = 1; - snake.dir = init_dir; - snake.parts = (SnakePart*) malloc(sizeof(SnakePart) * snake.max_length); - snake.parts[0] = (SnakePart) { .x = init_x, .y = init_y, .vis_char = '&' }; - - return snake; -} - -void snake_free(Snake* snake) { - free(snake->parts); -} - -void snake_move(Snake* snake) { - SnakePart* first_part = snake_get_part_ptr(snake, 0); - int first_part_old_x = first_part->x; - int first_part_old_y = first_part->y; - - // Move first part - switch (snake->dir) { - case 'w': - first_part->y -= 1; - break; - case 'a': - first_part->x -= 1; - break; - case 's': - first_part->y += 1; - break; - case 'd': - first_part->x += 1; - break; - default: - fprintf(stderr, "ERROR: Invalid direction in snake_move: %c.\n", snake->dir); - exit(EXIT_FAILURE); - } - - // Stop here if there is only one part - SnakePart* last_part = snake_get_part_ptr(snake, snake->length - 1); - if (last_part == first_part) { - return; - } - - // Move all other parts except for the second one, gets skipped if there are only 2 parts - SnakePart* second_part = first_part + 1; - SnakePart* prev_part = 0; - for (SnakePart* part = last_part; part != second_part; part--) { - prev_part = part - 1; - part->x = prev_part->x; - part->y = prev_part->y; - } - - // Move second part - second_part->x = first_part_old_x; - second_part->y = first_part_old_y; -} diff --git a/src/utils.c b/src/utils.c index 61885fa..1edcab1 100644 --- a/src/utils.c +++ b/src/utils.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "utils.h" @@ -23,5 +24,19 @@ void mallocError(const char* varName, const char* fileName, const char* function } void sleep_ms(const unsigned int ms) { - nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = ms * 1000000}, NULL); + struct timespec ts = { + .tv_sec = ms / 1000 + }; + if (ts.tv_sec == 0) { + ts.tv_nsec = ms * 1000000; + } else { + ts.tv_nsec = (ms - ts.tv_sec * 1000) * 1000000; + } + // printf("timespec: {\n"); + // printf(" tv_sec : %ld\n", ts.tv_sec); + // printf(" tv_nsec: %ld\n", ts.tv_nsec); + // printf("}\n"); + if (nanosleep(&ts, NULL) == -1) { + fprintf(stderr, "ERROR: Failed to sleep. ERRNO: %d\n", errno); + } } diff --git a/target/sanke b/target/sanke index 1a4da79..edb90db 100755 Binary files a/target/sanke and b/target/sanke differ