Holiday Trip 1.0.0

Struct, Enum, Union


If there is a conflict between the subject and the skeleton or moulinette, the subject is always right.
However, please report any inconsistencies to your assistant.

Submissions

Repository structure
At the end, your git repo must follow this architecture:
epita-prepa-computer-science-prog-103-p-04-2030-firstname.lastname
├── HolidayTrip
│   ├── Fundamentals
│   │   ├── my_strings
│   │   │   ├── Makefile
│   │   │   ├── main.c
│   │   │   ├── my_strings.c
│   │   │   └── my_strings.h
│   │   ├── radar
│   │   │   ├── Makefile
│   │   │   ├── main.c
│   │   │   ├── radar.c
│   │   │   └── radar.h
│   │   └── reap_and_tear
│   │       ├── Makefile
│   │       ├── demon.c
│   │       ├── demon.h
│   │       ├── main.c
│   │       ├── print.c
│   │       ├── villager.c
│   │       ├── villager.h
│   │       ├── weapons.c
│   │       └── weapons.h
│   └── Proficiencies
│       ├── animals
│       │   ├── Makefile
│       │   ├── animals.h
│       │   ├── fish.c
│       │   ├── insect.c
│       │   ├── main.c
│       │   ├── vector.c
│       │   └── vector.h
│       └── museum_restoration
│           ├── Makefile
│           ├── fossils.c
│           ├── fossils.h
│           └── main.c
├── .gitignore
└── README

Do not forget to check the following requirements before submitting your work:
  • You shall obviously replace firstname.lastname with your login.
  • The .gitignore file is mandatory.
  • Remove all personal tests from your code, except those from the Tests folder.
  • The given prototypes must be strictly respected.
  • The code MUST compile! Otherwise you will not receive a grade.

.gitignore example
Here is an example of a .gitignore file:
*.a
*.lib
*.o
*.obj
*.out

.idea/
*~
*.DotSettings.user

This needs to be setup before the first submission!

Introduction


Lore
In 2020, while preparing for her trip to the beautiful island we live on. Isabelle made a new friend, they spent some good times together, but on the 20th of March 2020, they had to part ways.
However they made a song together, and they promised that they would meet each other again.
She received a letter saying that he would arrive today. The issue is that he uses many items and technologies in his world that don't exist in yours, thus you'll have to help him during his trip to rebuild his items, or at least some that have the same purpose.
To do so, let's use structs and enums !!!

Danger(s)
Unless explicitly stated otherwise, the only allowed headers are <stddef.h>, <stdlib.h>, <stdio.h> and <string.h>.

Information
Makefiles with empty mains are included in the given files to make testing easier.
You can extract them with the command tar -xvf assets.tar.gz.

In this practical, even though you are required to submit the main files, their content will not be tested.

In this first section, we will focus on the basics of data organization in C.
You will learn how to define and use structs to group variables and enums to represent states or constants.



my_strings/my_strings.h


Lore
Communication is proving difficult: Isabelle's friend comes from a world where text is handled with precision, not just by hoping for a \0 character.
To process his data and rebuild his items correctly, you need to implement a struct string that keeps track of both the content and its exact length.

Lore
This header file acts as the blueprint for the new communication system. It defines the internal structure of the alien string and declares the set of functions (prototypes) you need to implement to make it work.

Hint(s)
Don't forget to add header guards.

This struct represents a string as follows:
  • char *data: A pointer to the character array (the actual string content).
  • size_t size: The number of characters in the string.

Code example(s)
struct string
{
    char *data;
    size_t size;
};

Prototype(s)
struct string *my_str_init(const char* s, size_t size);

void my_str_destroy(struct string *str);
void my_str_puts(struct string *str);
struct string *my_str_n_cat(struct string *str1, const char *str2, size_t str2_len);



my_strings/my_strings.c


Lore
This is the workshop where the translation tool comes to life.
Using the blueprints from the header file, you must now implement the logic that allows Isabelle's friend to communicate.

Be careful: unlike in his world, memory here is managed manually: you build it, you clean it.

This function allocates a new string structure on the heap using malloc(3) and returns a pointer to it.
If size is zero or s is NULL, the data field is initialized to NULL and the size field to 0.
Otherwise, initialize your data field with size bytes from s.

Information
You must include the my_strings.h header to use the struct string.

Warning(s)
Do not simply assign the pointer s to data (shallow copy). The structure must own its own memory. Modifying the original string s afterwards should not affect your structure.

Prototype(s)
struct string *my_str_init(const char* s, size_t size);

Code example(s)
struct string *str = my_str_init("Hello World!", 12);
printf("data: %.*s\n", (int)str->size, str->data);

Expected output
data: Hello World!

Hint(s)
You can get the size of a struct with sizeof(struct string).

Frees the memory allocated for the string structure and its internal data.

If the pointer str is NULL, the function does nothing.

Prototype(s)
void my_str_destroy(struct string *str);

Code example(s)
struct string *str = my_str_init("Hello World!", 12);
my_str_destroy(str);

Expected output
(No output, just ensure there are no memory leaks with -fsanitize=address)

Prints the content of the string structure to the standard output, followed by a newline.

If the data field is NULL or the size is 0, it should simply print a newline.
If the str pointer is NULL, the function does nothing.

Hint(s)
You can use the putchar(3) function.

Prototype(s)
void my_str_puts(struct string *str);

Code example(s)
struct string *str = my_str_init("Hello Alien World!", 18);
my_str_puts(str);
my_str_destroy(str);

Expected output
Hello Alien World!

Appends str2_len characters from str2 to the string structure.
You must ensure sufficient memory is allocated and update the size field accordingly.

The function must return the pointer to the string structure containing the concatenation.

If str1 is NULL, a new string structure must be initialized.
If str2 is NULL or the size is 0, no operation is performed.

Hint(s)
You might need to use realloc(3) to resize the allocated memory.

Prototype(s)
struct string *my_str_n_cat(struct string *str1, const char *str2, size_t str2_len);

Code example(s)
struct string *str = my_str_init("Hello ", 6);
str = my_str_n_cat(str, "Alien World!", 13);
my_str_puts(str);
my_str_destroy(str);

Expected output
Hello Alien World!



