Nine Men‘s Morris

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:

在这里插入图片描述

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 , has such a mill, but player 1, represented by , does not). Such removed counters play no further part in the game. This continues until both players have placed all their counters.OX

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:

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 containing four elements,g

g = [[…], p1_counters, p2_counters, active_player]
g[0] is itself a list of 24 integers, describing the board, with the th element of this list, describing what is present at point on the board. is:ig[0][i]ig[0][i]
0 if point is unoccupiedi
1 if point contains a counter of player 1i
2 if point contains a counter of player 2i
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 is provided for this purpose.request_location(question_str)

This function

displays the parameter string that is passed to it, as a prompt to the userquestion_str
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 function anywhere else in your code will result in the loss of marks.input(…)

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 that takes two integers representing points on the board, and returns the Boolean value if those two points are adjacent to one another (connected by a line without going through another point), or otherwise. A point is not adjacent to itself. For example should return , and and should both return .is_adjacent(i, j)TrueFalseis_adjacent(21,22)Trueis_adjacent(21, 23)is_adjacent(21,20)False

You can assume that the values of and provided are integers between 0 and 23 inclusive.ij

Task 2: setting up the game state (4 marks)
Write a function that returns the game state (the list of four elements, as described above), for a new game in which no counters have been placed.new_game()

Task 3: counting counters (4 marks)
Write a function that takes a game state and returns an integer containing the total number of counters that the current player () 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.remaining_counters(g)gg[3]

Task 4: checking for mills (6 marks)
Write a function that takes a game state and an integer index of a point . The function should return:is_in_mill(g, i)gi

-1 if is outside the range 0-23 inclusive, or if there is no counter at point ii
0 if the counter at point is not in a mill,i
1 if the counter at point belongs to player 1 and is in (one or more) millsi
2 if the counter at point belongs to player 2 and is in (one or more) millsi
Task 5: checking if the player can move (4 marks)
Write a function that returns if the current player has a valid move to make, or 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.player_can_move(g)TrueFalse

Task 6: placing a counter (4 marks)
Write a function that places a counter of the currently active player at point on the board. The function should raise a if there is already a counter at this location. The function should update the game state to place the counter on the board, and decrement the number of counters that the current player has in their hand.place_counter(g, i)iRuntimeErrorg

You can assume that 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.i

