diff --git a/assets/fruit.png b/assets/fruit.png new file mode 100644 index 0000000..e24547d Binary files /dev/null and b/assets/fruit.png differ diff --git a/assets/grass.png b/assets/grass.png new file mode 100644 index 0000000..98c4091 Binary files /dev/null and b/assets/grass.png differ diff --git a/assets/wall.png b/assets/wall.png new file mode 100644 index 0000000..628943b Binary files /dev/null and b/assets/wall.png differ diff --git a/game.c b/game.c index ede413c..dc10215 100644 --- a/game.c +++ b/game.c @@ -27,11 +27,14 @@ int main(int argc, char *argv[]) size_t size_buf = 0; int nbl, nbc, i; 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; int width = 640, height = 480; MLV_Keyboard_button touche = MLV_KEYBOARD_NONE; int initial_size = 0; + int current_level = 1; + int global_score = 0; + int previous_score = 0; Grid *g; @@ -40,17 +43,6 @@ int main(int argc, char *argv[]) {"input", required_argument, 0, 'i'}, {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) { switch (opt) @@ -67,155 +59,194 @@ int main(int argc, char *argv[]) } } - if (input_file == NULL) + while (1) { - input_file = "levels/default"; - } - stream = fopen(input_file, "r"); - if (!stream) - { - fprintf(stderr, "Error: unable to open file\n"); - exit(EXIT_FAILURE); - } + Snake *snake = new_snake(); - nbl = count_nb_lines(stream); - rewind(stream); - nbc = getline(&buf, &size_buf, stream); - if (nbc == -1) - { - fprintf(stderr, "Error: malformed file\n"); - exit(EXIT_FAILURE); - } - nbc--; + previous_score = global_score; - g = allocate_grid(nbl, nbc); + input_file = NULL; - copy(buf, g->grid[0]); - for (i = 1; i < nbl; i++) - { - int size_tmp = getline(&buf, &size_buf, stream); - if (size_tmp != nbc + 1) + if (input_file == NULL) { - fprintf(stderr, "Error: inconsistent line length\n"); + char level_file[256]; + snprintf(level_file, sizeof(level_file), "levels/level%d", current_level); + input_file = level_file; + } + + stream = fopen(input_file, "r"); + if (!stream) + { + fprintf(stderr, "Error: unable to open file for level %d\n", current_level); + exit(EXIT_FAILURE); + } + + nbl = count_nb_lines(stream); + rewind(stream); + nbc = getline(&buf, &size_buf, stream); + if (nbc == -1) + { + fprintf(stderr, "Error: malformed file\n"); free(buf); fclose(stream); exit(EXIT_FAILURE); } - copy(buf, g->grid[i]); - } + nbc--; - rewind(stream); - nb_fruit = count_fruits(stream, g); - if (nb_fruit == 0) - { - fprintf(stderr, "Error: no fruits in the grid\n"); - free(buf); - fclose(stream); - exit(EXIT_FAILURE); - } - if (nb_fruit == -1) - { - fprintf(stderr, "Error: unable to count fruits\n"); - free(buf); - fclose(stream); - exit(EXIT_FAILURE); - } + g = allocate_grid(nbl, nbc); - free(buf); - fclose(stream); - - place_snake(g, snake); - - MLV_create_window("SNAKE", "3R-IN1B", width, height); - MLV_change_frame_rate(24); - - while ( - MLV_get_event( - &touche, NULL, NULL, - NULL, NULL, - NULL, NULL, NULL, - NULL) == MLV_NONE || - touche != MLV_KEYBOARD_ESCAPE) - { - Element result; - MLV_clear_window(MLV_COLOR_BLACK); - - loop_count = (loop_count + 1) % DIFFICULTY; - if (loop_count == 0) + copy(buf, g->grid[0]); + for (i = 1; i < nbl; i++) { - result = move_snake(snake, g); - - if (result == WALL || result == SNAKE) + int size_tmp = getline(&buf, &size_buf, stream); + if (size_tmp != nbc + 1) { - if (result == WALL) - { - MLV_draw_text(width / 2 - 75, height / 2, "Game Over! You hit a wall.", MLV_COLOR_RED); - } - else if (result == SNAKE) - { - MLV_draw_text(width / 2 - 75, height / 2, "Game Over! You hit yourself.", MLV_COLOR_RED); - } - MLV_actualise_window(); - MLV_wait_seconds(3); - break; + fprintf(stderr, "Error: inconsistent line length\n"); + free(buf); + free_grid(g); + fclose(stream); + exit(EXIT_FAILURE); } - else if (result == FRUIT) + copy(buf, g->grid[i]); + } + + rewind(stream); + nb_fruit = count_fruits(stream, g); + total_fruits = nb_fruit; + if (nb_fruit == 0) + { + fprintf(stderr, "Error: no fruits in the grid\n"); + free(buf); + free_grid(g); + fclose(stream); + exit(EXIT_FAILURE); + } + if (nb_fruit == -1) + { + fprintf(stderr, "Error: unable to count fruits\n"); + free(buf); + free_grid(g); + fclose(stream); + exit(EXIT_FAILURE); + } + + free(buf); + buf = NULL; + fclose(stream); + + read_snake_from_grid(g, snake); + initial_size = snake->size; + + MLV_create_window("SNAKE", "3R-IN1B", width, height); + MLV_change_frame_rate(24); + + while ( + MLV_get_event( + &touche, NULL, NULL, + NULL, NULL, + NULL, NULL, NULL, + NULL) == MLV_NONE || + touche != MLV_KEYBOARD_ESCAPE) + { + Element result; + char stats[256]; + MLV_clear_window(MLV_COLOR_BLACK); + + loop_count = (loop_count + 1) % DIFFICULTY; + if (loop_count == 0) { - Position *tail = snake->segments_list; - Position *prev = NULL; - while (tail->next != NULL) + result = move_snake(snake, g); + + if (result == WALL || result == SNAKE) { - prev = tail; - tail = tail->next; - } - int dx = tail->x - (prev ? prev->x : tail->x); - int dy = tail->y - (prev ? prev->y : tail->y); - add_segment(snake, tail->x + dx, tail->y + dy); - place_snake(g, snake); - if (snake->size == initial_size + nb_fruit) - { - MLV_draw_text( - width / 2 - 75, height / 2, - "You Win! All fruits collected.", - MLV_COLOR_GREEN); + if (result == WALL) + { + MLV_draw_text(width / 2 - 80, height / 2, "Game Over! You hit a wall.", MLV_COLOR_RED); + } + else if (result == SNAKE) + { + MLV_draw_text(width / 2 - 80, height / 2, "Game Over! You hit yourself.", MLV_COLOR_RED); + } MLV_actualise_window(); MLV_wait_seconds(3); + + global_score = previous_score; break; } + else if (result == FRUIT) + { + Position *tail = snake->segments_list; + Position *prev = NULL; + int dx, dy; + while (tail->next != NULL) + { + prev = tail; + tail = tail->next; + } + dx = tail->x - (prev ? prev->x : tail->x); + dy = tail->y - (prev ? prev->y : tail->y); + add_segment(snake, tail->x + dx, tail->y + dy); + place_snake(g, snake); + nb_fruit--; + global_score++; + if (snake->size == initial_size + total_fruits) + { + MLV_draw_text( + width / 2 - 200, height / 2, + "You Win! All fruits collected. Proceeding to the next level.", + MLV_COLOR_GREEN); + MLV_actualise_window(); + MLV_wait_seconds(3); + current_level++; + break; + } + } } + + 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(); + + switch (touche) + { + case MLV_KEYBOARD_DOWN: + if (snake->dir != TOP) + snake->dir = BOTTOM; + break; + case MLV_KEYBOARD_UP: + if (snake->dir != BOTTOM) + snake->dir = TOP; + break; + case MLV_KEYBOARD_LEFT: + if (snake->dir != RIGHT) + snake->dir = LEFT; + break; + case MLV_KEYBOARD_RIGHT: + if (snake->dir != LEFT) + snake->dir = RIGHT; + break; + default: + break; + } + + touche = MLV_KEYBOARD_NONE; + MLV_delay_according_to_frame_rate(); } - draw_grid(g); - MLV_actualise_window(); + MLV_free_window(); + free_grid(g); + free_snake(snake); - switch (touche) + if (touche == MLV_KEYBOARD_ESCAPE) { - case MLV_KEYBOARD_DOWN: - if (snake->dir != TOP) - snake->dir = BOTTOM; - break; - case MLV_KEYBOARD_UP: - if (snake->dir != BOTTOM) - snake->dir = TOP; - break; - case MLV_KEYBOARD_LEFT: - if (snake->dir != RIGHT) - snake->dir = LEFT; - break; - case MLV_KEYBOARD_RIGHT: - if (snake->dir != LEFT) - snake->dir = RIGHT; - break; - default: break; } - - touche = MLV_KEYBOARD_NONE; - MLV_delay_according_to_frame_rate(); } - MLV_free_window(); - free_grid(g); - free_snake(snake); return 0; } \ No newline at end of file diff --git a/grid.c b/grid.c index e428984..0a6502f 100644 --- a/grid.c +++ b/grid.c @@ -1,5 +1,6 @@ #include #include +#include #include #include "grid.h" #include "snake.h" @@ -46,7 +47,7 @@ void free_grid(Grid *g) free(g); } -void debug(Grid *g) +void debug_grid(Grid *g) { int 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_height = MLV_get_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); @@ -81,16 +103,55 @@ void draw_grid(Grid *g) switch (g->grid[i][j]) { case WALL: - MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_BROWN); + if (image_wall == NULL) + { + MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_BROWN); + } + else + { + MLV_draw_image(image_wall, x, y); + } break; case EMPTY: - MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_WHITE); + 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); + } + break; case FRUIT: - MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_RED); + if (image_fruit == NULL) + { + MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_RED); + } + else + { + MLV_draw_image(image_fruit, x, y); + } break; case SNAKE: - MLV_draw_filled_rectangle(x, y, cell_size, cell_size, MLV_COLOR_GREEN); + 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; + 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; default: 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; + if (current != NULL) + { + g->grid[current->y][current->x] = SNAKEHEAD; + current = current->next; + } + while (current != NULL) { g->grid[current->y][current->x] = SNAKE; @@ -117,7 +184,8 @@ Element move_snake(struct SnakeStruct *snake, Grid *g) Position *head; Element element_at_head; - while (tail->next != NULL) { + while (tail->next != NULL) + { tail = tail->next; } @@ -128,7 +196,12 @@ Element move_snake(struct SnakeStruct *snake, Grid *g) head = snake->segments_list; 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; } @@ -179,4 +252,114 @@ void copy(const char *src, char *dst) i++; } 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); } \ No newline at end of file diff --git a/grid.h b/grid.h index 7208ef3..1ca46e0 100644 --- a/grid.h +++ b/grid.h @@ -19,12 +19,14 @@ typedef enum WALL = 'w', EMPTY = ' ', FRUIT = 'f', - SNAKE = 's' + SNAKE = 's', + SNAKEHEAD = 'S', + SNAKETAIL = 't' } Element; -Grid* allocate_grid(int n, int m); +Grid *allocate_grid(int n, int m); void free_grid(Grid *g); -void debug(Grid *g); +void debug_grid(Grid *g); int compute_size(Grid *g, int w, int h); void draw_grid(Grid *g); 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_fruits(FILE *stream, Grid *g); 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 */ \ No newline at end of file diff --git a/levels/default b/levels/default deleted file mode 100644 index 7991750..0000000 --- a/levels/default +++ /dev/null @@ -1,22 +0,0 @@ -w w - - f - - f f - - - f - - - wwwwwwwwww - - - - - - f - - f f - - f -w w diff --git a/levels/level1 b/levels/level1 index da62ce8..09cbe7e 100644 --- a/levels/level1 +++ b/levels/level1 @@ -1,7 +1,7 @@ w w f - + ssssS f f @@ -9,17 +9,14 @@ w w wwwwwwwwww - w w w - w f w - wwwwww w - w - w - f w - w - f f w - w - f w + + + + + + f + + f f + + f w w - w - w - w diff --git a/levels/level2 b/levels/level2 new file mode 100644 index 0000000..ba63226 --- /dev/null +++ b/levels/level2 @@ -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 diff --git a/snake.c b/snake.c index d942b02..f7d0373 100644 --- a/snake.c +++ b/snake.c @@ -13,6 +13,7 @@ Snake *new_snake(void) snake->size = 0; snake->segments_list = NULL; + snake->dir = RIGHT; return snake; } @@ -59,6 +60,18 @@ void free_snake(Snake *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) { Position *new_head = (Position *)malloc(sizeof(Position)); @@ -100,3 +113,35 @@ void crawl(Snake *snake, struct GridStruct *g) free(current->next); 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; +} \ No newline at end of file diff --git a/snake.h b/snake.h index fcb1435..de9d9f7 100644 --- a/snake.h +++ b/snake.h @@ -30,6 +30,8 @@ typedef struct SnakeStruct Snake; Snake *new_snake(void); void add_segment(Snake *snake, int x, int y); void free_snake(Snake *snake); +void debug_snake(Snake *snake); void crawl(Snake *snake, struct GridStruct *g); +Direction determine_initial_direction(struct GridStruct *g, Position *head); #endif /* SNAKE_H */ \ No newline at end of file