radar/radar.h


Lore
Now that the communication protocol is established, the identity of Isabelle's friend is finally revealed: it is the Doom Slayer!

Unfortunately, the Slayer doesn't understand the geography of your peaceful island.
He brought his own UAC-grade radar to locate himself, but its software is incompatible with this dimension's physics.
Isabelle asks for your help to patch his radar so he can navigate and find the "interesting places" she recommended.

In the radar.h file, you will find the definition of the struct point and the prototypes of the functions you need to implement.

Warning(s)
This is the last time a structure is provided for you.
In the upcoming parts, you will have to define the structs yourself!

Code example(s)
struct point
{
    double x;
    double y;
};

Prototype(s)
double compute_distance(struct point a, struct point b);
int is_in_area(struct point center, double radius, struct point point);
size_t detect_points(struct point center, double range, struct point* interesting_points, size_t nb_points);



radar/radar.c


In this file, you will implement the mathematical tools required to calculate positions and check if entities are within range of each other.

First, you'll have to compute the distance between two points.
Implement the function compute_distance to calculate the Euclidean distance between two points, a and b.

distance=(xbxa)2+(ybya)2 \text{distance} = \sqrt{(x_b - x_a)^2 + (y_b - y_a)^2}

Hint(s)
In this file, you can use the math.h header
You need to add the -lm flag when compiling your code to link the math library.

Prototype(s)
double compute_distance(struct point a, struct point b);

Code example(s)
struct point p1 = { 0.0, 0.0 };
struct point p2 = { 3.0, 4.0 };

double dist = compute_distance(p1, p2);
printf("Distance: %.2f\n", dist);

Expected output
Distance: 5.00

Write the function is_in_area that detects if a point is within the area defined by center and radius.
Note that points located exactly on the border are considered inside.

Prototype(s)
int is_in_area(struct point center, double radius, struct point point);

Hint(s)
You can use the previous function.

Code example(s)
struct point center = { 5.0, 5.0 };
double radius = 3.0;

struct point inside_point = { 7.0, 7.0 };
struct point outside_point = { 9.0, 9.0 };

int inside = is_in_area(center, radius, inside_point);
int outside = is_in_area(center, radius, outside_point);

printf("Inside: %d\n", inside);
printf("Outside: %d\n", outside);

Expected output
Inside: 1
Outside: 0

Lore
To finish the implementation of the radar, Isabelle told you she gave him a list of places to visit and their coordinates.
So you add the last feature, detecting all the interesting points in a given area.

This function iterates through the interesting_points array of length nb_points to find locations within the specified range of the center.

You must print the matching points according to the following format, and return the number of points found.
"{x, y}\n"

Prototype(s)
size_t detect_points(struct point center, double range, struct point *interesting_points, size_t nb_points);

Hint(s)
You can use the previous function.

Code example(s)
struct point center = { 10.0, 10.0 };
double range = 5.0;

struct point points[] = {
    { 11.0, 11.0 },
    { 10.0, 10.0 },
    { 20.0, 20.0 },
    { 14.0, 13.0 }
};

size_t size = sizeof(points) / sizeof(struct point);

size_t found = detect_points(center, range, points, size);
printf("Total found: %zu\n", found);

Expected output
{11.00, 11.00}
{10.00, 10.00}
{14.00, 13.00}
Total found: 3


While visiting the island, you talk a bit with doom guy, even though he is not very talkative, he seems to be interested about what you’re saying. You learn that he couldn’t take a fly, so he came by a portal just like the first time. Wait, is it closed ?
You hear a loud scream “THERE ARE DEMONS !!! DEMONS INVADED THE ISLAND !!”



reap_and_tear/weapons.h


Lore
Doom guy wants to fight the demons, but his weapons can't be used on the island since they don't exist.
Let's create them using structs and enums.

In this header, you will define the necessary data structures and function prototypes.

We don't want to bother calculating the exact range every time, so we're going to use an enum to approximate the range of weapons.

Code example(s)
enum distance
{
    CLOSE,
    NEAR,
    FAR,
};

Create a struct named gun with the following attributes:
  • name: A string of 50 characters at most (including the null terminator).
  • damage: An int representing how much damage it deals.
  • range: An enum distance representing the effective range.
  • magazine_size: An int representing the maximum capacity.
  • current_magazine: An int representing the current ammo count.

The name should be a fixed-size array.

Prototype(s)
struct gun *init_gun(char name[50], int damage, enum distance range, int magazine_size);
void reload(struct gun *gun);
void destroy_gun(struct gun *gun);



reap_and_tear/villager.h


Lore
The weapons are ready, and you see that Doom Guy has already started fighting and protecting the villagers.
You see Isabelle trying to take a gun to help him, but it looks impossible: the weapons you created aren't adapted to the residents of the island.
You need to do something about it so you can help the Slayer.

In this header, you will define the struct villager and the prototypes for their actions.

