diff --git a/README.md b/README.md index a53d95c..c2d7e98 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,52 @@ -# Random Walk Simulation - -This is a **group exercise**, so you should be working in pairs of two students. It's **30% of your final grade**. - -The Goal is to **practise writing readable, maintainable and reliable code collaboratively.** - -## Group Exercise - -1. One student of your group forks the code from [https://github.com/advanced-geoscripting-2021/random_walker.git](https://github.com/advanced-geoscripting-2021/random_walker.git) - -2. This student invites the other student as a collaborator to the forked repository. Now you can both work on the code. - -3. Adapt the code to fulfil the requirements (see below). - -4. Code review: Each group reviews the code of another group. - -5. Improve your code based on the review you got. - - -## Write an extended random walk program - -In this repo you find a basic implementation of a [random walk simulation](https://en.wikipedia.org/wiki/Random_walk) in 2-dimensional space taken from [this blogpost](https://www.geeksforgeeks.org/random-walk-implementation-python/). Running the code yields an image which shows the path of the random walk. - -![random_walk](rand_walk_100000.png) - -The program works but it is not very readable. In addition, you should **extend the program based on the requirements listed below. - -**Remember to apply the best practices in scientific computing** to make the code more readable, maintainable, reusable and efficient. - -### Minimum requirements: - -Extend the program so the following requirements are met: - -1. The program should be able to simulate multiple random walkers. -2. The program should be executable from the command line. -3. The user should be able to specify the number of random walkers through a command line parameter. -4. Document the dependencies and instructions of how to run the program in your README.md. - -### Additional requirements: - -1. Create three different types of walkers, e.g. a "fast walker" which has a bigger step size. -2. Add a "landscape" in which the random walkers are walking in which contains obstacles which the walkers cannot cross (e.g. a lake) -3. Invent and implement another functionality of your own. - -Be creative here! :) - -## Code Review - -Review the code of another group: (tuesday afternoon or wednesday morning) - -1. Does it work properly? Try to make it fail! -2. Are the best-practices implemented in the code? -3. Is the documentation clear? -4. Can you adapt the code easily? E.g. try to create a new type of random walker which moves two cells per iteration. - +# A Random Walk Simulation: + +In this repository you can find a basic implementation of a random walk simulation in a +2-dimensional space. The scenario is always the same: Our walker is bored with being +inside, he rather goes for a stroll outside. So, the walker leaves the house and starts +walking, without any destination in mind… and refuses to go back inside! + +You can specify for how long the walker should walk and at what pace. You’ll get +a map of the route the walker took returned! But be aware: if you let the walker walk to +far, he might rather take a plane... + + +## How to use the Script +In order to use the script, you have to clone this repository to your drive and the +following libraries need to be installed your python environment: +### Dependencies +For running: +- Python 3.7.10 +- NumPy 1.20.3 +- Matplotlib 3.3.4 + +For testing if the script is executed correctly: +- pytest 6.2.4 + +### Usage +To use the script, you need to navigate to the cloned repository on your machine from +the command line. If you're in your directory and the correct python environment is +activated, you can run the script. Therefore, you state the command `python walker.py ` +followed by your settings. You have to specify the following settings: +```` +1. The walking time +2. The number of walker who are walking at usual speed +3. The number of walker who are walking fast +4. The number of walker who are running +5. The path the outfile should be saved at +```` + +However, note that all settings have to be defined, so if you want to have a single +walker, just define all the others as 0. Furthermore, a maximum of 12 walker overall are +allowed. + +As an example, you would need to state the following command to get the walking maps of +two walker at usual speed, one fast-walking walker and a running walker if you let them +walk over a time of 1000: + +`python walker.py 1000 2 1 1 ./TheRoutesOfMyWalker.png` + +The resulting image of the maps would look similar to the one below: + +![image](https://github.com/hn437/random_walk/blob/main/TheRoutesOfMyWalker.png) + +### And now: let them walk! diff --git a/TheRoutesOfMyWalker.png b/TheRoutesOfMyWalker.png new file mode 100644 index 0000000..d244c49 Binary files /dev/null and b/TheRoutesOfMyWalker.png differ diff --git a/main.py b/main.py deleted file mode 100644 index 9aac7f0..0000000 --- a/main.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -"""A Random Walk Simulation """ - -# Python code for 2D random walk. -# Source: https://www.geeksforgeeks.org/random-walk-implementation-python/ -import numpy -import matplotlib.pyplot as plt -import random - -# defining the number of steps -n = 100000 - -# creating two array for containing x and y coordinate -# of size equals to the number of size and filled up with 0's -x = numpy.zeros(n) -y = numpy.zeros(n) - -# filling the coordinates with random variables -for i in range(1, n): - val = random.randint(1, 4) - if val == 1: - x[i] = x[i - 1] + 1 - y[i] = y[i - 1] - elif val == 2: - x[i] = x[i - 1] - 1 - y[i] = y[i - 1] - elif val == 3: - x[i] = x[i - 1] - y[i] = y[i - 1] + 1 - else: - x[i] = x[i - 1] - y[i] = y[i - 1] - 1 - - -# plotting the walk -plt.title("Random Walk ($n = " + str(n) + "$ steps)") -plt.plot(x, y) -plt.savefig("./rand_walk_{}.png".format(n)) -plt.show() \ No newline at end of file diff --git a/rand_walk_100000.png b/rand_walk_100000.png index 5582dd8..62b4d57 100644 Binary files a/rand_walk_100000.png and b/rand_walk_100000.png differ diff --git a/tasks.md b/tasks.md new file mode 100644 index 0000000..a53d95c --- /dev/null +++ b/tasks.md @@ -0,0 +1,55 @@ +# Random Walk Simulation + +This is a **group exercise**, so you should be working in pairs of two students. It's **30% of your final grade**. + +The Goal is to **practise writing readable, maintainable and reliable code collaboratively.** + +## Group Exercise + +1. One student of your group forks the code from [https://github.com/advanced-geoscripting-2021/random_walker.git](https://github.com/advanced-geoscripting-2021/random_walker.git) + +2. This student invites the other student as a collaborator to the forked repository. Now you can both work on the code. + +3. Adapt the code to fulfil the requirements (see below). + +4. Code review: Each group reviews the code of another group. + +5. Improve your code based on the review you got. + + +## Write an extended random walk program + +In this repo you find a basic implementation of a [random walk simulation](https://en.wikipedia.org/wiki/Random_walk) in 2-dimensional space taken from [this blogpost](https://www.geeksforgeeks.org/random-walk-implementation-python/). Running the code yields an image which shows the path of the random walk. + +![random_walk](rand_walk_100000.png) + +The program works but it is not very readable. In addition, you should **extend the program based on the requirements listed below. + +**Remember to apply the best practices in scientific computing** to make the code more readable, maintainable, reusable and efficient. + +### Minimum requirements: + +Extend the program so the following requirements are met: + +1. The program should be able to simulate multiple random walkers. +2. The program should be executable from the command line. +3. The user should be able to specify the number of random walkers through a command line parameter. +4. Document the dependencies and instructions of how to run the program in your README.md. + +### Additional requirements: + +1. Create three different types of walkers, e.g. a "fast walker" which has a bigger step size. +2. Add a "landscape" in which the random walkers are walking in which contains obstacles which the walkers cannot cross (e.g. a lake) +3. Invent and implement another functionality of your own. + +Be creative here! :) + +## Code Review + +Review the code of another group: (tuesday afternoon or wednesday morning) + +1. Does it work properly? Try to make it fail! +2. Are the best-practices implemented in the code? +3. Is the documentation clear? +4. Can you adapt the code easily? E.g. try to create a new type of random walker which moves two cells per iteration. + diff --git a/test_walker.py b/test_walker.py new file mode 100644 index 0000000..ee5ef44 --- /dev/null +++ b/test_walker.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Contains tests for the walker class""" + +import os +import tempfile + +import numpy as np +import pytest +from matplotlib.image import imread + +import walker + + +def test_scene_creation_successful(): + """Tests whether a scene is created according to the user input. As the path is + calculated while creating the scene, it also checks that no error message is + thrown while calculating it, however, it does not test if the calculation + function calculates the way it is supposed to""" + # define three walker + walking_time = 10 + number_of_usual_walker = 1 + number_of_fast_walker = 1 + number_of_running_walker = 1 + # create scene + scene = walker.create_walker( + walking_time, + number_of_usual_walker, + number_of_fast_walker, + number_of_running_walker, + ) + # check if result has the expected length + assert len(scene) == 3 + + +def test_scene_creation_invalid_input_no_walker(): + """Tests whether the validation check of the user input works and stops the + calculation due to invalid inputs""" + with pytest.raises(AssertionError): # error zero walker + walker.create_walker( + walking_time=1, + number_of_usual_walker=0, + number_of_fast_walker=0, + number_of_running_walker=0, + ) + + +def test_scene_creation_invalid_input_negative_number_of_walker(): + """Tests whether the validation check of the user input works and stops the + calculation due to invalid inputs""" + with pytest.raises(AssertionError): # error negative number of walker + walker.create_walker( + walking_time=1, + number_of_usual_walker=1, + number_of_fast_walker=1, + number_of_running_walker=-1, + ) + with pytest.raises(AssertionError): # error negative number of walker + walker.create_walker( + walking_time=1, + number_of_usual_walker=1, + number_of_fast_walker=-1, + number_of_running_walker=1, + ) + with pytest.raises(AssertionError): # error negative number of walker + walker.create_walker( + walking_time=1, + number_of_usual_walker=-1, + number_of_fast_walker=1, + number_of_running_walker=1, + ) + + +def test_scene_creation_invalid_input_too_many_walker(): + """Tests whether the validation check of the user input works and stops the + calculation due to invalid inputs""" + with pytest.raises(AssertionError): # error more than 12 walker + walker.create_walker( + walking_time=1, + number_of_usual_walker=10, + number_of_fast_walker=2, + number_of_running_walker=2, + ) + + +def test_usual_walker_coordinate_length(): + """Tests whether the correct amount of coordinates is created for the usual + walker""" + walking_time = 2 + walking_speed = 1 + # walker does one steps in 1 time, and has two times. plus needs a starting + # coordinate + expected_len_coordinates = 1 + 1 + 1 + usual_walker = walker.Walker(walking_time, walking_speed) + assert len(usual_walker.x_coordinates) == expected_len_coordinates + assert len(usual_walker.y_coordinates) == expected_len_coordinates + + +def test_fast_walker_coordinate_length(): + """Tests whether the correct amount of coordinates is created for the fast walker""" + walking_time = 2 + walking_speed = 2 + # walker does two steps in 1 time, and has two times. plus needs a starting + # coordinate + expected_len_coordinates = 2 + 2 + 1 + fast_walker = walker.Walker(walking_time, walking_speed) + assert len(fast_walker.x_coordinates) == expected_len_coordinates + assert len(fast_walker.y_coordinates) == expected_len_coordinates + + +def test_running_walker_coordinate_length(): + """Tests whether the correct amount of coordinates is created for the running + walker""" + walking_time = 2 + walking_speed = 4 + # walker does four steps in 1 time, and has two times. plus needs a starting + # coordinate + expected_len_coordinates = 4 + 4 + 1 + running_walker = walker.Walker(walking_time, walking_speed) + assert len(running_walker.x_coordinates) == expected_len_coordinates + assert len(running_walker.y_coordinates) == expected_len_coordinates + + +def test_path_calculation(): + """tests whether the path calculation works correctly by checking if each coordinate + varies from the coordinate before by max. 1""" + walking_time = 2 + walking_speed = 4 + running_walker = walker.Walker(walking_time, walking_speed) + running_walker.calculate_the_path() + + # prepare check for total coordinate variation + expected_total_difference = 4 + 4 + total_difference = 0 + + for i in range(1, len(running_walker.x_coordinates)): + x_difference = abs( + running_walker.x_coordinates[i] - running_walker.x_coordinates[i - 1] + ) + y_difference = abs( + running_walker.y_coordinates[i] - running_walker.y_coordinates[i - 1] + ) + total_difference += x_difference + y_difference + assert x_difference in (0, 1) + assert y_difference in (0, 1) + + assert total_difference == expected_total_difference + + +def test_calculate_next_step(): + """Tests whether all directions can be chosen by the walker. Calculates the next + step 100 times and check in which direction it was taken. Afterwards it is + checked that each direction was chosen at least once""" + walking_time = 1 + walking_speed = 1 + counter_east = counter_west = counter_north = counter_south = 0 + test_walker = walker.Walker(walking_time, walking_speed) + for _ in range(100): + test_walker.x_coordinates = np.array([1, 1]) + test_walker.y_coordinates = np.array([1, 1]) + test_walker.calculate_next_step(1) + if test_walker.x_coordinates[1] == 2: + counter_east += 1 + elif test_walker.x_coordinates[1] == 0: + counter_west += 1 + elif test_walker.y_coordinates[1] == 2: + counter_north += 1 + elif test_walker.y_coordinates[1] == 0: + counter_south += 1 + assert ( + counter_north > 0 + and counter_south > 0 + and counter_west > 0 + and counter_east > 0 + ) + + +def test_create_building(): + """Tests whether the building is created correctly. The starting coordinates are set + manually as well as the expected building. The actual building is calculated + with the walker function and compared to the expected""" + expected_building = [-20.5, 19.5, 0.5, 15.5] + walking_time = 1 + walking_speed = 1 + test_walker = walker.Walker(walking_time, walking_speed) + test_walker.x_coordinates = np.array([0, 0]) + test_walker.y_coordinates = np.array([0, 0]) + actual_building = test_walker.create_building() + assert actual_building == expected_building + + +def test_avoid_building(): + """tests whether the walker avoids the building. + + Testing mode: as the building is avoided using a while loop, the test is not + that simple using a real walker. Instead, the coordinates of the walker are set + and the building is set to be reached by the walker if he does a step eastwards + (x-coordinate +1). Then, the path-calculation is executed. The resulting + coordinate is checked, it must be outside the building. However, the chance of + the walker walking in the direction of the building is only 25%. Therefore, the + check is done 100 times, so hypothetical the walker tries to walk into the + building 25 times""" + walking_time = 1 + walking_speed = 1 + test_walker = walker.Walker(walking_time, walking_speed) + for _ in range(100): + test_walker.x_coordinates = np.array([1, 1]) + test_walker.y_coordinates = np.array([1, 1]) + test_walker.building = [1.5, 10.5, -20, 20] + test_walker.calculate_the_path() + assert test_walker.x_coordinates[1] != 2 + + +def test_get_start_point(): + """Test whether the correct coordinates are returned for the start point""" + walking_time = 1 + walking_speed = 1 + test_walker = walker.Walker(walking_time, walking_speed) + test_walker.x_coordinates = np.array([0, 1]) + test_walker.y_coordinates = np.array([9, 10]) + assert [0, 9] == test_walker.get_start_point() + + +def test_get_end_point(): + """Test whether the correct coordinates are returned for the end point""" + walking_time = 1 + walking_speed = 1 + test_walker = walker.Walker(walking_time, walking_speed) + test_walker.x_coordinates = np.array([0, 1]) + test_walker.y_coordinates = np.array([9, 10]) + assert [1, 10] == test_walker.get_end_point() + + +def test_plotting(): + """Test that an image is written by the function plot_the_paths() and that it can + be opened""" + walking_time = 1 + walking_speed = 1 + outfile_name = "test_image.png" + test_walker = walker.Walker(walking_time, walking_speed) + test_walker.calculate_the_path() + with tempfile.TemporaryDirectory() as tmp_dirname: + outfile = os.path.join(tmp_dirname, outfile_name) + walker.plot_the_paths([test_walker], outfile) + image = imread(outfile) + assert len(image.shape) == 3 diff --git a/walker.py b/walker.py new file mode 100644 index 0000000..9d9e9dd --- /dev/null +++ b/walker.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""A Random Walk Simulation For Multiple Walker""" +# based on the idea of: +# Python code for 2D random walk. +# Source: https://www.geeksforgeeks.org/random-walk-implementation-python/ + +import math +import sys + +import matplotlib.pyplot as plt +import numpy as np + + +def create_walker( + walking_time: int, + number_of_usual_walker: int, + number_of_fast_walker: int, + number_of_running_walker: int, +) -> list: + """ + Checks if all user definitions of the walker are valid. If so, Creates a list of + all walker objects and calculates their paths. + :param walking_time: the 'time' each walker walks and therefore (depending on it's + respective speed) the number of steps the walker will make + :param number_of_usual_walker: the number of usual walker defines the number of + objects of this respective class which will be appended to the scene + :param number_of_fast_walker:the number of fast walker defines the number of + objects of this respective class which will be appended to the scene + :param number_of_running_walker: the number of running walker defines the number + of objects of this respective class which will be appended to the scene + :return: a list, the 'scene' which contains all walker, so all objects and their + coordinates + """ + number_of_walker = ( + number_of_usual_walker + number_of_fast_walker + number_of_running_walker + ) + assert ( + walking_time >= 1 + ), "walking_time must be greater than '0'. " "You stated '{}'".format( + walking_time - 1 + ) + assert ( + number_of_usual_walker >= 0 + and number_of_fast_walker >= 0 + and number_of_running_walker >= 0 + ), ( + "The number of walker can't be negative for any class. You stated '{}', " + "'{}' and '{}' for the walker classes".format( + number_of_usual_walker, number_of_fast_walker, number_of_running_walker + ) + ) + assert ( + number_of_walker > 0 + ), "The number of walker must be greater than '1'. " "You stated {}".format( + number_of_walker + ) + assert ( + number_of_walker <= 12 + ), "The number of walker must be max. '12'. " "You stated {}".format( + number_of_walker + ) + + scene = [] + for _ in range(number_of_usual_walker): + # create the array of a walker and add it to the scene-list + scene.append(Walker(walking_time, 1)) + for _ in range(number_of_fast_walker): + # create the array of a walker and add it to the scene-list + scene.append(Walker(walking_time, 2)) + for _ in range(number_of_running_walker): + # create the array of a running walker and add it to the scene-list + scene.append(Walker(walking_time, 4)) + for walker in scene: + walker.calculate_the_path() + continue + return scene + + +def plot_the_paths(list_of_walker: list, outfile_name: str) -> None: + """ + Creates the plots of the calculated paths of the walker as well as the building + next to it + :param list_of_walker: A list holding the walker objects. Each object holds the + coordinates of the respective path the walker walked. + :param outfile_name: The file which will be created with the plotted paths + """ + # define the number of rows and columns of subplots + if len(list_of_walker) == 1: + columns_of_plots = 1 + else: + columns_of_plots = 2 + rows_of_plots = math.ceil(len(list_of_walker) / 2) + + # create the subplots + figure, axes = plt.subplots(rows_of_plots, columns_of_plots, squeeze=False) + figure.set_figheight(4.8 * rows_of_plots + 1) + flying_walker = [] + for walker in enumerate(list_of_walker): + # get the walker subplot and plot the path in it + row = int(walker[0] / 2) + if walker[0] % 2 == 0: + column = 0 + else: + column = 1 + if walker[1].walking_speed == 1: + walker_type = "walking casually" + elif walker[1].walking_speed == 2: + walker_type = "walking fast" + elif walker[1].walking_speed == 4: + walker_type = "running" + start_coordinates = np.array(walker[1].get_start_point()) + end_coordinates = np.array(walker[1].get_end_point()) + distance = start_coordinates - end_coordinates + if ( + math.sqrt(distance[0] ** 2 + distance[1] ** 2) + ) >= 150: # pythagorean theorem + axes[row, column].plot( + [start_coordinates[0], end_coordinates[0]], + [start_coordinates[1], end_coordinates[1]], + ) + flying_walker.append(walker[0] + 1) + else: + axes[row, column].plot(walker[1].x_coordinates, walker[1].y_coordinates) + axes[row, column].fill( + [ + walker[1].building[0], + walker[1].building[1], + walker[1].building[1], + walker[1].building[0], + walker[1].building[0], + ], + [ + walker[1].building[2], + walker[1].building[2], + walker[1].building[3], + walker[1].building[3], + walker[1].building[2], + ], + c="grey", + label="Building", + ) + axes[row, column].scatter( + walker[1].get_start_point()[0], + walker[1].get_start_point()[1], + label="Startposition", + marker="o", + c="turquoise", + ) + axes[row, column].scatter( + walker[1].get_end_point()[0], + walker[1].get_end_point()[1], + label="Endposition", + marker="^", + c="orange", + ) + axes[row, column].legend() + axes[row, column].set_title(f"Walker {walker[0] + 1} is {walker_type}") + + if len(list_of_walker) % 2 != 0 and len(list_of_walker) != 1: + # if the number of walker is odd, delete the last (unused) subplot + figure.delaxes(axes[(rows_of_plots - 1), 1]) + + # arrange subplot to not interfere each other, save the figure to the outfile and + # show the plot to the user + if len(flying_walker) > 0: + plt.figtext( + 0.5, + 0.01, + f"\nWalker(s) {flying_walker} took a plane, as they don't want to walk" + f"such a long distance!", + ha="center", + ) + figure.tight_layout(rect=[0, 0.01, 1, 1]) + plt.savefig(outfile_name) + plt.show() + + +class Walker: + """Represents a walker to which a walking speed and walking time are given""" + + def __init__(self, walking_time: int, walking_speed: int): + self.walking_speed = walking_speed + self.walking_time = walking_time + self.number_of_steps = self.walking_time * self.walking_speed + 1 + (self.x_coordinates, self.y_coordinates) = self.assign_random_coordinates() + self.building = self.create_building() + + def assign_random_coordinates(self) -> tuple: + """ + This function creates random coordinates, as many as the walker does steps. + These will be updated later when the walker walks by calling the function + calculate_the_path() + :return: A tuple of two arrays holding the x- or y-coordinates + """ + # create random coordinates + x_coord = np.random.randint( + low=0, high=self.number_of_steps, size=self.number_of_steps + ) + y_coord = np.random.randint( + low=0, high=self.number_of_steps, size=self.number_of_steps + ) + return x_coord, y_coord + + def calculate_the_path(self) -> None: + """ + Calculates the path the walker walks by updating each position and checking if + the walker does not collide with the building. + """ + # calculate new coordinates for each step + for step_number in range(1, self.number_of_steps): + self.calculate_next_step(step_number) + while ( + (self.x_coordinates[step_number] > self.building[0]) + and (self.x_coordinates[step_number] < self.building[1]) + and (self.y_coordinates[step_number] > self.building[2]) + and (self.y_coordinates[step_number] < self.building[3]) + ): + self.calculate_next_step(step_number) + + def get_start_point(self) -> list: + """ + gets the starting position of the walker + :return: a list with the x-coordinate as the first element and the y-coordinate + as the second element + """ + return [self.x_coordinates[0], self.y_coordinates[0]] + + def get_end_point(self) -> list: + """ + gets the last position of the walker + :return: a list with the x-coordinate as the first element and the y-coordinate + as the second element + """ + return [self.x_coordinates[-1], self.y_coordinates[-1]] + + def calculate_next_step(self, step_number: int) -> None: + """ + This is a helper function which is used by the function calculate_the_path. It + is executed each time the next step needs to be calculated or if the walker + would walk into the building. It takes a random direction and updates the + coordinates from the position before and adds a step according to the + randomly determined direction + :param step_number: The number of step to be calculated + """ + direction_of_step = np.random.randint(1, 5) + if direction_of_step == 1: # east + self.x_coordinates[step_number] = self.x_coordinates[step_number - 1] + 1 + self.y_coordinates[step_number] = self.y_coordinates[step_number - 1] + elif direction_of_step == 2: # west + self.x_coordinates[step_number] = self.x_coordinates[step_number - 1] - 1 + self.y_coordinates[step_number] = self.y_coordinates[step_number - 1] + elif direction_of_step == 3: # north + self.x_coordinates[step_number] = self.x_coordinates[step_number - 1] + self.y_coordinates[step_number] = self.y_coordinates[step_number - 1] + 1 + else: # south + self.x_coordinates[step_number] = self.x_coordinates[step_number - 1] + self.y_coordinates[step_number] = self.y_coordinates[step_number - 1] - 1 + + def create_building(self) -> list: + """ + This function calculates the building of the walker. It's a rectangle north of + the walker starting position + :return: a list holding the 4 coordinates of the building + """ + xmin = float(self.get_start_point()[0]) - 20.5 + xmax = xmin + 40 + ymin = float(self.get_start_point()[1]) + 0.5 + ymax = ymin + 15 + return [xmin, xmax, ymin, ymax] + + +def main(): + """The main program. Assigns the user definitions, creates the scene and plots it""" + # read in specifications defined by the user + try: + walking_time = int(sys.argv[1]) + number_of_usual_walker = int(sys.argv[2]) + number_of_fast_walker = int(sys.argv[3]) + number_of_running_walker = int(sys.argv[4]) + outfile_name = sys.argv[5] + except (SyntaxError, IndexError, ValueError) as err: + print( + "Error: At least one of the inputs was not correctly specified.\n" + "The Input must be specified as 'python walker.py walking_time " + "number_of_usual_walker number_of_fast_walker number_of_running_walker " + "outfile_name'.\n" + "Inputformats: walking time --> Integer, number of walker --> " + "Integer, outfile_name --> string\n" + "outfile_name: path_to_output/filename.png\n" + "The number of walker and the walking time must be positive & at least 1" + "\n\n\n", + err, + ) + sys.exit(1) + + scene = create_walker( + walking_time, + number_of_usual_walker, + number_of_fast_walker, + number_of_running_walker, + ) + plot_the_paths(scene, outfile_name) + + +if __name__ == "__main__": + main()