Project: Step 1
"Utility classe"


Disclaimer: the automatically generated English translation is provided only for convenience and it may contain wording flaws. The original French document must be taken as reference!


Goal: Implementation of a base class representing circular bodies that can move in a torus world.

Necessary Concepts: object classes, constructors/destructors, operator overloading.

These concepts are explained in courses 15 to 18. These exercises in series 19 contain material close to what you need to program.

Useful files: partie1.zip


Installation

The instructions below assume that you have created a specific folder for the project (we will assume here that it is a cpp/project directory). Adapt the commands to the location of your project.

Copy the provided archive partie1.zip into your cpp/project directory and unzip it:

In QtCreator, then configure the project by following precisely the instructions given here (this involves opening the CMakeLists.txt file from the src/ directory and following the instructions). The only difference is that you will use partie1.zip instead of sfml_with_cmake.zip as the archive name and partie1/ instead of sfmlCmake/ as the folder name. Once the project is configured, you should be able to compile and execute it. The execution should produce the following output in the "Application output" window:
	===================================================================
	All tests passed (60 assertions in 9 test cases)
      
The meaning of this execution will be explained below.

The provided archive contains:

To increase the modularity of your project, method prototypes and attribute declarations should be in a file with the .hpp extension, while their definitions should be in a file with the .cpp extension.

Regarding compilation, some useful indications:
  1. How to avoid multiple inclusions:

    The classes you will program in the project will combine in a relatively complex way, and you will sometimes need to include multiple existing .hpp files at the beginning of a new .hpp file. If files are redundantly included in these different .hpp files, the compiler will produce an error message. To avoid this situation, it is necessary to add the directive at the beginning of each myfile.hpp file:
    #pragma once
    
  2. Naming convention for files:

    Files will have the same names as the classes they contain: for example, the files Lizard.[hpp][cpp] will contain the code for the Lizard class. This is, of course, only a convention.
    Some operating systems like Windows are case-insensitive (this is also the case for EPFL VMs): Vec2d.hpp will therefore be interpreted the same as vec2d.hpp (starting with a lowercase "v"). Including the file vec2d.hpp will be considered correct by the compiler even if your file is named Vec2d.hpp. This is not the case in a classic Unix environment. Be careful with consistency as your project must run everywhere.
  3. Compilation in a terminal (Linux, MacOS):
    The provided material integrates with QtCreator, where you can compile and execute. On Linux and MacOS, you can also use the command line as indicated here.

Modules to Program

This part of the project consists of only one module (component).

The goal here is to apply your initial knowledge of object-oriented programming by implementing a utility class representing circular bodies that can move in a two-dimensional toric space.

The idea is that the program's entities (scorpions, lizards, lizard food, etc.) will evolve in a two-dimensional space.

These entities will "meet" each other, and it will be necessary to test this fact simply: for example, if a scorpion meets a lizard, it will be able to feed on it. It is therefore important to be able to test when a meeting occurs.

Testing the collision/meeting of two objects can be very complex depending on their actual shapes. In this project, we will use a simplification by approximating the shape of each entity with a circular body. Collision tests will then be performed simply based on these bodies, as illustrated in the image below:

item ← The collision test between a lizard and a scorpion is simplified by approximating each of these entities with a circular body (visible in light green).

Additionally, the two-dimensional space in which the program's entities evolve should be interpreted as a toric environment: meaning that an entity exceeding the environment's boundaries should reappear on the opposite side (as shown in the short video below):

[Video: movement in a toric world]

The Collider class that you are required to program will represent the circular bodies between which collision tests will be possible, and which can move in a toric environment.

An object of type Collider is characterized by:

General Methods

Your Collider class should include the following members:

[Question Q1.2] If the default definitions of copy constructors and the (re)copy operator are sufficient for our needs, why is it still preferable to explicitly code them? Think about it and answer this question in your REPONSES file.

Handling Movement in a Toric World

Next, add to your Collider class the methods that allow it to be modeled as an object capable of moving in a toric world:

[Question Q1.4] Which method arguments should be passed by constant reference? Answer in your REPONSES file.

[Question Q1.5] Which methods among those you were asked to implement above would be appropriate to declare as const? Answer this question in your REPONSES file.

Collision Management

Finally, add the following methods to your Collider class to enable it to handle collisions with other objects of the same type:

