summaryrefslogtreecommitdiff
path: root/src/snake.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/snake.c')
-rw-r--r--src/snake.c360
1 files changed, 360 insertions, 0 deletions
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 <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <pthread.h>
+#include <signal.h> /* handling ctrl-c */
+#include <sys/ioctl.h> /* terminal size */
+#include <string.h> /* argument parsing */
+#include <math.h> /* 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");
+ }
+}