This assignment will be closed on March 19, 2025 (23:59:59).
You must be authenticated to submit your files

Pale Machine CSE101

SJBerkemer

Objectives

What we will evaluate today: Everything!

Upload form is only available when connected

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 by right-clicking them, choosing Save as, and saving them to your PracticalExam_2 folder.

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

Now create a new file named santaclaus.py. This is the file in which you will be writing your code today.

Introduction

As it is already mid of December, you will be implementing a hunt for Santa Claus on campus today.

In our game all players except one play together as a team of students aiming to find a Santa Claus, played by the remaining player. We won’t play on a real map but on a simplified version being a grid of squares. Students can either move by foot to adjacent squares or by rental bike from one bike station to another. However, as the schedule is full of exams and reports, each student only has a limited amount of energy left to walk or bike. As Santa Claus had 11 months of holidays since last Christmas, he is still full of energy to walk or cycle (it is still too warm to use his reindeer sleigh). Santa Claus is moving secretly, however, his position is reveiled from time to time such that the students can correct their course. As soon as one of the students reaches the same position as Santa Claus, the students win the game. If the students spent all their energy or cannot move any further but haven’t found Santa Claus, Santa Claus wins the game.

Exercises

Exercise 1: Players

We first create a new class Player to represent game players. Players are defined by their name, their position on the board, and their role, either student or santa. Additionally, they have a certain amount of energy units to be used for walking or cycling.

Your class Player should begin as follows:

class Player:
    """
    Encapsulates players and their properties
    
    Data Attributes:
    name         -- the name of the player
    role         -- the role of the player, student or santa
    position     -- the current position of the player on the board
    energy       -- amount of energy to move around
    """

    def __init__(self, name, posx, posy, role, energy):
        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 Player in the console, for example as follows:

In [1]: p1 = Player('Benjamin', 2, 4, 'student', 4)

In [2]: print(p1)
Player Benjamin is student in position (2,4) with energy 4

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

In [4]: print(p1)
Player Benjamin is student in position (4,3) with energy 4

In [5]: p1.energy = 0

In [6]: print(p1)
Player Benjamin is student in position (4,3) with energy 0

In [7]: p2 = Player('Maxi', 5, 5, 'santa', 100)

In [8]: print(p2)
Player Maxi is santa in position (5,5) with energy 100

Exercise 2: Player Moves

Now complete the class Player with three other methods: 1. the method walk checks if the given position is adjacent to the player’s current position and if yes, return True. Otherwise, it returns False. 2. the method can_move tests if the player can move by walking or cycling based on the amount of energy. In order to use a bike, 2 units of energy are needed, whereas walking only takes 1 unit of energy. 3. the method move will move the player to the given next position. Hence, it has to update the position, the energy and printing a message as given in the examples. We assume that move is only called after can_move returned True.

   def walk(self,nextx,nexty):
   	"""
   Checks if (nextx,nexty) is adjacent to the current position and if yes, returns True.
   Otherwise, it returns False.
   """
   pass  # remove this line and replace with your own code


   def can_move(self, nextx, nexty):
       """
   tests if a player can move to the given position based on the amount of energy
       """
   pass  # remove this line and replace with your own code


   def move(self, nextx, nexty):
       """
   The method moves the player to another position by updating the
   position and the amount of energy, and printing a message about the
   action. We assume that move will only be called after can_move
   evaluated to True.
   """
       pass  # remove this line and replace with your own code

Testing

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

In [9]: p1 = Player('Benjamin', 4, 3, 'student', 4)

In [10]: print(p1)
Player Benjamin is a student in position (4,3) with energy 4

In [11]: p1.move(5, 3)
Player Benjamin walked to position (5,3)
Out[11]: (5,3)

In [12]: p1.energy
Out[12]: 3

In [13]: p1.move(1, 1)
Player Benjamin took a bike to position (1,1)
Out[13]: (1,1)

In [14]: p1.energy
Out[14]: 1

In [15]: p2 = Player('Maxi', 5, 5, 'santa', 100)

In [16]: print(p2)
Player Maxi is santa in position (5,5) with energy 100

In [17]: p2.move(2, 2)
Player Maxi took a bike to a secret position
Out[17]: (2,2)

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 santaclaus.py file to the moodle assignment corresponding to this exam.

