Added multi level support, dynamic snake location from level

This commit is contained in:
2025-05-27 11:50:20 +02:00
parent 4798ec85a3
commit c8a4a40618
11 changed files with 444 additions and 179 deletions

BIN
assets/fruit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

BIN
assets/grass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 KiB

BIN
assets/wall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

75
game.c
View File

@@ -27,11 +27,14 @@ int main(int argc, char *argv[])
size_t size_buf = 0; size_t size_buf = 0;
int nbl, nbc, i; int nbl, nbc, i;
int opt, option_index = 0; int opt, option_index = 0;
int loop_count = 0, nb_fruit = 0; int loop_count = 0, nb_fruit = 0, total_fruits = 0;
char *input_file = NULL; char *input_file = NULL;
int width = 640, height = 480; int width = 640, height = 480;
MLV_Keyboard_button touche = MLV_KEYBOARD_NONE; MLV_Keyboard_button touche = MLV_KEYBOARD_NONE;
int initial_size = 0; int initial_size = 0;
int current_level = 1;
int global_score = 0;
int previous_score = 0;
Grid *g; Grid *g;
@@ -40,17 +43,6 @@ int main(int argc, char *argv[])
{"input", required_argument, 0, 'i'}, {"input", required_argument, 0, 'i'},
{0, 0, 0, 0}}; {0, 0, 0, 0}};
Snake *snake = new_snake();
add_segment(snake, 1, 5);
add_segment(snake, 1, 4);
add_segment(snake, 1, 3);
add_segment(snake, 1, 2);
add_segment(snake, 1, 1);
add_segment(snake, 1, 0);
snake->dir = BOTTOM;
initial_size += snake->size;
while ((opt = getopt_long(argc, argv, "hi:", long_options, &option_index)) != -1) while ((opt = getopt_long(argc, argv, "hi:", long_options, &option_index)) != -1)
{ {
switch (opt) switch (opt)
@@ -67,14 +59,25 @@ int main(int argc, char *argv[])
} }
} }
while (1)
{
Snake *snake = new_snake();
previous_score = global_score;
input_file = NULL;
if (input_file == NULL) if (input_file == NULL)
{ {
input_file = "levels/default"; char level_file[256];
snprintf(level_file, sizeof(level_file), "levels/level%d", current_level);
input_file = level_file;
} }
stream = fopen(input_file, "r"); stream = fopen(input_file, "r");
if (!stream) if (!stream)
{ {
fprintf(stderr, "Error: unable to open file\n"); fprintf(stderr, "Error: unable to open file for level %d\n", current_level);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -84,6 +87,8 @@ int main(int argc, char *argv[])
if (nbc == -1) if (nbc == -1)
{ {
fprintf(stderr, "Error: malformed file\n"); fprintf(stderr, "Error: malformed file\n");
free(buf);
fclose(stream);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
nbc--; nbc--;
@@ -98,6 +103,7 @@ int main(int argc, char *argv[])
{ {
fprintf(stderr, "Error: inconsistent line length\n"); fprintf(stderr, "Error: inconsistent line length\n");
free(buf); free(buf);
free_grid(g);
fclose(stream); fclose(stream);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -106,10 +112,12 @@ int main(int argc, char *argv[])
rewind(stream); rewind(stream);
nb_fruit = count_fruits(stream, g); nb_fruit = count_fruits(stream, g);
total_fruits = nb_fruit;
if (nb_fruit == 0) if (nb_fruit == 0)
{ {
fprintf(stderr, "Error: no fruits in the grid\n"); fprintf(stderr, "Error: no fruits in the grid\n");
free(buf); free(buf);
free_grid(g);
fclose(stream); fclose(stream);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -117,14 +125,17 @@ int main(int argc, char *argv[])
{ {
fprintf(stderr, "Error: unable to count fruits\n"); fprintf(stderr, "Error: unable to count fruits\n");
free(buf); free(buf);
free_grid(g);
fclose(stream); fclose(stream);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
free(buf); free(buf);
buf = NULL;
fclose(stream); fclose(stream);
place_snake(g, snake); read_snake_from_grid(g, snake);
initial_size = snake->size;
MLV_create_window("SNAKE", "3R-IN1B", width, height); MLV_create_window("SNAKE", "3R-IN1B", width, height);
MLV_change_frame_rate(24); MLV_change_frame_rate(24);
@@ -138,6 +149,7 @@ int main(int argc, char *argv[])
touche != MLV_KEYBOARD_ESCAPE) touche != MLV_KEYBOARD_ESCAPE)
{ {
Element result; Element result;
char stats[256];
MLV_clear_window(MLV_COLOR_BLACK); MLV_clear_window(MLV_COLOR_BLACK);
loop_count = (loop_count + 1) % DIFFICULTY; loop_count = (loop_count + 1) % DIFFICULTY;
@@ -149,43 +161,55 @@ int main(int argc, char *argv[])
{ {
if (result == WALL) if (result == WALL)
{ {
MLV_draw_text(width / 2 - 75, height / 2, "Game Over! You hit a wall.", MLV_COLOR_RED); MLV_draw_text(width / 2 - 80, height / 2, "Game Over! You hit a wall.", MLV_COLOR_RED);
} }
else if (result == SNAKE) else if (result == SNAKE)
{ {
MLV_draw_text(width / 2 - 75, height / 2, "Game Over! You hit yourself.", MLV_COLOR_RED); MLV_draw_text(width / 2 - 80, height / 2, "Game Over! You hit yourself.", MLV_COLOR_RED);
} }
MLV_actualise_window(); MLV_actualise_window();
MLV_wait_seconds(3); MLV_wait_seconds(3);
global_score = previous_score;
break; break;
} }
else if (result == FRUIT) else if (result == FRUIT)
{ {
Position *tail = snake->segments_list; Position *tail = snake->segments_list;
Position *prev = NULL; Position *prev = NULL;
int dx, dy;
while (tail->next != NULL) while (tail->next != NULL)
{ {
prev = tail; prev = tail;
tail = tail->next; tail = tail->next;
} }
int dx = tail->x - (prev ? prev->x : tail->x); dx = tail->x - (prev ? prev->x : tail->x);
int dy = tail->y - (prev ? prev->y : tail->y); dy = tail->y - (prev ? prev->y : tail->y);
add_segment(snake, tail->x + dx, tail->y + dy); add_segment(snake, tail->x + dx, tail->y + dy);
place_snake(g, snake); place_snake(g, snake);
if (snake->size == initial_size + nb_fruit) nb_fruit--;
global_score++;
if (snake->size == initial_size + total_fruits)
{ {
MLV_draw_text( MLV_draw_text(
width / 2 - 75, height / 2, width / 2 - 200, height / 2,
"You Win! All fruits collected.", "You Win! All fruits collected. Proceeding to the next level.",
MLV_COLOR_GREEN); MLV_COLOR_GREEN);
MLV_actualise_window(); MLV_actualise_window();
MLV_wait_seconds(3); MLV_wait_seconds(3);
current_level++;
break; break;
} }
} }
} }
draw_grid(g); draw_grid(g);
snprintf(stats, sizeof(stats),
"Level: %d | Fruits Left: %d/%d | Score: %d | Snake Size: %d",
current_level, nb_fruit, total_fruits, global_score, snake->size);
MLV_draw_text(10, height - 20, stats, MLV_COLOR_WHITE);
MLV_actualise_window(); MLV_actualise_window();
switch (touche) switch (touche)
@@ -217,5 +241,12 @@ int main(int argc, char *argv[])
MLV_free_window(); MLV_free_window();
free_grid(g); free_grid(g);
free_snake(snake); free_snake(snake);
if (touche == MLV_KEYBOARD_ESCAPE)
{
break;
}
}
return 0; return 0;
} }

189
grid.c
View File

@@ -1,5 +1,6 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h>
#include <MLV/MLV_all.h> #include <MLV/MLV_all.h>
#include "grid.h" #include "grid.h"
#include "snake.h" #include "snake.h"
@@ -46,7 +47,7 @@ void free_grid(Grid *g)
free(g); free(g);
} }
void debug(Grid *g) void debug_grid(Grid *g)
{ {
int i; int i;
for (i = 0; i < g->nbl; i++) for (i = 0; i < g->nbl; i++)
@@ -68,6 +69,27 @@ void draw_grid(Grid *g)
int window_width = MLV_get_window_width(); int window_width = MLV_get_window_width();
int window_height = MLV_get_window_height(); int window_height = MLV_get_window_height();
int cell_size = compute_size(g, window_width, window_height); int cell_size = compute_size(g, window_width, window_height);
MLV_Image *image_wall, *image_fruit, *image_snake, *image_empty;
image_wall = MLV_load_image("./assets/wall.png");
image_fruit = MLV_load_image("./assets/fruit.png");
image_snake = MLV_load_image("./assets/snake.png");
image_empty = MLV_load_image("./assets/grass.png");
if (image_wall != NULL)
{
MLV_resize_image_with_proportions(image_wall, cell_size, cell_size);
}
if (image_fruit != NULL)
{
MLV_resize_image_with_proportions(image_fruit, cell_size, cell_size);
}
if (image_snake != NULL)
{
MLV_resize_image_with_proportions(image_snake, cell_size, cell_size);
}
if (image_empty != NULL)
{
MLV_resize_image_with_proportions(image_empty, cell_size, cell_size);
}
MLV_draw_filled_rectangle(0, 0, window_width, window_height, MLV_COLOR_BLACK); MLV_draw_filled_rectangle(0, 0, window_width, window_height, MLV_COLOR_BLACK);
@@ -81,16 +103,55 @@ void draw_grid(Grid *g)
switch (g->grid[i][j]) switch (g->grid[i][j])
{ {
case WALL: case WALL:
if (image_wall == NULL)
{
MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_BROWN); MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_BROWN);
}
else
{
MLV_draw_image(image_wall, x, y);
}
break; break;
case EMPTY: case EMPTY:
if (image_empty != NULL)
{
MLV_draw_image(image_empty, x, y);
}
else
{
MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_WHITE); MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_WHITE);
}
break; break;
case FRUIT: case FRUIT:
if (image_fruit == NULL)
{
MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_RED); MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_RED);
}
else
{
MLV_draw_image(image_fruit, x, y);
}
break; break;
case SNAKE: case SNAKE:
if (image_snake == NULL)
{
MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_GREEN); MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_GREEN);
}
else
{
MLV_draw_image(image_snake, x, y);
}
break;
case SNAKEHEAD:
if (image_snake == NULL)
{
MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_GREEN);
}
else
{
MLV_draw_image(image_snake, x, y);
}
break; break;
default: default:
MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_BLACK); MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_BLACK);
@@ -104,6 +165,12 @@ void place_snake(Grid *g, struct SnakeStruct *snake)
{ {
Position *current = snake->segments_list; Position *current = snake->segments_list;
if (current != NULL)
{
g->grid[current->y][current->x] = SNAKEHEAD;
current = current->next;
}
while (current != NULL) while (current != NULL)
{ {
g->grid[current->y][current->x] = SNAKE; g->grid[current->y][current->x] = SNAKE;
@@ -117,7 +184,8 @@ Element move_snake(struct SnakeStruct *snake, Grid *g)
Position *head; Position *head;
Element element_at_head; Element element_at_head;
while (tail->next != NULL) { while (tail->next != NULL)
{
tail = tail->next; tail = tail->next;
} }
@@ -128,7 +196,12 @@ Element move_snake(struct SnakeStruct *snake, Grid *g)
head = snake->segments_list; head = snake->segments_list;
element_at_head = g->grid[head->y][head->x]; element_at_head = g->grid[head->y][head->x];
g->grid[head->y][head->x] = SNAKE; g->grid[head->y][head->x] = SNAKEHEAD;
if (head->next != NULL)
{
g->grid[head->next->y][head->next->x] = SNAKE;
}
return element_at_head; return element_at_head;
} }
@@ -180,3 +253,113 @@ void copy(const char *src, char *dst)
} }
dst[i] = '\0'; dst[i] = '\0';
} }
static bool are_adjacent(Position *a, Position *b)
{
return (a->x == b->x && abs(a->y - b->y) == 1) ||
(a->y == b->y && abs(a->x - b->x) == 1);
}
int is_snake_connected(struct SnakeStruct *snake)
{
Position *current = snake->segments_list;
while (current && current->next)
{
if (!are_adjacent(current, current->next))
{
return 0;
}
current = current->next;
}
return 1;
}
void read_snake_from_grid(Grid *g, struct SnakeStruct *snake)
{
Position *positions = malloc(g->nbl * g->nbc * sizeof(Position));
int positions_count = 0;
Position *head = NULL;
Position *current;
int i;
int *used = NULL;
for (i = 0; i < g->nbl; i++)
{
int j;
for (j = 0; j < g->nbc; j++)
{
if (g->grid[i][j] == SNAKE || g->grid[i][j] == SNAKEHEAD)
{
positions[positions_count].x = j;
positions[positions_count].y = i;
positions[positions_count].next = NULL;
if (g->grid[i][j] == SNAKEHEAD)
{
head = &positions[positions_count];
}
positions_count++;
}
}
}
if (head == NULL)
{
fprintf(stderr, "Error: Snake head (S) not found in the grid.\n");
free(positions);
exit(EXIT_FAILURE);
}
used = malloc(positions_count * sizeof(int));
if (!used) {
fprintf(stderr, "Error: Could not allocate memory for used array.\n");
free(positions);
exit(EXIT_FAILURE);
}
for (i = 0; i < positions_count; i++)
{
used[i] = 0;
}
add_segment(snake, head->x, head->y);
used[head - positions] = 1;
current = head;
while (1)
{
int found = 0;
int j;
for (j = 0; j < positions_count; j++)
{
if (!used[j] && are_adjacent(current, &positions[j]))
{
add_segment(snake, positions[j].x, positions[j].y);
used[j] = 1;
current = &positions[j];
found = 1;
break;
}
}
if (!found)
{
break;
}
}
for (i = 0; i < positions_count; i++)
{
if (!used[i])
{
fprintf(stderr, "Error: Snake is not fully connected in the grid.\n");
free(positions);
exit(EXIT_FAILURE);
}
}
snake->dir = determine_initial_direction(g, snake->segments_list);
free(positions);
free(used);
}