Warning(s)
Since this file references struct demon (which isn't defined yet), you must add a forward declaration at the top of your file.

Code example(s)
struct demon;

The struct villager represents the residents fighting alongside the Slayer.

Attributes:
  • name: A string (max 50 chars) representing the villager's name.
  • cur_HP: An int for current health points.
  • HP_max: An int for maximum health points.
  • gun: A pointer to a struct gun (can be NULL).
  • gun_mastery: An int representing proficiency with firearms.
  • medicines: An int representing the inventory count (Max 10).
  • distance: An enum distance representing proximity to the demon.

The name should be a fixed-size array.

Prototype(s)
struct villager *init_villager(char name[50], int HP_max, struct gun *gun, int gun_mastery);
void pp_villager(struct villager *villager);
void destroy_villager(struct villager *villager);
void update_villager_hp(struct villager *villager, int amount);
void shoot(struct villager *villager, struct demon *demon);
void heal(struct villager *villager);
enum distance walk(struct villager *villager, int direction);
int prepare_medicine(struct villager *villager);



reap_and_tear/demon.h


Lore
The newly created weapons can't affect the demons yet. You need to adapt them, or you'll risk undefined behavior (and potentially destroying the world with a segfault).

Isabelle seems to be craving blood (weird...), and Timmy & Tommy have started classifying the demons slain by the Doom Slayer.
Since names like "Ultra-Nightmare" are too long, they decided to classify them by "Type" rather than danger level.

In this header, you will define the struct demon, the classification enum, and the AI prototypes.

Warning(s)
Since this file references struct villager, ensure you have the proper forward declarations.

Code example(s)
struct villager;

Timmy and Tommy's classification system.

Note: Enum values are just numbers. We want this one to start at 1 (not 0) to use it as a multiplier later.
And yes, Tom Nook is the highest level of threat.

Code example(s)
enum demon_category
{
    PARASITE = 1,
    THREAT,
    NIGHTMARE,
    CALAMITY,
    TOM_NOOK
};

Create the struct demon with the following attributes:
  • category: An enum demon_category representing the type.
  • name: A string (max 50 chars).
  • damage: An int for base damage.
  • range: An enum distance for attack range.
  • cur_HP: An int for current health.
  • HP_max: An int for maximum health.

The name should be a fixed-size array.

Prototype(s)
struct demon *init_demon(enum demon_category category, char name[50], int HP_max, int damage, enum distance range);
void pp_demon(struct demon *demon);
void destroy_demon(struct demon *demon);
void basic_attack(struct demon *demon, struct villager *target);
void draining_attack(struct demon *demon, struct villager *target);
void heavy_attack(struct demon *demon, struct villager *target);
void chase(struct demon *demon, struct villager *villager);
void update_demon_hp(struct demon *demon, int amount);
void demon_action(struct demon *demon, struct villager *villager);



reap_and_tear/weapons.c


In this source file, you must implement the logic to forge (allocate), manage, and destroy the weapons.

This function must:
  1. Allocate memory for a struct gun.
  2. Copy the name argument into the structure.
  3. Initialize damage, range, and magazine_size with the provided arguments.
  4. Set current_magazine to be equal to magazine_size (a new gun is always fully loaded).
  5. Return the pointer to the newly created gun.

Hint(s)
You can use the function strcpy(3) to copy strings.

Prototype(s)
struct gun *init_gun(char name[50], int damage, enum distance range, int magazine_size);

Code example(s)
char gun_name[50] = "BFG";
struct gun *gun = init_gun(gun_name, 50, FAR, 16);

printf("Gun name: %s\nDamage: %d\nRange: %d\nMagazine: %d/%d\n", gun->name,
      gun->damage, gun->range, gun->current_magazine, gun->magazine_size);

Output
Gun name: BFG
Damage: 50
Range: 2
Magazine: 16/16

This function refills the gun.

You must set the current_magazine value to be equal to the magazine_size.

If the gun pointer is NULL, the function does nothing.

Prototype(s)
void reload(struct gun *gun);

Code example(s)
char gun_name[50] = "BFG";
struct gun *gun = init_gun(gun_name, 50, FAR, 16);

gun->current_magazine = 2;
printf("Before reload: %d\n", gun->current_magazine);
reload(gun);
printf("After reload: %d\n", gun->current_magazine);

Output
Before reload: 2
After reload: 16

Frees the memory allocated for the gun structure.

If the gun pointer is NULL, the function does nothing.

Prototype(s)
void destroy_gun(struct gun *gun);



reap_and_tear/villager.c


In this source file, you must implement the logic for the villagers' behavior, combat mechanics, and inventory management.

The function init_villager allocates and returns a pointer to a struct villager initialized with the given attributes.

Default values:
  • medicines must be set to 10.
  • distance must be set to NEAR.
  • cur_HP must be initialized to HP_max.

Prototype(s)
struct villager *init_villager(char name[50], int HP_max, struct gun *gun, int gun_mastery);

Subtracts amount from the villager's HP (can be negative for healing).

If HP <= 0, display: "You died.\n"

Be careful not to exceed HP_max

If the villager pointer is NULL, the function does nothing.

Prototype(s)
void update_villager_hp(struct villager *villager, int amount);

Code example(s)
char name[50] = "Isabelle";
char gun_name[50] = "BFG";
struct gun *gun = init_gun(gun_name, 50, FAR, 16);
struct villager *villager =
    init_villager(name, 50, gun, 1);
update_villager_hp(villager, 100);

Output
You died.

Frees the villager structure and its equipment.

If the villager pointer is NULL, the function does nothing.

Prototype(s)
void destroy_villager(struct villager *villager);



reap_and_tear/print.c


In this file, you will implement functions that print all the info of a villager and a demon.

If the villager pointer is NULL, the function does nothing.

Print all the information of a villager using the following format:

Shell example(s)
============================= Villager info =============================
Name: {name}
HP: {current_HP}/{HP_max}
Gun: {Gun_name} | Magazine: {current_magazine}/{magazine_size} | Mastery: {gun_mastery}
Medicine: {current_medicine}/10
Distance: {distance}

Information
Don't forget the final \n.
This function will not be tested if the villager has no gun equipped.

Hint(s)
For the distance, print Close, Near or Far depending on the distance of the villager.
We advise you to make an auxiliary function that returns these values.

Prototype(s)
void pp_villager(struct villager* villager);

Code example(s)
char name[50] = "Isabelle";
char gun_name[50] = "BFG";
struct gun *gun = init_gun(gun_name, 50, FAR, 16);
struct villager *villager = init_villager(name, 50, gun, 1);
pp_villager(villager);
destroy_villager(villager);

Output
============================= Villager info =============================
Name: Isabelle
HP: 50/50
Gun: BFG | Magazine: 16/16 | Mastery: 1
Medicine: 10/10
Distance: Near



reap_and_tear/demon.c


In this file, you will implement the logic for the demons.

The init_demon function returns a pointer to a struct demon with the given attribute.
You'll have to multiply the HP_max and damage of the demon by their category (that's why we wanted it to start at 1 and not 0).

Example: A "THREAT" (value 2) with base HP 100 will actually have 200 HP.
cur_HP must be initialized to HP_max.

Prototype(s)
struct demon *init_demon(enum demon_category category, char name[50], int HP_max, int damage, enum distance range);

Subtracts amount from the demon's HP.

If HP <= 0, display: "You killed the demon {demon_name} !!\n"

If the demon pointer is NULL, the function does nothing.

