Skip to content

Latest commit

 

History

History
327 lines (267 loc) · 12.9 KB

File metadata and controls

327 lines (267 loc) · 12.9 KB

六十三、乌尔皇家游戏

原文:http://inventwithpython.com/bigbookpython/project63.html

乌尔的皇家游戏是一个来自美索不达米亚的有 5000 年历史的游戏。考古学家在 1922 年至 1934 年间的挖掘过程中,在现代伊拉克南部的乌尔皇家墓地重新发现了这款游戏。这些规则是根据游戏棋盘(如图 63-1 所示)和一块巴比伦泥板重建的,它们类似于 Parcheesi。你需要运气和技巧才能赢。

f63001

图 63-1 :在乌尔皇家墓地发现的五块游戏板之一

两名玩家每人从家中的七个代币开始,第一个将所有七个代币移动到目标位置的玩家获胜。玩家轮流掷出四个骰子。这些骰子是称为四面体的四角金字塔形状。每个骰子都有两个标记点,这使得骰子有标记或无标记的机会均等。我们的游戏用硬币代替骰子,硬币的头部作为标记点。玩家可以为出现的每一个标记点移动一格代币。这意味着他们可以在 0 到 4 个空格之间移动一个代币,尽管他们最有可能掷出两个空格。

代币沿着图 63-2 中所示的路径行进。一个空间上一次只能存在一个代币。如果一个代币在共享中间路径上落在对手的代币上,对手的代币会被送回家。如果一个代币落在中间的花方格上,它就不会被落在上面。如果一个代币落在其他四个花牌中的任何一个上,玩家可以再掷一次。我们的游戏将用字母XO来代表代币。

f63002

图 63-2 :每个玩家的代币从他们的家到他们的目标的路径

www.youtube.com/watch?v=WZskjLq040I可以找到优图伯·汤姆·斯科特和大英博物馆馆长欧文·芬克尔讨论乌尔王族游戏的视频。

运行示例

当您运行royalgameofur.py时,输出将如下所示:

The Royal Game of Ur, by Al Sweigart
`--snip--`
                   XXXXXXX           .......
                   Home              Goal
                     v                 ^
+-----+-----+-----+--v--+           +--^--+-----+
|*****|     |     |     |           |*****|     |
|*   *<     <     <     |           |*   *<     |
|****h|    g|    f|    e|           |****t|    s|
+--v--+-----+-----+-----+-----+-----+-----+--^--+
|     |     |     |*****|     |     |     |     |
|     >     >     >*   *>     >     >     >     |
|    i|    j|    k|****l|    m|    n|    o|    p|
+--^--+-----+-----+-----+-----+-----+-----+--v--+
|*****|     |     |     |           |*****|     |
|*   *<     <     <     |           |*   *<     |
|****d|    c|    b|    a|           |****r|    q|
+-----+-----+-----+--^--+           +--v--+-----+
                     ^                 v
                   Home              Goal
                   OOOOOOO           .......

It is O's turn. Press Enter to flip...
Flips: H-H-H-H  Select token to move 4 spaces: home quit
> home
O landed on a flower space and gets to go again.
Press Enter to continue...
`--snip--`

工作原理

就像项目 43“曼卡拉”一样,ASCII 艺术画游戏棋盘上的空格用字母at标注。掷骰子后,玩家可以选择一个包含其代币的空间来移动代币,或者他们可以选择home开始将代币从家中移到棋盘上。该程序将棋盘表示为一个字典,其中键为'a''t',值为'X''O'用于标记(或' '用于空格)。

此外,这个字典有关键字'x_home''o_home''x_goal''o_goal',这些关键字的值是七个字符的字符串,表示家庭和目标有多满。这些字符串中的'X''O'字符代表主场或球门的代币,'.'代表空位置。displayBoard()函数在屏幕上显示这七个字符串。

"""The Royal Game of Ur, by Al Sweigart email@protected
A 5,000 year old board game from Mesopotamia. Two players knock each
other back as they race for the goal.
More info https://en.wikipedia.org/wiki/Royal_Game_of_Ur
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: large, board game, game, two-player
"""

import random, sys

X_PLAYER = 'X'
O_PLAYER = 'O'
EMPTY = ' '

# Set up constants for the space labels:
X_HOME = 'x_home'
O_HOME = 'o_home'
X_GOAL = 'x_goal'
O_GOAL = 'o_goal'

