20 Commits

Author SHA1 Message Date
883aa91224 Removed outdated information. 2025-11-25 09:55:29 +02:00
77f3f937bb extracted border characters to a config header 2025-10-22 12:29:30 +03:00
57d2aba783 Changed version number to git hash 2025-09-15 11:30:51 +03:00
9d9dcbea5f changed version number 2025-09-14 20:09:26 +03:00
8b8160dcd7 added commandline arguments and deleted target 2025-09-14 20:05:40 +03:00
ad5b8beb99 working reworked game 2025-09-10 22:08:34 +03:00
7498a49f88 started rework 2025-09-07 23:52:02 +03:00
c69b5ec8a2 Added debug flags 2025-05-16 12:20:01 +03:00
7f5394353d Changed how game speed is incremented 2025-05-08 11:45:31 +03:00
12c328d4c5 Removed unnecessary comments 2025-03-30 22:42:29 +03:00
f96a344573 v1.1.8 Tuned game options 2025-03-30 22:41:29 +03:00
fcbeb38ea5 v1.1.7 moved board info to board.h 2025-03-30 22:32:56 +03:00
1dc0040fb3 Added null checks for malloc 2025-03-30 22:24:56 +03:00
0e992c3be5 Added a build folder in the compile scripts and a gitignore 2025-03-27 21:10:14 +02:00
a762a8673d Changed versioning and added a default value for gamespeed 2025-03-27 21:06:18 +02:00
30c0acbbfe added compile release build script 2025-02-15 23:34:14 +02:00
5bbc653b7e updated compile script 2025-02-15 23:33:58 +02:00
7b04c22d83 removed double definitions 2025-02-15 23:33:39 +02:00
53135f50ad added version printing 2025-02-15 23:20:45 +02:00
AaroSaila
a9863b3441 Update README.md 2025-01-31 08:34:49 +02:00
24 changed files with 709 additions and 528 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.clangd
target

View File

@@ -9,21 +9,8 @@ Click image for video
## How it works ## How it works
The program makes the terminal use non-canonical input and output by using termios. Then it updates and prints a 2D character array containing the board borders, snake, and food. Before terminating, the program restores canonical mode. The program makes the terminal use non-canonical input and output by using termios. Then it updates and prints a 2D character array containing the board borders, snake, and food. Before terminating, the program restores canonical mode.
Each part of the snake is a node of a dynamically allocated linked list that contains:
- The snake's x and y coordinates
- The character that represents it on the board
- The part's direction
- A pointer to a order node (more on orders below)
- A pointer to the next snake part node
An order keeps information on when the direction of the snake part has to change and to which direction it should change. An order is also a node in a dynamically allocated linked list that contains:
- The direction that this order eventually tells the snake part to go to
- The delay i.e. how many ticks until the snake part has to change direction.
- A pointer to the next order
## Info ## Info
Only tested and designed to work with the combination of Only tested with the combination of
- GCC - GCC
- Linux - Linux
- x86 CPU - x86 CPU

View File