Prototype(s)
void update_demon_hp(struct demon *demon, int amount);

Code example(s)
char demon_name[50] = "Cyberdemon";
struct demon *demon = init_demon(NIGHTMARE, demon_name, 40, 5, CLOSE);
update_demon_hp(demon, 200);

Output
You killed the demon Cyberdemon !!

Frees the memory allocated for the demon structure.

If the demon pointer is NULL, the function does nothing.

Prototype(s)
void destroy_demon(struct demon *demon);



reap_and_tear/print.c



If the demon pointer is NULL, the function does nothing.

Print all the information of a demon using the following format:

Shell example(s)
============================= Demon info =============================
Name: {name} | Category: {category}
Base damage: {damage} | Range: {range}
HP: {current_HP}/{HP_max}

Information
Don't forget the final \n.

Hint(s)
For the category, print Parasite, Threat, Nightmare, Calamity or Tom Nook depending on the category of the demon.

For the range, print Close, Near or Far depending on the range of the demon.

Prototype(s)
void pp_demon(struct demon* demon);

Code example(s)
char demon_name[50] = "Cyberdemon";
struct demon *demon = init_demon(NIGHTMARE, demon_name, 40, 5, CLOSE);
pp_demon(demon);

Output
============================= Demon info =============================
Name: Cyberdemon | Category: Nightmare
Base damage: 15 | Range: Close
HP: 120/120



reap_and_tear/villager.c



First of all, you need to use your weapons, the shoot function fires a shot with the gun that the villager is equipped with and lower the current_magazine of the gun by one.

If the player didn't equip a gun (if the gun attribute is NULL) display the message "Equip a gun before trying to use one.\n" and return.

If the magazine is empty, display the message "Empty gun : You gotta reload.\n" and return.

Else you have to lower the current_magazine by 1 and then :
  • If the demon is not in range, just display the message "No demon in range.\n" (use the villager's distance attribute and the gun's range attribute )
  • If the demon is in range, lower his HP by the damage of the weapon + the gun_mastery, the minimum damage you can deal is 0. Then display the message "BAM! {name_of_the_demon} lost {number_of_damage} HP.\n"

If the villager or demon pointer are NULL, the function does nothing.

Prototype(s)
void shoot(struct villager *villager, struct demon *demon);

Code example(s)
char name[50] = "Isabelle";
char gun_name[50] = "BFG";
struct gun *gun = init_gun(gun_name, 50, FAR, 16);
struct villager *villager =
    init_villager(name, 50, gun, 1);

// Init demon
char demon_name[50] = "Cyberdemon";
struct demon *demon = init_demon(NIGHTMARE, demon_name, 40, 5, CLOSE);
// // Shoot
shoot(villager, demon);
pp_villager(villager);
pp_demon(demon);
destroy_demon(demon);
destroy_villager(villager);

Output
BAM! Cyberdemon lost 51 HP.
============================= Villager info =============================
Name: Isabelle
HP: 50/50
Gun: BFG | Magazine: 15/16 | Mastery: 1
Medicine: 10/10
Distance: Near
============================= Demon info =============================
Name: Cyberdemon | Category: Nightmare
Base damage: 15 | Range: Close
HP: 69/120

Uses one medicine to recover HP.
  • If no medicine left, display: "No medicine left, maybe prepare some ?\n"
  • Else:
    • Decrement medicine count by 1.
    • Heal 25% of HP_max (rounded down). Ensure cur_HP does not exceed HP_max.
    • Display: "Nice, you healed {amount_healed} HP.\n"

If the villager pointer is NULL, the function does nothing.

Prototype(s)
void heal(struct villager *villager);

Code example(s)
char name[50] = "Isabelle";
char gun_name[50] = "BFG";
struct gun *gun = init_gun(gun_name, 50, FAR, 16);
struct villager *villager =
    init_villager(name, 50, gun, 1);

// Heal
villager->cur_HP -= 40;
pp_villager(villager);
putchar('\n');
heal(villager);
pp_villager(villager);
destroy_villager(villager);

Output
============================= Villager info =============================
Name: Isabelle
HP: 10/50
Gun: BFG | Magazine: 16/16 | Mastery : 1
Medicine: 10/10
Distance: Near

Nice, you healed 12 HP.
============================= Villager info =============================
Name: Isabelle
HP: 22/50
Gun: BFG | Magazine: 16/16 | Mastery : 1
Medicine: 9/10
Distance: Near

Moves the villager based on the direction integer.
  • Positive (>= 0): Move closer (FAR → NEAR → CLOSE). If already CLOSE, display "Too close.\n"
  • Negative (< 0): Move further (CLOSE → NEAR → FAR). If already FAR, display "I can't run away.\n"
  • If movement was successful, display "You decided to move.\n". A movement is considered successful if the villager actually moved.
Returns the new distance (enum).

If the villager pointer is NULL, the function does nothing and return NEAR.

Prototype(s)
enum distance walk(struct villager *villager, int direction);

Code example(s)
char name[50] = "Isabelle";
char gun_name[50] = "BFG";
struct gun *gun = init_gun(gun_name, 50, FAR, 16);
struct villager *villager =
    init_villager(name, 50, gun, 1);

// Walk
pp_villager(villager);
putchar('\n');
walk(villager, 1);
pp_villager(villager);
destroy_villager(villager);

Output
============================= Villager info =============================
Name: Isabelle
HP: 50/50
Gun: BFG | Magazine: 16/16 | Mastery : 1
Medicine: 10/10
Distance: Near

You decided to move.
============================= Villager info =============================
Name: Isabelle
HP: 50/50
Gun: BFG | Magazine: 16/16 | Mastery : 1
Medicine: 10/10
Distance: Close

Crafts one medicine.
  • Add 1 to the villager's medicine count (Cap at 10).
  • Display: "Preparation ready !\n"
Returns the new number of medicines owned.

If the villager pointer is NULL, the function does nothing and return 0.

Prototype(s)
int prepare_medicine(struct villager *villager);

Code example(s)
// Init Villager
char name[50] = "Isabelle";
char gun_name[50] = "BFG";
struct gun *gun = init_gun(gun_name, 50, FAR, 16);