# The spaces in left to right, top to bottom order:
ALL_SPACES = 'hgfetsijklmnopdcbarq'
X_TRACK = 'HefghijklmnopstG'  # (H stands for Home, G stands for Goal.)
O_TRACK = 'HabcdijklmnopqrG'

FLOWER_SPACES = ('h', 't', 'l', 'd', 'r')

BOARD_TEMPLATE = """
{}  {} 30\.                   Home              Goal
                    v                 ^
+-----+-----+-----+--v--+           +--^--+-----+
|*****|     |     |     |           |*****|     |
|* {} *< {} < {} < {} |           |* {} *< {} |
|****h|    g|    f|    e|           |****t|    s|
+--v--+-----+-----+-----+-----+-----+-----+--^--+
|     |     |     |*****|     |     |     |     |
| {} > {} > {} >* {} *> {} > {} > {} > {} |
|    i|    j|    k|****l|    m|    n|    o|    p|
+--^--+-----+-----+-----+-----+-----+-----+--v--+
|*****|     |     |     |           |*****|     |
|* {} *< {} < {} < {} |           |* {} *< {} |
|****d|    c|    b|    a|           |****r|    q|
+-----+-----+-----+--^--+           +--v--+-----+
                    ^                 v
                  Home              Goal
{}  {} 48\. """


def main():
   print('''The Royal Game of Ur, by Al Sweigart

This is a 5,000 year old game. Two players must move their tokens
from their home to their goal. On your turn you flip four coins and can
move one token a number of spaces equal to the heads you got.

Ur is a racing game; the first player to move all seven of their tokens
to their goal wins. To do this, tokens must travel from their home to
their goal:

           X Home      X Goal
             v           ^
+---+---+---+-v-+       +-^-+---+
|v<<<<<<<<<<<<< |       | ^<|<< |
|v  |   |   |   |       |   | ^ |
+v--+---+---+---+---+---+---+-^-+
|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>^ |
|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>v |
+^--+---+---+---+---+---+---+-v-+
|^  |   |   |   |       |   | v |
|^<<<<<<<<<<<<< |       | v<<<< |
+---+---+---+-^-+       +-v-+---+
             ^           v
           O Home      O Goal

If you land on an opponent's token in the middle track, it gets sent
back home. The **flower** spaces let you take another turn. Tokens in
the middle flower space are safe and cannot be landed on.''')
   input('Press Enter to begin...')

   gameBoard = getNewBoard()
   turn = O_PLAYER
   while True:  # Main game loop.
       # Set up some variables for this turn:
       if turn == X_PLAYER:
           opponent = O_PLAYER
           home = X_HOME
           track = X_TRACK
           goal = X_GOAL
           opponentHome = O_HOME
       elif turn == O_PLAYER:
           opponent = X_PLAYER
           home = O_HOME
           track = O_TRACK
           goal = O_GOAL
           opponentHome = X_HOME

       displayBoard(gameBoard)

        input('It is ' + turn + '\'s turn. Press Enter to flip...')

        flipTally = 0
        print('Flips: ', end='')
        for i in range(4):  # Flip 4 coins.
            result = random.randint(0, 1)
            if result == 0:
                print('T', end='')  # Tails.
            else:
                print('H', end='')  # Heads.
            if i != 3:
                print('-', end='')  # Print separator.
            flipTally += result
        print('  ', end='')

        if flipTally == 0:
            input('You lose a turn. Press Enter to continue...')
            turn = opponent  # Swap turns to the other player.
            continue

        # Ask the player for their move:
        validMoves = getValidMoves(gameBoard, turn, flipTally)

        if validMoves == []:
            print('There are no possible moves, so you lose a turn.')
            input('Press Enter to continue...')
            turn = opponent  # Swap turns to the other player.
            continue

        while True:
            print('Select move', flipTally, 'spaces: ', end='')
            print(' '.join(validMoves) + ' quit')
            move = input('> ').lower()

            if move == 'quit':
                print('Thanks for playing!')
                sys.exit()
            if move in validMoves:
                break  # Exit the loop when a valid move is selected.

            print('That is not a valid move.')

        # Perform the selected move on the board:
        if move == 'home':
            # Subtract tokens at home if moving from home:
            gameBoard[home] -= 1
            nextTrackSpaceIndex = flipTally
        else:
            gameBoard[move] = EMPTY  # Set the "from" space to empty.
            nextTrackSpaceIndex = track.index(move) + flipTally

        movingOntoGoal = nextTrackSpaceIndex == len(track) - 1
        if movingOntoGoal:
            gameBoard[goal] += 1
            # Check if the player has won:
            if gameBoard[goal] == 7:
                displayBoard(gameBoard)
                print(turn, 'has won the game!')
                print('Thanks for playing!')
                sys.exit()
        else:
            nextBoardSpace = track[nextTrackSpaceIndex]
            # Check if the opponent has a tile there:
            if gameBoard[nextBoardSpace] == opponent:
                gameBoard[opponentHome] += 1

            # Set the "to" space to the player's token:
            gameBoard[nextBoardSpace] = turn

        # Check if the player landed on a flower space and can go again:
        if nextBoardSpace in FLOWER_SPACES:
            print(turn, 'landed on a flower space and goes again.')
            input('Press Enter to continue...')
        else:
            turn = opponent  # Swap turns to the other player.

