From 305975051e804c6e9b3904f625528bb4e195807f Mon Sep 17 00:00:00 2001
From: mszczepa <marin.szczepanski@inria.fr>
Date: Tue, 31 May 2022 14:11:17 +0200
Subject: [PATCH] Add simple snake level

---
 levels/snake/Makefile        |  23 ++++++
 levels/snake/events_map.c    |  67 +++++++++++++++++
 levels/snake/events_map.h    |  24 ++++++
 levels/snake/input_manager.c |  60 +++++++++++++++
 levels/snake/input_manager.h |   9 +++
 levels/snake/main.c          | 141 +++++++++++++++++++++++++++++++++++
 levels/sokoban/main.c        |   2 +-
 7 files changed, 325 insertions(+), 1 deletion(-)
 create mode 100644 levels/snake/Makefile
 create mode 100644 levels/snake/events_map.c
 create mode 100644 levels/snake/events_map.h
 create mode 100644 levels/snake/input_manager.c
 create mode 100644 levels/snake/input_manager.h
 create mode 100644 levels/snake/main.c

diff --git a/levels/snake/Makefile b/levels/snake/Makefile
new file mode 100644
index 00000000..6a069157
--- /dev/null
+++ b/levels/snake/Makefile
@@ -0,0 +1,23 @@
+MAINPROG := main
+OBJECTS := main.o \
+		../../engines/fullgame/map.o ../../engines/fullgame/action.o ../../engines/fullgame/common.o ../../engines/fullgame/events.o ../../engines/fullgame/read_input.o \
+		events_map.o input_manager.o
+
+#DEFINES := -Dspontaneous_generation
+DEFILES :=
+CC := gcc
+CFLAGS := -g -Wall -Wextra $(DEFINES)
+
+all: main
+
+main: $(OBJECTS)
+	$(CC) $(CFLAGS) $(DEFINES) -o $@ $(OBJECTS)
+
+clean:
+	rm *.o
+	rm ../../engines/fullgame/*.o
+
+clear: clean
+	rm main
+
+.PHONY: all clean clear
diff --git a/levels/snake/events_map.c b/levels/snake/events_map.c
new file mode 100644
index 00000000..dd051c1c
--- /dev/null
+++ b/levels/snake/events_map.c
@@ -0,0 +1,67 @@
+#include "events_map.h"
+
+bool no_apple = true;
+int apple_id = 0;
+
+void update_snake(void) {
+    map * m = current_map();
+    int i = coord_idx(m->player_y, m->player_x);
+    
+    // snake ate itself, bad
+    if (m->entities[i]
+            && m->entities[i]->category == NPC
+            && m->entities[i]->id != head->body->id) {
+        printf("You chomped yourself!\n");
+        level_failed();
+        return;
+    }
+
+    // snake ate apple, good
+    if (m->entities[i]
+            && m->entities[i]->category == ITEM) {
+        m->entities[i]->category = NPC;
+        snake * new_head = malloc(sizeof(snake));
+        new_head->body = m->entities[i];
+        new_head->next = head;
+        head = new_head;
+        no_apple = true;
+        return;
+    }
+
+    // snake moved
+    snake * pred = NULL;
+    snake * tail = head;
+    while (tail->next) {
+        pred = tail;
+        tail = tail->next;
+    }
+    tail->next = head;
+    pred->next = NULL;
+    head = tail;
+    
+    int j = 0;
+    while (!m->entities[j]
+            || m->entities[j]->id != head->body->id)
+        j++;
+    m->entities[j] = NULL;
+    m->entities[i] = head->body;
+}
+
+
+void spawn_apple(void) {
+    if (!no_apple)
+        return;
+    
+    map * m = current_map();
+    int i = rand() % (m->width * m->height);
+
+    while (m->entities[i])
+        i = rand() % (m->width * m->height);
+    
+    m->entities[i] = malloc(sizeof(entity));
+    m->entities[i]->category = ITEM;
+    m->entities[i]->id = apple_id++ + (m->width * m->height);
+    #ifndef spontaneous_generation
+    no_apple = false;
+    #endif
+}
diff --git a/levels/snake/events_map.h b/levels/snake/events_map.h
new file mode 100644
index 00000000..1c425dbd
--- /dev/null
+++ b/levels/snake/events_map.h
@@ -0,0 +1,24 @@
+#ifndef EVENTS_MAP_H
+#define EVENTS_MAP_H
+
+#include "../../engines/fullgame/agdbentures.h"
+
+typedef struct Snake {
+    entity * body;
+    struct Snake * next;
+} snake;
+
+snake * head;
+
+/**
+ * moves the snake and makes it longer if it ate an apple.
+ * Have it crash and game over if it ate itself.
+ */
+void update_snake(void);
+
+/**
+ * Creates an apple at a random location outside of the snake's body.
+ */
+void spawn_apple(void);
+
+#endif // EVENTS_MAP_H
diff --git a/levels/snake/input_manager.c b/levels/snake/input_manager.c
new file mode 100644
index 00000000..4c958b91
--- /dev/null
+++ b/levels/snake/input_manager.c
@@ -0,0 +1,60 @@
+#include "input_manager.h"
+
+void apply_input(command * c) {
+    map * m = current_map();
+    direction dir = m->player_direction;
+
+    if (!strncmp(c->command_buffer, "U", 1)) {
+        dir = UP;
+    } else if (!strncmp(c->command_buffer, "D", 1)) {
+        dir = DOWN;
+    } else if (!strncmp(c->command_buffer, "L", 1)) {
+        dir = LEFT;
+    } else if (!strncmp(c->command_buffer, "R", 1)) {
+        dir = RIGHT;
+    } else {
+        printf("Unknown input command. Continuing in the same direction\n");
+    }
+
+    int y, x;
+    coord_in_dir(m->player_y, m->player_x, dir, &y, &x);
+
+    // there is no wall in this game so collision will be 0 or -1
+    if (collision(x, y)) {
+        switch (dir) {
+        case UP:
+            m->player_y = m->height - 1;
+            break;
+        case DOWN:
+            m->player_y = 0;
+            break;
+        case LEFT:
+            m->player_x = m->width - 1;
+            break;
+        case RIGHT:
+            m->player_x = 0;
+            break;        
+        default:
+            printf("apply_input: invalid direction\n");
+            break;
+        }
+    } else {
+        switch (dir) {
+        case UP:
+            up();
+            break;
+        case DOWN:
+            down();
+            break;
+        case LEFT:
+            left();
+            break;
+        case RIGHT:
+            right();
+            break;        
+        default:
+            printf("apply_input: invalid direction\n");
+            break;
+        }
+    }
+}
diff --git a/levels/snake/input_manager.h b/levels/snake/input_manager.h
new file mode 100644
index 00000000..0ce4098e
--- /dev/null
+++ b/levels/snake/input_manager.h
@@ -0,0 +1,9 @@
+#ifndef INPUT_MANAGER_H
+#define INPUT_MANAGER_H
+
+#include "../../engines/fullgame/agdbentures.h"
+
+// examine la commande c et appelle les fonctions d'action ou de déplacement correspondantes
+void apply_input(command * c);
+
+#endif // INPUT_MANAGER_H
\ No newline at end of file
diff --git a/levels/snake/main.c b/levels/snake/main.c
new file mode 100644
index 00000000..4dd30231
--- /dev/null
+++ b/levels/snake/main.c
@@ -0,0 +1,141 @@
+/** @AGDB
+ * level_name: Snake
+ * program_name: main
+ * level_number: 1109
+ * available_commands: next step continue
+ * map_height: 10
+ * map_width: 10
+ * 
+ * BUG: We forgot to reset no_apple to false in spawn_apple, so apples spawn at each step.
+ * tag: snake simple chained_list
+ * 
+ * HINT1: Do 1 box at a time
+ */ 
+
+/**
+ * Entry point of the game. Manages the main loop, and apart from
+ * that only calls functions.
+ * 
+ * Game design related tip:
+ *   Snake is a weird game you can't win, yet many people like it, maybe
+ *   because it is simplistic and allows you to pass time
+ *   on your brand new Nokia phone, or TI82.
+*/
+
+#include "../../engines/fullgame/agdbentures.h"
+#include "events_map.h"
+#include "input_manager.h"
+
+const char * str_map =
+"\
+          \n\
+     ^    \n\
+     A    \n\
+     A    \n\
+     A    \n\
+          \n\
+          \n\
+          \n\
+          \n\
+          \n\
+";
+
+
+/**
+ * Generates a snake in the form of a linked list
+ */
+void create_snake(void) {
+    snake * cur;
+    snake * pred = NULL;
+    map * m = current_map();
+
+    for (int i = 0; i < m->width * m->height; i++) {
+        if (m->entities[i] && m->entities[i]->category == NPC) {
+                cur = malloc(sizeof(snake));
+                if (pred)
+                    pred->next = cur;
+                else
+                    head = cur;
+                cur->body = m->entities[i];
+                cur->next = NULL;
+                pred = cur;
+        }
+    }
+
+    // actual head is where the player is
+    int i = coord_idx(m->player_y, m->player_x);
+    m->entities[i] = malloc(sizeof(entity));
+    m->entities[i]->category = NPC;
+    m->entities[i]->id = pred->body->id + 1;
+    cur = malloc(sizeof(snake));
+    cur->next = head;
+    cur->body = m->entities[i];
+    head = cur;
+}
+
+
+/**
+ * Frees the body of a snake starting from its parameter
+ */
+void free_snake(snake * s) {
+    if (s->next)
+        free_snake(s->next);
+    free(s);
+}
+
+
+int main(int argc, char ** argv) {
+    bool manual_mode = true;
+    if (argc > 1)
+        manual_mode = false;
+
+    if (manual_mode) {
+        printf("Manual mode. Available commands: UP, DOWN, LEFT, RIGHT and *_N variants.\nUse Ctrl+C to quit.\n");
+    } else {
+        if (!init_inputs_file(argv[1])) {
+            printf("Error while reading the inputs!\n");
+            return EXIT_FAILURE;
+        }
+    }
+
+
+    load_map("torus", str_map);
+    create_snake();
+    srand(0xC0FF33); // random seed
+    spawn_apple();
+    add_event(update_snake);
+    add_event(spawn_apple);
+
+    if (manual_mode)
+        show_map();
+
+    command * com = get_next_input();
+
+    // The Holy Main Loop
+    while (!exit_main_loop) {
+        apply_input(com);
+
+        /* since the events may be applied if the input is FORWARD_N, 
+           we need to check to avoid triggering the same event twice*/
+        if (!exit_main_loop)
+            apply_events();
+        
+        if (manual_mode)
+            show_map();
+
+        if (!exit_main_loop)
+            com = get_and_free_input(com);
+
+        // plus aucun input mais on a pas encore gagné ou perdu -> défaite
+        if (!manual_mode && com == NULL && !exit_main_loop)
+            level_failed();
+    }
+
+    free_snake(head);
+    free_command(com);
+    remove_all_events();
+    free_map_stack();
+    close_inputs_file();
+    
+    return EXIT_SUCCESS;
+}
diff --git a/levels/sokoban/main.c b/levels/sokoban/main.c
index 21fee5b4..c51503ac 100644
--- a/levels/sokoban/main.c
+++ b/levels/sokoban/main.c
@@ -43,7 +43,7 @@ int main(int argc, char ** argv) {
         manual_mode = false;
 
     if (manual_mode) {
-        printf("Manual mode. Available commands: UP, DOWN, LEFT, RIGHT and *_N variants; TOUCH.\nUse Ctrl+C to quit.\n");
+        printf("Manual mode. Available commands: UP, DOWN, LEFT, RIGHT and *_N variants.\nUse Ctrl+C to quit.\n");
     } else {
         if (!init_inputs_file(argv[1])) {
             printf("Error while reading the inputs!\n");
-- 
GitLab