Skip to content

hbiegacz/mower_simulator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

264 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Mower Simulator | Real-time physics & logic simulator ๐Ÿ•น๏ธ๐Ÿšœ๐Ÿƒ

Contributors Commit Activity GitHub commits Repo Size Lines of Code Test Coverage License: MIT

Mow Your Way to Coding Mastery

This simulator is a tool designed to help beginners and students learn programming and logical thinking in a fun, visual way. By controlling a virtual robotic lawn mower, users can experiment with algorithms, spatial reasoning, and real-time system control.

How it works

Take command of the mower's movements using the controller object. The application provides a real-time environment where you can observe how your code directly affects the world around you, simulating position, orientation, and mowing paths as you go.

๐Ÿ“บ Example of a simulation

Drawing an 8
Drawing an 8
Drawing a Star
Drawing a star
Drawing a Sine Wave
Drawing a sine wave
Drawing a Spiral
Drawing a spiral

๐Ÿ› ๏ธ Architecture

C++ CMake Qt Google Test Git GitHub

The system follows the Single Responsibility Principle (SRP) by decoupling physics, control logic, and presentation layers. This ensures that changes in rendering don't break the movement laws.

Decoupled Control (Command Pattern)

  • MowerController: Provides a clean API for the user to queue actions.
  • Engine: A central coordinator that executes the queue step-by-step, keeping the user logic isolated from the internal simulation loop.

Physics vs. Presentation

Workflow: StateSimulation โ†’ SimulationSnapshot โ†’ StateInterpolator โ†’ Visualizer
To maintain a "Single Source of Truth," physics is calculated independently of frame rates, and only the StateSimulation class can actually change the world state.

  • StateSimulation: The core engine responsible for the physics and logic rules of the simulation.
  • SimulationSnapshot: Since the simulation runs on its own thread, we use this object to store the state at a given moment. This allows us to interpolate and visualize independently without blocking the simulation thread.
  • StateInterpolator: Smoothens movement by interpolating between snapshots, making sure the mower doesn't "teleport" or jump between frame updates.
  • Visualizer (Qt): Only handles drawing. It doesn't know why the mower moved, only where to render it.

Isolated Logging

Workflow: Logger โ†’ FileLogger
Generating diagnostic data is separated from saving it to files.

  • Logger: Collects events without knowing how they will be stored.
  • FileLogger: Handles the specific task of physical disk I/O, allowing the system to switch to other storage methods without touching simulation code.

Thorough Testing

The project maintains high code quality with 80.7% line coverage across the core logic, verified using GCOVR. Comprehensive unit tests are implemented with Google Test.

All 13 test modules can be found in the CODE/tests/ directory.

โ–ถ๏ธ Running the Simulation

Dependencies and necesary tools

  • Libraries: Google Test, Qt5, pthread
  • Tools: CMake, Make

For a quick setup, please verify the contents of setup_extra_dependencies.sh and run it.

In order to start the mower simulator, run:

./setup_extra_libraries.sh
mkdir build/
cd build/
cmake ..
make 
./mower_simulator

After that you can run tests with the following command:

ctest

โœ๏ธ Customizing mower movements

The mower movements can be customized by modifying the Main.cc file. There is a special method called 'customUserLogic' in which the user can define the mower's movements using the 'controller' object.

Aviable commands:

  • move(double cm)

  • rotate(short deg)

  • setMowing(bool enable)

  • addPoint(double x, double y)

  • deletePoint(unsigned int id)

  • moveToPoint(unsigned int point_id)

  • rotateTowardsPoint(unsigned int point_id)

  • getDistanceToPoint(unsigned int point_id, double& out_distance)

  • getCurrentAngle(unsigned short& out_angle)

  • getCurrentPosition(double& out_x, double& out_y)

Note: since the commands are queued, the results received from the out_parameters will not be updated until the command is executed.

Users are also able to customize other simulation parameters, such as the mower's speed and dimensions, as well as the lawn's dimensions. Another thing that can be customized is the overall simulation speed.