def getNewBoard():
    """
    Returns a dictionary that represents the state of the board. The
    keys are strings of the space labels, the values are X_PLAYER,
    O_PLAYER, or EMPTY. There are also counters for how many tokens are
    at the home and goal of both players.
    """
    board = {X_HOME: 7, X_GOAL: 0, O_HOME: 7, O_GOAL: 0}
    # Set each space as empty to start:
    for spaceLabel in ALL_SPACES:
        board[spaceLabel] = EMPTY
    return board


def displayBoard(board):
    """Display the board on the screen."""
    # "Clear" the screen by printing many newlines, so the old
    # board isn't visible anymore.
    print('\n' * 60)

    xHomeTokens = ('X' * board[X_HOME]).ljust(7, '.')
    xGoalTokens = ('X' * board[X_GOAL]).ljust(7, '.')
    oHomeTokens = ('O' * board[O_HOME]).ljust(7, '.')
    oGoalTokens = ('O' * board[O_GOAL]).ljust(7, '.')

    # Add the strings that should populate BOARD_TEMPLATE in order,
    # going from left to right, top to bottom.
    spaces = []
    spaces.append(xHomeTokens)
    spaces.append(xGoalTokens)
    for spaceLabel in ALL_SPACES:
        spaces.append(board[spaceLabel])
    spaces.append(oHomeTokens)
    spaces.append(oGoalTokens)

    print(BOARD_TEMPLATE.format(*spaces))


def getValidMoves(board, player, flipTally):
    validMoves = []  # Contains the spaces with tokens that can move.
    if player == X_PLAYER:
        opponent = O_PLAYER
        track = X_TRACK
        home = X_HOME
    elif player == O_PLAYER:
        opponent = X_PLAYER
        track = O_TRACK
        home = O_HOME

    # Check if the player can move a token from home:
    if board[home] > 0 and board[track[flipTally]] == EMPTY:
        validMoves.append('home')

    # Check which spaces have a token the player can move:
    for trackSpaceIndex, space in enumerate(track):
        if space == 'H' or space == 'G' or board[space] != player:
            continue
        nextTrackSpaceIndex = trackSpaceIndex + flipTally
        if nextTrackSpaceIndex >= len(track):
            # You must flip an exact number of moves onto the goal,
            # otherwise you can't move on the goal.
            continue
        else:
            nextBoardSpaceKey = track[nextTrackSpaceIndex]
            if nextBoardSpaceKey == 'G':
                # This token can move off the board:
                validMoves.append(space)
                continue
        if board[nextBoardSpaceKey] in (EMPTY, opponent):
            # If the next space is the protected middle space, you
            # can only move there if it is empty:
            if nextBoardSpaceKey == 'l' and board['l'] == opponent:
                continue  # Skip this move, the space is protected.
            validMoves.append(space)

    return validMoves


if __name__ == '__main__':
    main() 

探索程序

试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。

  1. 如果把 152 行的nextTrackSpaceIndex == len(track) - 1改成nextTrackSpaceIndex == 1会怎么样?
  2. 如果把 106 行的result = random.randint(0, 1)改成result = 1会怎么样?
  3. 如果把 184 行的board = {X_HOME: 7, X_GOAL: 0, O_HOME: 7, O_GOAL: 0}改成board = {}会导致什么错误?