10
grid.h
View File

@@ -19,12 +19,14 @@ typedef enum
WALL = 'w', WALL = 'w',
EMPTY = ' ', EMPTY = ' ',
FRUIT = 'f', FRUIT = 'f',
SNAKE = 's' SNAKE = 's',
SNAKEHEAD = 'S',
SNAKETAIL = 't'
} Element; } Element;
Grid* allocate_grid(int n, int m); Grid *allocate_grid(int n, int m);
void free_grid(Grid *g); void free_grid(Grid *g);
void debug(Grid *g); void debug_grid(Grid *g);
int compute_size(Grid *g, int w, int h); int compute_size(Grid *g, int w, int h);
void draw_grid(Grid *g); void draw_grid(Grid *g);
void place_snake(Grid *g, struct SnakeStruct *snake); void place_snake(Grid *g, struct SnakeStruct *snake);
@@ -32,5 +34,7 @@ Element move_snake(struct SnakeStruct *snake, Grid *g);
int count_nb_lines(FILE *stream); int count_nb_lines(FILE *stream);
int count_fruits(FILE *stream, Grid *g); int count_fruits(FILE *stream, Grid *g);
void copy(const char *src, char *dst); void copy(const char *src, char *dst);
int is_snake_connected(struct SnakeStruct *snake);
void read_snake_from_grid(Grid *g, struct SnakeStruct *snake);
#endif /* GRID_H */ #endif /* GRID_H */