struct villager *villager =
    init_villager(name, 50, gun, 1);

// Prepare Medicine
villager->medicines -= 5;
pp_villager(villager);
prepare_medicine(villager);
pp_villager(villager);
destroy_villager(villager);

Output
============================= Villager info =============================
Name: Isabelle
HP: 50/50
Gun: BFG | Magazine: 16/16 | Mastery: 1
Medicine: 5/10
Distance: Near
Preparation ready !
============================= Villager info =============================
Name: Isabelle
HP: 50/50
Gun: BFG | Magazine: 16/16 | Mastery: 1
Medicine: 6/10
Distance: Near



reap_and_tear/demon.c



The demon attempts to close the gap to attack the villager.

You must update the distance attribute of the villager:
  • If the villager is at FAR, change it to NEAR.
  • If the villager is at NEAR, change it to CLOSE.
  • If they are already at CLOSE, the distance does not change.
Then, you must display the message: "The demon {demon_name} is chasing you.\n"
If the villager or demon pointer are NULL, the function does nothing.

Prototype(s)
void chase(struct demon *demon, struct villager *villager);

Code example(s)
// Init Villager
char name[50] = "Isabelle";
char gun_name[50] = "BFG";
struct gun *gun = init_gun(gun_name, 50, FAR, 16);
struct villager *villager =
    init_villager(name, 50, gun, 1);

// Init demon
char demon_name[50] = "Cyberdemon";
struct demon *demon = init_demon(NIGHTMARE, demon_name, 40, 5, CLOSE);

// Chase
pp_villager(villager);
chase(demon, villager);
pp_villager(villager);
destroy_villager(villager);
destroy_demon(demon);

Output
============================= Villager info =============================
Name: Isabelle
HP: 50/50
Gun: BFG | Magazine: 16/16 | Mastery: 1
Medicine: 10/10
Distance: Near
The demon Cyberdemon is chasing you.
============================= Villager info =============================
Name: Isabelle
HP: 50/50
Gun: BFG | Magazine: 16/16 | Mastery: 1
Medicine: 10/10
Distance: Close

This is the attack the demons use most of the time, it allows the demon to deal a number of damage equal to their damage attribute to the target if he is in range.
You'll have to decrease the HP of the target by the amount of damage they took.
Display "RAAAAAGH! {name_of_the_demon} attacks you to deal {number_of_damage} damage.\n"
If the target or demon pointer are NULL, the function does nothing.

Prototype(s)
void basic_attack(struct demon *demon, struct villager *target);

Code example(s)
// Init Villager
char name[50] = "Isabelle";
char gun_name[50] = "BFG";
struct gun *gun = init_gun(gun_name, 50, FAR, 16);

struct villager *villager =
    init_villager(name, 50, gun, 1);

// Init demon
char demon_name[50] = "Cyberdemon";
struct demon *demon = init_demon(NIGHTMARE, demon_name, 40, 5, FAR);

// Basic attack
basic_attack(demon, villager);
pp_villager(villager);
destroy_villager(villager);
destroy_demon(demon);

Output
RAAAAAGH! Cyberdemon attacks you to deal 15 damage.
============================= Villager info =============================
Name: Isabelle
HP: 35/50
Gun: BFG | Magazine: 16/16 | Mastery: 1
Medicine: 10/10
Distance: Near

This is the attack that demons use when they have low health. It is weaker than the basic attack but it allows them to heal some HP.
  • They deal half the damage they usually deal (minimum 1)
  • They heal half the damage they dealt (add half the damage they dealt to their HP)
The range is the same as the basic attack.
Display "SLURRRRP! {name_of_the_demon} attacks you to deal {number_of_damage} damage and regenerates {number_of_regenerated_hp} HP.\n"
If the target or demon pointer are NULL, the function does nothing.

Prototype(s)
void draining_attack(struct demon *demon, struct villager *target);

Code example(s)
// Init Villager
char name[50] = "Isabelle";
char gun_name[50] = "BFG";

struct gun *gun = init_gun(gun_name, 50, FAR, 16);

struct villager *villager =
    init_villager(name, 50, gun, 1);

// Init demon
char demon_name[50] = "Cyberdemon";
struct demon *demon = init_demon(NIGHTMARE, demon_name, 40, 5, FAR);

// Draining attack
demon->cur_HP -= 10;
draining_attack(demon, villager);
pp_villager(villager);
pp_demon(demon);
destroy_demon(demon);
destroy_villager(villager);

Output
SLURRRRP! Cyberdemon attacks you to deal 7 damage and regenerates 3 HP.
============================= Villager info =============================
Name: Isabelle
HP: 43/50
Gun: BFG | Magazine: 16/16 | Mastery: 1
Medicine: 10/10
Distance: Near
============================= Demon info =============================
Name: Cyberdemon | Category: Nightmare
Base damage: 15 | Range: Far
HP: 113/120

A risky but powerful attack.

Deals double damage to the target, but the demon takes some recoil damage (10% of its own max HP).
Display "BAM! {demon name} attacks you with all his strength to deal {nb of dommage} damage.\n"

If the target or demon pointer are NULL, the function does nothing.

Prototype(s)
void heavy_attack(struct demon *demon, struct villager *target);

Code example(s)
// Init Villager
char name[50] = "Isabelle";
char gun_name[50] = "BFG";

struct gun *gun = init_gun(gun_name, 50, FAR, 16);

struct villager *villager =
    init_villager(name, 50, gun, 1);

// Init demon
char demon_name[50] = "Cyberdemon";
struct demon *demon = init_demon(NIGHTMARE, demon_name, 40, 5, FAR);

// Heavy attack
heavy_attack(demon, villager);
pp_villager(villager);
pp_demon(demon);
destroy_demon(demon);
destroy_villager(villager);

Output
BAM! Cyberdemon attacks you with all his strength to deal 30 damage.
============================= Villager info =============================
Name: Isabelle
HP: 20/50
Gun: BFG | Magazine: 16/16 | Mastery : 1
Medicine: 10/10
Distance: Near
============================= Demon info =============================
Name: Cyberdemon | Category: Nightmare
Base damage: 15 | Range: Far
HP: 108/120

