working reworked game

This commit is contained in:
2025-09-10 22:08:34 +03:00
parent 7498a49f88
commit ad5b8beb99
15 changed files with 416 additions and 144 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,2 @@
build/
.clangd .clangd
target/

View File

@@ -1,7 +1,26 @@
#!/bin/sh #!/bin/sh
arg=-1
if [ -v 1 ]; then
arg=$1
fi
if [ $arg = "clean" ]; then
rm -rf ./target/*
exit
fi
oflag="-Og"
debug_flag="-ggdb"
if [ $arg = "release" ]; then
oflag="-O3"
debug_flag=""
fi
src="src/*.c" src="src/*.c"
flags="-std=c23 -ggdb -Og -Wall -Wextra -Werror -Wpedantic -pedantic-errors" flags="-std=c23 $oflag $debug_flag -Wall -Wextra -Werror -Wpedantic -pedantic-errors"
includes="-I src/headers" includes="-I src/headers"
cmd="gcc $flags $includes $src -o target/sanke" cmd="gcc $flags $includes $src -o target/sanke"
@@ -9,14 +28,12 @@ cmd="gcc $flags $includes $src -o target/sanke"
echo $cmd echo $cmd
$cmd $cmd
if [ $? -gt 0 ]; then if [ $? -gt 0 ]; then
exit exit
fi fi
echo if [ $arg = "run" ]; then
echo
if [ -v 1 ]; then ./target/sanke
if [ $1 = "run" ]; then
./target/sanke
fi
fi fi

View File

@@ -2,8 +2,8 @@
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
#include "board.h" #include "Board.h"
#include "snake.h" #include "Snake.h"
#define MAT_INDEX(mat, w, i, j) (mat)[(j) + (w) * (i)] #define MAT_INDEX(mat, w, i, j) (mat)[(j) + (w) * (i)]
@@ -73,17 +73,17 @@ void board_set_square(
void board_clear(Board* board) { void board_clear(Board* board) {
for (size_t i = 0; i < board->height; i++) { for (size_t i = 0; i < board->height; i++) {
for (size_t j = 0; j < board->width; j++) { for (size_t j = 0; j < board->width; j++) {
printf("Clearing board: i: %zu j: %zu\n", i, j); // printf("Clearing board: i: %zu j: %zu\n", i, j);
board_set_square(board, j, i, ' '); board_set_square(board, j, i, ' ');
} }
} }
} }
void board_draw_snake(Board* board, Snake* snake) { void board_draw_snake(Board* board, Snake* snake) {
SnakePart part = {}; BoardPiece part = {};
for (size_t i = 0; i < snake->length; i++) { for (size_t i = 0; i < snake->length; i++) {
part = snake_get_part(snake, i); part = snake_get_part(snake, i);
board_set_square(board, part.y, part.x, part.vis_char); board_set_square(board, part.x, part.y, part.vis_char);
} }
} }

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++;
}

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,7 +1,7 @@
#ifndef BOARD_H_ #ifndef BOARD_H_
#define BOARD_H_ #define BOARD_H_
#include "snake.h" #include "Snake.h"
typedef struct { typedef struct {
size_t width; size_t width;

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_

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_

View File

@@ -1,29 +0,0 @@
#ifndef SNAKE_H_
#define SNAKE_H_
#include <stddef.h>
typedef struct {
int x;
int y;
char vis_char;
} SnakePart;
typedef struct {
SnakePart* parts;
size_t max_length;
size_t length;
char dir;
} Snake;
SnakePart 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);
#endif // SNAKE_H_

View File

@@ -6,55 +6,117 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include "board.h" #include "Board.h"
#include "snake.h" #include "Snake.h"
#include "utils.h" #include "utils.h"
#include "food.h"
#include "BoardPiece.h"
#define TERMIOS 1
#define DEBUG 0
const char* VERSION = "1.1.9"; const char* VERSION = "1.1.9";
const char SNAKE_VIS = '#'; const char snake_vis = '#';
void cmd_args(int argc, char** argv); void cmd_args(int argc, char** argv);
void empty_stdin_buffer(); void empty_stdin_buffer();
void get_int_or_minus_one(int* dst); void get_int_or_minus_one(int* dst);
struct termios set_termios(); struct termios set_termios();
int main() { int main(int argc, char** argv) {
// cmd_args(argc, argv); cmd_args(argc, argv);
// const clock_t initClock = clock();
// Set gamespeed
// int gameSpeed = 0;
// printf("Enter gamespeed (default: 0, max: 180): ");
// getIntOrMinusOne(&gameSpeed);
// if (gameSpeed == -1 || gameSpeed > 180) {
// gameSpeed = 0;
// }
srand(time(NULL));
// Termios setup // Termios setup
#if TERMIOS
const struct termios attr_orig = set_termios(); const struct termios attr_orig = set_termios();
#endif // TERMIOS
// Game model init // Game model init
const int board_w = 15; const int board_w = 15;
const int board_h = 15; const int board_h = 15;
Board board = board_alloc(board_w, board_h); Board board = board_alloc(board_w, board_h);
Snake snake = snake_alloc(board_w * board_h, 0, 0, 'd'); 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 // Screen init
system("clear"); system("clear");
int frame = 0;
int fps = 0;
long second_start = time(NULL);
int frame_stamp = frame;
while (true) { while (true) {
long second_check = time(NULL);
long elapsed_time = second_check - second_start;
if (elapsed_time >= 1) {
fps = frame - frame_stamp;
if (elapsed_time > 1) {
fps /= 2;
}
frame_stamp = frame;
second_start = time(NULL);
}
system("clear"); system("clear");
snake_move(&snake);
// 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_clear(&board);
board_draw_snake(&board, &snake); board_draw_snake(&board, &snake);
board_set_square(&board, food.x, food.y, food.vis_char);
printf("FPS: %d\n", fps);
print_board(&board); print_board(&board);
sleep_ms(1000); printf("Score: %d\n", score);
BoardPiece snake_head = snake_get_part(&snake, 0);
if (snake_collides_with_tail(&snake)) {
printf("GAME OVER\n");
printf("Final score: %d\n", score);
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");
#endif // DEBUG
frame++;
sleep_ms(150);
} }
#if TERMIOS
tcsetattr(STDIN_FILENO, 0, &attr_orig); tcsetattr(STDIN_FILENO, 0, &attr_orig);
#endif // TERMIOS
return 0; return 0;
} }

View File

@@ -1,83 +0,0 @@
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "snake.h"
static SnakePart* snake_get_part_ptr(const Snake* snake, const size_t index) {
if (index >= snake->max_length) {
return NULL;
}
return snake->parts + index;
}
SnakePart 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 = (SnakePart*) malloc(sizeof(SnakePart) * snake.max_length);
snake.parts[0] = (SnakePart) { .x = init_x, .y = init_y, .vis_char = '&' };
return snake;
}
void snake_free(Snake* snake) {
free(snake->parts);
}
void snake_move(Snake* snake) {
SnakePart* 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 -= 1;
break;
case 's':
first_part->y += 1;
break;
case 'd':
first_part->x += 1;
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
SnakePart* last_part = snake_get_part_ptr(snake, snake->length - 1);
if (last_part == first_part) {
return;
}
// Move all other parts except for the second one, gets skipped if there are only 2 parts
SnakePart* second_part = first_part + 1;
SnakePart* prev_part = 0;
for (SnakePart* 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;
}

View File

@@ -3,6 +3,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
#include <errno.h>
#include "utils.h" #include "utils.h"
@@ -23,5 +24,19 @@ void mallocError(const char* varName, const char* fileName, const char* function
} }
void sleep_ms(const unsigned int ms) { void sleep_ms(const unsigned int ms) {
nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = ms * 1000000}, NULL); 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);
}
} }

Binary file not shown.