View File

@@ -1,22 +0,0 @@
w w
f
f f
f
wwwwwwwwww
f
f f
f
w w

View File

@@ -1,7 +1,7 @@
w w w w
f f
ssssS
f f f f
@@ -9,17 +9,14 @@ w w
wwwwwwwwww wwwwwwwwww
w w w
w f w
wwwwww w
w
w
f w f
w
f f w f f
w
f w f
w w w w
w
w
w

25
levels/level2 Normal file
View File

@@ -0,0 +1,25 @@
w w
f
f f
f
wwwwwwwwww
Sssssw w w
w f w
wwwwww w
w
w
f w
w
f f w
w
f w
w w
w
w
w

45
snake.c
View File

@@ -13,6 +13,7 @@ Snake *new_snake(void)
snake->size = 0; snake->size = 0;
snake->segments_list = NULL; snake->segments_list = NULL;
snake->dir = RIGHT;
return snake; return snake;
} }
@@ -59,6 +60,18 @@ void free_snake(Snake *snake)
free(snake); free(snake);
} }
void debug_snake(Snake *snake)
{
Position *current = snake->segments_list;
printf("Snake segments:\n");
while (current != NULL)
{
printf(" (%d, %d)\n", current->x, current->y);
current = current->next;
}
printf("Snake size: %d\n", snake->size);
}
void crawl(Snake *snake, struct GridStruct *g) void crawl(Snake *snake, struct GridStruct *g)
{ {
Position *new_head = (Position *)malloc(sizeof(Position)); Position *new_head = (Position *)malloc(sizeof(Position));
@@ -100,3 +113,35 @@ void crawl(Snake *snake, struct GridStruct *g)
free(current->next); free(current->next);
current->next = NULL; current->next = NULL;
} }
Direction determine_initial_direction(struct GridStruct *g, Position *head)
{
int x = head->x;
int y = head->y;
printf("Determining initial direction from position (%d, %d)\n", x, y);
if (x + 1 < g->nbc && g->grid[y][x + 1] != WALL && g->grid[y][x + 1] != SNAKE)
{
printf("Direction: RIGHT\n");
return RIGHT;
}
if (y + 1 < g->nbl && g->grid[y + 1][x] != WALL && g->grid[y + 1][x] != SNAKE)
{
printf("Direction: BOTTOM\n");
return BOTTOM;
}
if (x - 1 >= 0 && g->grid[y][x - 1] != WALL && g->grid[y][x - 1] != SNAKE)
{
printf("Direction: LEFT\n");
return LEFT;
}
if (y - 1 >= 0 && g->grid[y - 1][x] != WALL && g->grid[y - 1][x] != SNAKE)
{
printf("Direction: TOP\n");
return TOP;
}
printf("Default direction: BOTTOM\n");
return BOTTOM;
}

View File

@@ -30,6 +30,8 @@ typedef struct SnakeStruct Snake;
Snake *new_snake(void); Snake *new_snake(void);
void add_segment(Snake *snake, int x, int y); void add_segment(Snake *snake, int x, int y);
void free_snake(Snake *snake); void free_snake(Snake *snake);
void debug_snake(Snake *snake);
void crawl(Snake *snake, struct GridStruct *g); void crawl(Snake *snake, struct GridStruct *g);
Direction determine_initial_direction(struct GridStruct *g, Position *head);
#endif /* SNAKE_H */ #endif /* SNAKE_H */