This function determines how the demon attacks.
  • If distance > range => chase
  • Else if demon.HP > 75% => heavy attack
  • Else if demon.HP < 25% => draining attack
  • Else basic attack

If the villager or demon pointer are NULL, the function does nothing.

Prototype(s)
void demon_action(struct demon *demon, struct villager *villager);

Code example(s)
// Init Villager
char name[50] = "Isabelle";
char gun_name[50] = "BFG";
struct gun *gun = init_gun(gun_name, 50, FAR, 16);
struct villager *villager =
    init_villager(name, 500, gun, 1);

// Init demon
char demon_name[50] = "Cyberdemon";
struct demon *demon = init_demon(NIGHTMARE, demon_name, 40, 5, CLOSE);

// Demon action
demon_action(demon, villager); // Distance = Near but its range is close
demon_action(demon, villager); // In range and high HP
demon->cur_HP = demon->HP_max / 2;
demon_action(demon, villager); // In range and mid HP
demon->cur_HP = 1;
demon_action(demon, villager); // In range and low HP

destroy_villager(villager);
destroy_demon(demon);

Output
The demon Cyberdemon is chasing you.
BAM! Cyberdemon attacks you with all his strength to deal 30 damage.
RAAAAAGH! Cyberdemon attack you to deal 15 damage.
SLURRRRP! Cyberdemon attack you to deal 7 damage and regenerates 3 HP.

Now the real fight can start, you managed to create weapons to protect the island, but now it's time to use them !
To control your character, you'll have to type specific commands :
  • s to shoot with you gun.
  • r to reload your gun.
  • h to heal yourself.
  • p to prepare medicine.
  • w+ to walk towards the demon.
  • w- to walk away from the demon.
Any other action won't work.
Copy this in a file main.c and run your program.

Warning(s)
Be careful, when compiling make sure that you don't have any other main function, otherwise you won't be able to compile

Hint(s)
For more fun, feel free to modify the main function however you like.

Code example(s)
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "demon.h"
#include "villager.h"
#include "weapons.h"

#define PURPLE(string) "\x1b[35m" string "\x1b[0m"

#define MAX_BUFFER 1024

void villager_action(struct villager *villager, struct demon *demon, char *command)
{
    if (!strcmp(command, "s"))
    {
        shoot(villager, demon);
    }
    else if (!strcmp(command, "r"))
    {
        printf("You reload your gun");
        reload(villager->gun);
    }
    else if (!strcmp(command, "h"))
    {
        heal(villager);
    }
    else if (!strcmp(command, "w+"))
    {
        walk(villager, 1);
    }
    else if (!strcmp(command, "w-"))
    {
        walk(villager, -1);
    }
    else if (!strcmp(command, "p"))
    {
        prepare_medicine(villager);
    }
    else
        printf(PURPLE("No such action\n"));
}

static void end_fight(struct villager *villager, struct demon *demon)
{
    if (villager->cur_HP <= 0 || demon->cur_HP <= 0)
    {
        pp_villager(villager);
        pp_demon(demon);
        destroy_villager(villager);
        destroy_demon(demon);
        exit(0);
    }
}

int main()
{
    printf("\033[2J\033[1;1H");
    // Init gun and villager
    char name[50] = "Isabelle";
    char gun_name[50] = "BFG";
    struct gun *gun = init_gun(gun_name, 50, FAR, 16);
    struct villager *villager =
        init_villager(name, 50, gun, 1);

    // Init demon
    char demon_name[50] = "Cyberdemon";
    struct demon *demon = init_demon(NIGHTMARE, demon_name, 40, 5, CLOSE);
    pp_villager(villager);
    pp_demon(demon);

    char input[MAX_BUFFER];

    while (fgets(input, MAX_BUFFER, stdin) != NULL)
    {
        printf("\033[2J\033[1;1H");
        int i = 0;
        while (input[i] != '\0')
        {
            if (input[i] == '\n')
            {
                input[i] = '\0';
                break;
            }
            i++;
        }

        printf("\x1b[34m");
        villager_action(villager, demon, input);
        printf("\x1b[0m");

        end_fight(villager, demon);

        printf("\x1b[31m");
        demon_action(demon, villager);
        printf("\x1b[0m");

        end_fight(villager, demon);

        pp_villager(villager);
        pp_demon(demon);

        printf("> ");
    }

    destroy_demon(demon);
    destroy_villager(villager);
    return 0;
}


In this part, we introduce unions. Since this concept wasn't covered in class, we will explore it here.

A union is similar to a struct, but with a key difference: all its members share the same memory space. This means only one member can be stored (or "active") at a time.

Unions are primarily used for memory optimization or when you need a variable that can hold different data types at different times (a concept known as polymorphism in C).

They are often paired with enums (a pattern called a Tagged Union). The enum acts as a "tag" to indicate which member of the union is currently set. Using this tag, you can safely determine which attribute to access.



animals/animals.h


Here are the structs, union and enums that we'll use, just copy them, we'll explain everything you need to know later on:

Code example(s)
enum animal_type
{
    INSECT,
    FISH,
};

Code example(s)
struct fish
{
    char* species;
    int swimming_speed;
    size_t size;
    int lives_in_fresh_water;
};

Code example(s)
struct insect
{
    char* species;
    size_t size;
    int can_fly;
    int can_swim;
};

Code example(s)
union animals
{
    struct insect* insect;
    struct fish* fish;   
};

Code example(s)
struct animal
{
    enum animal_type type;
    union animals animal;
    char* color; 
};

Prototype(s)
struct fish* fish_init(const char* species, int speed, size_t size, int fresh_water);
struct animal* animal_from_fish(const char* color, struct fish* fish);
void free_fish(struct fish* fish);
struct insect *insect_init(const char *species, size_t size, int can_fly, int can_swim);
struct animal* animal_from_insect(const char* color, struct insect* insect);
void free_insect(struct insect* insect);
void free_animal(struct animal* animal);

The struct animal uses a Union to store data. Unlike a struct where every field exists in memory at once, a union uses a single shared memory space for all its fields.
This means struct animal can hold a struct fish* OR a struct insect*, but never both at the same time.

