summaryrefslogtreecommitdiff
path: root/snake.c
diff options
context:
space:
mode:
Diffstat (limited to 'snake.c')
-rw-r--r--snake.c329
1 files changed, 329 insertions, 0 deletions
diff --git a/snake.c b/snake.c
new file mode 100644
index 0000000..24c0900
--- /dev/null
+++ b/snake.c
@@ -0,0 +1,329 @@
+/*
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <ncurses.h>
+
+/* direction definitions for easy to read code */
+#define DOWN 0
+#define UP 1
+#define LEFT 2
+#define RIGHT 3
+
+/* game area size */
+#define WIDTH 20
+#define HEIGHT 20
+
+typedef struct snakepiece
+{
+ /* snakepiece is a doubly linked list */
+ int x, y; /* current x and y position */
+ int oldx, oldy; /* previous x and y position */
+ struct snakepiece *prev; /* will point to the previous piece or null */
+ struct snakepiece *next; /* will point to the piece in front or null */
+} piece;
+
+typedef struct snakehead
+{
+ int x, y; /* current head position */
+ int oldx, oldy; /* old x and y position */
+ int direction; /* current direction the head will move */
+ int score; /* incremented when food is eaten */
+ piece *firstpiece; /* will point to the head of the snakepiece linked list or null */
+ int state; /* game state int, 1 = running 0 = quit */
+ int npc; /* determines if the snake controls itself 1 = bot 0 = human */
+ WINDOW *area; /* terrible place to store the play area window */
+} head;
+
+typedef struct foodpiece
+{
+ int x, y; /* food piece location */
+} food;
+
+void *update(head *snake);
+void draw(head *snake, food *eat);
+void setup_food(head *snake, food *eat);
+void create_body(head *snake);
+void npc_logic(head *snake, food *eat);
+
+int main(void)
+{
+ /* seed rand */
+ srand(time(NULL));
+ /* ncurses setup */
+ initscr();
+ noecho();
+ curs_set(0);
+ timeout(50);
+ /* terminal width and height */
+ int w, h;
+ getmaxyx(stdscr, h, w);
+ /* setup snake */
+ head *snake;
+ snake->x = 0;
+ snake->y = 5;
+ snake->oldx = snake->x;
+ snake->oldy = snake->y;
+ snake->direction = RIGHT;
+ snake->state = 1;
+ snake->npc = 0; /* start asa huma nplayer */
+ snake->firstpiece = NULL;
+ /* setup game area */
+ snake->area = newwin(HEIGHT, WIDTH, 1, 8);
+ /* setup update thread */
+ pthread_t updatethread;
+ pthread_create(&updatethread, NULL, update, snake); /* we pass our snake pointer to update(...) */
+ /* input loop */
+ int ch; /* stores our current input */
+ while(snake->state) /* while state == 1 aka while game running */
+ {
+ ch = getch();
+ switch(ch)
+ {
+ /* direction key detection */
+ case 's':
+ case 'j':
+ snake->direction = DOWN;
+ break;
+ case 'w':
+ case 'k':
+ snake->direction = UP;
+ break;
+ case 'a':
+ case 'h':
+ snake->direction = LEFT;
+ break;
+ case 'd':
+ case 'l':
+ snake->direction = RIGHT;
+ break;
+ case 'q': /* q quits the game */
+ snake->state = 0;
+ break;
+ case 'b': /* b toggles bot playing */
+ snake->npc = !snake->npc;
+ break;
+ default:
+ break;
+ }
+ }
+ /* end ncurses for a sane terminal */
+ endwin();
+ printf("final score: %d\n", snake->score);
+ /* free body pieces */
+ piece *finger = snake->firstpiece;
+ while (finger != NULL)
+ {
+ free(finger->prev);
+ finger = finger->next;
+ }
+ free(snake->firstpiece);
+ free(finger);
+ return 0;
+}
+
+void *update(head *snake)
+{
+ food eat;
+ setup_food(snake, &eat);
+ while(1)
+ {
+ /* store snake x and y, the head body piece needs to know this */
+ snake->oldx = snake->x;
+ snake->oldy = snake->y;
+ /* move the snake towards the direction it's pointing */
+ switch(snake->direction)
+ {
+ case DOWN:
+ snake->y++;
+ break;
+ case UP:
+ snake->y--;
+ break;
+ case LEFT:
+ snake->x--;
+ break;
+ case RIGHT:
+ snake->x++;
+ break;
+ default:
+ break;
+ }
+ /* detect if head is touching the border */
+ if (snake->x == WIDTH-1 ||
+ snake-> y == HEIGHT-1 ||
+ snake->x == 0 ||
+ snake->y == 0)
+ snake->state = 0;
+ /* check if food eaten */
+ if (snake->x == eat.x &&
+ snake->y == eat.y)
+ {
+ /* create a body piece */
+ create_body(snake);
+ snake->score++;
+ setup_food(snake, &eat);
+ }
+ /* check if touching any body part */
+ piece *finger = snake->firstpiece;
+ while (finger != NULL)
+ {
+ if (snake->x == finger->x && snake->y == finger->y)
+ {
+ snake->state = 0; /* exit the game */
+ }
+ finger = finger->next;
+ }
+
+ /* update body pieces position if any exist */
+ finger = snake->firstpiece;
+ while (finger != NULL)
+ {
+ /* store our old x and y, the finger->next needs it */
+ finger->oldx = finger->x;
+ finger->oldy = finger->y;
+ /* check if we are the head of the list, if we are prev will be NULL */
+ if (finger->prev == NULL)
+ {
+ finger->x = snake->oldx;
+ finger->y = snake->oldy;
+ }
+ else
+ {
+ finger->x = finger->prev->oldx;
+ finger->y = finger->prev->oldy;
+ }
+ finger = finger->next;
+ }
+ free(finger);
+ /* check if the game should play itself */
+ if (snake->npc)
+ npc_logic(snake, &eat);
+ draw(snake, &eat);
+ /* sleep to keep game at a reasonable speed */
+ usleep(200000);
+ //usleep(60000);
+ }
+}
+
+void draw(head *snake, food *eat)
+{
+ /* clear the game area */
+ wclear(snake->area);
+ /* box our game area */
+ box(snake->area, 0, 0);
+ /* draw the snake head */
+ mvwaddch(snake->area, snake->y, snake->x, ACS_BLOCK);
+ /* draw the food */
+ mvwaddch(snake->area, eat->y, eat->x, '*');
+ /* render body pieces if any exist */
+ piece *finger = snake->firstpiece;
+ while (finger != NULL)
+ {
+ mvwaddch(snake->area, finger->y, finger->x, ACS_BLOCK);
+ finger = finger->next;
+ }
+ free (finger);
+ mvprintw(0, 0, "score: %d\n", snake->score);
+ mvprintw(HEIGHT+2, 0, " 'wasd/hjkl' to control the snake.\n" \
+ " 'b' to toggle bot control.\n" \
+ " 'q' to quit.");
+ refresh();
+ wrefresh(snake->area);
+}
+
+void setup_food(head *snake, food *eat)
+{
+ /* set random x and y positions */
+ /* restart label in the event food touches snake */
+ restart:
+ eat->x = rand() % WIDTH;
+ eat->y = rand() % HEIGHT;
+ /* check if the food is touching the nsake or a body piece */
+ if (eat->x == snake->x && eat->y == snake->y)
+ goto restart;
+ piece *finger = snake->firstpiece;
+ while (finger != NULL)
+ {
+ if (eat->x == finger->x && eat->y == finger->y)
+ goto restart;
+ finger = finger->next;
+ }
+ free(finger);
+ /* make sure the food is in bounds */
+ if (eat->x < 3 ||
+ eat->x > WIDTH-3)
+ goto restart;
+ if (eat->y < 3 ||
+ eat->y > HEIGHT-3)
+ goto restart;
+}
+
+void create_body(head *snake)
+{
+ piece *new = (piece *) malloc(sizeof(piece)); /* allocate memory for our new piece */
+ /* check if a head body piece exists */
+ if (snake->firstpiece == NULL)
+ {
+ /* a head body piece doesn't exist, make it */
+ /* set our initial x and y */
+ new->x = 0;
+ new->y = 0;
+ /* these aren't used without updating, so the value doesn't really matter */
+ new->oldx = 0;
+ new->oldy = 0;
+ /* our head wont will never have a prev */
+ new->prev = NULL;
+ snake->firstpiece = new;
+ /* we have nothing else to do, so return */
+ return;
+ }
+ /* we aren't the head, so add a new piece */
+ /* allocate memory to store our previous piece */
+ new->prev = malloc(sizeof(piece));
+ //piece *finger = (piece *) malloc(sizeof(piece));
+ piece *finger = snake->firstpiece;
+ /* loop through every body piece */
+ while (finger->next != NULL)
+ {
+ finger = finger->next;
+ }
+ /* finger is now the last piece, set it as new->prev */
+ new->prev = finger;
+ /* set our initial x and y */
+ new->x = 0;
+ new->y = 0;
+ /* these aren't used without updating, so the values doesn't really matter */
+ new->oldx = 0;
+ new->oldy = 0;
+ /* set our new piece as the last piece in the list */
+ finger->next = new;
+}
+
+void npc_logic(head *snake, food *eat)
+{
+ if (snake->y > eat->y)
+ snake->direction = UP;
+ else if (snake->y < eat->y)
+ snake->direction = DOWN;
+ else if (snake->x > eat->x)
+ snake->direction = LEFT;
+ else if (snake->x < eat->x)
+ snake->direction = RIGHT;
+ }