[Question Q1.6] How can the three previously described operators be implemented in Collider.cpp while avoiding code duplication? Think about it and answer this question in your REPONSES file.

  • The << operator for displaying information in the terminal. This operator will output the position and radius of the Collider in the following format:
  • Collider: position = <position> radius = <radius>
    

    where <position> represents the position of the Collider and <radius> represents its radius.

    [Question Q1.7] Should these operators be overloaded as member functions or external functions? Justify your answer in REPONSES.

    [Question Q1.8] Which method arguments, among those you were asked to code above, do you think should be passed by constant reference? Answer this question in your REPONSES file.

    [Question Q1.9] Which methods, among those you were asked to code above, do you think should be declared as const? Answer this question in your REPONSES file.

    Test Programs

    To test this part of the project, you have:

    1. The colliderTest target defined in the partie1/src/CMakeLists.txt file, which references multiple files.

    2. This target allows you to run the test program src/Tests/UnitTests/ColliderTest.cpp. This program calls the various functionalities you have implemented in the Collider class to verify their correctness.

    Take a quick look at the CMakeLists.txt file to see how this target is defined:

    #       add_executable (colliderTest  ${TEST_DIR}/UnitTests/ColliderTest.cpp ${SOURCES} ${CACHE_SOURCES})
    #	target_link_libraries(...)
    

    To understand how these tests work, open and examine the src/tests/UnitTests/ColliderTest.cpp file.

    This test program uses a specific framework called Catch, which allows for what is known in programming as unit testing. This involves writing test scenarios to validate different coded functionalities.

    Below, we explain how these tests function.

    Principle of Non-Graphical Unit Tests

    The provided test program in ColliderTest.cpp defines test scenarios ("test cases") to verify the functionalities of the Collider class by calling the methods you were asked to implement.

    Each scenario contains a number of messages (labeled with the keywords GIVEN, THEN, or AND_THEN) that describe the conditions of the test execution and their results. These messages will only be displayed if a test fails.

    If all tests in a scenario pass successfully, you will see a message like this:

    ===============================================================================
    All tests passed 
    

    The tests verify a number of assertions using functions such as CHECK, CHECK_FALSE, and CHECK_APPROX_EQUAL.

    The provided test includes only two scenarios (from line 24 to line 159 and from line 161 to the end), but in general, there could be more. Let's examine part of the first scenario.

    Lines 28 and 29 create two identical Collider objects at position {1,1} with a radius of 2. These two objects are expected to collide, as indicated by the THEN statement on line 31. To verify that your code functions correctly in this case, the test checks four assertions:

    1. That the first object is colliding with the second (according to the return values of your isColliding method and your operator|);
    2. That the second object is colliding with the first (under the same conditions).

    As you might have guessed, a CHECK succeeds if the assertion it checks returns true. Similarly, a CHECK_FALSE (for example, on line 57) succeeds if the condition it evaluates returns false.

    Suppose your isColliding method is incorrectly implemented. Running the test program ColliderTest.cpp will display:

    -------------------------------------------------------------------------------
    Scenario: Collision/IsColliderInside with Collide
         Given: Two identical bodies
          Then: they collide
    -------------------------------------------------------------------------------
    ...
    src/Tests/UnitTests/ColliderTest.cpp:35: FAILED:
    CHECK( o1.isColliding(o2) )
    with expansion:
      false                    
    ...............................................................................
    

    The test output displays the scenario name, the messages associated with GIVEN and THEN to indicate the test conditions (two identical Collider objects) and what should be true in this case ("...they collide"). It then lists the failed assertions and their corresponding lines. The with expansion section shows that the test expected an assertion to be true, but it actually returned false.

    Once you fix the program, running the colliderTest target should produce the following final output:

    using ./../res/app.json for configuration.
    
    ===============================================================================
    All tests passed (109 assertions in 2 test cases)
    

    Note: In QtCreator, this output is visible in the “Application output” window.

    This output indicates that 109 assertions (CHECK, CHECK_FALSE, etc.) were successfully executed across two test scenarios ("2 test cases").

    Note that if the tested functionalities are missing from your code, running the test will result in compilation errors.

    For example, if your constructor is not implemented correctly (e.g., missing or incorrectly ordered arguments), you will get an error message like:

    error: no matching function for call to 'Collider::Collider(const Vec2d&, double)'
    

    If you want to test the methods as you implement them, which is recommended, you can simply comment out the CHECK or CHECK_FALSE statements that call methods you haven't implemented yet.

    Test 1: Collider Functionalities

    The tests are globally numbered throughout the project. This is the first test in the project. Each test is graded.

    Running the colliderTest target should produce the following final output:

    Collider: position = (1, 1), radius = 2
    ===============================================================================
    All tests passed (109 assertions in 2 test cases)
    

    Return to the main document