Coursework 3
In this coursework, worth 70% of the marks for the course, you are required to write Python functions, by filling in code into the template below. Submit your Python script on Blackboard before the deadline on Thursday, 14th December 2023 at 1pm. (I suggest aiming to submit at least hour earlier, to avoid any last-minute computer problems).
Working independently
As with all assessed work in this unit, you must complete this coursework independently on your own. You are allowed to use online resources and all lecture notes and exercise solutions. The lecture notes from weeks 1 to 8 contain all that is required to solve the below problems. You are not allowed to ask other humans for help, and in particular, you are not allowed to send, give, or receive Python code to/from classmates and others. Use of AI generative tools, such as ChatGPT, is not permitted for this coursework.
See the University Guidelines for Academic Malpractice http://documents.manchester.ac.uk/display.aspx?DocID=2870 .
Code template
Use the template below (also available at https://personalpages.manchester.ac.uk/staff/chris.johnson/python/content/coursework_3/template.py ) to complete the assignment.
Add your name, university ID number and email address in the docstring at top of the file, where indicated
Add code in the functions where TODO comments are present, to implement the functions in the ten tasks below. To check that your code is working correctly, you may wish to add some of your own tests in the main() function, but nothing in your main() function will be assessed.
You are very welcome to add your own functions to the code, and call these from the assessed functions, if you feel that this will improve the structure your code.
Important:
Do not alter the names of any of the required functions, the format for the game state, the numbering of points on the board, or the file formats for saved/loaded games. Doing so may result in the loss of a significant number of marks. Ensure that the functions can be called with the parameters precisely as specified, and that, they return the appropriate values.
Do not add any code to your solution that is outside a function. The only line of code outside a function should be the final line, main() that calls your main function.
Ensure that the code you submit is valid python code which runs without crashing. If you are unable to complete some of the tasks, please make sure your code is still valid (e.g. by including the pass statement for any functions in which you do not have any code written)
“”"
MATH20621 - Coursework 3
Student name:
Student id:
Student mail:
Do not change any part of this string except to replace
the with your name, id and university email address.
“”"
def request_location(question_str):
“”"
Prompt the user for a board location, and return that location.
Takes a string parameter, which is displayed to the user as a prompt.
Raises InputError if input is not a valid integer,
or RuntimeError if the location typed is not in the valid range.
*************************************************************
DO NOT change this function in any way
You MUST use this function for ALL user input in your program
*************************************************************
"""
loc = int(input(question_str))
if loc<0 or loc>=24:
raise RuntimeError("Not a valid location")
return loc
def draw_board(g):
“”"
Display the board corresponding to the board state g to console.
Also displays the numbering for each point on the board, and the
number of counters left in each players hand, if any.
A reference to remind players of the number of each point is also displayed.
You may use this function in your program to display the board
to the user, but you may also use your own similar function, or
improve this one, to customise the display of the game as you choose
"""
def colored(r, g, b, text):
"""
Spyder supports coloured text! This function creates coloured
version of the text 'text' that can be printed to the console.
The colour is specified with red (r), green (g), blue (b) components,
each of which has a range 0-255.
"""
return f"\033[38;2;{r};{g};{b}m{text}\033[0m"
def piece_char(i):
"""
Return the (coloured) character corresponding to player i's counter,
or a + to indicate an unoccupied point
"""
if i==0:
return colored(100,100,100,'+')
elif i==1:
return colored(255,60,60,'X')
elif i==2:
return colored(60,120,255,'O')
board = '''
x--------x--------x 0--------1--------2
| | | | | |
| x-----x-----x | | 3-----4-----5 |
| | | | | | | | | |
| | x–x–x | | | | 6–7–8 | |
| | | | | | | | | | | |
x–x–x x–x–x 9-10-11 12-13-14
| | | | | | | | | | | |
| | x–x–x | | | | 15-16-17 | |
| | | | | | | | | |
| x-----x-----x | | 18—19----20 |
| | | | | |
x--------x--------x 21------22-------23
‘’’
boardstr = ‘’
i = 0
for c in board:
if c==‘x’:
boardstr += piece_char(g[0][i])
i += 1
else:
boardstr += colored(100,100,100,c)
if g[1]>0 or g[2]>0:
boardstr += '\nPlayer 1: ’ + (piece_char(1)*g[1])
boardstr += '\nPlayer 2: ’ + (piece_char(2)*g[2])
print(boardstr)
#############################
The functions for each task
def is_adjacent(i, j):
# TODO: implement function here
pass
def new_game():
# TODO: implement function here
pass
def remaining_counters(g):
# TODO: implement function here
pass
def is_in_mill(g, i):
# TODO: implement function here
pass
def player_can_move(g):
# TODO: implement function here
pass
def place_counter(g, i):
# TODO: implement function here
pass
def move_counter(g, i, j):
# TODO: implement function here
pass
def remove_opponent_counter(g, i):
# TODO: implement function here
pass
def turn(g):
# TODO: implement function here
pass
def save_state(g, filename):
# TODO: implement function here
pass
def load_state(filename):
# TODO: implement function here
pass
def play_game():
# TODO: implement function here
pass
def main():
# You could add some tests to main()
# to check your functions are working as expected
# The main function will not be assessed. All code to
# play the game should be in the play_game() function,
# and so your main function should simply call this.
play_game()
main()
Feedback
As this is part of the course assessment, we will not be able to help you solving these problems in the lab classes or elsewhere. You will receive a feedback report with a score for each function, and some overall feedback on the code quality.
Please read the problem descriptions carefully! The problems should (hopefully) be formulated in a way that there are no doubts. In case there remains a question on a problem formulation, please post it to the Blackboard Discussion Forum and it will be answered. Errors in this document will be corrected and noted on the ‘errata’ list on Blackboard.
Nine Men’s Morris
Nine Men’s Morris is a two-player game played with counters on a board. It was popular in mediæval England, but has been played for over 2000 years. The game board consists of 16 lines, as follows:
┌──────────┬──────────┐
│ │ │
│ ┌──────┼──────┐ │
│ │ │ │ │
│ │ ┌──┴──┐ │ │
│ │ │ │ │ │
├───┼───┤ ├───┼───┤
│ │ │ │ │ │
│ │ └──┬──┘ │ │
│ │ │ │ │
│ └──────┼──────┘ │
│ │ │
└──────────┴──────────┘
Two players can place their counters (X for player 1, and O for player 2) on each of the 24 corners or intersections of the lines (we’ll call these ‘points’), leading to boards in play that look like, for example,
X──────────┬──────────┐
│ │ │
│ O──────O──────O │
│ │ │ │ │
│ │ ┌──┴──┐ │ │
│ │ │ │ │ │
X───X───┤ O───┼───X
│ │ │ │ │ │
│ │ └──┬──┘ │ │
│ │ │ │ │
│ └──────O──────┘ │
│ │ │
└──────────┴──────────X
The game is played in two stages:
The board starts with no counters on it, and each player starts with nine counters in their hand. Starting with player 1, they take it in turns to place their counters on unoccupied points on the board. If in placing a piece, a player manages to occupy all three points on a straight line with one of their counters (called forming a mill) then they may then remove one of their opponents counters from the board. (For example, in the diagram above, player 2, represented by O, has such a mill, but player 1, represented by X, does not). Such removed counters play no further part in the game. This continues until both players have placed all their counters.
Players continue to alternate turns. In a turn, a player moves one of their counters along a line to an adjacent unoccupied point. As before, if this move forms a mill, then the player removes one of their opponents counters from the board. A player loses if they are unable to move any of their pieces, or if they have only two counters left on the board.
Simulating the game in Python
The object of this coursework is to write a simulator for the game, which allows either two players to play each other.
The board
We’ll identify points on the board with integers 0 to 23 as follows:
0──────────1──────────2
│ │ │
│ 3──────4──────5 │
│ │ │ │ │
│ │ 6──7──8 │ │
│ │ │ │ │ │
9──10──11 12─13──14
│ │ │ │ │ │
│ │ 15─16─17 │ │
│ │ │ │ │
│ 18────19─────20 │
│ │ │
21────────22─────────23
The game state
The game state – that is, everything required to describe a game in play – consists of four elements:
The location and type of counters on the board
The number of unplaced counters that each player has in their hand
The player who taking a turn
We will store game state in a Python list g containing four elements,
g = [[…], p1_counters, p2_counters, active_player]
g[0] is itself a list of 24 integers, describing the board, with the ith element of this list, g[0][i] describing what is present at point i on the board. g[0][i] is:
0 if point i is unoccupied
1 if point i contains a counter of player 1
2 if point i contains a counter of player 2
g[1] stores an integer containing the number of counters that player 1 has in hand (not yet placed on the board)
g[2] stores an integer containing the number of counters that player 2 has in hand (not yet placed on the board)
g[3] stores an integer, either 1 or 2, depending on whether the player currently taking their turn is player 1 or player 2, respectively.
A function draw_board() is provided that displays a board state to the console, using diagrams similar to those above. You may use this function as-is, as part of your program, or you may wish to modify it to improve the appearance of the board.
Asking the user for input
The game should interact with users only by asking for integer board locations, and a function request_location(question_str) is provided for this purpose.
This function
displays the parameter string question_str that is passed to it, as a prompt to the user
waits for the user to type an integer
either: -returns this integer, if it corresponds to a location on the board (between 0 and 23 inclusive)
or raises a ValueError if the text typed was not an integer
or raises a RuntimeError if the text typed was an integer, but not in the range 0 to 23 inclusive.
The request_location function is the only way in which you should ask the user for input. You must not modify this function, and you must not use the input(…) function anywhere else in your code. (The reason for this is that, when testing the code, I may modify the request_location function to automate the user input to your program). Using the input(…) function anywhere else in your code will result in the loss of marks.
The tasks:
The following tasks each ask you to write a Python function that forms some part of the game. Generally speaking, the earlier tasks are easier than the later tasks. It is expected that you will call some of the functions from earlier tasks in the later tasks. Unless otherwise specified, functions do not return a value.
The function definitions are already defined in the template code provided. Use this template, and fill in the implementation of the functions.
For each function, write a short docstring (~ 1-3 sentences) that explains what the function does, what parameters it takes, and what it returns (if anything).
Document each of your functions using comments (#) to explain what the code is doing. On average, I suggest writing one comment line for every ~5 lines of code, though this varies greatly depending on the code being written
Task 1: checking adjacency (4 marks)
Write a function is_adjacent(i, j) that takes two integers representing points on the board, and returns the Boolean value True if those two points are adjacent to one another (connected by a line without going through another point), or False otherwise. A point is not adjacent to itself. For example is_adjacent(21,22) should return True, and is_adjacent(21, 23) and is_adjacent(21,20) should both return False.
You can assume that the values of i and j provided are integers between 0 and 23 inclusive.
Task 2: setting up the game state (4 marks)
Write a function new_game() that returns the game state (the list of four elements, as described above), for a new game in which no counters have been placed.
Task 3: counting counters (4 marks)
Write a function remaining_counters(g) that takes a game state g and returns an integer containing the total number of counters that the current player (g[3]) has available. This is the sum of the number of counters the player has in hand (yet to place on the board) and the number of counters they have on the board.
Task 4: checking for mills (6 marks)
Write a function is_in_mill(g, i) that takes a game state g and an integer index of a point i. The function should return:
-1 if i is outside the range 0-23 inclusive, or if there is no counter at point i
0 if the counter at point i is not in a mill,
1 if the counter at point i belongs to player 1 and is in (one or more) mills
2 if the counter at point i belongs to player 2 and is in (one or more) mills
Task 5: checking if the player can move (4 marks)
Write a function player_can_move(g) that returns True if the current player has a valid move to make, or False if they do not. A player has a valid move if they have one or more counters left in their hand, or if not, if any of their counters on the board are next to an adjacent unoccupied space.
Task 6: placing a counter (4 marks)
Write a function place_counter(g, i) that places a counter of the currently active player at point i on the board. The function should raise a RuntimeError if there is already a counter at this location. The function should update the game state g to place the counter on the board, and decrement the number of counters that the current player has in their hand.
You can assume that i will be an integer between 0 and 23 inclusive, and that this function is only called if the active player has at least one counter in their hand.
(Hint: Recall that changes to the game state g inside this function will be visible outside the function too, because g is a list. This is the desired behaviour and the functions in this and subsequent tasks should update the game state that is provided to them (and not return a new game state). You should not attempt to make a copy of the game state.)
Task 7: moving a counter (4 marks)
Write a function move_counter(g, i, j) that moves a counter of the currently active player from point i on the board to the adjacent point j. The function should raise a RuntimeError if this move is not possible, namely if points i and j are not adjacent, if point i doesn’t contain a counter of the current player, or if there is already a counter at point j. The function should update the game state to reflect the moved counter.
You can assume that i and j will be an integers between 0 and 23 inclusive.
Task 8: removing an opponent’s counter (4 marks)
Write a function remove_opponent_counter(g, i) that takes a game state g and point i and removes the counter at point i on the board. The function succeeds if the point i is occupied by a counter of the opposing player (i.e. the player who is not the current player), and raises a RuntimeError otherwise.
Task 9: taking a turn (8 marks)
Write a function turn(g) that simulates taking a turn of the game. This function takes a game state g and should do five things in sequence:
Check whether the current player is unable to move or has otherwise lost the game, and return False if so, without modifying g.
If the current player has one or more counters in hand,
ask the player for a location to place a counter on the board using request_location,
update the game state g to place this counter
If the player gives an invalid location, or the placement is not possible under the rules of the game, the player should be prompted to enter the location again, until a valid placement is made.
otherwise, the current player has no counters in hand, so,
ask the player for the location of a counter to move using request_location,
ask the player for a location to move said counter to, again using request_location.
update the game state g to move this counter
If the player gives invalid location(s), or the move is not possible under the rules of the game, the player should be prompted to enter both the locations again, until a valid move is made.
If the play in step 2 has formed a mill, ask the player for the location of an opposing counter to be removed, and update the game state g to remove this counter. As before, if an invalid location is given, the player should be re-prompted until a valid location of an opposing counter is provided.
Update the game state to switch the current player.
Return True to indicate that the game is not over yet
At appropriate points during the turn, you should display the board, using the provided draw_board() function, or otherwise.
Task 10: saving and loading a game state (8 marks)
Write a function save_state(g, filename) that saves the game state g to a text file, and a function load_state(filename) that returns a game state loaded from a text file. In both cases, the filename is given in the string filename.
Both functions should raise a RuntimeError if the file cannot be saved/loaded.
The file format for both functions is the same, so that the code
save_state(g, ‘my_file.txt’)
g_new = load_state(‘my_file.txt’)
reloads the same game state that has just been saved, and g_new is equal to g.
The text file used by both functions has four lines, in the following format:
The first line of the file contains the board state g[0], stored as integers from g[0][0] to g[0][23], separated by spaces and commas, as follows 0, 2, 1, 0,… etc. There is no comma after the final number g[0][23].
The second line of the file contains a single integer, the value of g[1]
The third line of the file contains a single integer, the value of g[2]
The fourth line of the file contains a single integer, the value of g[3]
Task 11: playing a game
Write a simple function play_game() that creates a new game state g and repeatedly calls turn(g) to simulate an entire game. Once the game is over, display some text to congratulate the winner.
The play_game function is assessed by hand, rather than by automated testing.
How this coursework will be assessed
This coursework is worth 70 marks, and 70% of the course overall.
50 marks are for correctness of the functions defined in tasks 1-9. These will be tested in an automated way, as in the previous courseworks. The number of automatically assessed marks for each task 1-10 is shown above.
8 marks will be for the overall quality of experience of a player playing the game in Task 11. This will be assessed by humans, and will be based on:
Correct behaviour of the play_game function
Clear display of the board at appropriate points in the game
Clear prompts to the players throughout the game, for example when positions on the board are being requested
Appropriate handling of errors and clear error messages when invalid input is provided
12 marks will be for the quality of the code. This will be assessed by humans, and will be based on:
Clarity and precision of docstrings for each function
Clarity of comments elsewhere in the code that describe what the code is doing
Code quality. Code should be clear, efficient and easy to maintain. Marks may be lost for
Very inefficient algorithms
Code that is unnecessarily verbose or difficult to understand
Unnecessary duplication of code
The model answer to coursework 2, to be released during week 9, provides an example of good quality code, comments and docstrings
Before you submit…
Re-read the instructions carefully and make sure you have not missed anything.
Make sure you have filled out your name, student ID number, and email at the top of the code
Be sure to submit to Blackboard just the .py file containing the python code. Do not submit the code in any other format (for example, a screenshot, PDF, jupyer notebook or word document are not acceptable formats).