乌尔的皇家游戏是一个来自美索不达米亚的有 5000 年历史的游戏。考古学家在 1922 年至 1934 年间的挖掘过程中,在现代伊拉克南部的乌尔皇家墓地重新发现了这款游戏。这些规则是根据游戏棋盘(如图 63-1 所示)和一块巴比伦泥板重建的,它们类似于 Parcheesi。你需要运气和技巧才能赢。
图 63-1 :在乌尔皇家墓地发现的五块游戏板之一
两名玩家每人从家中的七个代币开始,第一个将所有七个代币移动到目标位置的玩家获胜。玩家轮流掷出四个骰子。这些骰子是称为四面体的四角金字塔形状。每个骰子都有两个标记点,这使得骰子有标记或无标记的机会均等。我们的游戏用硬币代替骰子,硬币的头部作为标记点。玩家可以为出现的每一个标记点移动一格代币。这意味着他们可以在 0 到 4 个空格之间移动一个代币,尽管他们最有可能掷出两个空格。
代币沿着图 63-2 中所示的路径行进。一个空间上一次只能存在一个代币。如果一个代币在共享中间路径上落在对手的代币上,对手的代币会被送回家。如果一个代币落在中间的花方格上,它就不会被落在上面。如果一个代币落在其他四个花牌中的任何一个上,玩家可以再掷一次。我们的游戏将用字母X和O来代表代币。
图 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 艺术画游戏棋盘上的空格用字母a到t标注。掷骰子后,玩家可以选择一个包含其代币的空间来移动代币,或者他们可以选择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() 试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。
- 如果把 152 行的
nextTrackSpaceIndex == len(track) - 1改成nextTrackSpaceIndex == 1会怎么样? - 如果把 106 行的
result = random.randint(0, 1)改成result = 1会怎么样? - 如果把 184 行的
board = {X_HOME: 7, X_GOAL: 0, O_HOME: 7, O_GOAL: 0}改成board = {}会导致什么错误?