(You can upload santaclaus.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 3: Bike station

We now implement the bike stations as instances of the class BikeStation. To define a bike station, we need to know its position on the board.

Our class BikeStation will therefore work as follows: each object has two data attributes, position and bike_number, being a the amount of available bikes. If a bike is available, the player can take the bike to any other bike station on the board.

Here is some code to get you started:

class BikeStation:
    """
    Data Attributes:
    position    -- own position of the station
    bike_number -- number of available bikes
    """

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

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

Add the methods to the class BikeStation called take_bike(self) and place_bike(self) that add or remove a bike from the station. Make sure that a bike station cannot have less than 0 bikes, thus no bike can be taken if the bike_number is 0. Hence, take_bike(self) should return True if it is possible to take a bike or False if not.

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

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

Testing

You should be able to reproduce the following behavior:

In [18]: s = BikeStation(2,3,5)

In [19]: print(s)
Bike station at position (2,3) with 5 bikes

In [20]: s.take_bike()
Out[20]: True

In [21]: s.bike_number
Out[21]: 4

In [22]: print(s)
Bike station at position (2,3) with 4 bikes

Upload

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

(Remember, you can upload santaclaus.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 4: The board

Objects from the class Board for our game have four data attributes:

  • xsize which is the number of squares in x direction of the board,
  • ysize which is the number of squares in y direction of the board,
  • a dictionary bike_stations, whose keys are tuples of coordinates on the board, with the corresponding values being BikeStation objects at those positions, and
  • a two-dimensional list representing our gameboard. Here ‘-’ are empty squares, ‘B’ shows bike stations, ‘P’ the position of students (players) and ‘S’ the position of Santa Claus.

Note: The coordinates of the board are always between 1 and xszie or ysize!

Copy the following code.

class Board:
    """
    A gameboard for our game
    
    Data Attributes:
    xsize          -- the number of squares in x direction
    ysize          -- the number of squares in y direction
    bike_stations -- a dictionary with the bike stations on the board
    gameboard      -- a two-dimensional list as the map of the board
    """
 
    def __init__(self, xsize, ysize):
    	self.xsize = xsize
	self.ysize = ysize
	self.bike_stations = dict()
	self.create_gameboard()

    def __str__(self):
        outstr = f'Board of size {self.xsize}x{self.ysize}'
        for p,m in self.bike_stations.items():
            outstr += '\n'
            outstr += f'{m}'
        return outstr

    def create_gameboard(self):
    	"""
	Method to represent the board in its current state
	"""
    	self.gameboard = [['-'] * (self.ysize+1) for i in range(self.xsize+1)]
        for j in range(len(self.gameboard[0])):
            self.gameboard[0][j] = ""
        for i in range(len(self.gameboard)):
            self.gameboard[i][0] = ""

    def draw_board(self):
      	"""
	Method to draw the board in the console
	"""

            outstr = f'Board of size {self.xsize}x{self.ysize}'
        for i in range(len(self.gameboard)):
            outstr+="\n"
            for j in range(len(self.gameboard[i])):
                outstr+=(self.gameboard[i][j])
        print(outstr)

Testing

In [23]: b = Board(8,8)

In [24]: b.xsize
Out[24]: 8

In [25]: print(b)
Board of size 8x8

In [26]: b1 = BikeStation(2,2,4)

In [27]: b.bike_stations[(2,2)] = b1

In [28]: print(b)
Board of size 8x8
Bike station at position (2, 2) with 4 bikes


In the sequel it will be convenient to have methods dealing with the bike stations on the board.

Copy the following code into the class Board and complete the three methods.

  1. The method has_bike_station returns True, if there is a bike station on this position of the board, False otherwise.
  2. Te method create_bike_station will create a new BikeStation, add it to the board and return the bike station object. We assume create_bike_station will be called only with valid arguments: the integers x and y are between 1 and the size of the board (inclusive).
  3. The method get_distance returns the distance on the board between two positions of the board being the sum of the squared distances of x and y coordinates. The Euclidian distance of two positions (x1,y1) and (x2,y2): (x1-x2)2+(y1-y2)2.
    def has_bike_station(self, pos):
        """True if and only if pos has a bike station on the board."""
        pass  # remove this line and replace with your own code

    def create_bike_station(self, x, y, num_bikes):
        """Set bike station at (x,y) and return the bike station object"""
        pass  # remove this line and replace with your own code

    def get_distance(self, pos1, pos2):
        """Calculate (x1-x2)^2+(y1-y2)^2 and return it"""
        pass  # remove this line and replace with your own code

Testing

In [29]: b = Board(8,8)

In [30]: b.has_bike_station((4,3))
Out[30]: False

In [31]: b.draw_board()
Board of size 8x8

--------
--------
--------
--------
--------
--------
--------
--------

In [32]: b.get_distance((2,2),(4,4))
Out[32]: 8

In [33]: m1 = b.create_bike_station(1,1,2)

In [34]: m2 = b.create_bike_station(8,8,4)

In [35]: print(b)
Board of size 8x8
Bike station at position (1, 1) with 2 bikes
Bike station at position (8, 8) with 4 bikes

In [36]: b.draw_board()
Board of size 8x8

B-------
--------
--------
--------
--------
--------
--------
-------B

In [37]: b.has_bike_station((8,8))
Out[37]: True

Note: We allow printing the bike stations in any order, so the output does not have to have exactly the same order.

Exercise 5: The next available positions

Copy the following code into the class Board and complete the method.

The method get_next_pos returns a list of possible positions the player can move to by walking or bike, if there is a bike station. The player cannot move outside of the board, hence the coordinates cannot go below 1 or above xsize or ysize. The player can move up, down, left or right by walking or to any bike station if the square has a bike station with bikes (use has_bike_station). If no move is possible, return an empty list. Use the method is_free to check if the possible next position is still free.


    def is_free(self, pos):
    	"""
	This method uses the board_game in order to check if a square is already occupied by a
	student
	"""
    	x = pos[0]
	y = pos[1]
	return (self.gameboard[x][y]!='P')

    def clean_position(self, pos):
    	"""
	remove player symbol P from board
	and redraw B or -
	"""
        x = pos[0]
        y = pos[1]
        if(self.has_bike_station(pos)):
           self.gameboard[x][y]='B'
        else:
           self.gameboard[x][y]='-'


    def draw_player(self, player):
    	"""
	Gets a player object and draws it on the gamebord
    	"""
	x = player.position[0]
    	y = player.position[1]
	if(player.role == "student"):
	    self.gameboard[x][y]='P'
	if(player.role == "santa"):
	    self.gameboard[x][y]='S'


    def get_next_pos(self, player_pos):
        """
	Get next position a player can move to.
	Use the method is_free() to check if the possible next position is still free
	If the player is on a bike station, check if there is at least one bike.
	"""
        pass  # remove this line and replace with your own code

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

In [38]: b = Board(8,8)

In [39]: b.get_next_pos((2,2))
Out[39]: [(1,2), (2,1), (2,3), (3,2)]

In [40]: m1 = b.create_bike_station(2,2,5)

In [41]: m2 = b.create_bike_station(4,4,5)

In [42]: b.get_next_pos((2,2))
Out[42]: [(1,2), (2,1), (2,3), (3,2), (4,4)]

In [43]: print(b)
Board of size 8x8
Bike station at position (2,2) with 5 bikes
Bike station at position (4,4) with 5 bikes

In [44]: b.draw_board()
Board of size 8x8

--------
-B------
--------
---B----
--------
--------
--------
--------

In [45]: b.is_free((2,3))
Out[46]: True

We allow printing the next positions and the bike stations in any order, so the output does not have to have exactly the same order.

Upload

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

(Remember, you can upload santaclaus.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 Game

We have now everything in hand to define the main class Game: to define a game, we need a board, a list of players’ names whereas we choose the last player to be santa.

Game objects have four data attributes:

  • an integer turn which contains the current turn,
  • board which contains a Board object,
  • players, a list of Player objects.
  • a position last_x_pos storing the position where santa has been seen last

Copy and add the class Game with the methods given below to your code.

Add import random to the beginning of your santaclaus.py file, so that you can use the randint method of the random module.

Complete the __init__(board, players) method. Follow the steps when creating the list of Player objects: 1. The positions of the players should be chosen randomly, however, at most one player can be placed on each position of the board, use the method is_free(). 2. The amount of energy per player depends on the board size and the number of players, hence: energy = 2*(board.sizex+board.sizey)/number_players. Use integer division as we only have full units of energy. Santa (the last player in the list) is very energetic and always starts with 100 energy 3. Call the function draw_player() of your board in order to add your players to the gameboard representation. 4. Initialize last_x_pos to the start position of santa

class Game:
    """
    Data Attributes:
    board      -- the board of the game
    players    -- a list of Player objects with the last one playing santa
    turn       -- the number of the current turn
    last_x_pos -- the position santa has been seen last 
    """ 
    
    def __init__(self, board, player_names):
        self.turn = 0
	self.board = board
        pass # remove this line and replace with your own code
       
    def print_game_state(self):
        """Print state of game after every turn."""
        print('-------------game state-------------')
        for player in self.players:
            print(player)
        print('-------------game state-------------\n')

Test your method __init__ in the console, for example as follows.

In [47]: b = Board(8,8)

In [48]: g = Game(b, ['Benjamin', 'Noemie', 'Maxi'])

In [49]: g.print_game_state()
-------------game state-------------
Player Benjamin is student in position (1, 2) with 10 energy
Player Noemie is student in position (1, 5) with 10 energy
Player Maxi is santa in position (5, 5) with 100 energy
-------------game state-------------


In [50]: g.board.draw_board()
Board of size 8x8

-P--P---
--------
--------
--------
----S---
--------
--------
--------

Note: As the players are placed on random positions, your gameboard may look different.

Upload

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

(Remember, you can upload santaclaus.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 7: Reading from a file

To test your function, it will be convenient to use pre-defined boards and player lists. The three files (board1.txt, board2.txt and board3.txt) given at the top of this page contain information to define a game.

  1. The first line contains the size of the board separated by white space
  2. The second line contains the names of the players, separated by a white space.
  3. The remaining lines list bike stations, one per line, with three numbers xpos,ypos,bike_number separated by a ‘,’

Your function should return a Game, hence you first have to create a Board including its bike stations. Then you can create a Game object with the board and the list of player names as arguments.

We strongly encourage you to open the files board1.txt, board2.txt and board3.txt to see how they are made.

Write the function load_from_file(filename) (in santaclaus.py, outside the Player, BikeStation, Game and Board classes), and test it, for example as follows:

In [50]: g = load_from_file('board1.txt')

In [51]: g.board.draw_board()
Board of size 8x8

B------B
-----P--
------P-
--------
-----P--
--------
-----S--
B------B

As the players are set in random positions, your gameboard may look different. If a player is on a bike station, you only see the ‘P’.

Hint: recall that the split method accepts a separator as an argument, and this separator may be ':' or ' '.

Upload

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

(Remember, you can upload santaclaus.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 8: Prepare to play

We detail a bit more the rules of the game. At each turn of the game, the players move in the given order by the list of players.

In each turn:

  1. we update the attribute turn by change_turn
  2. for each player in the list, it calls move_player
  3. if none of the students changed position, santa won
  4. if a student moved to santa’s position, the students won

To win, a student must catch santa by moving on the same position. If none of the students can take a further move, santa has won the game. We defined several methods that will help for the next exercise.

Copy this code inside the class Game, and complete the methods.

  • The method change_turn will update the turn by 1 and every 5th turn (turn % 5 == 0) it will update the position of santa (last_x_pos) as well as the drawing on the board (use clean_position and draw_player)
  • The method move_player:
    1. first, it stores the current position of the player, as we might have to return it (see below)
    2. calls get_next_pos to get possible next positions
    3. checks if the player can_move to the given positions
    4. if no possible next positions are left, return the original position as the player cannot move
    5. for all possible next positions,
      1. if the player is student: take the one with the minimal distance to last_x_pos
      2. if the player is santa: take a random position using random.choice
    6. update the player’s position (using the function move in the player class), clean the previous position from the board (clean_position), redraw the player (draw_player) and return the player’s new position. If the player moved from one bike station to another, update the number of bikes in both stations. You can check if a player walked by using the player’s walk function.
    def change_turn(self):
        pass  # remove this line and replace with your own code

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

Testing

Test your methods change_turn, and move_player in the console; for example, you should have the following behavior:

In [52]: g = load_from_file('board1.txt')

In [53]: g.print_game_state()
-------------game state-------------
Player Andi is student in position (7, 3) with energy 8
Player Maxi is student in position (8, 7) with energy 8
Player Kim is student in position (6, 2) with energy 8
Player Alex is santa in position (7, 5) with energy 100
-------------game state-------------

In [54]: g.board.draw_board()
Board of size 8x8

B------B
--------
--------
--------
--------
-P------
--P-S---
B-----PB

In [55]: g.turn
Out[55]: 0

In [56]: g.change_turn()

In [57]: g.turn
Out[57]: 1

In [58]: g.last_x_pos
Out[58]: (7, 5)

In [59]: g.move_player(g.players[0])
Player Andi walked to position (7, 4)
Out[60]: (7, 4)

In [61]: g.move_player(g.players[1])
Player Maxi walked to position (8, 6)
Out[62]: (8, 6)

In [63]: g.move_player(g.players[2])
Player Kim walked to position (6, 3)
Out[64]: (6, 3)

In [65]: g.move_player(g.players[3])
Player Alex walked to a secret position
Out[66]: (6, 5)

In [67]: g.board.draw_board()
Board of size 8x8

B------B
--------
--------
--------
--------
--P-S---
---P----
B----P-B


In [68]: g.print_game_state()
-------------game state-------------
Player Andi is student in position (7, 4) with energy 7
Player Maxi is student in position (8, 6) with energy 7
Player Kim is student in position (6, 3) with energy 7
Player Alex is santa in position (6, 5) with energy 99
-------------game state-------------

Note: the position of the criminal hasn’t been redrawn yet, so the position in game_state and in draw_board are different!

Upload

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

(Remember, you can upload santaclaus.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: Combining everything: let’s play!

We now have everything in place to create the main method in the class Game. Add some print statements after the game is over as in the example. It is also helful to print the number of the turn and draw_board to see the progress of the game. The function play returns 0 if santa won and 1 if the students won the game.

Copy and complete the following code in your class Game:

    def play(self):
        """
        Starting point the of game, which continues while players can move or the santa is still free:
	    - draw board to see the progress of the game
            - change_turn, print the turn after each change
	    - move players one by one
	    - check if a player caught santa
	    - return 1 if the students won and 0 if santa won
        """
	 pass  # remove this line and replace with your own code

Testing

Test your method play() in the console; for example, you should have the following behavior with board1.txt, board2.txt and board3.txt (though given that we are using the random module, you may get different values and final result):

In [69]: g = load_from_file('board1.txt')

In [70]: g.play()
Board of size 8x8

B----P-B
--------
P-------
-------S
--------
--------
--------
B---P--B
Turn: 1
Player Andi walked to position (2, 6)
Player Maxi walked to position (3, 2)
Player Kim walked to position (7, 5)
Player Alex walked to a secret position
Board of size 8x8

B------B
-----P--
-P------
-------S
--------
--------
----P---
B------B
Turn: 2
Player Andi walked to position (3, 6)
Player Maxi walked to position (3, 3)
Player Kim walked to position (6, 5)
Player Alex walked to a secret position
Board of size 8x8

B------B
--------
--P--P--
-------S
--------
----P---
--------
B------B
Turn: 3
Player Andi walked to position (3, 7)
Student Andi caught santa in position (3, 7)
Board of size 8x8

B------B
--------
--P---P-
-------S
--------
----P---
--------
B------B
Out[70]: 1


In [71]: g = load_from_file('board2.txt')

In [72]: g.play()
Board of size 5x5

-PP--
----P
BS--B
---P-
--B--
Turn: 1
Player Mum walked to position (2, 2)
Player Dad walked to position (4, 3)
Player Grandpa took the bike to position (3, 1)
Player Grandma walked to position (2, 4)
Player Me walked to a secret position
Board of size 5x5

--B--
-P-P-
PS--B
--P--
--B--
Turn: 2
Player Mum walked to position (3, 2)
Player Dad walked to position (3, 3)
Student Dad caught santa in position (3, 3)
Board of size 5x5

--B--
---P-
PPP-B
-----
--B--
Out[72]: 1


In [73]: g = load_from_file('board3.txt')

In [74]: g.play()
Board of size 3x3

BS-
--P
--P
Turn: 1
Player Mia took the bike to position (1, 1)
Player Oscar walked to position (1, 3)
Player Ali walked to a secret position
Board of size 3x3

PSP
---
--B
Turn: 2
Player Mia walked to position (1, 2)
Player Oscar walked to position (2, 3)
Player Ali walked to a secret position
Board of size 3x3

BP-
--P
--B
Turn: 3
Player Mia walked to position (2, 2)
Player Oscar walked to position (1, 3)
Player Ali walked to a secret position
Board of size 3x3

B-P
-P-
--B
Turn: 4
Player Oscar walked to position (1, 2)
Player Ali took the bike to a secret position
Board of size 3x3

BP-
-P-
--B
Turn: 5
Player Ali walked to a secret position
Students cannot move, santa won!
Board of size 3x3

S--
-P-
--B
Out[74]: 0

Upload

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

(Remember, you can upload santaclaus.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!)