(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 that moves a counter of the currently active player from point on the board to the adjacent point . The function should raise a if this move is not possible, namely if points and are not adjacent, if point doesn’t contain a counter of the current player, or if there is already a counter at point . The function should update the game state to reflect the moved counter.move_counter(g, i, j)ijRuntimeErrorijij

You can assume that and will be an integers between 0 and 23 inclusive.ij

Task 8: removing an opponent’s counter (4 marks)
Write a function that takes a game state and point and removes the counter at point on the board. The function succeeds if the point is occupied by a counter of the opposing player (i.e. the player who is not the current player), and raises a otherwise.remove_opponent_counter(g, i)giiiRuntimeError

Task 9: taking a turn (8 marks)
Write a function that simulates taking a turn of the game. This function takes a game state and should do five things in sequence:turn(g)g

Check whether the current player is unable to move or has otherwise lost the game, and return if so, without modifying .Falseg

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 to place this counterg
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 to move this counterg
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 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.g

Update the game state to switch the current player.

Return to indicate that the game is not over yetTrue

At appropriate points during the turn, you should display the board, using the provided function, or otherwise.draw_board()

Task 10: saving and loading a game state (8 marks)
Write a function that saves the game state to a text file, and a function that returns a game state loaded from a text file. In both cases, the filename is given in the string .save_state(g, filename)gload_state(filename)filename

Both functions should raise a if the file cannot be saved/loaded.RuntimeError

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 is equal to .g_newg

The text file used by both functions has four lines, in the following format:

The first line of the file contains the board state , stored as integers from to , separated by spaces and commas, as follows … etc. There is no comma after the final number .g[0]g[0][0]g[0][23]0, 2, 1, 0,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 that creates a new game state and repeatedly calls to simulate an entire game. Once the game is over, display some text to congratulate the winner.play_game()gturn(g)

The function is assessed by hand, rather than by automated testing.play_game

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 functionplay_game
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


def place_counter(g, i):
    """
     places a counter of the currently active player at point on the board.
     The function should raise a if there is already a counter at this location.
     The function should update the game state to place the counter on the board,
     and decrement the number of counters that the current player has in their hand.
    """
    if g[0][i] != 0:
        raise RuntimeError("There is already a counter at this location")
    # Set the point to the current player's counter
    g[0][i] = g[3]
    # Decrement the counter from the player's hand
    g[g[3]] -= 1


def move_counter(g, i, j):
    """
    moves a counter of the currently active player from point on the board to the adjacent point .
    The function should raise a if this move is not possible, namely if points and are not adjacent,
    if point doesn't contain a counter of the current player, or if there is already a counter at point
    The function should update the game state to reflect the moved counter.
    """
    # Check if the specified points are adjacent and valid
    if not is_adjacent(i, j) or g[0][i] != g[3] or g[0][j] != 0:
        raise RuntimeError("Invalid move: cannot move the counter to this location")

    # Move the counter from point i to point j for the current player
    g[0][i] = 0
    g[0][j] = g[3]


def remove_opponent_counter(g, i):
    """
    Takes a game state and point and removes the counter at point on the board.
    The function succeeds if the point is occupied by a counter of the opposing player
    (i.e. the player who is not the current player), and raises a otherwise
    """
    # Check if the specified point contains an opponent's counter
    if g[0][i] != 3 - g[3] or g[0][i] == 0:
        raise RuntimeError("Invalid move: no opponent's counter to remove at this location")

    # Remove the opponent's counter from the specified point
    g[0][i] = 0


def turn(g):
    """
    Simulates taking a turn of the game.
    1. Check whether the current player is unable to move or has otherwise lost the game, return false.
    2. 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 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 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.
    3. 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 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.
    4. Update the game state to switch the current player.
    5. Return to indicate that the game is not over yet True
    At appropriate points during the turn, you should display the board,
    using the provided function, or otherwise.draw_board()
    """
    # Check if the current player has lost the game or unable to move
    if not player_can_move(g):
        return False

    # Get the current player and display the board
    current_player = g[3]
    draw_board(g)

    # Check if the current player has counters in hand to place on the board
    if g[current_player] > 0:
        while True:
            try:
                location = request_location(f"Player {current_player}, place a counter (0-23): ")
                place_counter(g, location)
                # If the play has formed a mill, ask the player for the location of an opposing counter to be removed
                if is_in_mill(g, location):
                    while True:
                        try:
                            remove_location = request_location(
                                f"Player {current_player}, choose an opponent's counter to remove: ")
                            remove_opponent_counter(g, remove_location)
                            break
                        except RuntimeError as e:
                            print(e)
                            continue
                break
            except RuntimeError as e:
                print(e)
                continue
    else:
        # Ask the player for the location of a counter to move
        while True:
            try:
                start = request_location(f"Player {current_player}, select a counter to move (0-23): ")
                end = request_location(f"Player {current_player}, move the counter to (0-23): ")
                move_counter(g, start, end)
                # If the play has formed a mill
                if is_in_mill(g, end):
                    while True:
                        try:
                            remove_location = request_location(
                                f"Player {current_player}, choose an opponent's counter to remove: ")
                            remove_opponent_counter(g, remove_location)
                            break
                        except RuntimeError as e:
                            print(e)
                            continue
                break
            except RuntimeError as e:
                print(e)
                continue

    # Switch the player after the turn
    g[3] = 3 - current_player
    return True


def save_state(g, filename):
    """
    The first line of the file contains the board state
    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]
    """
    with open(filename, 'w', encoding='utf-8') as file:
        file.write(','.join(map(str, g[0])) + '\n')
        file.write(str(g[1]) + '\n')
        file.write(str(g[2]) + '\n')
        file.write(str(g[3]))


def load_state(filename):
    """
    g_new = load_state('my_file.txt')
    """
    loaded_state = []

    # Open file
    with open(filename, 'r', encoding='utf-8') as file:
        # Read each line from the file and append it to the loaded_state list
        for line in file:
            loaded_state.append(list(map(int, line.strip().split(','))))

    return loaded_state


def play_game():
    """
    Creates a new game state and repeatedly calls to simulate an entire game.
    Once the game is over, display some text to congratulate the winner.
    """
    # Start a new game
    game_state = new_game()

    while True:

        game_over = not turn(game_state)
        draw_board(game_state)

        # If the game is over, display the winner
        if game_over:
            winner = 3 - game_state[3]  # the winner is the current player
            print(f"Player {winner} wins!")
            break
  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值