@@ -1,3 +1,42 @@
#!/bin/bash #!/bin/sh
gcc -Wall -Werror -pedantic -o sanke src/main.c src/snake/**.c src/utils/**.c arg=-1
if [ -v 1 ]; then
arg=$1
fi
if [ $arg = "clean" ]; then
rm -rf ./target/*
exit
fi
version=$(git rev-parse HEAD)
oflag="-Og"
debug_flag="-ggdb"
if [ $arg = "release" ]; then
oflag="-O3"
debug_flag=""
fi
src="src/*.c"
macros="-DVERSION=\"$version\""
flags="-std=c23 $oflag $debug_flag $macros -Wall -Wextra -Werror -Wpedantic -pedantic-errors"
includes="-I src/headers"
cmd="gcc $flags $includes $src -o target/sanke"
echo $cmd
$cmd
if [ $? -gt 0 ]; then
exit
fi
if [ $arg = "run" ]; then
echo
./target/sanke
fi

98
src/Board.c Normal file
View File

@@ -0,0 +1,98 @@
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "config.h"
#include "Board.h"
#include "Snake.h"
#define MAT_INDEX(mat, w, i, j) (mat)[(j) + (w) * (i)]
Board board_alloc(const int width, const int height) {
Board board = {};
board.width = width * 2;
board.height = height;
board.width_with_borders = board.width + 2;
board.height_with_borders = board.height + 2;
board.squares = (char*) malloc(sizeof(char) * board.width_with_borders * board.height_with_borders);
for (size_t i = 1; i < board.height_with_borders; i++) {
for (size_t j = 1; j < board.width_with_borders; j++) {
MAT_INDEX(board.squares, board.width_with_borders, i, j) = ' ';
}
}
// Create border pattern
const int width_with_borders_last_i = board.width_with_borders - 1;
const int height_with_borders_last_i = board.height_with_borders - 1;
// Vertical bars
for (int i = 1; i < height_with_borders_last_i; i++) {
MAT_INDEX(board.squares, board.width_with_borders, i, 0) = CHAR_BORDER_VER;
MAT_INDEX(board.squares, board.width_with_borders, i, width_with_borders_last_i) = CHAR_BORDER_VER;
}
// Horizontal lines
for (int j = 1; j < width_with_borders_last_i; j++) {
MAT_INDEX(board.squares, board.width_with_borders, 0, j) = CHAR_BORDER_HOR;
MAT_INDEX(board.squares, board.width_with_borders, height_with_borders_last_i, j) = CHAR_BORDER_HOR;
}
// Corners
MAT_INDEX(board.squares, board.width_with_borders, 0, 0) = CHAR_BORDER_CORNER_TL;
MAT_INDEX(board.squares, board.width_with_borders, height_with_borders_last_i, 0) = CHAR_BORDER_CORNER_BL;
MAT_INDEX(board.squares, board.width_with_borders, 0, width_with_borders_last_i) = CHAR_BORDER_CORNER_TR;
MAT_INDEX(board.squares, board.width_with_borders, height_with_borders_last_i, width_with_borders_last_i) = CHAR_BORDER_CORNER_BR;
return board;
}
void board_free(Board* board) {
free(board->squares);
}
bool board_coords_out_of_bounds(Board* board, const size_t x, const size_t y) {
return x >= board->width_with_borders || y >= board->height_with_borders;
}
void board_set_square(
Board* board,
int x,
int y,
const char ch
)
{
x++;
y++;
if (board_coords_out_of_bounds(board, x, y)) {
fprintf(stderr, "ERROR: Board coords out of bounds: x: %d y: %d\n", x, y);
exit(EXIT_FAILURE);
}
// assert(board_coords_out_of_bounds(board, x, y));
MAT_INDEX(board->squares, board->width_with_borders, y, x) = ch;
}
void board_clear(Board* board) {
for (size_t i = 0; i < board->height; i++) {
for (size_t j = 0; j < board->width; j++) {
// printf("Clearing board: i: %zu j: %zu\n", i, j);
board_set_square(board, j, i, ' ');
}
}
}
void board_draw_snake(Board* board, Snake* snake) {
BoardPiece part = {};
for (size_t i = 0; i < snake->length; i++) {
part = snake_get_part(snake, i);
board_set_square(board, part.x, part.y, part.vis_char);
}
}
void print_board(Board* board) {
for (size_t i = 0; i < board->height_with_borders; i++) {
for (size_t j = 0; j < board->width_with_borders; j++) {
printf("%c", MAT_INDEX(board->squares, board->width_with_borders, i, j));
}
printf("\n");
}
}

15
src/BoardPiece.c Normal file
View File

@@ -0,0 +1,15 @@
#include <stdio.h>
#include "BoardPiece.h"
void board_piece_print_info(BoardPiece* piece, char* name) {
printf("%s: {\n", name);
printf(" x: %d\n", piece->x);
printf(" y: %d\n", piece->y);
printf(" vis_char: %c\n", piece->vis_char);
printf("}\n");
}
bool pieces_collide(BoardPiece* piece1, BoardPiece* piece2) {
return piece1->x == piece2->x && piece1->y == piece2->y;
}

210
src/Snake.c Normal file
View File

@@ -0,0 +1,210 @@
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "Snake.h"
extern const char snake_vis;
static BoardPiece* snake_get_part_ptr(const Snake* snake, const size_t index) {
if (index >= snake->max_length) {
return NULL;
}
return snake->parts + index;
}
BoardPiece snake_get_part(const Snake* snake, const size_t index) {
assert(index < snake->max_length);
return snake->parts[index];
}
Snake snake_alloc(
const int board_square_count,
const int init_x,
const int init_y,
const char init_dir
)
{
Snake snake = {0};
snake.max_length = board_square_count;
snake.length = 1;
snake.dir = init_dir;
snake.parts = (BoardPiece*) malloc(sizeof(BoardPiece) * snake.max_length);
snake.parts[0] = (BoardPiece) { .x = init_x, .y = init_y, .vis_char = '&' };
return snake;
}
void snake_free(Snake* snake) {
free(snake->parts);
}
static void check_bounds(Snake* snake, const int width, const int height) {
for (size_t i = 0;i < snake->length; i++) {
BoardPiece* part = snake->parts + i;
if (part->x < 0) {
part->x = width - 2;
} else if (part->x >= width) {
part->x = 0;
}
if (part->y < 0) {
part->y = height - 1;
} else if (part->y >= height) {
part->y = 0;
}
}
}
void snake_move(Snake* snake, const int width, const int height) {
BoardPiece* first_part = snake_get_part_ptr(snake, 0);
int first_part_old_x = first_part->x;
int first_part_old_y = first_part->y;
// Move first part
switch (snake->dir) {
case 'w':
first_part->y -= 1;
break;
case 'a':
first_part->x -= 2;
break;
case 's':
first_part->y += 1;
break;
case 'd':
first_part->x += 2;
break;
default:
fprintf(stderr, "ERROR: Invalid direction in snake_move: %c.\n", snake->dir);
exit(EXIT_FAILURE);
}
// Stop here if there is only one part
BoardPiece* last_part = snake_get_part_ptr(snake, snake->length - 1);
if (last_part == first_part) {
check_bounds(snake, width, height);
return;
}
// Move all other parts except for the second one, gets skipped if there are only 2 parts
BoardPiece* second_part = first_part + 1;
BoardPiece* prev_part = 0;
for (BoardPiece* part = last_part; part != second_part; part--) {
prev_part = part - 1;
part->x = prev_part->x;
part->y = prev_part->y;
}
// Move second part
second_part->x = first_part_old_x;
second_part->y = first_part_old_y;
check_bounds(snake, width, height);
}
void snake_print_info(Snake* snake) {
printf("snake: {\n");
printf(" parts: {\n");
for (size_t i = 0; i < snake->length; i++) {
BoardPiece part = snake->parts[i];
printf(" x: %d\n", part.x);
printf(" y: %d\n", part.y);
}
printf(" }\n");
printf(" max_length: %zu\n", snake->max_length);
printf(" length: %zu\n", snake->length);
printf(" dir: %c\n", snake->dir);
printf("}\n");
}
void snake_change_direction(Snake* snake, const char direction) {
snake->dir = direction;
}
bool snake_collides(const Snake* snake, const BoardPiece* piece) {
for (size_t i = 0; i < snake->length; i++) {
BoardPiece part = snake->parts[i];
if (part.x == piece->x && part.y == piece->y) {
return true;
}
}
return false;
}
bool snake_collides_with_tail(const Snake* snake) {
const BoardPiece* head = snake->parts;
for (size_t i = 1; i < snake->length; i++) {
BoardPiece part = snake->parts[i];
if (part.x == head->x && part.y == head->y) {
return true;
}
}
return false;
}
void snake_add_part(Snake* snake) {
if (snake->length == snake->max_length) {
fprintf(stderr, "ERROR: Cannot add another part to snake. Would exceed max_length.\n");
exit(EXIT_FAILURE);
}
int x_shift;
int y_shift;
BoardPiece last_part;
char prev_part_dir;
if (snake->length == 1) {
last_part = snake_get_part(snake, 0);
prev_part_dir = snake->dir;
} else {
last_part = snake_get_part(snake, snake->length - 1);
const BoardPiece second_to_last_part = snake_get_part(snake, snake->length - 2);
if (second_to_last_part.y < last_part.y && second_to_last_part.x == last_part.x) {
prev_part_dir = 'w';
} else if (second_to_last_part.x < last_part.x && second_to_last_part.y == last_part.y) {
prev_part_dir = 'a';
} else if (second_to_last_part.y > last_part.y && second_to_last_part.x == last_part.x) {
prev_part_dir = 's';
} else if (second_to_last_part.x > last_part.x && second_to_last_part.y == last_part.y) {
prev_part_dir = 'd';
} else {
fprintf(stderr, "%s:%d: ERROR: Invalid direction.\n", __FILE__, __LINE__);
exit(EXIT_FAILURE);
}
}
switch (prev_part_dir) {
case 'w':
x_shift = 0;
y_shift = 1;
break;
case 'a':
x_shift = 1;
y_shift = 0;
break;
case 's':
x_shift = 0;
y_shift = -1;
break;
case 'd':
x_shift = -1;
y_shift = 0;
break;
default:
fprintf(stderr, "%s:%d: ERROR: Invalid direction.\n", __FILE__, __LINE__);
exit(EXIT_FAILURE);
}
snake->parts[snake->length] = (BoardPiece){
.x = last_part.x + x_shift,
.y = last_part.y + y_shift,
.vis_char = snake_vis
};
snake->length++;
}

82
src/args.c Normal file
View File

@@ -0,0 +1,82 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "args.h"
static void handle_version() {
printf("Sanke version %s\n", VERSION);
exit(0);
}
static void set_width(Arguments* args, char* width_str) {
printf("width_str: %s\n", width_str);
int width = atoi(width_str);
if (width < 10) {
printf("Width must be between 10 and 50. Was %d.\n", width);
exit(0);
}
args->width = width;
}
static void set_height(Arguments* args, char* height_str) {
int height = atoi(height_str);
if (height < 10) {
printf("Width must be between 10 and 50. Was %d.\n", height);
exit(0);
}
args->height = height;
}
static void set_speed(Arguments* args, char* speed_str) {
int squares_per_second = atoi(speed_str);
if (squares_per_second < 1 || squares_per_second > 1000) {
printf("Speed must be between 1 and 1000. Was %d.\n", squares_per_second);
exit(0);
}
args->sleep_ms = 1000 / squares_per_second;
}
Arguments cmd_args(int argc, char** argv) {
Arguments args = {
.width = 15,
.height = 15,
.sleep_ms = 150
};
for (int i = 1; i < argc; i++) {
if (
strcmp(argv[i], "--version") == 0
|| strcmp(argv[i], "-v") == 0
)
{
handle_version();
} else if (
strcmp(argv[i], "--width") == 0
|| strcmp(argv[i], "-w") == 0
)
{
set_width(&args, argv[i + 1]);
} else if (
strcmp(argv[i], "--height") == 0
|| strcmp(argv[i], "-h") == 0
)
{
set_height(&args, argv[i + 1]);
} else if (
strcmp(argv[i], "--speed") == 0
|| strcmp(argv[i], "-s") == 0
)
{
set_speed(&args, argv[i + 1]);
}
}
return args;
}

View File

@@ -1,31 +0,0 @@
#include <stdio.h>
#include "../globals.h"
extern boardInfo brdInfo;
void setBoardBorders(char board[][brdInfo.x]) {
for (int i = 0; i < brdInfo.y; i++) {
for (int j = 0; j < brdInfo.x; j++) {
if (i == 0 || i == brdInfo.y - 1)
board[i][j] = '-';
else if (j == 0 || j == brdInfo.x - 1)
board[i][j] = '|';
else
board[i][j] = ' ';
}
}
board[0][0] = '+';
board[brdInfo.y - 1][0] = '+';
board[0][brdInfo.x - 1] = '+';
board[brdInfo.y - 1][brdInfo.x - 1] = '+';
}
void printBoard(char board[][brdInfo.x]) {
for (int i = 0; i < brdInfo.y; i++) {
for (int j = 0; j < brdInfo.x; j++) {
printf("%c", board[i][j]);
}
printf("\n");
}
}

View File

@@ -1,11 +0,0 @@
#ifndef BOARD_H_
#define BOARD_H_
#include "../globals.h"
extern boardInfo brdInfo;
void setBoardBorders(char board[][brdInfo.x]);
void printBoard(char board[][brdInfo.x]);
#endif

13
src/food.c Normal file
View File

@@ -0,0 +1,13 @@
#include <stdlib.h>
#include "food.h"
#include "Board.h"
#include "BoardPiece.h"
#include "Snake.h"
void food_new_location(Board* board, BoardPiece* food, Snake* snake) {
do {
food->x = rand() % board->width;
food->y = rand() % board->height;
} while (food->x % 2 != 0 || snake_collides(snake, food));
}

View File

@@ -1,16 +0,0 @@
#ifndef GLOBALS_H_
#define GLOBALS_H_
typedef struct {
int x;
int y;
} boardInfo;
typedef struct {
int xs;
int xe;
int ys;
int ye;
} playableBoardInfo;
#endif // GLOBALS_H_

30
src/headers/Board.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef BOARD_H_
#define BOARD_H_
#include "Snake.h"
typedef struct {
size_t width;
size_t height;
size_t width_with_borders;
size_t height_with_borders;
char* squares;
} Board;
Board board_alloc(
const int width,
const int height
);
void board_free(Board* board);
bool board_coords_out_of_bounds(Board* board, const size_t x, const size_t y);
void board_set_square(
Board* board,
const int x,
const int y,
const char ch
);
void board_clear(Board* board);
void board_draw_snake(Board* board, Snake* snake);
void print_board(Board* board);
#endif

13
src/headers/BoardPiece.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef BOARD_PIECE_H_
#define BOARD_PIECE_H_
typedef struct {
int x;
int y;
char vis_char;
} BoardPiece;
void board_piece_print_info(BoardPiece* piece, char* name);
bool pieces_collide(BoardPiece* piece1, BoardPiece* piece2);
#endif // BOARD_PIECE_H_

29
src/headers/Snake.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef SNAKE_H_
#define SNAKE_H_
#include <stddef.h>
#include "BoardPiece.h"
typedef struct {
BoardPiece* parts;
size_t max_length;
size_t length;
char dir;
} Snake;
BoardPiece snake_get_part(const Snake* snake, const size_t index);
Snake snake_alloc(
const int board_square_count,
const int init_x,
const int init_y,
const char init_dir
);
void snake_free(Snake* snake);
void snake_move(Snake* snake, const int width, const int height);
void snake_print_info(Snake* snake);
void snake_change_direction(Snake* snake, const char direction);
bool snake_collides(const Snake* snake, const BoardPiece* piece);
bool snake_collides_with_tail(const Snake* snake);
void snake_add_part(Snake* snake);
#endif // SNAKE_H_

12
src/headers/args.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef ARGS_H_
#define ARGS_H_
typedef struct {
int width;
int height;
int sleep_ms;
} Arguments;
Arguments cmd_args(int argc, char** argv);
#endif // ARGS_H_

11
src/headers/config.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef CONFIG_H_
#define CONFIG_H_
#define CHAR_BORDER_VER '|'
#define CHAR_BORDER_HOR '-'
#define CHAR_BORDER_CORNER_TL '+'
#define CHAR_BORDER_CORNER_TR '+'
#define CHAR_BORDER_CORNER_BL '+'
#define CHAR_BORDER_CORNER_BR '+'
#endif // CONFIG_H_

10
src/headers/food.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef FOOD_H_
#define FOOD_H_
#include "BoardPiece.h"
#include "Board.h"
#include "Snake.h"
void food_new_location(Board* board, BoardPiece* food, Snake* snake);
#endif // FOOD_H_

8
src/headers/utils.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef UTILS_H_
#define UTILS_H_
int randomInt(const int start, const int end, const unsigned int seed);
void mallocError(const char* varName, const char* fileName, const char* functionName);
void sleep_ms(const unsigned int ms);
#endif // UTILS_H_

View File

@@ -3,46 +3,110 @@
#include <termios.h> #include <termios.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#include <string.h>
#include <time.h> #include <time.h>
#include <stdbool.h>
#include "./utils/utils.h"
#include "./snake/snake.h"
#include "./board/board.h"
#include "globals.h"
const char SNAKE_VIS = '#'; #include "Board.h"
#include "Snake.h"
#include "utils.h"
#include "food.h"
#include "BoardPiece.h"
#include "args.h"
boardInfo brdInfo; #define TERMIOS 1
playableBoardInfo plBrdInfo; #define DEBUG 0
int main() { const char snake_vis = '#';
const clock_t initClock = clock();
// Board Constraints struct termios set_termios();
printf("Set board size (15 - 60): ");
scanf("%d", &brdInfo.y);
if (!(brdInfo.y >= 15 && brdInfo.y <= 60)) {
printf("Invalid input. Board size must be greater than 0.\n");
exit(0);
}
brdInfo.x = brdInfo.y * 2;
printf("brdInfo.x: %d\n", brdInfo.x);
printf("brdInfo.y: %d\n", brdInfo.y);
char board[brdInfo.y][brdInfo.x];
plBrdInfo.xs = 1; int main(int argc, char** argv) {
plBrdInfo.xe = brdInfo.x - 2; Arguments args = cmd_args(argc, argv);
plBrdInfo.ys = 1;
plBrdInfo.ye = brdInfo.y - 2;
srand(time(NULL));
// Termios setup // Termios setup
#if TERMIOS
const struct termios attr_orig = set_termios();
#endif // TERMIOS
// Game model init
const int board_w = args.width;
const int board_h = args.height;
Board board = board_alloc(board_w, board_h);
Snake snake = snake_alloc(board_w * board_h, 0, 0, 'd');
BoardPiece food = { .vis_char = '$' };
food_new_location(&board, &food, &snake);
unsigned int score = 0;
// Screen init
system("clear");
#if DEBUG
long long frame = 0;
#endif // DEBUG
while (true) {
system("clear");
// Process input
char input = '0';
read(STDIN_FILENO, &input, 1);
if (isalpha(input)) {
if (
(input == 'w' && snake.dir != 's')
|| (input == 'a' && snake.dir != 'd')
|| (input == 's' && snake.dir != 'w')
|| (input == 'd' && snake.dir != 'a')
) {
snake_change_direction(&snake, input);
}
}
snake_move(&snake, board.width, board.height);
board_clear(&board);
board_draw_snake(&board, &snake);
board_set_square(&board, food.x, food.y, food.vis_char);
print_board(&board);
printf("Score: %d\n", score);
BoardPiece snake_head = snake_get_part(&snake, 0);
if (snake_collides_with_tail(&snake)) {
printf("GAME OVER\n");
exit(EXIT_SUCCESS);
}
if (pieces_collide(&snake_head, &food)) {
score++;
snake_add_part(&snake);
food_new_location(&board, &food, &snake);
}
#if DEBUG
printf("input: %c\n", input);
printf("Frame: %d\n", frame);
snake_print_info(&snake);
board_piece_print_info(&food, "Food");
frame++;
#endif // DEBUG
sleep_ms(args.sleep_ms);
}
#if TERMIOS
tcsetattr(STDIN_FILENO, 0, &attr_orig);
#endif // TERMIOS
return 0;
}
struct termios set_termios() {
struct termios attr; struct termios attr;
tcgetattr(STDIN_FILENO, &attr); tcgetattr(STDIN_FILENO, &attr);
const struct termios ATTR_ORIG = attr; const struct termios attr_orig = attr;
attr.c_lflag &= ~(ECHO | ICANON); attr.c_lflag &= ~(ECHO | ICANON);
attr.c_cc[VMIN] = 0; attr.c_cc[VMIN] = 0;
@@ -50,113 +114,5 @@ int main() {
tcsetattr(STDIN_FILENO, 0, &attr); tcsetattr(STDIN_FILENO, 0, &attr);
// Game board setup return attr_orig;
setBoardBorders(board);
int points = 0;
int gameSpeed = 0;
const int sleepInterval = 200;
// Snake head setup
snakePart* snakeHead = (snakePart*) malloc(sizeof(snakePart));
snakeHead->x = randomX(initClock);
snakeHead->y = randomY(initClock);
snakeHead->visChar = '&';
snakeHead->dir = 'w';
snakeHead->order = (order*) malloc(sizeof(order));
snakeHead->order->dir = snakeHead->dir;
snakeHead->order->delay = -1;
snakeHead->order->next = NULL;
snakeHead->next = NULL;
board[snakeHead->y][snakeHead->x] = snakeHead->visChar;
// Food setup
struct {
int x;
int y;
char visChar;
} food;
food.x = randomX(initClock);
food.y = randomY(initClock);
food.visChar = '$';
board[food.y][food.x] = food.visChar;
// Screen init
system("clear");
// Game loop
while (1) {
fflush(stdout);
char buf[1] = {0};
// Food collision
if (snakeHead->x == food.x
&& snakeHead->y == food.y) {
points++;
if (points % 5 == 0)
gameSpeed += 15;
do {
food.x = randomX(initClock);
food.y = randomY(initClock);
} while (checkCollision(snakeHead, food.x, food.y));
addSnakePart(board, snakeHead);
}
// Input handling
if (read(STDIN_FILENO, buf, 1) != 0 && isalpha(buf[0])) {
char input = buf[0];
input = tolower(input);
if (
(input == 'w' && snakeHead->dir != 's')
|| (input == 's' && snakeHead->dir != 'w')
|| (input == 'a' && snakeHead->dir != 'd')
|| (input == 'd' && snakeHead->dir != 'a')
) {
snakeHead->dir = input;
addOrders(snakeHead, input);
}
}
// Update board
board[food.y][food.x] = food.visChar;
mvSnakeParts(board, snakeHead);
// Snake collision
if (snakeHead->next != NULL) {
snakePart* current = snakeHead->next;
while (1) {
if (current->x == snakeHead->x
&& current->y == snakeHead->y)
goto game_over;
if (current->next == NULL)
break;
current = current->next;
}
}
system("clear");
printBoard(board);
printf("Points: %d\n", points);
printf("Game Speed: %d\n", gameSpeed);
sleep_ms(sleepInterval - gameSpeed);
}
game_over:
// Game over
system("clear");
printBoard(board);
printf("Final Points: %d\n", points);
printf("Final Game Speed: %d\n", gameSpeed);
tcsetattr(STDIN_FILENO, 0, &ATTR_ORIG);
return 0;
} }

View File

@@ -1,203 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "snake.h"
#include "../board/board.h"
extern char SNAKE_VIS;
extern boardInfo brdInfo;
extern playableBoardInfo plBrdInfo;
void mvSnakeParts(char board[][brdInfo.x], snakePart* head) {
snakePart* part = head;
while (1) {
order* orderHead = part->order;
if (orderHead->next != NULL) {
if (orderHead->next->delay == 0) {
part->dir = orderHead->next->dir;
removeOrder(orderHead);
}
}
if (orderHead->next != NULL) {
order* current = orderHead->next;
while (1) {
if (current->delay > 0)
current->delay--;
if (current->next == NULL)
break;
current = current->next;
}
}
int x = part->x;
int y = part->y;
board[y][x] = ' ';
switch (part->dir) {
case 'w':
y = y - 1 < plBrdInfo.ys ? plBrdInfo.ye : y - 1;
break;
case 's':
y = y + 1 > plBrdInfo.ye ? plBrdInfo.ys : y + 1;
break;
case 'a':
x = x - 2 < plBrdInfo.xs ? plBrdInfo.xe - 1 : x - 2;
break;
case 'd':
x = x + 2 > plBrdInfo.xe ? plBrdInfo.xs : x + 2;
break;
default:
printf("ERROR in func mvSnakeParts\n");
printf("dir: %c\n", part->dir);
exit(1);
}
part->x = x;
part->y = y;
board[y][x] = part->visChar;
if (part->next == NULL)
return;
part = part->next;
}
}
void addSnakePart(char board[][brdInfo.x], snakePart* head) {
snakePart* tail = head;
while (tail->next != NULL)
tail = tail->next;
snakePart* newTail = (snakePart*) malloc(sizeof(snakePart));
newTail->visChar = SNAKE_VIS;
newTail->dir = tail->dir;
// Order head
newTail->order = (order*) malloc(sizeof(order));
newTail->order->dir = newTail->dir;
newTail->order->delay = -1;
// First order if exists
if (tail->order->next != NULL)
copyOrders(tail->order, newTail->order);
switch (newTail->dir) {
case 'w':
newTail->x = tail->x;
newTail->y = tail->y + 1;
break;
case 's':
newTail->x = tail->x;
newTail->y = tail->y - 1;
break;
case 'a':
newTail->x = tail->x + 2;
newTail->y = tail->y;
break;
case 'd':
newTail->x = tail->x - 2;
newTail->y = tail->y;
break;
default:
printf("Invalid direction in func addSnakePart\n");
exit(1);
}
/*
Untested fix to a bug where the new part
would replace a part of the board border.
*/
if (newTail->x > plBrdInfo.xe) {
newTail->x = plBrdInfo.xs;
} else if (newTail->x < plBrdInfo.xs) {
newTail->x = plBrdInfo.xe;
}
if (newTail->y > plBrdInfo.ye) {
newTail->y = plBrdInfo.ys;
} else if (newTail->y < plBrdInfo.ys) {
newTail->y = plBrdInfo.ye;
}
// bugfix end
newTail->next = NULL;
tail->next = newTail;
board[newTail->y][newTail->x] = newTail->visChar;
}
void pushOrder(order* head, char dir, int delay) {
order* current = head;
while (current->next != NULL)
current = current->next;
order* newOrder = (order*) malloc(sizeof(order));
newOrder->dir = dir;
newOrder->delay = delay;
newOrder->next = NULL;
current->next = newOrder;
}
void removeOrder(order* head) {
order* newFirstOrder = head->next->next;
free(head->next);
head->next = newFirstOrder;
}
void addOrders(snakePart* head, char dir) {
snakePart* current = head;
int i = 1;
while (1) {
if (current->next == NULL)
break;
current = current->next;
pushOrder(current->order, dir, i);
i++;
}
}
void copyOrders(order* srcHead, order* destHead) {
if (srcHead->next == NULL) {
printf("ERROR in copyOrders: no orders to copy\n");
exit(1);
}
order* srcCurrent = srcHead;
order* destCurrent = destHead;
order* destPrev = destHead;
while (srcCurrent->next != NULL) {
srcCurrent = srcCurrent->next;
destPrev = destCurrent;
destCurrent = (order*) malloc(sizeof(order));
destPrev->next = destCurrent;
destCurrent->dir = srcCurrent->dir;
destCurrent->delay = srcCurrent->delay + 1;
destCurrent->next = NULL;
}
}
bool checkCollision(snakePart* head, int x, int y) {
snakePart* current = head;
while (1) {
if (current->x == x && current->y == y)
return true;
if (current->next == NULL)
break;
current = current->next;
}
return false;
}