To handle this safely, we use a Tagged Union pattern:
  1. The Tag (Enum): You must set the type attribute of the animal to the correct enum value (e.g., FISH). This tells the program "The data currently stored in the union is a fish".
  2. The Union (Data): You access the union field (e.g., value or data) and assign your pointer to the specific member: animal->data.fish = fish;.
For example, you can access the fish in the union with my_animal->animal.fish.



animals/fish.c


In this file, you will manage the creation of specific animals (Fish) and their encapsulation into the generic struct animal using unions.

This function allocates and initializes a struct fish with the given argument.
You must copy the species string.

Hint(s)
You can use the function strdup(3) to malloc and copy strings.

Prototype(s)
struct fish *fish_init(const char *species, int speed, size_t size, int fresh_water);

Code example(s)
struct fish *f = fish_init("Salmon", 10, 5, 1);
printf("Fish species: %s\n", f->species);
printf("Swimming speed: %d\n", f->swimming_speed);
printf("Size: %zu\n", f->size);
printf("Lives in fresh water: %d\n", f->lives_in_fresh_water);

Example Output
Fish species: Salmon
Swimming speed: 10
Size: 5
Lives in fresh water: 1

This function creates a generic struct animal from a specific struct fish.
You must copy the color string.
If one of the arguments is NULL, do nothing and return NULL.

Prototype(s)
struct animal *animal_from_fish(const char *color, struct fish *fish);

Code example(s)
struct fish *f = fish_init("Trout", 8, 4, 1);
struct animal *a = animal_from_fish("Blue", f);
printf("Animal type: %d\n", a->type);
printf("Animal color: %s\n", a->color);
printf("Fish species from animal: %s\n", a->animal.fish->species);

Example Output
Animal type: 1
Animal color: Blue
Fish species from animal: Trout

Frees the memory allocated for the struct fish.
If the fish pointer is NULL, the function does nothing.

Prototype(s)
void free_fish(struct fish *fish);

Code example(s)
struct fish *f = fish_init("Carp", 6, 3, 0);
free_fish(f);

Example Output
(No output, just ensure no memory leaks occur with -fsanitize=address)



animals/insect.c


In this file, you will manage the creation of insects and their encapsulation into the generic struct animal.

Allocates and initializes a struct insect.
You must copy the species string.

Prototype(s)
struct insect *insect_init(const char *species, size_t size, int can_fly, int can_swim);

Code example(s)
struct insect *i = insect_init("Dragonfly", 2, 1, 0);
printf("Insect species: %s\n", i->species);
printf("Size: %zu\n", i->size);
printf("Can fly: %d\n", i->can_fly);
printf("Can swim: %d\n", i->can_swim);

Example Output
Insect species: Dragonfly
Size: 2
Can fly: 1
Can swim: 0

Creates a generic struct animal from a specific struct insect.
You must copy the color string.
If one of the arguments is NULL, do nothing and return NULL.

Prototype(s)
struct animal *animal_from_insect(const char *color, struct insect *insect);

Code example(s)
struct insect *i = insect_init("Butterfly", 1, 1, 0);
struct animal *a = animal_from_insect("Yellow", i);
printf("Animal type: %d\n", a->type);
printf("Animal color: %s\n", a->color);
printf("Insect species from animal: %s\n", a->animal.insect->species);

Example Output
Animal type: 0
Animal color: Yellow
Insect species from animal: Butterfly

Frees the memory allocated for the struct insect.
If the insect pointer is NULL, the function does nothing.

Prototype(s)
void free_insect(struct insect *insect);

Code example(s)
struct insect *i = insect_init("Beetle", 3, 0, 1);
free_insect(i);

Example Output
(No output, just ensure no memory leaks occur with -fsanitize=address)



animals/vector.h


Lore
You are catching a lot of fish and insects, but your pockets have a limit.
You need a storage system that can grow as your collection expands.
Since standard arrays in C have a fixed size, we need something smarter: A Vector.

In this header, you will define the struct vector and the interface to manipulate it.

Define the structure that manages your dynamic list of animals.

Code example(s)
struct vector
{
    struct animal **animal;
    size_t size;
    size_t capacity;
};

Prototype(s)
struct vector *vector_init(size_t n);
struct vector *vector_resize(struct vector *v, size_t new_capacity);
struct vector *vector_append(struct vector *v, struct animal *animal);
void vector_destroy(struct vector *v);

What is a Vector?

In C, standard arrays have a fixed size defined at compilation or creation. If you define int tab[10];, you cannot store an 11th element without causing a buffer overflow.

A Vector (or Dynamic Array) solves this by managing memory dynamically. It behaves like a normal array but can grow automatically. It relies on two key properties:
  • Size: The number of elements actually stored and used in the array (what the user sees).
  • Capacity: The total amount of memory currently allocated (the hidden "buffer").

Calling realloc is computationally expensive (it may need to copy the whole array to a new memory location).
To avoid doing this every time we add an item, we allocate extra space. When the size reaches the capacity (the vector is full), we don't just add +1 slot. Instead, we typically double the capacity. This minimizes the number of times we need to ask the system for more memory.



animals/vector.c


In this file, you will implement the logic for the dynamic array (Vector) that will hold your collection of animals.

Initializes an empty vector with an initial capacity of n.
  • Allocate memory for the struct vector.
  • Allocate memory for the internal array of pointers.
  • Set size to 0.
  • Set capacity to n.

Prototype(s)
struct vector *vector_init(size_t n);

Code example(s)
struct vector *v = vector_init(5);
printf("Initial size: %zu\n", v->size);
printf("Initial capacity: %zu\n", v->capacity);

Example Output
Initial size: 0
Initial capacity: 5

Changes the capacity of the vector to new_capacity.
  • Use realloc to resize the internal animal array to the new capacity.
  • Update the capacity attribute of the structure.
Returns the pointer to the vector.
If the vector pointer is NULL, the function does nothing and return NULL.

Prototype(s)
struct vector *vector_resize(struct vector *v, size_t new_capacity);

Code example(s)
struct vector *v = vector_init(2);
v = vector_resize(v, 4);
printf("Resized capacity: %zu\n", v->capacity);

Example Output
Resized capacity: 4

Adds the given animal to the end of the list and updates the size.

