This assignment will be closed on February 13, 2026 (23:59:59).
You must be authenticated to submit your files

Pale Machine CSE101

SJBerkemer

Objectives

What we will evaluate today: Everything!

Setup: Before you start

Launch Spyder (from the Applications/Programming menu). If you see a “Spyder update” message, just click OK and ignore it.

Before you start: create a new project, called PracticalExam_2 (using “New Project” from the “Projects” menu). This will ensure that files from last week (and future weeks) do not get mixed up together.

Now download the following files located in the same moodle folder as the exam and save them in your PracticalExam_2 folder. You can use save as, and choose your PracticalExam_2 folder.

Check and make sure that they have all appeared in your PracticalExam_2 project in Spyder.

Now open the file named mower.py. This is the file in which you will be writing your code today. We already provide a code basis, you have to fill in the missing parts.

Upload form is only available when connected

Introduction

Before the winter really starts, you once more want to cut the grass in your garden. To save some time, you want to automatize the grass cutting (= yawn mowing) by using a yawn mower robot. The image below shows an example.

Your garden will be divided into square tiles with coordinates (x,y). Your robot will start at its station (S). The station is currently located at position (1,1,) but it can be anywhere in the garden. Then the lawn mowing robot will go through the garden and cut the grass on every tile it passes. The robot will have to go around the stones (#) and make sure to not fall into the pool (P). The robot can only continue as long as it has enough energy. The image below gives an example of the garden where W shows the tiles with long grass and _ the tiles with short grass. The arrow shows the robot position and its orientation. The numbers show the x and y coordinates of the garden tiles.

We will implement the robot as its own class (Robot) as well as the single tiles (Tile) and the garden in another class (Garden).

Example

Exercises

Exercise 1: Directions

Look at the three lists in the beginning of the file mower.py: Directions, Direction_steps and Direction_chars. They will be used to turn and move the robot to the right positions.

Directions = ["north", "east", "south", "west"]
Direction_chars = ["^", ">", "v", "<"]
Direction_steps = [(0,1), (1,0), (0,-1), (-1,0)]

Run the following examples in order to see how these lists will be used.

In [1]: myorientation = "south"
In [2]: myindex = Directions.index(myorientation)
In [3]: myindex
Out[4]: 2
In [5]: mychar = Direction_chars[myindex]
In [6]: mychar
Out[7]: 'v'
In [8]: mystep = Direction_steps[myindex]
In [9]: mystep
Out[10]: (0, -1)
In [11]: myposition = (3,2)
In [12]: newposition = (myposition[0]+mystep[0],myposition[1]+mystep[1])
In [13]: newposition
Out[14]: (3, 1)

We will need these directions in the next exercises in order to change the robot’s orientation to the left or right and to move one step in the direction of the orientation.

Exercise 2: Robot

We first look at the class Robot to represent the grass mowing robot. The robot is defined by its position in the garden, its energy level, and its orientation. Additionally, it stores the position of its station which is also the starting point of the robot, thus the __init__ function will set the robot’s position to it’s station in the beginning.

Note that the coordinates of the garden tiles are always given as (x,y) where x is the column number and y the row number as shown in the image above.

Your class Robot begins as follows:

class Robot:
    """
    Encapsulates the robot and its properties
    
    Data Attributes:
    position     -- the current position of the robot in the garden (x,y) initialized with station
    energy   	 -- the energy level of the robot
    orientation	 -- the orientation of the robot (north, east, south, west)
    station	 -- position of the station
    """

    def __init__(self, posx, posy, energy, orientation):
		 self.station = (posx,posy)
		 pass  # remove this line and replace with your own code

    def __str__(self):
		pass  # remove this line and replace with your own code

Complete the two methods __init__ and __str__.

Testing

Test your class Robot in the console, for example as follows:

In [15]: robo = Robot(1, 1, 100, "north")

In [16]: print(robo)
Lawn mowing robot:
Position (1, 1)
Energy 100
Orientation north
Station (1, 1)

In [17]: robo.position = (4,3) 

In [18]: print(robo)
Lawn mowing robot:
Position (4, 3)
Energy 100
Orientation north
Station (1, 1)

In [19]: robo.energy = 42

In [20]: print(robo)
Lawn mowing robot:
Position (4, 3)
Energy 42
Orientation north
Station (1, 1)

Exercise 3: Robot Moves

Now complete the method move in the Robot class: The method move(self) moves the robot one step in the direction of its orientation by updating the position and reduce energy by -1. If the energy is 0, the robot will stay on its position.

Hint: use the examples from the first exercise (e.g. Direction_steps) in order to get the steps right.

def move(self):
    """
    Checks if energy > 0, then updates the position by 1 corresponding to the 
    robot's orientation and decreases the energy by 1.
    """
    pass  # remove this line and replace with your own code

Testing

Test your class Robot in the console, for example as follows:

In [21]: robo = Robot(1, 1, 100, "north")
In [22]: print(robo)
Lawn mowing robot:
Position (1, 1)
Energy 100
Orientation north
Station (1, 1)

In [23]: robo.move()
In [24]: print(robo)
Lawn mowing robot:
Position (1, 2)
Energy 99    
Orientation north
Station (1, 1)

In [25]: robo2 = Robot(3, 4, 100, "west")
In [26]: print(robo2)
Lawn mowing robot:
Position (3, 4)
Energy 100
Orientation west
Station (3, 4)

In [27]: robo2.move()
In [28]: print(robo2)
Lawn mowing robot:
Position (2, 4)
Energy 99    
Orientation west
Station (3, 4)

In [29]: robo3 = Robot(2, 6, 1, "south")
In [30]: print(robo3)
Lawn mowing robot:
Position (2, 6)
Energy 1
Orientation south
Station (2, 6)

In [31]: robo3.move()
In [32]: print(robo3)
Lawn mowing robot:
Position (2, 5)
Energy 0    
Orientation south
Station (2, 6)

In [33]: robo3.move()
In [34]: print(robo3)
Lawn mowing robot:
Position (2, 5)
Energy 0    
Orientation south
Station (2, 6)

Note: It is important to test your function in the console as there is no grader feedback.

Exercise 4: Robot Turns

Now, complete the class Robot with two other methods: 1. turn_left(self) and 2. turn_right(self) that will update the orientation of the robot in the corresponding direction.

Hint: use the examples from the first exercise in order to get the directions right.

def turn_left(self):
    """
    Change orientation of the robot to the left 
    """
    pass  # remove this line and replace with your own code

def turn_right(self):
    """
    Change orientation of the robot to the right
    """
    pass  # remove this line and replace with your own code

Testing

Test your class Robot in the console, for example as follows:

In [35]: robo = Robot(1, 1, 100, "north")
In [36]: print(robo)
Lawn mowing robot:
Position (1, 1)
Energy 100
Orientation north
Station (1, 1)

In [37]: robo.turn_right()
In [38]: print(robo)
Lawn mowing robot:
Position (1, 1)
Energy 100
Orientation east
Station (1, 1)

In [39]: robo2 = Robot(3, 4, 100, "west")
In [40]: print(robo2)
Lawn mowing robot:
Position (3, 4)
Energy 100
Orientation west
Station (3, 4)

In [41]: robo2.turn_left()
In [42]: print(robo2)
Lawn mowing robot:
Position (3, 4)
Energy 100  
Orientation south
Station (3, 4)

Note: It is important to test your function in the console as there is no grader feedback.

Upload

Now take a moment to upload your mower.py file to the moodle assignment corresponding to this exam.

(You can upload mower.py several times, but only your final upload will be graded - don’t wait until the last moment to upload a preliminary version, in case you miss the cut-off!)

Exercise 5: Tiles

We imagine our garden is organized by a grid of tiles each with a unique position. We now implement the class Tile with the following attributes:

  • coordinates: the x and y integer coordinates of the tile on the grid
  • state: a string that can be “short” or “long”
  • kind: a string that can be “plain”, “station”, “stones” or “pool”

Our class Tile will therefore work as follows: Each object will get coordinates (x,y) and a kind can be plain, station, pool and stones. As the grass in the garden is really long, each plain tile’sstate is set to long in the beginning. It will be set to short as soon as the robot entered the tile. All the non-plain tiles will be short from the beginning.

Look for the class in your python file and complete the methods __init__ and __str__.

class Tile:
    """
    Data Attributes:
    coordinates -- integer coordinates (x,y) of the tile
    state	-- a string: short or long
    kind	-- a string: plain, station, stones or pool

    In the beginning, set all the plain tiles to long, all the others to short.
    """

    def __init__(self, x, y, kind):
        pass  # remove this line and replace with your own code

    def __str__(self):
        pass  # remove this line and replace with your own code

Complete the method called cut(self) to the class Tile that changes the state of a tile from long to short.

   def cut(self):
       pass  # remove this line and replace with your own code

Testing

You should be able to reproduce the following behavior:

In [43]: t1 = Tile(2,3,"plain")
In [44]: print(t1)
Coordinates (2, 3)
Kind plain
State long

In [45]: t1.cut()
In [46]: print(t1)
Coordinates (2, 3)
Kind plain
State short

In [47]: t2 = Tile(5,6,"station")
In [48]: print(t2)
Coordinates (5, 6)
Kind station
State short

Upload

Now take a moment to upload your mower.py file to the moodle assignment corresponding to this exam.

(Remember, you can upload mower.py several times, but only your final upload will be graded - don’t wait until the last moment to upload a preliminary version, in case you miss the cut-off!)

Exercise 6: The Garden

Objects from the class Garden have the following data attributes:

  • xsize which is the number of squares in x direction of the grid,
  • ysize which is the number of squares in y direction of the grid,
  • a dictionary tiles, whose keys are tuples of coordinates on the grid, with the corresponding values being Tile objects at those positions,
  • the position of the tile with the robot station
  • a list of coordinates that specify the stones
  • the position of the pool

Note: The coordinates of the grid are always between 1 and xsize or ysize!

Note 2 : To initialize a garden object, you need xsize, ysize, the stones list, the position of the pool and the station position. If a robot, station or pool position is set to (-1,-1) it means that they are not in the garden! If the stones list is empty, there are no stones in the garden!

Now, complete the __str__ method of your Garden class and test your class Garden and the already implemented method draw.

Note 3: The garden.draw(position,orientation) will get the position and orientation of the robot as arguments in order to draw also the robot. However, if the robot position is (-1,-1), there is no robot in the garden!

Testing

In [49]: b = Garden(3,2,[],(-1,-1),(1,1))
In [50]: print(b)
Garden of size 3x2
In [51]: b.draw((-1,-1), "north")
WWW
SWW

In [52]: b2 = Garden(4, 5, [(4,4),(1,5),(3,4)], (2,3), (1,1))
In [53]: print(b2)
Garden of size 4x5
In [54]: b2.draw((-1,-1), "north")
#WWW
WW##
WPWW
WWWW
SWWW

In [55]: b3 = Garden(4, 5, [(4,4),(1,5),(3,4)], (2,3), (1,1)) 
In [56]: print(b3)
Garden of size 4x5
In [57]: robo = Robot(4,2,100,"east")
In [58]: print(robo)
Lawn mowing robot:
Position (4, 2)
Energy 100
Orientation east
Station (4, 2)

In [59]: b3.draw(robo.position,robo.orientation)
#WWW
WW##
WPWW
WWW>
SWWW

Exercise 7: The RoboRun class

In order to control the behaviour of the robot, we create the class RoboRun that will combine all the classes so far.

Objects from the class RoboRun have the following data attributes:

  • garden which is an instance of the garden class
  • robot which is an instance of the robot class

The __init__ function of the class will initialize the robot object and get the garden object as argument. For the robot, we will calculate its energy rounded to the next integer as int(xsize*ysize*1.5) and set the orientation as a default to north.

Write the __init__ and __str__ functions. Both should use the corresponding functions from the Robot and Garden classes.

Note: The starting position of the robot will be the station.

Implement the __init__ and __str__ methods using the corresponding methods from the classes for Robot and Garden.

class RoboRun:
        """
        The class RoboRun which organizes the course of the robot through the garden
 	Data Attributes:
            garden -- Garden object
            robot  -- Robot object
        """
    
	def __init__(self, garden):
	    pass  # remove this line and replace with your own code


	def __str__(self):
	    pass  # remove this line and replace with your own code

Testing

In [60]: b3 = Garden(4, 5, [(4,4),(1,5),(3,4)], (2,3), (1,1)) 
In [61]: print(b3)
Garden of size 4x5

In [62]: rr = RoboRun(b3)
In [63]: print(rr)
RoboRun with
Lawn mowing robot:
Position (1, 1)
Energy 30    
Orientation north
Station (1, 1)

and Garden of size 4x5

In [64]: rr.garden.draw(rr.robot.position, rr.robot.orientation)
#WWW
WW##
WPWW
WWWW
^WWW

Note If the robot is located on the station, only the robot is drawn.

Exercise 8: The allowed directions for the next step

Complete the method get_allowed_directions in the RoboRun class.

The method get_allowed_directions returns a list of possible positions the robot can move to (as defined in the Directions list in the first exercise). The robot can move by one tile north, east, south or west but it cannot move outside of the garden, hence the coordinates cannot go below 1 or above xsize or ysize. The robot cannot move to a tile occupied by stones but it can move into the pool which will result in the robot drowning such that it won’t be able to continue. If the robot can’t move, return an empty list.

Reminder Use the lists Directions and Direction_steps at the beginning of the file and the examples in exercise 1 to get the directions right.

    def get_allowed_directions(self):
        """
        Return a list of allowed Directions the robot could move to.
        The output list should contain the directions as defined in the 
        Directions list above and only if 
        - the next tile in the direction is not occupied by stones 
        - and still inside the garden
	"""
        pass  # remove this line and replace with your own code

Test your methods in the console, for example as follows.

In [65]: garden = Garden(9, 6, [(4,4),(1,5),(3,4)], (2,3), (1,1))
In [66]: rr = RoboRun(garden)
In [67]: print(rr)
RoboRun with
Lawn mowing robot:
Position (1, 1)
Energy 81
Orientation north
Station (1, 1)

and Garden of size 9x6

In [68]: rr.garden.draw(rr.robot.position,rr.robot.orientation)
WWWWWWWWW
#WWWWWWWW
WW##WWWWW
WPWWWWWWW
WWWWWWWWW
^WWWWWWWW

In [69]: npos = rr.get_allowed_directions()
In [70]: print(npos)
Out[71]: ['north', 'east']

In [72]: rr.robot.move()
In [73]: print(rr)
RoboRun with
Lawn mowing robot:
Position (1, 2)
Energy 80
Orientation north
Station (1, 1)

and Garden of size 9x6

In [74]: rr.garden.draw(rr.robot.position,rr.robot.orientation)
WWWWWWWWW
#WWWWWWWW
WW##WWWWW
WPWWWWWWW
^WWWWWWWW
SWWWWWWWW

In [75]: npos = rr.get_allowed_directions()
In [76]: print(npos)
Out[77]: ['north', 'east', 'south']

We allow printing the allowed directions in any order, so the output does not have to have exactly the same order.

Upload

Now take a moment to upload your mower.py file to the moodle assignment corresponding to this exam.

(Remember, you can upload mower.py several times, but only your final upload will be graded - don’t wait until the last moment to upload a preliminary version, in case you miss the cut-off!)

Exercise 9: One step

Our yawn mowing robot will run in rounds. It will be able to do one step per round which includes:

  • getting the allowed directions,
  • choosing one of the directions,
  • cutting the grass on the tile the robot is on,
  • setting the new direction,
  • moving to the next position according to the direction.

As our robot doesn’t have a good strategy, we will use random.choice to choose one of the next possible positions. Hence random.choice(inputlist) will return an element of the inputlist.

Hint: We recommend to use already implemented functions (e.g. robot.move, tile.cut).

Note 1: As we choose the next position randomly, your output may look different!

Note 2: The robot is able to move into the pool currently, we will implement this in the next step!

Complete the method one_round(self) in the RoboRun class.

    def one_round(self):
        """
        Get allowed directions,
        choose one direction randomly,
        cut the grass on the tile the robot is on,
        set the new direction,
        move to the next tile accordingly.
	"""
        pass  # remove this line and replace with your own code

Test your methods in the console, for example as follows.

In [78]: garden = Garden(9, 6, [(4,4),(1,5),(3,4),(9,2),(9,3)], (-1,-1), (1,1))
In [79]: rr = RoboRun(garden)
In [80]: print(rr)
RoboRun with
Lawn mowing robot:
Position (1, 1)
Energy 81
Orientation north
Station (1, 1)

and Garden of size 9x6

In [81]: rr.one_round()
In [82]: rr.garden.draw(rr.robot.position,rr.robot.orientation)
WWWWWWWWW
#WWWWWWWW
WW##WWWWW
WWWWWWWW#
^WWWWWWW#
SWWWWWWWW

In [83]: rr.one_round()
In [84]: rr.garden.draw(rr.robot.position,rr.robot.orientation)
WWWWWWWWW
#WWWWWWWW
WW##WWWWW
WWWWWWWW#
_>WWWWWW#
SWWWWWWWW

In [85]: rr.one_round()
In [86]: rr.garden.draw(rr.robot.position,rr.robot.orientation)
WWWWWWWWW
#WWWWWWWW
WW##WWWWW
W^WWWWWW#
__WWWWWW#
SWWWWWWWW

Exercise 10: Robo run!

We now have everything to let the robot run automatically through the garden as long as it has enough energy and doesn’t fall into the pool (if any). Write a method run(self) into the class RoboRun which lets the robot run as long as it did not hit the pool and still has energy.

The method should:

  • count the rounds
  • print START as well as the garden and robot at the beginning
  • print the round number every round
  • print the garden every 10 rounds
  • print END as well as the garden and robot after the run ended

Complete the method run(self) in the class RoboRun.

    def run(self):
        """
        Let the robot run as long as the robot still has energy and did not fall into the pool
        Print START as well as the garden and robot at the beginning
        Print the round number every round
        Print the garden every 10 rounds
        Print END as well as the garden and robot after the run ended
        """
	
        pass  # remove this line and replace with your own code

Test your methods in the console, for example as follows.

In [87]: garden = Garden(2, 3, [], (-1,-1), (1,1))
In [88]: rr = RoboRun(garden)
In [89]: rr.run()
START
WW
WW
^W

Lawn mowing robot:
Position (1, 1)
Energy 9
Orientation north
Station (1, 1)

ROUND 1
ROUND 2
ROUND 3
ROUND 4
ROUND 5
ROUND 6
ROUND 7
ROUND 8
ROUND 9
END
__
__
Sv

Lawn mowing robot:
Position (2, 1)
Energy 0
Orientation south
Station (1, 1)

In [90]: garden2 = Garden(3, 4, [(2,2),(2,3)], (-1,-1), (1,1))
In [91]: rr2 = RoboRun(garden2)
In [92]: rr2.run()
START
WWW
W#W
W#W
^WW

Lawn mowing robot:
Position (1, 1)
Energy 18
Orientation north
Station (1, 1)

ROUND 1
ROUND 2
ROUND 3
ROUND 4
ROUND 5
ROUND 6
ROUND 7
ROUND 8
ROUND 9
ROUND 10
WWW
W#^
_#_
S__

ROUND 11
ROUND 12
ROUND 13
ROUND 14
ROUND 15
ROUND 16
ROUND 17
ROUND 18
END
_>_
W#_
_#_
S__

Lawn mowing robot:
Position (2, 4)
Energy 0
Orientation east
Station (1, 1)

In [93]: garden3 = Garden(3, 4, [(2,2),(2,3)], (2,4), (1,1))
In [94]: rr3 = RoboRun(garden3)
In [95]: rr3.run()
START
WPW
W#W
W#W
^WW

Lawn mowing robot:
Position (1, 1)
Energy 18
Orientation north
Station (1, 1)

ROUND 1
ROUND 2
ROUND 3
ROUND 4
ROUND 5
ROUND 6
ROUND 7
ROUND 8
END
_>W
_#W
_#W
SWW

Lawn mowing robot:
Position (2, 4)
Energy 10
Orientation east
Station (1, 1)

Upload

Now take a moment to upload your mower.py file to the moodle assignment corresponding to this exam.

(Remember, you can upload mower.py several times, but only your final upload will be graded - don’t wait until the last moment to upload a preliminary version, in case you miss the cut-off!)

Exercise 11: Reading from a file

To test your function, it will be convenient to use pre-defined gardens. The three files (garden1.txt, garden2.txt and garden3.txt) given at the top of this page contain the gardens where we will have to cut the grass.

We strongly encourage you to open the files garden1.txt, garden2.txt and garden3.txt to see how they look like.

Write the function load_from_file(filename) (in mower.py, outside the Robot, Tile, Garden and RoboRun classes). The function should return a garden object that can be used to initiate a RoboRun.

Hints:

  • The garden coordinates are between 1 and xsize and 1 and ysize!
  • There are no spaces within the tuples!
  • The elements (ysize, xsize, pool, station and atones) can be in any order
  • station and pool do not have to be present, then they should be initialized with (-1,-1)
  • if there are not stones, they should be initialized with []

Test your function, for example as follows:

In [95]: garden1 = load_from_file('garden1.txt')
In [96]: garden1.draw((-1,-1),"north")
WWWSWWWW
W##WW##W
W##WW##W
W##WW##W
WWWWWWWW
WWWWPWWW


In [96]: garden2 = load_from_file('garden2.txt')
In [97]: garden2.draw((-1,-1),"north")
###
WWW
WWW
WW#
PWS
WW#
WWW
WWW
###

In [97]: garden3 = load_from_file('garden3.txt')
In [98]: garden3.draw((-1,-1),"north")
WWWWWWWW
WW#WW#WW
WW#WW#WW
WW####WW
WWWWSWWW

Upload

Now take a moment to upload your mower.py file to the moodle assignment corresponding to this exam.

(Remember, you can upload mower.py several times, but only your final upload will be graded - don’t wait until the last moment to upload a preliminary version, in case you miss the cut-off!)