View File

@@ -1,32 +0,0 @@
#ifndef SNAKE_H_
#define SNAKE_H_
#include "../globals.h"
extern boardInfo brdInfo;
typedef struct orderNode {
char dir;
int delay;
struct orderNode* next;
} order;
typedef struct snakeNode {
int x;
int y;
char visChar;
char dir;
order* order;
struct snakeNode* next;
} snakePart;
void mvSnakeParts(char board[][brdInfo.x], snakePart* head);
void addSnakePart(char board[][brdInfo.x], snakePart* head);
void pushOrder(order* head, char dir, int delay);
void removeOrder(order* head);
void addOrders(snakePart* head, char dir);
void copyOrders(order* srcHead, order* destHead);
bool checkCollision(snakePart* head, int x, int y);
#endif // SNAKE_H_

42
src/utils.c Normal file
View File

@@ -0,0 +1,42 @@
#define _POSIX_C_SOURCE 199309L
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include "utils.h"
int randomInt(const int start, const int end, const unsigned int seed) {
/*
* Gets random int from range
* [start, end[
*/
srand(seed);
int result = rand() % end;
result = result >= start ? result : result + start;
return result;
}
void mallocError(const char* varName, const char* fileName, const char* functionName) {
printf("Ran out of memory to allocate to %s in %s/%s\n", varName, fileName, functionName);
exit(1);
}
void sleep_ms(const unsigned int ms) {
struct timespec ts = {
.tv_sec = ms / 1000
};
if (ts.tv_sec == 0) {
ts.tv_nsec = ms * 1000000;
} else {
ts.tv_nsec = (ms - ts.tv_sec * 1000) * 1000000;
}
// printf("timespec: {\n");
// printf(" tv_sec : %ld\n", ts.tv_sec);
// printf(" tv_nsec: %ld\n", ts.tv_nsec);
// printf("}\n");
if (nanosleep(&ts, NULL) == -1) {
fprintf(stderr, "ERROR: Failed to sleep. ERRNO: %d\n", errno);
}
}