Growth Logic:
  • Check if the vector is full.
  • If it is full, call vector_resize to double the current capacity.
  • Add the animal pointer at the correct index.
  • Increment the size.

Prototype(s)
struct vector *vector_append(struct vector *v, struct animal *animal);

Code example(s)
struct vector *v = vector_init(2);

struct fish *f1 = fish_init("Salmon", 10, 5, 1);
struct animal *a1 = animal_from_fish("Blue", f1);
v = vector_append(v, a1);

struct fish *f2 = fish_init("Trout", 8, 4, 1);
struct animal *a2 = animal_from_fish("Green", f2);
v = vector_append(v, a2);

struct fish *f3 = fish_init("Carp", 6, 3, 0);
struct animal *a3 = animal_from_fish("Red", f3);
v = vector_append(v, a3);

printf("Vector size after appends: %zu\n", v->size);
printf("Vector capacity after appends: %zu\n", v->capacity);

Example Output
Vector size after appends: 3
Vector capacity after appends: 4

Frees the memory allocated for a generic struct animal.

Since the animal uses a Union, you cannot simply free the content. You must check its type attribute (the Tag) to know which specific free function to call:
  • If the type is FISH: Call free_fish on the specific union member.
  • If the type is INSECT: Call free_insect on the specific union member.
Once the specific content is freed, do not forget to free the color string, and finally the animal structure itself.

Prototype(s)
void free_animal(struct animal *animal);

Code example(s)
struct fish *f = fish_init("Salmon", 10, 5, 1);
struct animal *a = animal_from_fish("Blue", f);
free_animal(a);

Example Output
(No output, just ensure no memory leaks occur with -fsanitize=address)

Frees the memory allocated for the vector container.
  • First, free the internal array of pointers.
  • Then, free the vector structure itself.

Prototype(s)
void vector_destroy(struct vector *v);

Code example(s)
struct vector *v = vector_init(5);

struct fish *f = fish_init("Trout", 8, 4, 1);
struct animal *a = animal_from_fish("Green", f);
v = vector_append(v, a);

vector_destroy(v);

Example Output
(No output, just ensure no memory leaks occur with -fsanitize=address)


Lore
The battle is over (for now). The demons have been pushed back, but they left a trail of destruction in their wake.
You find Blathers in a state of total panic: the museum has been ransacked!

Millions of years of history are scattered on the floor. Skulls, tails, and wings are mixed up in a giant pile of ancient bones.
To everyone's surprise, the Doom Slayer starts picking up a T-Rex skull with gentle care. It seems he has a soft spot for big, dead predators.

It's time to help them reassemble the collection before Blathers faints!

In this final part, you will manipulate arrays of structures and implement algorithms to sort and reassemble matching pieces together.



museum_restoration/fossils.h


Lore
After the attack of the demons, Blathers' museum got all messed up.
But since it's the best way to discover everything about the island, you and the Doom Slayer offered to help.

In this header, you will define the structures and enums necessary to categorize and reconstruct the fossils scattered around the museum.

Here is the enum that represents the different body parts of a fossil.

Code example(s)
enum body_part
{
    SKULL,
    TORSO,
    TAIL,
    WINGS,
    LEGS,
    COMPLETE,
};

Create the structure struct fossil with the following attributes:
  • species: char* representing the name of the species.
  • body_part: enum body_part indicating which part this is (or COMPLETE).
  • age: size_t representing the fossil's age in thousands of years (e.g., 10 = 10,000 years).
  • has_wings: char acting as a boolean (1 if the species should have wings, 0 otherwise).

Prototype(s)
struct fossil *init_fossil(char *species, enum body_part body_part, size_t age, char has_wings);
struct fossil *assemble_wingless(struct fossil *fossils[4]);
struct fossil *assemble_with_wings(struct fossil *fossils[5]);
struct fossil **assemble_all_fossils(struct fossil **fossils, size_t nb_fossils);



museum_restoration/fossils.c


In this file, you will implement the logic to initialize fossils and the algorithms to reassemble them into complete skeletons.

Allocates and returns a pointer to a struct fossil initialized with the given attributes.
You must dynamically allocate memory for the species string and copy the provided string into it.

Prototype(s)
struct fossil *init_fossil(char *species, enum body_part body_part, size_t age, char has_wings);

Tries to assemble a fossil that does not have wings.

Input: An array of 4 pointers to struct fossil.

Logic:
  1. Check that all 4 fossils have has_wings == 0. If not, return NULL.
  2. Check that they all share the same species and age.
  3. Check that you have exactly one of each required part: SKULL, TORSO, TAIL, LEGS.
Output:
  • If successful: Return a new COMPLETE fossil (same age/species). You MUST free the 4 input parts and set their pointers to NULL in the array.
  • If invalid: Return NULL (do not free anything).

Prototype(s)
struct fossil *assemble_wingless(struct fossil *fossils[4]);

Tries to assemble a fossil that has wings.

Input: An array of 5 pointers to struct fossil.

Logic:
  1. Check that all 5 fossils have has_wings != 0. If not, return NULL.
  2. Check that they all share the same species and age.
  3. Check that you have exactly one of each required part: SKULL, TORSO, TAIL, LEGS, WING.
Output:
  • If successful: Return a new COMPLETE fossil. You MUST free the 5 input parts and set their pointers to NULL.
  • If invalid: Return NULL.

Prototype(s)
struct fossil *assemble_with_wings(struct fossil *fossils[5]);

Takes a large list of scattered fossils and assembles as many as possible.

Input: A pointer to an array of fossil pointers (fossils) and its size (nb_fossils).

Behavior:
  • Iterate through the list to find matching groups (same species/age/wing_status).
  • If a valid group is found (4 parts for wingless, 5 for winged), call the appropriate assemble function.
  • Store the newly created COMPLETE fossils in a new array.
Return: A dynamically allocated NULL-terminated array containing pointers to all the completed fossils.

Warning(s)
If a fossil is already complete, it should also be added the list

Prototype(s)
struct fossil **assemble_all_fossils(struct fossil **fossils, size_t nb_fossils);



This page and all subpages are for internal use at EPITA only.
The use of this document must abide by the following rules:
Copyright © 2026-2027 - EPITA