// Emilio Espada and Kameron Dear
// CS 355 - Systems Programming Course Project - Spring 2024
// Implement a classic snake game
// Final deliverable 05/02/2024
#include <curses.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
// struct to define position for game objects (snake,body,trophy,direction) -Emilio
struct position {
int x;
int y;
};
// Delcaration of global variables to be used throughout program and functions -Emilio
int length = 2; // Length of snake's body, increases when a trophy is touched
int max_y, max_x; // max height and width of screen
int halfPerimter; // half perimeter of screen
int alive = 1; // Control if game is over or not
int win = 0; // determines if game won message prints at end
// Functions declarations, used in the main function
void initializeGame(); // Run required curses function calls, get screen size, and set rand seed -Emilio
void drawSnakePit(int max_y, int max_x); // Snake Pit -Kameron
void changeDirection(struct position *direction); // Change snake's moving direction -Emilio
void updateSnakePosition(struct position *body, struct position *head, struct position direction); // Update posistions -Emilio
int collision(struct position a, struct position b); // Collision detection -Emilio
void checkWinLoss(struct position head, struct position *body); // Check win/loss -Emilio
void trophyPlacement(struct position* body, struct position head, struct position* trophy); // Trophy placement -Emilio
void drawScreen(struct position *body, struct position head, struct position trophy); // Draw updated positions -Emilio
int main() {
initializeGame(); // Function call to initalize curses, get screen size, set rand seed
// struct position declarations for game objects -Emilio
struct position head = {max_x/4, max_y/2}; // position for snake's head, start around center of the screen
struct position direction = {1, 0}; // position for snake's moving direction, snake starts off moving right
struct position body[halfPerimter+10]; // position for snake's body, declared as an array
struct position trophy; // position for trophy, randomly position within snakepit borders
trophyPlacement(body, head, &trophy); // Function call to place trophy at a valid spot
// Game loop that runs until user presses ctrl+c, gameover, or victory -Emilio
while(alive){
drawSnakePit(max_y, max_x); // Function call to draw the border
changeDirection(&direction); // Function call to change direction of snake based on arrow key presses
updateSnakePosition(body, &head, direction); // Function call to update positions
checkWinLoss(head, body); // Function call to check for win and loss conditions
// If snake is on same space as trophy, increase snake's length and change position of trophy - Emilio
if (head.x == trophy.x && head.y == trophy.y){
length += 1;
trophyPlacement(body, head, &trophy); // Function call to place trophy at valid spot
}
erase(); // Clear screen
drawScreen(body, head, trophy); // Function call to draw updated game object positions
usleep(110000-(length*1000)); // Speed of game is modified by snake's length
}
endwin(); // Restore terminal to normal and print results of game
printf("*** GAME OVER ***\n");
if (win)
printf("*** YOU WON! ***\n");
else
printf("*** YOU LOST! ***\n");
return 0;
}
// Function to initialize required curses function calls, get screen size, and set rand seed -Emilio
void initializeGame() {
initscr(); // Initalizes screen using curses
keypad(stdscr, TRUE); // Enable the use of keypad for the snake direction logic below
nodelay(stdscr, TRUE); // Allow program to run continously without waiting for user input
curs_set(0); // Hide cursor from being shown
noecho(); // Hide user input from showing if they press a non-arrowkey
getmaxyx(stdscr, max_y, max_x); // Get the dimensions of the terminal window -Kameron
halfPerimter = max_x + (max_y - 2); // Used for victory condition and max snake length
srand(length * halfPerimter); // change random seed without including time.h
}
// Function to draw the snake pit border -Kameron
void drawSnakePit(int max_y, int max_x) {
// Draw snake pit border across the top of the window
for (int i = 0; i < max_x; i++) {
mvaddch(0, i, '-');
}
// Draw snake pit border across the bottom of the window
for (int i = 0; i < max_x; i++) {
mvaddch(max_y - 1, i, '-');
}
// Draw snake pit border across the left and right sides of the window
for (int i = 1; i < max_y - 1; i++) {
mvaddch(i, 0, '|');
mvaddch(i, max_x - 1, '|');
}
}
// Function to change direction of snake based on arrow key presses -Emilio
void changeDirection(struct position *direction) {
int ch = getch();
switch (ch) {
case KEY_UP:
if (direction->y == 1) // Kills snake if you attempt to reverse direction
alive = 0;
direction->x = 0;
direction->y = -1;
break;
case KEY_DOWN:
if (direction->y == -1)
alive = 0;
direction->x = 0;
direction->y = 1;
break;
case KEY_LEFT:
if (direction->x == 1)
alive = 0;
direction->x = -1;
direction->y = 0;
break;
case KEY_RIGHT:
if (direction->x == -1)
alive = 0;
direction->x = 1;
direction->y = 0;
break;
}
}
// Function to determine position for snake head, body, and direction -Emilio
void updateSnakePosition(struct position *body, struct position *head, struct position direction) {
for (int i = length; i > 0; i--) {
body[i] = body[i - 1];
}
body[0] = *head;
head->x += direction.x;
head->y += direction.y;
}
// Look at the position of a compared to b (ie. snake to it's body, or snake to wall) -Emilio
int collision(struct position a, struct position b) {
if (a.x == b.x && a.y == b.y) {
return 1; // If a's and b's x and y positiona are the same return 1;
}
return 0; // otherwise, return 0 no collision
}
// Check if snake's position or length leads to a gameover or victory -Emilio
void checkWinLoss(struct position head, struct position *body) {
// Check if the snake hits the border
if (head.x == 0 || head.x == max_x / 2 || head.y == 0 || head.y == max_y - 1)
alive = 0;
// Check if the snake runs into itself
for (int i = 0; i < length; i++)
if (collision(head, body[i]))
alive = 0;
// Check if the player wins the game
if (length >= halfPerimter) {
win = 1;
alive = 0;
}
}
// Function to generate a random position for the trophy that doesn't overlap with the snake -Emilio
void trophyPlacement(struct position* body, struct position head, struct position* trophy) {
do {
// Set the x and y position of trophy to a random number
trophy->x = rand() % (max_x/2 - 1) + 1;
trophy->y = rand() % (max_y - 2) + 1;
// Check if the trophy overlaps with the snake's head
if (collision(*trophy, head)) {
continue; // If it does, start the do loop again from the beginning
}
// Check if the trophy overlaps with any part of the snake's body
for (int i = 0; i < length; i++) {
if (collision(*trophy, body[i])) {
continue;
}
}
// If no overlap is found, break out of the loop
break;
} while (1); // Loops continues until a valid trophy position is found
}
// Function to draw the updated position of game objects -Emilio
void drawScreen(struct position *body, struct position head, struct position trophy) {
mvaddch(trophy.y, trophy.x * 2, '@'); // Draw trophy at its position, x*2 to stay consistent with snake
for (int i = 0; i < length; i++) // Draw snake's body
mvaddch(body[i].y, body[i].x * 2, 'o'); //mult by 2 so horizontal speed = vertical
mvaddch(head.y, head.x * 2, 'O'); //head.x is mult by 2 so horizontal speed = vertical
}