๐Ÿ’ก Examples of interesting user's programs

DRAWING A STAR

void customUserLogic(MowerController& controller) {
    
    double cx = LAWN_WIDTH_CM * 0.5;
    double cy = LAWN_LENGTH_CM * 0.5;
    double radius = LAWN_LENGTH_CM * 0.3;
    
    for (int i = 0; i < 5; i++) {
        double angle = (90 - i * 72) * M_PI / 180.0;
        double x = cx + radius * cos(angle);
        double y = cy + radius * sin(angle);
        controller.addPoint(x, y); 
    }
    
    controller.setMowing(false);
    controller.moveToPoint(0);
    controller.setMowing(true);
    
    int drawing_order[] = {2, 4, 1, 3, 0};
    
    for (int i = 0; i < 5; i++) {
        controller.rotateTowardsPoint(drawing_order[i]);
        controller.moveToPoint(drawing_order[i]);
    }
    
    controller.setMowing(false);
    
    for (int i = 4; i >= 0; i--) {
        controller.deletePoint(i);
    }
}

DRAWING AN 8

void customUserLogic(MowerController& controller) {
    
    static double distance_p1_p0 = 0.0;
    static double distance_p1_p2 = 0.0;  
    controller.addPoint(LAWN_WIDTH_CM * 0.5, LAWN_LENGTH_CM * 0.29); // Point 0
    controller.addPoint(LAWN_WIDTH_CM * 0.5, LAWN_LENGTH_CM * 0.55); // Point 1 --- centre of the 8
    controller.addPoint(LAWN_WIDTH_CM * 0.5, LAWN_LENGTH_CM * 0.75); // Point 2

    controller.setMowing(false);
    controller.moveToPoint(1);

    controller.getDistanceToPoint(0, distance_p1_p0);
    controller.getDistanceToPoint(2, distance_p1_p2);



    // DRAWING FIRST CIRCLE
    controller.rotateTowardsPoint(0);
    controller.rotate(90);
    controller.setMowing(true);

    for (double angle = 0; angle < 360; angle += 1) {
        controller.move(&distance_p1_p0, (M_PI / 180.0));
        controller.rotate(-1);
    }

    // DRAWING SECOND CIRCLE
    controller.setMowing(false);
    controller.moveToPoint(1);
    controller.rotateTowardsPoint(2);
    controller.rotate(-90);
    controller.setMowing(true);
    for (double angle = 0; angle < 360; angle += 1) {
        controller.rotate(1);
        controller.move(&distance_p1_p2, (M_PI / 180.0));
    }

    controller.deletePoint(0);
    controller.deletePoint(1);
    controller.deletePoint(2);
}

DRAWING A SINE WAVE

void customUserLogic(MowerController& controller) {
    const int numPoints = 80;  
    double amplitude = LAWN_LENGTH_CM * 0.2;  
    double wavelength = LAWN_WIDTH_CM * 0.8;  
    double centerY = LAWN_LENGTH_CM * 0.5;    
    double startX = LAWN_WIDTH_CM * 0.1;      
    
    for (int i = 0; i < numPoints; i++) {
        double t = static_cast<double>(i) / (numPoints - 1);  
        double x = startX + t * wavelength;
        double y = centerY + amplitude * sin(2 * M_PI * t * 2);  
        
        controller.addPoint(x, y); 
    }
    
    controller.setMowing(false);
    static double distance = 0.0;
    controller.moveToPoint(0);
    controller.setMowing(true);
    
    for (int i = 1; i < numPoints; i++) {
        controller.moveToPoint(i);
    }
    
    for (int i = numPoints - 1; i >= 0; i--) {
        controller.deletePoint(i);
    }
}

Thanks for reading this far! ๐Ÿ“–

About

An interactive C++ simulator for learning algorithms and programming logic by controlling a virtual lawn mower

Topics

Resources

License

Stars

Watchers

Forks

Contributors