View File

@@ -1,67 +0,0 @@
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include "utils.h"
extern boardInfo brdInfo;
extern playableBoardInfo plBrdInfo;
int randomInt(const int start, const int end, const unsigned int seed) {
/*
* Gets random int from range
* [start, end[
*/
srand(seed);
int result = rand() % end;
result = result >= start ? result : result + start;
return result;
}
int randomX(const clock_t initClock) {
int x;
const unsigned int seed = clock() - initClock;
for (int i = randomInt(0, 100, seed); ; i++) {
x = randomInt(plBrdInfo.xs, plBrdInfo.xe, i);
if (x % 2 != 0)
break;
}
return x;
}
int randomY(const clock_t initClock) {
const unsigned int seed = clock() - initClock;
return randomInt(plBrdInfo.ys, plBrdInfo.ye, seed);
}
void sleep_ms(const int ms) {
usleep(ms * 1000);
}
void setBoardBorders(char board[][brdInfo.x]) {
for (int i = 0; i < brdInfo.y; i++) {
for (int j = 0; j < brdInfo.x; j++) {
if (i == 0 || i == brdInfo.y - 1)
board[i][j] = '-';
else if (j == 0 || j == brdInfo.x - 1)
board[i][j] = '|';
else
board[i][j] = ' ';
}
}
board[0][0] = '+';
board[brdInfo.y - 1][0] = '+';
board[0][brdInfo.x - 1] = '+';
board[brdInfo.y - 1][brdInfo.x - 1] = '+';
}
void printBoard(char board[][brdInfo.x]) {
for (int i = 0; i < brdInfo.y; i++) {
for (int j = 0; j < brdInfo.x; j++) {
printf("%c", board[i][j]);
}
printf("\n");
}
}

View File

@@ -1,16 +0,0 @@
#ifndef UTILS_H_
#define UTILS_H_
#include <time.h>
#include "../globals.h"
extern boardInfo brdInfo;
int randomInt(const int start, const int end, const unsigned int seed);
int randomX(const clock_t initClock);
int randomY(const clock_t initClock);
void sleep_ms(const int ms);
void setBoardBorders(char board[][brdInfo.x]);
void printBoard(char board[][brdInfo.x]);
#endif // UTILS_H_