Pale Machine CSE101
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 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 beingBikeStation
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):
= f'Board of size {self.xsize}x{self.ysize}'
outstr for p,m in self.bike_stations.items():
+= '\n'
outstr += f'{m}'
outstr 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
"""
= f'Board of size {self.xsize}x{self.ysize}'
outstr for i in range(len(self.gameboard)):
+="\n"
outstrfor j in range(len(self.gameboard[i])):
+=(self.gameboard[i][j])
outstrprint(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.
- The method
has_bike_station
returns True, if there is a bike station on this position of the board, False otherwise. - Te method
create_bike_station
will create a new BikeStation, add it to the board and return the bike station object. We assumecreate_bike_station
will be called only with valid arguments: the integersx
andy
are between 1 and the size of the board (inclusive). - 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
"""
= pos[0]
x = pos[1]
y return (self.gameboard[x][y]!='P')
def clean_position(self, pos):
"""
remove player symbol P from board
and redraw B or -
"""
= pos[0]
x = pos[1]
y 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
"""
= player.position[0]
x = player.position[1]
y 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 aBoard
object,players
, a list ofPlayer
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.
- The first line contains the size of the board separated by white space
- The second line contains the names of the players, separated by a white space.
- 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:
- we update the attribute
turn
bychange_turn
- for each player in the list, it calls
move_player
- if none of the students changed position, santa won
- 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 (useclean_position
anddraw_player
) - The method
move_player
:- first, it stores the current position of the player, as we might have to return it (see below)
- calls
get_next_pos
to get possible next positions - checks if the player
can_move
to the given positions - if no possible next positions are left, return the original position as the player cannot move
- for all possible next positions,
- if the player is student: take the one with the minimal distance to
last_x_pos
- if the player is santa: take a random position using random.choice
- if the player is student: take the one with the minimal distance to
- 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’swalk
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!)