summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2023-03-04 17:17:13 -0800
committerKai Stevenson <kai@kaistevenson.com>2023-03-04 17:17:13 -0800
commit79fa249aa729e03b17991c3991f5bb090ffb6b61 (patch)
treecd302a308e51144ddf1e89c13d2e1652a68d0b81 /src
parentaaa2e22835d80cf51fee21025207644ae19d8d66 (diff)
restructured project
Diffstat (limited to 'src')
-rw-r--r--src/colors.h80
-rw-r--r--src/config.h9
-rw-r--r--src/conio.c16
-rw-r--r--src/conio.h6
-rw-r--r--src/snake.c360
-rw-r--r--src/snake.h16
-rw-r--r--src/vector.c48
-rw-r--r--src/vector.h27
8 files changed, 562 insertions, 0 deletions
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 <termios.h>
+#include <unistd.h>
+#include <stdio.h>
+
+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 <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");
+ }
+}
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 <stdlib.h>
+#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