/*
* Copyright (C) 2018 Daniel Jones daniel@danieljon.es
*
* 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 2 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 .
*/
#include
#include
#include
#include
#include
#include
/* 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':
/* prevent changing direction into yourself */
if (snake->direction != UP)
snake->direction = DOWN;
break;
case 'w':
case 'k':
/* prevent changing direction into yourself */
if (snake->direction != DOWN)
snake->direction = UP;
break;
case 'a':
case 'h':
/* prevent changing direction into yourself */
if (snake->direction != RIGHT)
snake->direction = LEFT;
break;
case 'd':
case 'l':
/* prevent changing direction into yourself */
if (snake->direction != LEFT)
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();
pthread_cancel(updatethread);
printf("final score: %d\n", snake->score);
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;
}
/* 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;
}
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;
}
/* 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 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)
{
/*
* our goal location is eat->x and eat->y
* we must avoid:
* - running into our own body
* - running into the edge
* how to achieve this:
* - scan towards the x and y of the food (up/down and left/right, one of each)
* - if we hit the edge that is a safe direction to go
* - if we hit a body piece it isn't safe, but not out of the picture yet
* - if we hit the food, go that direction
*/
/*
* these hold our desired x and y direction, defaults aren't important.
* a -1 means we are at the correct x or y position
*/
int xdir = UP;
int ydir = RIGHT;
/* find our desired x position */
/* check if the food is to the left of the snake */
if (eat->x < snake->x)
xdir = LEFT;
/* check if the food is to the right of the snake */
else if (eat->x > snake->x)
xdir = RIGHT;
/* the food must be on the same x position as the snake */
else
xdir = -1;
/* find our desired y position */
/* check if the food is below the snake */
if (eat->y > snake->y)
ydir = DOWN;
/* check if the food is abovce the snake */
else if (eat->y < snake->y)
ydir = UP;
/* the food must be on the same y position as the snake */
else
ydir = -1;
/* if either value is -1, use the other and return */
if (xdir == -1)
{
snake->direction = ydir;
return;
}
if (ydir == -1)
{
snake->direction = xdir;
return;
}
/* randomly choose which desired direciton to take */
int x = rand() % 2;
if (x == 0)
snake->direction = ydir;
else
snake->direction = xdir;
/*
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;
*/
}