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 scotlandyard.py. This is the file in which you will be writing your code today.

Introduction

In the series of old board games that bring tears to the eyes of CSE101 teachers is ‘Scotland Yard’, played by two or more players. The game is named after the headquarters of London’s Metropolitan Police Service.

In this game, all players except one play together as a team of detectives. The detectives search a criminal – played by the remaining player – who tries to hide. The detectives as well as the criminal use public transport in order to move through the city, however, due to financial reasons, each detective only has a limited amount of money to spend on tickets. The criminal is moving secretly, however, his position is reveiled from time to time such that the detectives can correct their course. As soon as one of the detectives reaches the same position as the criminal, the detectives win the game. If the detectives spent all their money or cannot move any further but haven’t found the criminal, the criminal wins the game.

We will implement a simplified version of this game today and imagine playing on a grid of squares. Hence, we can move to adjacent squares (horizontal or vertical) by bus or we can do longer moves by metro from one station to the next. The bus tickets cost 1 coin and the metro tickets 2 coins.

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 detective or criminal. Additionally, they own a number of coins to be spent on public transportation tickets.

Your class Player should begin as follows:

class Player:
    """
    Encapsulates players and their properties
    
    Data Attributes:
    name         -- the name of the player
    position     -- the current position of the player on the board
    role         -- the role of the player, detective or criminal
    coins   	 -- number of coins for tickets
    """

    def __init__(self, name, posx, posy, role, coins):
        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, 'detective', 4)

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

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

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

In [5]: p1.coins = 0

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

In [7]: p2 = Player('Maxi', 5, 5, 'criminal', 3)

In [8]: print(p2)
Player Maxi is criminal in position (5,5) with 3 coins

You do not need to adapt the word coins, it is ok to write 1 coins.

Exercise 2: Player Moves

Now complete the class Player with three other methods: 1. the method bus_move checks if the given position is adjacent to the player’s current position and if yes, returns True. Otherwise, it returns False. 2. the method can_move tests if the player can move by bus or metro based on the number of coins. For a bus move, 1 coin is needed and taking a metro costs 2 coins. 3. the method move will move the player to the given next position. It updates the position of the player, the number of coins and prints a message as given in the examples. We assume that move is only called after can_move returned True.


def bus_move(self, nextx, nexty):
   """
   Checks if (nextx,nexty) is adjacent to the current position and if yes, returns True.
   Otherwise, return 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 number of coins
   """
   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 number
   of coins, 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, 'detective', 4)

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

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

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

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

In [14]: p2 = Player('Maxi', 5, 5, 'criminal', 6)

In [15]: print(p2)
Player Maxi is criminal in position (5,5) with 6 coins

