From 79fa249aa729e03b17991c3991f5bb090ffb6b61 Mon Sep 17 00:00:00 2001 From: Kai Stevenson Date: Sat, 4 Mar 2023 17:17:13 -0800 Subject: restructured project --- Makefile | 2 +- colors.h | 80 ------------- config.h | 9 -- conio.c | 16 --- conio.h | 6 - snake.c | 360 ----------------------------------------------------------- snake.h | 16 --- src/colors.h | 80 +++++++++++++ src/config.h | 9 ++ src/conio.c | 16 +++ src/conio.h | 6 + src/snake.c | 360 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/snake.h | 16 +++ src/vector.c | 48 ++++++++ src/vector.h | 27 +++++ vector.c | 48 -------- vector.h | 27 ----- 17 files changed, 563 insertions(+), 563 deletions(-) delete mode 100644 colors.h delete mode 100644 config.h delete mode 100644 conio.c delete mode 100644 conio.h delete mode 100644 snake.c delete mode 100644 snake.h create mode 100644 src/colors.h create mode 100644 src/config.h create mode 100644 src/conio.c create mode 100644 src/conio.h create mode 100644 src/snake.c create mode 100644 src/snake.h create mode 100644 src/vector.c create mode 100644 src/vector.h delete mode 100644 vector.c delete mode 100644 vector.h diff --git a/Makefile b/Makefile index 2f8d956..aa99baa 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ BINDIR ?= ${PREFIX}/bin MANDIR = ${PREFIX}/share/man/ generate: - $(CC) -o snake3 snake.c vector.c conio.c $(CFLAGS) + $(CC) -o snake3 src/snake.c src/vector.c src/conio.c $(CFLAGS) install: snake3 install -d ${DESTDIR}${BINDIR} ${DESTDIR}${MANDIR}/man1 diff --git a/colors.h b/colors.h deleted file mode 100644 index 8c8a654..0000000 --- a/colors.h +++ /dev/null @@ -1,80 +0,0 @@ -/* Code taken from https://gist.github.com/RabaDabaDoba/145049536f815903c79944599c6f952a; unlicensed */ - -#ifndef COLORS_H -#define COLORS_H - -#define BLK "\033[0;30m" -#define RED "\033[0;31m" -#define GRN "\033[0;32m" -#define YEL "\033[0;33m" -#define BLU "\033[0;34m" -#define MAG "\033[0;35m" -#define CYN "\033[0;36m" -#define WHT "\033[0;37m" - -/* Regular bold text */ -#define BBLK "\033[1;30m" -#define BRED "\033[1;31m" -#define BGRN "\033[1;32m" -#define BYEL "\033[1;33m" -#define BBLU "\033[1;34m" -#define BMAG "\033[1;35m" -#define BCYN "\033[1;36m" -#define BWHT "\033[1;37m" - -/* Regular underline text */ -#define UBLK "\033[4;30m" -#define URED "\033[4;31m" -#define UGRN "\033[4;32m" -#define UYEL "\033[4;33m" -#define UBLU "\033[4;34m" -#define UMAG "\033[4;35m" -#define UCYN "\033[4;36m" -#define UWHT "\033[4;37m" - -/* Regular background */ -#define BLKB "\033[40m" -#define REDB "\033[41m" -#define GRNB "\033[42m" -#define YELB "\033[43m" -#define BLUB "\033[44m" -#define MAGB "\033[45m" -#define CYNB "\033[46m" -#define WHTB "\033[47m" - -/* High intensty background */ -#define BLKHB "\033[0;100m" -#define REDHB "\033[0;101m" -#define GRNHB "\033[0;102m" -#define YELHB "\033[0;103m" -#define BLUHB "\033[0;104m" -#define MAGHB "\033[0;105m" -#define CYNHB "\033[0;106m" -#define WHTHB "\033[0;107m" - -/* High intensty text */ -#define HBLK "\033[0;90m" -#define HRED "\033[0;91m" -#define HGRN "\033[0;92m" -#define HYEL "\033[0;93m" -#define HBLU "\033[0;94m" -#define HMAG "\033[0;95m" -#define HCYN "\033[0;96m" -#define HWHT "\033[0;97m" - -/* Bold high intensity text */ -#define BHBLK "\033[1;90m" -#define BHRED "\033[1;91m" -#define BHGRN "\033[1;92m" -#define BHYEL "\033[1;93m" -#define BHBLU "\033[1;94m" -#define BHMAG "\033[1;95m" -#define BHCYN "\033[1;96m" -#define BHWHT "\033[1;97m" - -/* Reset */ -#define reset "\033[0m" -#define CRESET "\033[0m" -#define COLOR_RESET "\033[0m" - -#endif diff --git a/config.h b/config.h deleted file mode 100644 index 20fabe4..0000000 --- a/config.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -int boardSize = 20; -int obstacleSize = 2; -int obstacles = 4; -int speed = 3; - -#endif diff --git a/conio.c b/conio.c deleted file mode 100644 index 50b7e0f..0000000 --- a/conio.c +++ /dev/null @@ -1,16 +0,0 @@ -#include "./conio.h" -#include -#include -#include - -int getch(void) { - struct termios oldattr, newattr; - int ch; - tcgetattr(STDIN_FILENO, &oldattr); - newattr = oldattr; - newattr.c_lflag &= ~(ICANON | ECHO); - tcsetattr(STDIN_FILENO, TCSANOW, &newattr); - ch = getchar(); - tcsetattr(STDIN_FILENO, TCSANOW, &oldattr); - return ch; -} diff --git a/conio.h b/conio.h deleted file mode 100644 index 520e146..0000000 --- a/conio.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef CONIO_H -#define CONIO_H - -int getch(void); - -#endif diff --git a/snake.c b/snake.c deleted file mode 100644 index 577c832..0000000 --- a/snake.c +++ /dev/null @@ -1,360 +0,0 @@ -#include -#include -#include -#include -#include /* handling ctrl-c */ -#include /* terminal size */ -#include /* argument parsing */ -#include /* time calculations */ - -#include "vector.h" -#include "snake.h" -#include "conio.h" -#include "colors.h" -#include "config.h" -#define UNUSED(x) (void)(x) - -/*** GLOBAL VARIABLES ***/ -volatile sig_atomic_t intCtrlC = 0; /* flag for ctrl-c interrupt */ -int score = 0; -int updateInterval = 300000000L; - -vectorp_t obstaclePositions; -vectorp_t snakePositions; -vector_t foodPos; -vector_t lastMoveDir; - -/* unsafe functions like printf cannot be called in async interrupt handler */ -void interrupt_handler(int sig) { - UNUSED(sig); - intCtrlC = 1; -} - -int main(int argc, char **argv) { - /* parsing arguments */ - char *last_flag = NULL; - int was_flag = 0; - for(int i = 1; i < argc; i++) { - if(strcmp("--version", argv[i]) == 0) { - printf("snake3, version 2.0\n"); - printf("Copyright 2023 Kai Stevenson\n"); - printf("Copyright 2023 Preston Pan\n"); - printf("This software was licensed under the MIT license;\n"); - printf("for more information, see the license file.\n"); - return 0; - } - else if(strcmp("--help", argv[i]) == 0) { - fprintf(stderr, "usage: snake3 [--version] [--help] [--size board_size] [--speed speed] [--obstacles obstacles]\n"); - return 1; - } - else if(strcmp("--size", argv[i]) == 0 || strcmp("--speed", argv[i]) == 0 || strcmp("--obstacles", argv[i]) == 0) { - last_flag = argv[i]; - was_flag = 1; - } - else if(was_flag) { - if(strcmp("--size", last_flag) == 0) { - boardSize = atoi(argv[i]); - if(boardSize < 3) { - fprintf(stderr, "%sboard size too small or argument invalid, please try again.%s\n", RED, COLOR_RESET); - return 1; - } - } - else if(strcmp("--speed", last_flag) == 0) { - speed = atoi(argv[i]); - if(speed <= 0) { - fprintf(stderr, "%sspeed level must be greater than zero and integer. Please try again.%s\n", RED, COLOR_RESET); - return 1; - } - else if(speed > 10) { - fprintf(stderr, "%sspeed must not be greater than ten. Please try again.%s\n", RED, COLOR_RESET); - return 1; - } - } - else if(strcmp("--obstacles", last_flag) == 0) { - obstacles = atoi(argv[i]); - if(obstacles < 0) { - fprintf(stderr, "%sobstacle count cannot be negative. Please try again.%s\n", RED, COLOR_RESET); - return 1; - } - } - was_flag = 0; - } - else { - printf("usage: snake3 [--version] [--help]\n"); - return 1; - } - } - /* check argument compatibility */ - if((obstacles * obstacleSize) * 3 > pow(boardSize, 2)) { - fprintf(stderr, "%stoo many obstacles. Please try again.%s\n", RED, COLOR_RESET); - exit(1); - } - /* get terminal size */ - struct winsize win; - ioctl(fileno(stdout), TIOCGWINSZ, &win); - int width = (int)(win.ws_col); - int height = (int)(win.ws_row); - /* multiplied by 2 to account for the fact that the board is twice as wide as high */ - int smallestAxis = width / 2 < height ? width / 2 : height; - boardSize = smallestAxis < boardSize ? smallestAxis : boardSize; - /* hide cursor */ - printf("\033[?25l"); - /* make a new thread handling interrupts */ - signal(SIGINT, interrupt_handler); - /* init values */ - snakePositions = init_vectorp(malloc(5 * sizeof(vector_t)), 5); - snakePositions.vectors[0] = init_vector(5, 5); - snakePositions.vectors[1] = init_vector(5, 4); - snakePositions.vectors[2] = init_vector(5, 3); - snakePositions.vectors[3] = init_vector(4, 3); - snakePositions.vectors[4] = init_vector(3, 3); - foodPos = init_vector(7, 9); - lastMoveDir = init_vector(0, 1); - int opc = (int)pow(obstacleSize, 2) * obstacles; - obstaclePositions = init_vectorp(malloc(opc * sizeof(vector_t)), opc); - srand(time(NULL)); - /* place the obstacles */ - /* TODO: make this less recursive */ - int opi = 0; - for(int i = 0; i < obstacles; i++) { - while(1) { - vector_t basePos = init_vector((rand() % (boardSize - (obstacleSize - 1))), (rand() % (boardSize - (obstacleSize - 1)))); - /* if obstacle overlaps snake, try again */ - uint8 overlap = 0; - for(int r = 0; r < obstacleSize; r++) { - for(int c = 0; c < obstacleSize; c++) { - vector_t voxelPos = init_vector(basePos.mx + c, basePos.my + r); - for(int j = 0; j < snakePositions.length; j++) { - if((snakePositions.vectors[j].mx == voxelPos.mx) && (snakePositions.vectors[j].my == voxelPos.my)) { - overlap = 1; - } - } - if(foodPos.mx == voxelPos.mx && foodPos.my == voxelPos.my) { - overlap = 1; - } - } - } - if(overlap) { - continue; - } - for(int r = 0; r < obstacleSize; r++) { - for(int c = 0; c < obstacleSize; c++) { - vector_t voxelPos = init_vector(basePos.mx + c, basePos.my + r); - obstaclePositions.vectors[opi] = voxelPos; - opi++; - } - } - break; - } - } - /* clear the screen so as to prevent artifacting */ - printf("\033[2J"); - /* prepare the pointers which will be used to communicate with the input getting thread */ - volatile int* lastInput = calloc(1, sizeof(int)); - int* cancellationToken = calloc(1, sizeof(int)); - /* and the wrapper struct */ - threadArg_t threadArg; - threadArg.returnV = (void*)lastInput; - threadArg.cancellationToken = cancellationToken; - /* create listener thread */ - pthread_t listenerID; - pthread_create(&listenerID, NULL, get_dir_loop, &threadArg); - /* main game logic loop */ - while(1) { - /* handle ctrl-c interrupt */ - if(intCtrlC) { - goto EXIT; - } - - /* first, draw the current state of the board */ - draw_board(); - /* handle player input; - * wait for the specified interval */ - struct timespec time; - time.tv_sec = 0; - time.tv_nsec = round((float)updateInterval / ((float)speed / 3)); - nanosleep(&time, NULL); - /* read the most recent value emitted by the listener */ - vector_t dir = parse_dir(*(char*)lastInput); - /* if the player inputted a valid direction, use it. otherwise, use his last valid input */ - if((dir.mx != 0 || dir.my != 0) && !(dir.mx == 1 && lastMoveDir.mx == -1) && !(dir.mx == -1 && lastMoveDir.mx == 1) && !(dir.my == 1 && lastMoveDir.my == -1) && !(dir.my == -1 && lastMoveDir.my == 1)) { - lastMoveDir = dir; - } - - uint8 ateFood = 0; - /* check if snake is moving onto food */ - if((snakePositions.vectors[0].mx + lastMoveDir.mx == foodPos.mx) && (snakePositions.vectors[0].my + lastMoveDir.my == foodPos.my)) { - ateFood = 1; - score += 1; - if(updateInterval > 100000000) { - updateInterval -= 10000000; - } - /* move the food */ - while(1) { - uint8 overlap = 0; - foodPos = init_vector(1 + (rand() % (boardSize - 2)), 1 + (rand() % (boardSize - 2))); - /* if food overlaps snake or obstacles, try again */ - for(int i = 0; i < snakePositions.length; i++) { - if((snakePositions.vectors[i].mx == foodPos.mx) && (snakePositions.vectors[i].my == foodPos.my)) { - overlap = 1; - } - } - for(int i = 0; i < obstaclePositions.length; i++) { - if((obstaclePositions.vectors[i].mx == foodPos.mx) && (obstaclePositions.vectors[i].my == foodPos.my)) { - overlap = 1; - } - } - if(!overlap) { - break; - } - } - } - /* move the snake */ - vectorp_t newSnakePositions = move_snake(lastMoveDir, ateFood); - free(snakePositions.vectors); - snakePositions = newSnakePositions; - /* check if snake landed on self */ - for(int i = 1; i < snakePositions.length; i++) { - if(snakePositions.vectors[0].mx == snakePositions.vectors[i].mx && snakePositions.vectors[0].my == snakePositions.vectors[i].my) { - /* game over */ - goto EXIT; - } - } - /* check if snake landed on obstacle */ - for(int i = 0; i < obstaclePositions.length; i++) { - if((snakePositions.vectors[0].mx == obstaclePositions.vectors[i].mx) && (snakePositions.vectors[0].my == obstaclePositions.vectors[i].my)) { - goto EXIT; - } - } - } -EXIT: - /* clear the last getch call from the listener */ - *cancellationToken = 1; - printf("\n%sYou died. Score: %d. Press any key to exit%s\n", RED, score, COLOR_RESET); - printf("\033[?25h"); - pthread_join(listenerID, NULL); - free(cancellationToken); - free((int*)lastInput); - return 0; -} -void* get_dir_loop(void* threadArg) { - while(!*(int*)((threadArg_t*)threadArg)->cancellationToken) { - *(char*)((threadArg_t*)threadArg)->returnV = getch(); - } - return NULL; -} -/* parses WASD or HJKL, returns the necessary deltas to move within a 2d array */ -vector_t parse_dir(char cDir) { - if(cDir == 'w' || cDir == 'k') { - return init_vector(0, -1); - } - else if(cDir == 'd' || cDir == 'l') { - return init_vector(1, 0); - } - else if(cDir == 's' || cDir == 'j') { - return init_vector(0, 1); - } - else if(cDir == 'a' || cDir == 'h') { - return init_vector(-1, 0); - } - else { - return init_vector(0, 0); - } -} -vectorp_t move_snake(vector_t dir, int lengthen) { - int newLength = snakePositions.length + (lengthen ? 1 : 0); - vector_t* newSnakePositions = malloc((newLength * sizeof(vector_t))); - /* wrap around edges */ - int nhx = snakePositions.vectors[0].mx + dir.mx; - nhx = nhx < 0 ? boardSize - 1 : nhx >= boardSize ? 0 : nhx; - int nhy = snakePositions.vectors[0].my + dir.my; - nhy = nhy < 0 ? boardSize - 1 : nhy >= boardSize ? 0 : nhy; - newSnakePositions[0] = init_vector(nhx, nhy); - for(int i = 1; i < newLength; i++) { - newSnakePositions[i] = snakePositions.vectors[i - 1]; - } - return init_vectorp(newSnakePositions, newLength); -} -void draw_board() { - /* move the cursor to the top left corner of the screen */ - printf("\033[0;0H"); - /* iterate through and write the board */ - for(int r = 0; r < boardSize; r++) { - for(int c = 0; c < boardSize; c++) { - /* determine the contents of the board position */ - int type = 0; - /* 0=horizontal, 1=vertical, 2=diagonal slant right, 3=diagonal slant left */ - int tailOrientation = 0; - for(int i = 0; i < snakePositions.length; i++) { - if(snakePositions.vectors[i].mx == c && snakePositions.vectors[i].my == r) { - if(i > 0) { - type = 2; - /* get the delta to the connecting tail */ - int dx; - int dy; - if(i < snakePositions.length - 1) { - dx = snakePositions.vectors[i - 1].mx - snakePositions.vectors[i + 1].mx; - dy = snakePositions.vectors[i - 1].my - snakePositions.vectors[i + 1].my; - } - else { - dx = snakePositions.vectors[i - 1].mx - c; - dy = snakePositions.vectors[i - 1].my - r; - } - if(dy == 0) { - tailOrientation = 0; - } - else if(dx == 0) { - tailOrientation = 1; - } - else if((dx == 1 && dy == 1) || (dx == -1 && dy == -1)) { - tailOrientation = 3; - } - else if((dx == -1 && dy == 1) || (dx == 1 && dy == -1)) { - tailOrientation = 2; - } - } - else { - type = 1; - } - break; - } - } - if(foodPos.mx == c && foodPos.my == r) { - type = 3; - } - for(int i = 0; i < obstaclePositions.length; i++) { - if(obstaclePositions.vectors[i].mx == c && obstaclePositions.vectors[i].my == r) { - type = 4; - break; - } - } - /* append the appropriate value */ - if(type == 0) { - /* empty square */ - printf("` "); - } - else if(type == 1) { - /* snake head */ - printf("%s@ %s", GRN, COLOR_RESET); - } - else if(type == 2) { - /* snake body */ - char bodyChar = tailOrientation == 0 ? '-' - : tailOrientation == 1 ? '|' - : tailOrientation == 2 ? '/' - : tailOrientation == 3 ? '\\' - : '*'; - printf("%s%c %s", GRN, bodyChar, COLOR_RESET); - } - else if(type == 3) { - /* food */ - printf("%sF %s", MAG, COLOR_RESET); - } - else if(type == 4) { - /* obstacle */ - printf("%s# %s", RED, COLOR_RESET); - } - } - printf("\n"); - } -} diff --git a/snake.h b/snake.h deleted file mode 100644 index c783f9f..0000000 --- a/snake.h +++ /dev/null @@ -1,16 +0,0 @@ -#include "vector.h" -#ifndef SNAKE -#define SNAKE -/* used as bool and int */ -typedef char uint8; - -typedef struct ThreadArg { - int* cancellationToken; - void* returnV; -} threadArg_t; -void interrupt_handler(int sig); -void* get_dir_loop(void* threadArg); -vector_t parse_dir(char cDir); -vectorp_t move_snake(vector_t dir, int lengthen); -void draw_board(void); -#endif diff --git a/src/colors.h b/src/colors.h new file mode 100644 index 0000000..8c8a654 --- /dev/null +++ b/src/colors.h @@ -0,0 +1,80 @@ +/* Code taken from https://gist.github.com/RabaDabaDoba/145049536f815903c79944599c6f952a; unlicensed */ + +#ifndef COLORS_H +#define COLORS_H + +#define BLK "\033[0;30m" +#define RED "\033[0;31m" +#define GRN "\033[0;32m" +#define YEL "\033[0;33m" +#define BLU "\033[0;34m" +#define MAG "\033[0;35m" +#define CYN "\033[0;36m" +#define WHT "\033[0;37m" + +/* Regular bold text */ +#define BBLK "\033[1;30m" +#define BRED "\033[1;31m" +#define BGRN "\033[1;32m" +#define BYEL "\033[1;33m" +#define BBLU "\033[1;34m" +#define BMAG "\033[1;35m" +#define BCYN "\033[1;36m" +#define BWHT "\033[1;37m" + +/* Regular underline text */ +#define UBLK "\033[4;30m" +#define URED "\033[4;31m" +#define UGRN "\033[4;32m" +#define UYEL "\033[4;33m" +#define UBLU "\033[4;34m" +#define UMAG "\033[4;35m" +#define UCYN "\033[4;36m" +#define UWHT "\033[4;37m" + +/* Regular background */ +#define BLKB "\033[40m" +#define REDB "\033[41m" +#define GRNB "\033[42m" +#define YELB "\033[43m" +#define BLUB "\033[44m" +#define MAGB "\033[45m" +#define CYNB "\033[46m" +#define WHTB "\033[47m" + +/* High intensty background */ +#define BLKHB "\033[0;100m" +#define REDHB "\033[0;101m" +#define GRNHB "\033[0;102m" +#define YELHB "\033[0;103m" +#define BLUHB "\033[0;104m" +#define MAGHB "\033[0;105m" +#define CYNHB "\033[0;106m" +#define WHTHB "\033[0;107m" + +/* High intensty text */ +#define HBLK "\033[0;90m" +#define HRED "\033[0;91m" +#define HGRN "\033[0;92m" +#define HYEL "\033[0;93m" +#define HBLU "\033[0;94m" +#define HMAG "\033[0;95m" +#define HCYN "\033[0;96m" +#define HWHT "\033[0;97m" + +/* Bold high intensity text */ +#define BHBLK "\033[1;90m" +#define BHRED "\033[1;91m" +#define BHGRN "\033[1;92m" +#define BHYEL "\033[1;93m" +#define BHBLU "\033[1;94m" +#define BHMAG "\033[1;95m" +#define BHCYN "\033[1;96m" +#define BHWHT "\033[1;97m" + +/* Reset */ +#define reset "\033[0m" +#define CRESET "\033[0m" +#define COLOR_RESET "\033[0m" + +#endif diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..20fabe4 --- /dev/null +++ b/src/config.h @@ -0,0 +1,9 @@ +#ifndef CONFIG_H +#define CONFIG_H + +int boardSize = 20; +int obstacleSize = 2; +int obstacles = 4; +int speed = 3; + +#endif diff --git a/src/conio.c b/src/conio.c new file mode 100644 index 0000000..50b7e0f --- /dev/null +++ b/src/conio.c @@ -0,0 +1,16 @@ +#include "./conio.h" +#include +#include +#include + +int getch(void) { + struct termios oldattr, newattr; + int ch; + tcgetattr(STDIN_FILENO, &oldattr); + newattr = oldattr; + newattr.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newattr); + ch = getchar(); + tcsetattr(STDIN_FILENO, TCSANOW, &oldattr); + return ch; +} diff --git a/src/conio.h b/src/conio.h new file mode 100644 index 0000000..520e146 --- /dev/null +++ b/src/conio.h @@ -0,0 +1,6 @@ +#ifndef CONIO_H +#define CONIO_H + +int getch(void); + +#endif diff --git a/src/snake.c b/src/snake.c new file mode 100644 index 0000000..577c832 --- /dev/null +++ b/src/snake.c @@ -0,0 +1,360 @@ +#include +#include +#include +#include +#include /* handling ctrl-c */ +#include /* terminal size */ +#include /* argument parsing */ +#include /* time calculations */ + +#include "vector.h" +#include "snake.h" +#include "conio.h" +#include "colors.h" +#include "config.h" +#define UNUSED(x) (void)(x) + +/*** GLOBAL VARIABLES ***/ +volatile sig_atomic_t intCtrlC = 0; /* flag for ctrl-c interrupt */ +int score = 0; +int updateInterval = 300000000L; + +vectorp_t obstaclePositions; +vectorp_t snakePositions; +vector_t foodPos; +vector_t lastMoveDir; + +/* unsafe functions like printf cannot be called in async interrupt handler */ +void interrupt_handler(int sig) { + UNUSED(sig); + intCtrlC = 1; +} + +int main(int argc, char **argv) { + /* parsing arguments */ + char *last_flag = NULL; + int was_flag = 0; + for(int i = 1; i < argc; i++) { + if(strcmp("--version", argv[i]) == 0) { + printf("snake3, version 2.0\n"); + printf("Copyright 2023 Kai Stevenson\n"); + printf("Copyright 2023 Preston Pan\n"); + printf("This software was licensed under the MIT license;\n"); + printf("for more information, see the license file.\n"); + return 0; + } + else if(strcmp("--help", argv[i]) == 0) { + fprintf(stderr, "usage: snake3 [--version] [--help] [--size board_size] [--speed speed] [--obstacles obstacles]\n"); + return 1; + } + else if(strcmp("--size", argv[i]) == 0 || strcmp("--speed", argv[i]) == 0 || strcmp("--obstacles", argv[i]) == 0) { + last_flag = argv[i]; + was_flag = 1; + } + else if(was_flag) { + if(strcmp("--size", last_flag) == 0) { + boardSize = atoi(argv[i]); + if(boardSize < 3) { + fprintf(stderr, "%sboard size too small or argument invalid, please try again.%s\n", RED, COLOR_RESET); + return 1; + } + } + else if(strcmp("--speed", last_flag) == 0) { + speed = atoi(argv[i]); + if(speed <= 0) { + fprintf(stderr, "%sspeed level must be greater than zero and integer. Please try again.%s\n", RED, COLOR_RESET); + return 1; + } + else if(speed > 10) { + fprintf(stderr, "%sspeed must not be greater than ten. Please try again.%s\n", RED, COLOR_RESET); + return 1; + } + } + else if(strcmp("--obstacles", last_flag) == 0) { + obstacles = atoi(argv[i]); + if(obstacles < 0) { + fprintf(stderr, "%sobstacle count cannot be negative. Please try again.%s\n", RED, COLOR_RESET); + return 1; + } + } + was_flag = 0; + } + else { + printf("usage: snake3 [--version] [--help]\n"); + return 1; + } + } + /* check argument compatibility */ + if((obstacles * obstacleSize) * 3 > pow(boardSize, 2)) { + fprintf(stderr, "%stoo many obstacles. Please try again.%s\n", RED, COLOR_RESET); + exit(1); + } + /* get terminal size */ + struct winsize win; + ioctl(fileno(stdout), TIOCGWINSZ, &win); + int width = (int)(win.ws_col); + int height = (int)(win.ws_row); + /* multiplied by 2 to account for the fact that the board is twice as wide as high */ + int smallestAxis = width / 2 < height ? width / 2 : height; + boardSize = smallestAxis < boardSize ? smallestAxis : boardSize; + /* hide cursor */ + printf("\033[?25l"); + /* make a new thread handling interrupts */ + signal(SIGINT, interrupt_handler); + /* init values */ + snakePositions = init_vectorp(malloc(5 * sizeof(vector_t)), 5); + snakePositions.vectors[0] = init_vector(5, 5); + snakePositions.vectors[1] = init_vector(5, 4); + snakePositions.vectors[2] = init_vector(5, 3); + snakePositions.vectors[3] = init_vector(4, 3); + snakePositions.vectors[4] = init_vector(3, 3); + foodPos = init_vector(7, 9); + lastMoveDir = init_vector(0, 1); + int opc = (int)pow(obstacleSize, 2) * obstacles; + obstaclePositions = init_vectorp(malloc(opc * sizeof(vector_t)), opc); + srand(time(NULL)); + /* place the obstacles */ + /* TODO: make this less recursive */ + int opi = 0; + for(int i = 0; i < obstacles; i++) { + while(1) { + vector_t basePos = init_vector((rand() % (boardSize - (obstacleSize - 1))), (rand() % (boardSize - (obstacleSize - 1)))); + /* if obstacle overlaps snake, try again */ + uint8 overlap = 0; + for(int r = 0; r < obstacleSize; r++) { + for(int c = 0; c < obstacleSize; c++) { + vector_t voxelPos = init_vector(basePos.mx + c, basePos.my + r); + for(int j = 0; j < snakePositions.length; j++) { + if((snakePositions.vectors[j].mx == voxelPos.mx) && (snakePositions.vectors[j].my == voxelPos.my)) { + overlap = 1; + } + } + if(foodPos.mx == voxelPos.mx && foodPos.my == voxelPos.my) { + overlap = 1; + } + } + } + if(overlap) { + continue; + } + for(int r = 0; r < obstacleSize; r++) { + for(int c = 0; c < obstacleSize; c++) { + vector_t voxelPos = init_vector(basePos.mx + c, basePos.my + r); + obstaclePositions.vectors[opi] = voxelPos; + opi++; + } + } + break; + } + } + /* clear the screen so as to prevent artifacting */ + printf("\033[2J"); + /* prepare the pointers which will be used to communicate with the input getting thread */ + volatile int* lastInput = calloc(1, sizeof(int)); + int* cancellationToken = calloc(1, sizeof(int)); + /* and the wrapper struct */ + threadArg_t threadArg; + threadArg.returnV = (void*)lastInput; + threadArg.cancellationToken = cancellationToken; + /* create listener thread */ + pthread_t listenerID; + pthread_create(&listenerID, NULL, get_dir_loop, &threadArg); + /* main game logic loop */ + while(1) { + /* handle ctrl-c interrupt */ + if(intCtrlC) { + goto EXIT; + } + + /* first, draw the current state of the board */ + draw_board(); + /* handle player input; + * wait for the specified interval */ + struct timespec time; + time.tv_sec = 0; + time.tv_nsec = round((float)updateInterval / ((float)speed / 3)); + nanosleep(&time, NULL); + /* read the most recent value emitted by the listener */ + vector_t dir = parse_dir(*(char*)lastInput); + /* if the player inputted a valid direction, use it. otherwise, use his last valid input */ + if((dir.mx != 0 || dir.my != 0) && !(dir.mx == 1 && lastMoveDir.mx == -1) && !(dir.mx == -1 && lastMoveDir.mx == 1) && !(dir.my == 1 && lastMoveDir.my == -1) && !(dir.my == -1 && lastMoveDir.my == 1)) { + lastMoveDir = dir; + } + + uint8 ateFood = 0; + /* check if snake is moving onto food */ + if((snakePositions.vectors[0].mx + lastMoveDir.mx == foodPos.mx) && (snakePositions.vectors[0].my + lastMoveDir.my == foodPos.my)) { + ateFood = 1; + score += 1; + if(updateInterval > 100000000) { + updateInterval -= 10000000; + } + /* move the food */ + while(1) { + uint8 overlap = 0; + foodPos = init_vector(1 + (rand() % (boardSize - 2)), 1 + (rand() % (boardSize - 2))); + /* if food overlaps snake or obstacles, try again */ + for(int i = 0; i < snakePositions.length; i++) { + if((snakePositions.vectors[i].mx == foodPos.mx) && (snakePositions.vectors[i].my == foodPos.my)) { + overlap = 1; + } + } + for(int i = 0; i < obstaclePositions.length; i++) { + if((obstaclePositions.vectors[i].mx == foodPos.mx) && (obstaclePositions.vectors[i].my == foodPos.my)) { + overlap = 1; + } + } + if(!overlap) { + break; + } + } + } + /* move the snake */ + vectorp_t newSnakePositions = move_snake(lastMoveDir, ateFood); + free(snakePositions.vectors); + snakePositions = newSnakePositions; + /* check if snake landed on self */ + for(int i = 1; i < snakePositions.length; i++) { + if(snakePositions.vectors[0].mx == snakePositions.vectors[i].mx && snakePositions.vectors[0].my == snakePositions.vectors[i].my) { + /* game over */ + goto EXIT; + } + } + /* check if snake landed on obstacle */ + for(int i = 0; i < obstaclePositions.length; i++) { + if((snakePositions.vectors[0].mx == obstaclePositions.vectors[i].mx) && (snakePositions.vectors[0].my == obstaclePositions.vectors[i].my)) { + goto EXIT; + } + } + } +EXIT: + /* clear the last getch call from the listener */ + *cancellationToken = 1; + printf("\n%sYou died. Score: %d. Press any key to exit%s\n", RED, score, COLOR_RESET); + printf("\033[?25h"); + pthread_join(listenerID, NULL); + free(cancellationToken); + free((int*)lastInput); + return 0; +} +void* get_dir_loop(void* threadArg) { + while(!*(int*)((threadArg_t*)threadArg)->cancellationToken) { + *(char*)((threadArg_t*)threadArg)->returnV = getch(); + } + return NULL; +} +/* parses WASD or HJKL, returns the necessary deltas to move within a 2d array */ +vector_t parse_dir(char cDir) { + if(cDir == 'w' || cDir == 'k') { + return init_vector(0, -1); + } + else if(cDir == 'd' || cDir == 'l') { + return init_vector(1, 0); + } + else if(cDir == 's' || cDir == 'j') { + return init_vector(0, 1); + } + else if(cDir == 'a' || cDir == 'h') { + return init_vector(-1, 0); + } + else { + return init_vector(0, 0); + } +} +vectorp_t move_snake(vector_t dir, int lengthen) { + int newLength = snakePositions.length + (lengthen ? 1 : 0); + vector_t* newSnakePositions = malloc((newLength * sizeof(vector_t))); + /* wrap around edges */ + int nhx = snakePositions.vectors[0].mx + dir.mx; + nhx = nhx < 0 ? boardSize - 1 : nhx >= boardSize ? 0 : nhx; + int nhy = snakePositions.vectors[0].my + dir.my; + nhy = nhy < 0 ? boardSize - 1 : nhy >= boardSize ? 0 : nhy; + newSnakePositions[0] = init_vector(nhx, nhy); + for(int i = 1; i < newLength; i++) { + newSnakePositions[i] = snakePositions.vectors[i - 1]; + } + return init_vectorp(newSnakePositions, newLength); +} +void draw_board() { + /* move the cursor to the top left corner of the screen */ + printf("\033[0;0H"); + /* iterate through and write the board */ + for(int r = 0; r < boardSize; r++) { + for(int c = 0; c < boardSize; c++) { + /* determine the contents of the board position */ + int type = 0; + /* 0=horizontal, 1=vertical, 2=diagonal slant right, 3=diagonal slant left */ + int tailOrientation = 0; + for(int i = 0; i < snakePositions.length; i++) { + if(snakePositions.vectors[i].mx == c && snakePositions.vectors[i].my == r) { + if(i > 0) { + type = 2; + /* get the delta to the connecting tail */ + int dx; + int dy; + if(i < snakePositions.length - 1) { + dx = snakePositions.vectors[i - 1].mx - snakePositions.vectors[i + 1].mx; + dy = snakePositions.vectors[i - 1].my - snakePositions.vectors[i + 1].my; + } + else { + dx = snakePositions.vectors[i - 1].mx - c; + dy = snakePositions.vectors[i - 1].my - r; + } + if(dy == 0) { + tailOrientation = 0; + } + else if(dx == 0) { + tailOrientation = 1; + } + else if((dx == 1 && dy == 1) || (dx == -1 && dy == -1)) { + tailOrientation = 3; + } + else if((dx == -1 && dy == 1) || (dx == 1 && dy == -1)) { + tailOrientation = 2; + } + } + else { + type = 1; + } + break; + } + } + if(foodPos.mx == c && foodPos.my == r) { + type = 3; + } + for(int i = 0; i < obstaclePositions.length; i++) { + if(obstaclePositions.vectors[i].mx == c && obstaclePositions.vectors[i].my == r) { + type = 4; + break; + } + } + /* append the appropriate value */ + if(type == 0) { + /* empty square */ + printf("` "); + } + else if(type == 1) { + /* snake head */ + printf("%s@ %s", GRN, COLOR_RESET); + } + else if(type == 2) { + /* snake body */ + char bodyChar = tailOrientation == 0 ? '-' + : tailOrientation == 1 ? '|' + : tailOrientation == 2 ? '/' + : tailOrientation == 3 ? '\\' + : '*'; + printf("%s%c %s", GRN, bodyChar, COLOR_RESET); + } + else if(type == 3) { + /* food */ + printf("%sF %s", MAG, COLOR_RESET); + } + else if(type == 4) { + /* obstacle */ + printf("%s# %s", RED, COLOR_RESET); + } + } + printf("\n"); + } +} diff --git a/src/snake.h b/src/snake.h new file mode 100644 index 0000000..c783f9f --- /dev/null +++ b/src/snake.h @@ -0,0 +1,16 @@ +#include "vector.h" +#ifndef SNAKE +#define SNAKE +/* used as bool and int */ +typedef char uint8; + +typedef struct ThreadArg { + int* cancellationToken; + void* returnV; +} threadArg_t; +void interrupt_handler(int sig); +void* get_dir_loop(void* threadArg); +vector_t parse_dir(char cDir); +vectorp_t move_snake(vector_t dir, int lengthen); +void draw_board(void); +#endif diff --git a/src/vector.c b/src/vector.c new file mode 100644 index 0000000..6ab3bf2 --- /dev/null +++ b/src/vector.c @@ -0,0 +1,48 @@ +#include +#include "vector.h" +vector_t init_vector(int mx, int my) { + vector_t vector; + vector.mx = mx; + vector.my = my; + return vector; +} +vectorp_t init_vectorp(vector_t* vectors, int length) { + vectorp_t vectorP; + vectorP.vectors = vectors; + vectorP.length = length; + return vectorP; +} +vectorp_t remove_vector(vectorp_t vectorp, int index) { + vector_t* vectors = malloc((vectorp.length - 1) * sizeof(vector_t)); + for(int i = 0; i < vectorp.length - 1; i++) { + int correctedIndex = i > index ? i - 1 : i; + if(i != index) { + vectors[correctedIndex] = vectorp.vectors[i]; + } + } + return init_vectorp(vectors, vectorp.length - 1); +} +vectorp_t remove_vectors(vectorp_t vectorp, int* indexes, int indexCount) { + vector_t* vectors = malloc((vectorp.length - indexCount) * sizeof(vector_t)); + int correctedIndex = 0; + for(int i = 0; i < vectorp.length - indexCount; i++) { + int toRemove = 0; + for(int j = 0; j < indexCount; j++) { + if(i == indexes[j]) { + toRemove = 1; + break; + } + } + if(!toRemove) { + vectors[correctedIndex] = vectorp.vectors[i]; + correctedIndex++; + } + } + return init_vectorp(vectors, vectorp.length - indexCount); +} +vectorpp_t init_vectorpp(vectorp_t* vectorps, int length) { + vectorpp_t vectorPP; + vectorPP.vectorps = vectorps; + vectorPP.length = length; + return vectorPP; +} diff --git a/src/vector.h b/src/vector.h new file mode 100644 index 0000000..877da5b --- /dev/null +++ b/src/vector.h @@ -0,0 +1,27 @@ +#ifndef VECTOR +#define VECTOR +typedef struct Vector { + int mx; + int my; +} vector_t; + +vector_t init_vector(int mx, int my); + +typedef struct VectorP { + vector_t* vectors; + int length; +} vectorp_t; + +vectorp_t init_vectorp(vector_t* vectors, int length); + +/* removes a vector from vectorp */ +vectorp_t remove_vector(vectorp_t vectorp, int index); +vectorp_t remove_vectors(vectorp_t vectorp, int* indexes, int indexCount); +typedef struct VectorPP { + vectorp_t* vectorps; + int length; +} vectorpp_t; + +vectorpp_t init_vectorpp(vectorp_t* vectorps, int length); + +#endif diff --git a/vector.c b/vector.c deleted file mode 100644 index 6ab3bf2..0000000 --- a/vector.c +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include "vector.h" -vector_t init_vector(int mx, int my) { - vector_t vector; - vector.mx = mx; - vector.my = my; - return vector; -} -vectorp_t init_vectorp(vector_t* vectors, int length) { - vectorp_t vectorP; - vectorP.vectors = vectors; - vectorP.length = length; - return vectorP; -} -vectorp_t remove_vector(vectorp_t vectorp, int index) { - vector_t* vectors = malloc((vectorp.length - 1) * sizeof(vector_t)); - for(int i = 0; i < vectorp.length - 1; i++) { - int correctedIndex = i > index ? i - 1 : i; - if(i != index) { - vectors[correctedIndex] = vectorp.vectors[i]; - } - } - return init_vectorp(vectors, vectorp.length - 1); -} -vectorp_t remove_vectors(vectorp_t vectorp, int* indexes, int indexCount) { - vector_t* vectors = malloc((vectorp.length - indexCount) * sizeof(vector_t)); - int correctedIndex = 0; - for(int i = 0; i < vectorp.length - indexCount; i++) { - int toRemove = 0; - for(int j = 0; j < indexCount; j++) { - if(i == indexes[j]) { - toRemove = 1; - break; - } - } - if(!toRemove) { - vectors[correctedIndex] = vectorp.vectors[i]; - correctedIndex++; - } - } - return init_vectorp(vectors, vectorp.length - indexCount); -} -vectorpp_t init_vectorpp(vectorp_t* vectorps, int length) { - vectorpp_t vectorPP; - vectorPP.vectorps = vectorps; - vectorPP.length = length; - return vectorPP; -} diff --git a/vector.h b/vector.h deleted file mode 100644 index 877da5b..0000000 --- a/vector.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef VECTOR -#define VECTOR -typedef struct Vector { - int mx; - int my; -} vector_t; - -vector_t init_vector(int mx, int my); - -typedef struct VectorP { - vector_t* vectors; - int length; -} vectorp_t; - -vectorp_t init_vectorp(vector_t* vectors, int length); - -/* removes a vector from vectorp */ -vectorp_t remove_vector(vectorp_t vectorp, int index); -vectorp_t remove_vectors(vectorp_t vectorp, int* indexes, int indexCount); -typedef struct VectorPP { - vectorp_t* vectorps; - int length; -} vectorpp_t; - -vectorpp_t init_vectorpp(vectorp_t* vectorps, int length); - -#endif -- cgit v1.3.1