In [16]: p2.move(2, 2)
Player Maxi took the metro to a secret position
Out[16]: (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 scotlandyard.py file to the moodle assignment corresponding to this exam.

(You can upload scotlandyard.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: Metro station

We now implement the metro stations as instances of the class MetroStation. A metro station is connected to other metro stations on the board by a metro line. To define a metro station, we need to know its position on the board. The second class attribute is a set of metro stations to go to.

Our class MetroStation will therefore work as follows: each object has two data attributes, position and next_stations, being a set of positions on the board with connected metro stations.

Here is some code to get you started:

class MetroStation:
    """
    Data Attributes:
    position -- own position of the station
    next_stations -- a set of coordinates for the subsequent stations on the line
    """

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

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

Hint: In an f-string you can double the curly braces {{}} to prevent them from being interpreted as part of the expression.

Add a method to the class MetroStation called add_next_station(self, position) that adds the coordinates of a connected metro stations to the set of neighboring stations

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

Testing

You should be able to reproduce the following behavior:

In [17]: s = MetroStation(2,3)

In [18]: print(s)
Metro station at position (2,3) with next stations {}

In [19]: s.add_next_station((5,4))

In [20]: s.next_stations
Out[20]: {(5,4)}

In [21]: print(s)
Metro station at position (2,3) with next stations {(5,4)}

In [22]: s.add_next_station((7,3))

In [23]: s.next_stations
Out[23]: {(5,4),(7,3)}

In [24]: print(s)
Metro station at position (2,3) with next stations {(5,4),(7,3)}

Upload

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

(Remember, you can upload scotlandyard.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 the Scotland Yard 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 metro_stations, whose keys are tuples of coordinates on the board, with the corresponding values being MetroStation objects at those positions, and
  • a two-dimensional list representing our gameboard. Here ‘-’ are empty squares, ‘M’ shows metro stations, ‘P’ the position of detectives and ‘X’ the position of the criminal.

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

Copy the following code.

class Board:
    """
    A gameboard for our version of Scotland Yard
    
    Data Attributes:
    xsize          -- the number of squares in x direction
    ysize          -- the number of squares in y direction
    metro_stations -- a dictionary with the metro 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.metro_stations = dict()
        self.create_gameboard()

    def __str__(self):
        outstr = f'Board of size {self.xsize}x{self.ysize}'
        for p,m in self.metro_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 [25]: b = Board(8,8)

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

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

In [28]: m1 = MetroStation(2,2)

In [29]: b.metro_stations[(2,2)] = m1

In [30]: print(b)
Board of size 8x8
Metro station at position (2, 2) with next stations {}

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

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

  1. The method has_metro_station returns True, if there is a metro station on this position of the board, False otherwise.
  2. The method create_metro_station will create a new MetroStation, add it to the board and return the metro station object. We assume create_metro_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_metro_station(self, pos):
        """True if and only if pos has a metro station on the board."""
        pass  # remove this line and replace with your own code

    def create_metro_station(self, x, y):
        """Set metro station at (x,y) and return the metro 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 [31]: b = Board(8,8)

In [32]: b.has_metro_station((4,3))
Out[32]: False

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

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

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

In [35]: m1 = b.create_metro_station(1,1)

In [36]: print(m1)
Metro station at position (1, 1) with next stations {}

In [37]: m2 = b.create_metro_station(8,8)

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

M-------
--------
--------
--------
--------
--------
--------
-------M

In [39]: b.has_metro_station((8,8))
Out[39]: True

We allow printing the metro 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 bus or metro, if there is a 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 bus or to connected metro stations if the square has a metro station (use has_metro_station). For each position, it can be checked if the player can move with the method can_move. If the player can’t move, 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
	detective
	"""
    	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 M or -
	"""
        x = pos[0]
        y = pos[1]
        if(self.has_metro_station(pos)):
           self.gameboard[x][y]='M'
        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 == "detective"):
	    self.gameboard[x][y]='P'
	if(player.role == "criminal"):
	    self.gameboard[x][y]='X'


    def add_metro_lines(self,pos):
    	"""
	Creates the connections between metro stations.
	Calculate the 2 nearest neighboring metro stations on the board
	and add them to next_stations.
	The input argument is the position of the current metro station which has been created before
	using create_metro_station.
	"""
        mpos = list(self.metro_stations.keys())
        mpos.remove(pos)
	"""The following sort function sorts the metro stations by distance from the current one"""
        mpos.sort(key = lambda x : self.get_distance(x, pos))
        if(len(mpos)>0):
            self.metro_stations[pos].add_next_station(mpos[0])
        if(len(mpos)>1):
            self.metro_stations[pos].add_next_station(mpos[1])


    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
	"""
        pass  # remove this line and replace with your own code

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

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

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

In [42]: m1 = b.create_metro_station(1,2)

In [43]: m2 = b.create_metro_station(7,1)

In [44]: m3 = b.create_metro_station(2,8)

In [45]: m4 = b.create_metro_station(8,8)

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

-M------
-------M
--------
--------
--------
--------
M-------
-------M

In [47]: print(b)
Board of size 8x8
Metro station at position (1, 2) with next stations {}
Metro station at position (7, 1) with next stations {}
Metro station at position (2, 8) with next stations {}
Metro station at position (8, 8) with next stations {}

In [48]: b.add_metro_lines((1, 2))

In [49]: print(b)
Board of size 8x8
Metro station at position (1, 2) with next stations {(7, 1), (2, 8)}
Metro station at position (7, 1) with next stations {}
Metro station at position (2, 8) with next stations {}
Metro station at position (8, 8) with next stations {}


In [50]: b.add_metro_lines((7, 1))

In [51]: b.add_metro_lines((2, 8))

In [52]: b.add_metro_lines((8, 8))

In [53]: print(b)
Board of size 8x8
Metro station at position (1, 2) with next stations {(7, 1), (2, 8)}
Metro station at position (7, 1) with next stations {(8, 8), (1, 2)}
Metro station at position (2, 8) with next stations {(8, 8), (1, 2)}
Metro station at position (8, 8) with next stations {(7, 1), (2, 8)}

In [54]: b.get_next_pos((1,2))
Out[54]: [(2, 2), (1, 3), (1, 1), (7, 1), (2,8)]


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

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

Upload

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

(Remember, you can upload scotlandyard.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 the criminal.

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 the criminal 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 scotlandyard.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(). Thus, get a random position for each player 2. The number of coins depends on the board size and the number of players, hence: coins = 2(board.sizex+board.sizey)/number_players. The criminal (the last player in the list) is very smart and owns double the amount of coins than each of the detectives. Use integer division as half a coin doesn’t exist in our game. 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 the criminal

class Game:
    """
    A game of Scotland Yard.

    Data Attributes:
    board      -- the board of the game
    players    -- a list of Player objects with the last one playing Mr. X
    turn       -- the number of the current turn
    last_x_pos -- the position the criminal 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 [56]: b = Board(8,8)

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

In [58]: g.print_game_state()
-------------game state-------------
Player Benjamin is detective in position (1, 2) with 10 coins
Player Noemie is detective in position (1, 5) with 10 coins
Player Maxi is criminal in position (5, 5) with 20 coins
-------------game state-------------


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

-P--P---
--------
--------
--------
----X---
--------
--------
--------

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

Upload

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

(Remember, you can upload scotlandyard.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 contain the position of a metro station, one station per line

Your function should return a Game, hence you first have to create a Board including its metro 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.

Don’t forget to call add_metro_lines for each metro station in order to connect the stations. This should be done after all the metro stations have been created.

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

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

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

P------P
---X----
------P-
--------
--------
--------
--------
M------M

In [60]: g.print_game_state()
-------------game state-------------
Player Andi is detective in position (1, 8) with 8 coins
Player Maxi is detective in position (3, 7) with 8 coins
Player Kim is detective in position (1, 1) with 8 coins
Player Alex is criminal in position (2, 4) with 16 coins
-------------game state-------------

In [60]: print(g.board)
Board of size 8x8
Metro station at position (1, 1) with next stations {(1, 1), (1, 8)}
Metro station at position (1, 8) with next stations {(1, 1), (1, 8)}
Metro station at position (8, 8) with next stations {(8, 8), (1, 8)}
Metro station at position (8, 1) with next stations {(1, 1), (8, 1)}

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

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

(Remember, you can upload scotlandyard.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 detectives changed position, the criminal won
  4. if a detective moved to the criminal’s position, the detectives won

To win, a player must catch the criminal by moving on the same position. If none of the detectives can take a further move, the criminal 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 the criminal (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. filter the positions in case no bustickets or metrotickets are left
    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 detective:take the one with the minimal distance to last_x_pos
      2. if the player is criminal: 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
    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 [61]: g = load_from_file('board1.txt')

In [62]: g.print_game_state()
------------game state-------------
Player Andi is detective in position (5, 3) with 8 coins
Player Maxi is detective in position (2, 8) with 8 coins
Player Kim is detective in position (3, 1) with 8 coins
Player Alex is criminal in position (8, 4) with 16 coins
-------------game state-------------


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

M------M
-------P
P-------
--------
--P-----
--------
--------
M--X---M


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

In [65]: g.change_turn()

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

In [67]: g.last_x_pos
Out[67]: (8, 4)

In [68]: g.move_player(g.players[0])
Player Andi took the bus to position (6, 3)
Out[68]: (6, 3)

In [69]: g.move_player(g.players[1])
Player Maxi took the bus to position (3, 8)
Out[69]: (3, 8)

In [70]: g.move_player(g.players[2])
Player Kim took the bus to position (4, 1)
Out[70]: (4, 1)

In [71]: g.move_player(g.players[3])
Player Alex took the bus to a secret position
Out[71]: (8, 3)

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

M------M
--------
-------P
P-------
--------
--P-----
--------
M-X----M


In [73]: g.print_game_state()
-------------game state-------------
Player Andi is detective in position (6, 3) with 7 coins
Player Maxi is detective in position (3, 8) with 7 coins
Player Kim is detective in position (4, 1) with 7 coins
Player Alex is criminal in position (8, 3) with 15 coins
-------------game state-------------

Upload

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

(Remember, you can upload scotlandyard.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 the criminal won and 1 if the detectives 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 criminal 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 the criminal
	    - return 1 if the detectives won and 0 if the criminal 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):

Do not forget that the position of the criminal in shown by draw_board will only be updated every 5th turn, as well as last_x_pos. However print_game_state always knows the exaxt position of all players.

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

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

M-P----M
--------
-----P--
--------
-------P
X-------
--------
M------M
Turn: 1
Player Andi took the bus to position (3, 5)
Player Maxi took the bus to position (5, 7)
Player Kim took the bus to position (2, 3)
Player Alex took the bus to a secret position
Board of size 8x8

M------M
--P-----
----P---
--------
------P-
X-------
--------
M------M
Turn: 2
Player Andi took the bus to position (3, 4)
Player Maxi took the bus to position (5, 6)
Player Kim took the bus to position (3, 3)
Player Alex took the bus to a secret position
Board of size 8x8

M------M
--------
--PP----
--------
-----P--
X-------
--------
M------M
Turn: 3
Player Andi took the bus to position (4, 4)
Player Maxi took the bus to position (5, 5)
Player Kim took the bus to position (4, 3)
Player Alex took the bus to a secret position
Board of size 8x8

M------M
--------
--------
--PP----
----P---
X-------
--------
M------M
Turn: 4
Player Andi took the bus to position (5, 4)
Player Maxi took the bus to position (6, 5)
Player Kim took the bus to position (5, 3)
Player Alex took the bus to a secret position
Board of size 8x8

M------M
--------
--------
--------
--PP----
X---P---
--------
M------M
Turn: 5
Player Andi took the bus to position (6, 4)
Player Maxi took the bus to position (7, 5)
Player Kim took the bus to position (6, 3)
Player Alex took the bus to a secret position
Board of size 8x8

M------M
--------
--------
--------
--------
--PP----
-X--P---
M------M
Turn: 6
Player Andi took the bus to position (7, 4)
Player Maxi took the bus to position (6, 5)
Player Kim took the bus to position (7, 3)
Detective Kim caught the criminal in position (7, 3)
Board of size 8x8

M------M
--------
--------
--------
--------
----P---
-XPP----
M------M
Out[75]: 1


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

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

PXM--
----P
P---M
-----
--M-P
Turn: 1
Player Mum took the bus to position (4, 5)
Player Dad took the metro to position (1, 3)
Player Grandpa took the bus to position (1, 2)
Detective Grandpa caught the criminal in position (1, 2)
Board of size 5x5

-PP--
----P
M---M
----P
--M--
Out[77]: 1


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

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

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

--M--
-PPP-
M-X-M
---P-
--M--
Turn: 2
Player Mum took the bus to position (3, 2)
Detective Mum caught the criminal in position (3, 2)
Board of size 5x5

--M--
--PP-
MPX-M
---P-
--M--
Out[79]: 1

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

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

X-P
--P
--M
Turn: 1
Player Mia took the bus to position (2, 2)
Player Oscar took the bus to position (1, 2)
Player Ali took the metro to a secret position
Board of size 3x3

XP-
-P-
--M
Turn: 2
Player Mia took the bus to position (2, 1)
Player Oscar took the bus to position (1, 1)
Player Ali took the bus to a secret position
Board of size 3x3

P--
P--
--M
Turn: 3
Player Mia took the bus to position (2, 2)
Player Oscar took the bus to position (2, 1)
Player Ali took the bus to a secret position
Board of size 3x3

M--
PP-
--M
Turn: 4
Player Mia took the bus to position (1, 2)
Player Oscar took the bus to position (1, 1)
Player Ali took the bus to a secret position
Board of size 3x3

PP-
---
--M
Turn: 5
Player Ali took the bus to a secret position
Detectives cannot move, criminal won!
Board of size 3x3

MP-
---
-XM
Out[81]: 0

Upload

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

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