# 使用 Python 创造你自己的计算机游戏（游戏编程快速上手）第四版：第十五章到第十八章

## 十五、反转棋游戏

• 如何玩反转棋
• bool()函数
• 在反转棋棋盘上模拟移动
• 编写反转棋 AI

#### 反转棋的示例运行

Welcome to Reversegam!
Do you want to be X or O?
x
The player will go first.
12345678
+--------+
1|        |1
2|        |2
3|        |3
4|   XO   |4
5|   OX   |5
6|        |6
7|        |7
8|        |8
+--------+
12345678
You: 2 points. Computer: 2 points.
Enter your move, "quit" to end the game, or "hints" to toggle hints.
53
12345678
+--------+
1|        |1
2|        |2
3|    X   |3
4|   XX   |4
5|   OX   |5
6|        |6
7|        |7
8|        |8
+--------+
12345678
You: 4 points. Computer: 1 points.
Press Enter to see the computer's move.
--snip--
12345678
+--------+
1|OOOOOOOO|1
2|OXXXOOOO|2
3|OXOOOOOO|3
4|OXXOXXOX|4
5|OXXOOXOX|5
6|OXXXXOOX|6
7|OOXXOOOO|7
8|OOXOOOOO|8
+--------+
12345678
X scored 21 points. O scored 43 points.
You lost. The computer beat you by 22 points.
Do you want to play again? (yes or no)
no

12345678
+--------+
1|        |1
2|   .    |2
3|  XO.   |3
4|   XOX  |4
5|   OOO  |5
6|   . .  |6
7|        |7
8|        |8
+--------+
12345678

#### 反转棋的源代码

reversegam.py

# Reversegam: a clone of Othello/Reversi
import random
import sys
WIDTH = 8 # Board is 8 spaces wide.
HEIGHT = 8 # Board is 8 spaces tall.
def drawBoard(board):
# Print the board passed to this function. Return None.
print('  12345678')
print(' +--------+')
for y in range(HEIGHT):
print('%s|' % (y+1), end='')
for x in range(WIDTH):
print(board[x][y], end='')
print('|%s' % (y+1))
print(' +--------+')
print('  12345678')
def getNewBoard():
# Create a brand-new, blank board data structure.
board = []
for i in range(WIDTH):
board.append([' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '])
return board
def isValidMove(board, tile, xstart, ystart):
# Return False if the player's move on space xstart, ystart is
invalid.
# If it is a valid move, return a list of spaces that would become
the player's if they made a move here.
if board[xstart][ystart] != ' ' or not isOnBoard(xstart, ystart):
return False
if tile == 'X':
otherTile = 'O'
else:
otherTile = 'X'
tilesToFlip = []
for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1],
[0, -1], [-1, -1], [-1, 0], [-1, 1]]:
x, y = xstart, ystart
x += xdirection # First step in the x direction
y += ydirection # First step in the y direction
while isOnBoard(x, y) and board[x][y] == otherTile:
# Keep moving in this x & y direction.
x += xdirection
y += ydirection
if isOnBoard(x, y) and board[x][y] == tile:
# There are pieces to flip over. Go in the reverse
direction until we reach the original space, noting all
the tiles along the way.
while True:
x -= xdirection
y -= ydirection
if x == xstart and y == ystart:
break
tilesToFlip.append([x, y])
if len(tilesToFlip) == 0: # If no tiles were flipped, this is not a
valid move.
return False
return tilesToFlip
def isOnBoard(x, y):
# Return True if the coordinates are located on the board.
return x >= 0 and x <= WIDTH - 1 and y >= 0 and y <= HEIGHT - 1
def getBoardWithValidMoves(board, tile):
# Return a new board with periods marking the valid moves the player
can make.
boardCopy = getBoardCopy(board)
for x, y in getValidMoves(boardCopy, tile):
boardCopy[x][y] = '.'
return boardCopy
def getValidMoves(board, tile):
# Return a list of [x,y] lists of valid moves for the given player
on the given board.
validMoves = []
for x in range(WIDTH):
for y in range(HEIGHT):
if isValidMove(board, tile, x, y) != False:
validMoves.append([x, y])
return validMoves
def getScoreOfBoard(board):
# Determine the score by counting the tiles. Return a dictionary
with keys 'X' and 'O'.
xscore = 0
oscore = 0
for x in range(WIDTH):
for y in range(HEIGHT):
if board[x][y] == 'X':
xscore += 1
if board[x][y] == 'O':
oscore += 1
return {'X':xscore, 'O':oscore}
def enterPlayerTile():
# Let the player enter which tile they want to be.
# Return a list with the player's tile as the first item and the
computer's tile as the second.
tile = ''
while not (tile == 'X' or tile == 'O'):
print('Do you want to be X or O?')
tile = input().upper()
# The first element in the list is the player's tile, and the second
is the computer's tile.
if tile == 'X':
return ['X', 'O']
else:
return ['O', 'X']
def whoGoesFirst():
# Randomly choose who goes first.
if random.randint(0, 1) == 0:
return 'computer'
else:
return 'player'
def makeMove(board, tile, xstart, ystart):
# Place the tile on the board at xstart, ystart and flip any of the
opponent's pieces.
# Return False if this is an invalid move; True if it is valid.
tilesToFlip = isValidMove(board, tile, xstart, ystart)
if tilesToFlip == False:
return False
board[xstart][ystart] = tile
for x, y in tilesToFlip:
board[x][y] = tile
return True
def getBoardCopy(board):
# Make a duplicate of the board list and return it.
boardCopy = getNewBoard()
for x in range(WIDTH):
for y in range(HEIGHT):
boardCopy[x][y] = board[x][y]
return boardCopy
def isOnCorner(x, y):
# Return True if the position is in one of the four corners.
return (x == 0 or x == WIDTH - 1) and (y == 0 or y == HEIGHT - 1)
def getPlayerMove(board, playerTile):
# Let the player enter their move.
# Return the move as [x, y] (or return the strings 'hints' or
'quit').
DIGITS1TO8 = '1 2 3 4 5 6 7 8'.split()
while True:
print('Enter your move, "quit" to end the game, or "hints" to
toggle hints.')
move = input().lower()
if move == 'quit' or move == 'hints':
return move
if len(move) == 2 and move[0] in DIGITS1TO8 and move[1] in
DIGITS1TO8:
x = int(move[0]) - 1
y = int(move[1]) - 1
if isValidMove(board, playerTile, x, y) == False:
continue
else:
break
else:
print('That is not a valid move. Enter the column (1-8) and
then the row (1-8).')
print('For example, 81 will move on the top-right corner.')
return [x, y]
def getComputerMove(board, computerTile):
# Given a board and the computer's tile, determine where to
# move and return that move as an [x, y] list.
possibleMoves = getValidMoves(board, computerTile)
random.shuffle(possibleMoves) # Randomize the order of the moves.
# Always go for a corner if available.
for x, y in possibleMoves:
if isOnCorner(x, y):
return [x, y]
# Find the highest-scoring move possible.
bestScore = -1
for x, y in possibleMoves:
boardCopy = getBoardCopy(board)
makeMove(boardCopy, computerTile, x, y)
score = getScoreOfBoard(boardCopy)[computerTile]
if score > bestScore:
bestMove = [x, y]
bestScore = score
return bestMove
def printScore(board, playerTile, computerTile):
scores = getScoreOfBoard(board)
print('You: %s points. Computer: %s points.' % (scores[playerTile],
scores[computerTile]))
def playGame(playerTile, computerTile):
showHints = False
turn = whoGoesFirst()
print('The ' + turn + ' will go first.')
# Clear the board and place starting pieces.
board = getNewBoard()
board[3][3] = 'X'
board[3][4] = 'O'
board[4][3] = 'O'
board[4][4] = 'X'
while True:
playerValidMoves = getValidMoves(board, playerTile)
computerValidMoves = getValidMoves(board, computerTile)
if playerValidMoves == [] and computerValidMoves == []:
return board # No one can move, so end the game.
elif turn == 'player': # Player's turn
if playerValidMoves != []:
if showHints:
validMovesBoard = getBoardWithValidMoves(board,
playerTile)
drawBoard(validMovesBoard)
else:
drawBoard(board)
printScore(board, playerTile, computerTile)
move = getPlayerMove(board, playerTile)
if move == 'quit':
print('Thanks for playing!')
sys.exit() # Terminate the program.
elif move == 'hints':
showHints = not showHints
continue
else:
makeMove(board, playerTile, move[0], move[1])
turn = 'computer'
elif turn == 'computer': # Computer's turn
if computerValidMoves != []:
drawBoard(board)
printScore(board, playerTile, computerTile)
input('Press Enter to see the computer\'s move.')
move = getComputerMove(board, computerTile)
makeMove(board, computerTile, move[0], move[1])
turn = 'player'
print('Welcome to Reversegam!')
playerTile, computerTile = enterPlayerTile()
while True:
finalBoard = playGame(playerTile, computerTile)
# Display the final score.
drawBoard(finalBoard)
scores = getScoreOfBoard(finalBoard)
print('X scored %s points. O scored %s points.' % (scores['X'],
scores['O']))
if scores[playerTile] > scores[computerTile]:
print('You beat the computer by %s points! Congratulations!' %
(scores[playerTile] - scores[computerTile]))
elif scores[playerTile] < scores[computerTile]:
print('You lost. The computer beat you by %s points.' %
(scores[computerTile] - scores[playerTile]))
else:
print('The game was a tie!')
print('Do you want to play again? (yes or no)')
if not input().lower().startswith('y'):
break

#### 导入模块和设置常量

# Reversegam: a clone of Othello/Reversi
import random
import sys
WIDTH = 8  # Board is 8 spaces wide.
HEIGHT = 8 # Board is 8 spaces tall.

#### 游戏棋盘数据结构

##### 在屏幕上绘制棋盘数据结构

def drawBoard(board):
# Print the board passed to this function. Return None.
print('  12345678')
print(' +--------+')
for y in range(HEIGHT):
print('%s|' % (y+1), end='')
for x in range(WIDTH):
print(board[x][y], end='')
print('|%s' % (y+1))
print(' +--------+')
print('  12345678')

drawBoard()函数根据board中的数据结构打印当前游戏棋盘。

12345678
+--------+
1|XXXXXXXX|1
2|XXXXXXXX|2
3|XXXXXXXX|3
4|XXXXXXXX|4
5|XXXXXXXX|5
6|XXXXXXXX|6
7|XXXXXXXX|7
8|XXXXXXXX|8
+--------+
12345678

##### 创建一个新的棋盘数据结构

drawBoard()函数将在屏幕上显示一个棋盘数据结构，但我们也需要一种方法来创建这些棋盘数据结构。getNewBoard()函数返回一个包含八个列表的列表，每个列表包含八个' '字符串，表示一个没有移动的空白棋盘：

def getNewBoard():
# Create a brand-new, blank board data structure.
board = []
for i in range(WIDTH):
board.append([' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '])
return board

#### 检查移动是否有效

def isValidMove(board, tile, xstart, ystart):
# Return False if the player's move on space xstart, ystart is
invalid.
# If it is a valid move, return a list of spaces that would become
the player's if they made a move here.
if board[xstart][ystart] != ' ' or not isOnBoard(xstart, ystart):
return False
if tile == 'X':
otherTile = 'O'
else:
otherTile = 'X'
tilesToFlip = []

##### 检查每个八个方向

for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1],
[0, -1], [-1, -1], [-1, 0], [-1, 1]]:

for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1],
[0, -1], [-1, -1], [-1, 0], [-1, 1]]:
x, y = xstart, ystart
x += xdirection # First step in the x direction
y += ydirection # First step in the y direction

xstartystart变量将保持不变，以便程序可以记住它最初从哪个空格开始。

while isOnBoard(x, y) and board[x][y] == otherTile:
# Keep moving in this x & y direction.
x += xdirection
y += ydirection

##### 找出是否有可以翻转的棋子

if isOnBoard(x, y) and board[x][y] == tile:
# There are pieces to flip over. Go in the reverse
direction until we reach the original space, noting all
the tiles along the way.
while True:
x -= xdirection
y -= ydirection
if x == xstart and y == ystart:
break
tilesToFlip.append([x, y])

while循环在第 48 和 49 行反向移动xy。直到xy回到原始的xstartystart位置，xdirectionydirectionxy中减去，并且每个xy位置都被附加到tilesToFlip列表中。当xy达到xstartystart位置时，第 51 行中断了循环的执行。由于原始的xstartystart位置是一个空格（我们确保这是在第 28 和 29 行的情况下），因此第 41 行while循环的条件将是False。程序继续执行到第 37 行，for循环检查下一个方向。

for循环在所有八个方向上执行此操作。循环结束后，tilesToFlip列表将包含我们所有对手的棋子的 x 和 y 坐标，如果玩家在xstartystart上移动，这些棋子将被翻转。记住，isValidMove()函数只是检查原始移动是否有效；它实际上不会永久改变游戏棋盘的数据结构。

if len(tilesToFlip) == 0: # If no tiles were flipped, this is not a
valid move.
return False
return tilesToFlip

#### 检查有效坐标

isOnBoard()函数从isValidMove()中调用。它简单地检查给定的 x 和 y 坐标是否在棋盘上。例如，x 坐标为4，y 坐标为9999将不在棋盘上，因为 y 坐标只能到7，这等于WIDTH - 1HEIGHT - 1

def isOnBoard(x, y):
# Return True if the coordinates are located on the board.
return x >= 0 and x <= WIDTH - 1 and y >= 0 and y <= HEIGHT - 1

##### 获取所有有效移动的列表

def getBoardWithValidMoves(board, tile):
# Return a new board with periods marking the valid moves the player
can make.
boardCopy = getBoardCopy(board)
for x, y in getValidMoves(boardCopy, tile):
boardCopy[x][y] = '.'
return boardCopy

getValidMoves()函数返回一个两项列表的列表。这两项列表保存了board参数中给定的tile的所有有效移动的 x 和 y 坐标：

def getValidMoves(board, tile):
# Return a list of [x,y] lists of valid moves for the given player
on the given board.
validMoves = []
for x in range(WIDTH):
for y in range(HEIGHT):
if isValidMove(board, tile, x, y) != False:
validMoves.append([x, y])
return validMoves

##### 调用 bool()函数

bool()函数类似于int()str()函数。它返回传递给它的值的布尔值形式。

>>> bool(0)
False
>>> bool(0.0)
False
>>> bool('')
False
>>> bool([])
False
>>> bool({})
False
>>> bool(1)
True
>>> bool('Hello')
True
>>> bool([1, 2, 3, 4, 5])
True
>>> bool({'spam':'cheese', 'fizz':'buzz'})
True

#### 获取游戏板的分数

getScoreOfBoard()函数使用嵌套的for循环来检查板上的所有 64 个位置，并查看哪个玩家的瓷砖（如果有）在上面：

def getScoreOfBoard(board):
# Determine the score by counting the tiles. Return a dictionary
with keys 'X' and 'O'.
xscore = 0
oscore = 0
for x in range(WIDTH):
for y in range(HEIGHT):
if board[x][y] == 'X':
xscore += 1
if board[x][y] == 'O':
oscore += 1
return {'X':xscore, 'O':oscore}

#### 获取玩家的选择

enterPlayerTile()函数询问玩家想要成为哪种瓷砖，XO

def enterPlayerTile():
# Let the player enter which tile they want to be.
# Return a list with the player's tile as the first item and the
computer's tile as the second.
tile = ''
while not (tile == 'X' or tile == 'O'):
print('Do you want to be X or O?')
tile = input().upper()
# The first element in the list is the player's tile, and the second
is the computer's tile.
if tile == 'X':
return ['X', 'O']
else:
return ['O', 'X']

for循环将一直循环，直到玩家输入大写或小写的XO。然后enterPlayerTile()函数返回一个两项列表，玩家的选择是第一项，计算机的选择是第二项。稍后，第 241 行调用enterPlayerTile()并使用多重赋值将这两个返回的项放入两个变量中。

#### 确定谁先走

whoGoesFirst()函数随机选择谁先走，并返回字符串'computer'或字符串'player'

def whoGoesFirst():
# Randomly choose who goes first.
if random.randint(0, 1) == 0:
return 'computer'
else:
return 'player'

#### 在板上放置一个瓷砖

def makeMove(board, tile, xstart, ystart):
# Place the tile on the board at xstart, ystart and flip any of the
opponent's pieces.
# Return False if this is an invalid move; True if it is valid.
tilesToFlip = isValidMove(board, tile, xstart, ystart)

if tilesToFlip == False:
return False
board[xstart][ystart] = tile
for x, y in tilesToFlip:
board[x][y] = tile
return True

#### 复制棋盘数据结构

getBoardCopy()函数与getNewBoard()不同。getNewBoard()函数创建一个只有空格和四个起始瓷砖的空白游戏棋盘数据结构。getBoardCopy()创建一个空白游戏棋盘数据结构，然后使用嵌套循环将board参数中的所有位置复制到副本棋盘数据结构中。AI 使用getBoardCopy()函数，以便可以对游戏棋盘副本进行更改，而不会改变真实的游戏棋盘。这种技术也被 Tic-Tac-Toe 程序在第 10 章中使用过。

def getBoardCopy(board):
# Make a duplicate of the board list and return it.
boardCopy = getNewBoard()
for x in range(WIDTH):
for y in range(HEIGHT):
boardCopy[x][y] = board[x][y]
return boardCopy

#### 确定空间是否在角落

isOnCorner()函数返回True，如果坐标位于角落空间，坐标为(0, 0)，(7, 0)，(0, 7)，或(7, 7)：

def isOnCorner(x, y):
# Return True if the position is in one of the four corners.
return (x == 0 or x == WIDTH - 1) and (y == 0 or y == HEIGHT - 1)

#### 获取玩家的移动

def getPlayerMove(board, playerTile):
# Let the player enter their move.
# Return the move as [x, y] (or return the strings 'hints' or
'quit').
DIGITS1TO8 = '1 2 3 4 5 6 7 8'.split()

DIGITS1TO8常量变量是列表['1', '2', '3', '4', '5', '6', '7', '8']getPlayerMove()函数多次使用DIGITS1TO8，这个常量比完整的列表值更易读。不能使用isdigit()方法，因为这将允许输入 0 和 9，这在 8×8 棋盘上是无效的坐标。

while循环会一直循环，直到玩家输入有效的移动：

while True:
print('Enter your move, "quit" to end the game, or "hints" to
toggle hints.')
move = input().lower()
if move == 'quit' or move == 'hints':
return move

if len(move) == 2 and move[0] in DIGITS1TO8 and move[1] in
DIGITS1TO8:
x = int(move[0]) - 1
y = int(move[1]) - 1
if isValidMove(board, playerTile, x, y) == False:
continue
else:
break

else:
print('That is not a valid move. Enter the column (1-8) and
then the row (1-8).')
print('For example, 81 will move on the top-right corner.')

return [x, y]

#### 获取计算机的移动

getComputerMove()函数是实现 AI 算法的地方：

def getComputerMove(board, computerTile):
# Given a board and the computer's tile, determine where to
# move and return that move as an [x, y] list.
possibleMoves = getValidMoves(board, computerTile)

random.shuffle(possibleMoves) # Randomize the order of the moves.

##### 使用角落移动进行策略

# Always go for a corner if available.
for x, y in possibleMoves:
if isOnCorner(x, y):
return [x, y]

##### 获取最高得分移动的列表

# Find the highest-scoring move possible.
bestScore = -1
for x, y in possibleMoves:
boardCopy = getBoardCopy(board)
makeMove(boardCopy, computerTile, x, y)
score = getScoreOfBoard(boardCopy)[computerTile]
if score > bestScore:
bestMove = [x, y]
bestScore = score
return bestMove

#### 将分数打印到屏幕上

showPoints()函数调用getScoreOfBoard()函数，然后打印玩家和计算机的分数：

def printScore(board, playerTile, computerTile):
scores = getScoreOfBoard(board)
print('You: %s points. Computer: %s points.' % (scores[playerTile],
scores[computerTile]))

#### 开始游戏

playGame()函数调用我们编写的先前函数来进行单场比赛：

def playGame(playerTile, computerTile):
showHints = False
turn = whoGoesFirst()
print('The ' + turn + ' will go first.')
# Clear the board and place starting pieces.
board = getNewBoard()
board[3][3] = 'X'
board[3][4] = 'O'
board[4][3] = 'O'
board[4][4] = 'X'

playGame()函数传递给playerTilecomputerTile字符串'X''O'。第 190 行确定先手玩家。turn变量包含字符串'computer''player'，以跟踪轮到谁了。第 194 行创建一个空白的棋盘数据结构，而第 195 到 198 行设置了棋盘上的初始四个方块。游戏现在准备好开始了。

##### 检查僵局

while True:
playerValidMoves = getValidMoves(board, playerTile)
computerValidMoves = getValidMoves(board, computerTile)
if playerValidMoves == [] and computerValidMoves == []:
return board # No one can move, so end the game.

##### 运行玩家的轮次

elif turn == 'player': # Player's turn
if playerValidMoves != []:
if showHints:
validMovesBoard = getBoardWithValidMoves(board,
playerTile)
drawBoard(validMovesBoard)
else:
drawBoard(board)
printScore(board, playerTile, computerTile)

move = getPlayerMove(board, playerTile)

getPlayerMove()函数可能返回字符串'quit''hints'，而不是棋盘上的移动。行 217 到 222 处理这些情况：

if move == 'quit':
print('Thanks for playing!')
sys.exit() # Terminate the program.
elif move == 'hints':
showHints = not showHints
continue
else:
makeMove(board, playerTile, move[0], move[1])
turn = 'computer'

##### 运行计算机的回合

elif turn == 'computer': # Computer's turn
if computerValidMoves != []:
drawBoard(board)
printScore(board, playerTile, computerTile)
input('Press Enter to see the computer\'s move.')
move = getComputerMove(board, computerTile)
makeMove(board, computerTile, move[0], move[1])

turn = 'player'

#### 游戏循环

print('Welcome to Reversegam!')
playerTile, computerTile = enterPlayerTile()

while True:
finalBoard = playGame(playerTile, computerTile)
# Display the final score.
drawBoard(finalBoard)
scores = getScoreOfBoard(finalBoard)
print('X scored %s points. O scored %s points.' % (scores['X'],
scores['O']))
if scores[playerTile] > scores[computerTile]:
print('You beat the computer by %s points! Congratulations!' %
(scores[playerTile] - scores[computerTile]))
elif scores[playerTile] < scores[computerTile]:
print('You lost. The computer beat you by %s points.' %
(scores[computerTile] - scores[playerTile]))
else:
print('The game was a tie!')

#### 询问玩家是否再玩一次

print('Do you want to play again? (yes or no)')
if not input().lower().startswith('y'):
break

## 十六、反转棋 AI 模拟

• 模拟 1：AISim1.py 将对 reversegam.py 进行更改。
• 模拟 2：AISim2.py 将对 AISim1.py 进行更改。
• 模拟 3：AISim3.py 将对 AISim2.py 进行更改。

• 模拟
• 百分比
• 整数除法
• round() 函数
• 计算机对战游戏

#### 让计算机自己对战

##### 模拟 1 的示例运行

Welcome to Reversegam!
The computer will go first.
12345678
+--------+
1|XXXXXXXX|1
2|OXXXXXXX|2
3|XOXXOXXX|3
4|XXOOXOOX|4
5|XXOOXXXX|5
6|XXOXOXXX|6
7|XXXOXOXX|7
8|XXXXXXXX|8
+--------+
12345678
X scored 51 points. O scored 13 points.
You beat the computer by 38 points! Congratulations!
Do you want to play again? (yes or no)
no
##### 模拟 1 的源代码

1. 选择 文件另存为
2. 将此文件另存为 AISim1.py，以便您可以在不影响 reversegam.py 的情况下进行更改。(此时，reversegam.pyAISim1.py 仍具有相同的代码。)
3. AISim1.py 进行更改并保存该文件以保留任何更改。(AISim1.py 将有新更改，reversegam.py 将保持原始的未更改的代码。)

move = getComputerMove(board, playerTile)

AISim1.py

elif turn == 'player': # Player's turn
if playerValidMoves != []:
#if showHints:
#    validMovesBoard = getBoardWithValidMoves(board,
playerTile)
#    drawBoard(validMovesBoard)
#else:
#    drawBoard(board)
#printScore(board, playerTile, computerTile)
move = getComputerMove(board, playerTile)
#if move == 'quit':
#    print('Thanks for playing!')
#    sys.exit() # Terminate the program.
#elif move == 'hints':
#    showHints = not showHints
#    continue
#else:
makeMove(board, playerTile, move[0], move[1])
turn = 'computer'
elif turn == 'computer': # Computer's turn
if computerValidMoves != []:
#drawBoard(board)
#printScore(board, playerTile, computerTile)
#input('Press Enter to see the computer\'s move.')
move = getComputerMove(board, computerTile)
makeMove(board, computerTile, move[0], move[1])
turn = 'player'
print('Welcome to Reversegam!')
playerTile, computerTile = ['X', 'O'] #enterPlayerTile()

#### 让电脑自己玩几次

1. 选择文件另存为
2. 将此文件另存为AISim2.py，这样你就可以在不影响AISim1.py的情况下进行更改。（此时，AISim1.pyAISim2.py仍然具有相同的代码。）
##### 模拟 2 的示例运行

Welcome to Reversegam!
#1: X scored 45 points. O scored 19 points.
#2: X scored 38 points. O scored 26 points.
#3: X scored 20 points. O scored 44 points.
#4: X scored 24 points. O scored 40 points.
#5: X scored 8 points. O scored 56 points.
--snip--
#249: X scored 24 points. O scored 40 points.
#250: X scored 43 points. O scored 21 points.
X wins: 119 (47.6%)
O wins: 127 (50.8%)
Ties:   4 (1.6%)

##### 模拟 2 的源代码

AISim2.py

turn = 'player'
NUM_GAMES = 250
xWins = oWins = ties = 0
print('Welcome to Reversegam!')
playerTile, computerTile = ['X', 'O'] #enterPlayerTile()
for i in range(NUM_GAMES): #while True:
finalBoard = playGame(playerTile, computerTile)
# Display the final score.
#drawBoard(finalBoard)
scores = getScoreOfBoard(finalBoard)
print('#%s: X scored %s points. O scored %s points.' % (i + 1,
scores['X'], scores['O']))
if scores[playerTile] > scores[computerTile]:
xWins += 1 #print('You beat the computer by %s points!
Congratulations!' % (scores[playerTile] -
scores[computerTile]))
elif scores[playerTile] < scores[computerTile]:
oWins += 1 #print('You lost. The computer beat you by %s points.'
% (scores[computerTile] - scores[playerTile]))
else:
ties += 1 #print('The game was a tie!')
#print('Do you want to play again? (yes or no)')
#if not input().lower().startswith('y'):
#    break
print('X wins: %s (%s%%)' % (xWins, round(xWins / NUM_GAMES * 100, 1)))
print('O wins: %s (%s%%)' % (oWins, round(oWins / NUM_GAMES * 100, 1)))
print('Ties:   %s (%s%%)' % (ties, round(ties / NUM_GAMES * 100, 1)))

##### 跟踪多场比赛

NUM_GAMES = 250
xWins = oWins = ties = 0

NUM_GAMESfor循环中使用，取代了第 243 行的游戏循环：

for i in range(NUM_GAMES): #while True:

for循环运行了NUM_GAMES次游戏。这取代了以前循环直到玩家表示不想再玩另一场比赛的while循环。

if scores[playerTile] > scores[computerTile]:
xWins += 1 #print('You beat the computer by %s points!
Congratulations!' % (scores[playerTile] -
scores[computerTile]))
elif scores[playerTile] < scores[computerTile]:
oWins += 1 #print('You lost. The computer beat you by %s points.'
% (scores[computerTile] - scores[playerTile]))
else:
ties += 1 #print('The game was a tie!')

##### 使用百分比来评估 AI

print('X wins: %s (%s%%)' % (xWins, round(xWins / NUM_GAMES * 100, 1)))
print('O wins: %s (%s%%)' % (oWins, round(oWins / NUM_GAMES * 100, 1)))
print('Ties:   %s (%s%%)' % (ties, round(ties / NUM_GAMES * 100, 1)))

###### 除法求值为浮点数

>>> spam = 100 / 4
>>> spam
25.0
>>> spam = spam + 20
>>> spam
45.0

###### round()函数

round()函数将浮点数四舍五入到最接近的整数。将以下内容输入到交互式 shell 中：

>>> round(10.0)
10
>>> round(10.2)
10
>>> round(8.7)
9
>>> round(3.4999)
3
>>> round(2.5422, 2)
2.54

round()函数还有一个可选的第二个参数，你可以指定要将数字舍入到的位置。这将使舍入后的数字成为浮点数，而不是整数。例如，表达式round(2.5422, 2)求值为2.54round(2.5422, 3)求值为2.542。在AISim2.py的 261 到 263 行，我们使用带有参数1round()来找到XO赢得或平局的比赛的百分比，直到小数点后一位，这给我们准确的百分比。

#### 比较不同的 AI 算法

AISim3.py中，第 257 行对getComputerMove()的调用将被更改为getCornerBestMove()，第 274 行的getComputerMove()将变为getWorstMove()，这是我们将为最差移动算法编写的函数。这样，我们将有常规的角落最佳算法与一个故意选择翻转最少棋子的移动的算法对抗。

##### 模拟 3 的源代码

AISim3.py

def getCornerBestMove(board, computerTile):
--snip--
def getWorstMove(board, tile):
# Return the move that flips the least number of tiles.
possibleMoves = getValidMoves(board, tile)
random.shuffle(possibleMoves) # Randomize the order of the moves.
# Find the lowest-scoring move possible.
worstScore = 64
for x, y in possibleMoves:
boardCopy = getBoardCopy(board)
makeMove(boardCopy, tile, x, y)
score = getScoreOfBoard(boardCopy)[tile]
if score < worstScore:
worstMove = [x, y]
worstScore = score
return worstMove
def getRandomMove(board, tile):
possibleMoves = getValidMoves(board, tile)
return random.choice(possibleMoves)
def isOnSide(x, y):
return x == 0 or x == WIDTH - 1 or y == 0 or y == HEIGHT - 1
def getCornerSideBestMove(board, tile):
# Return a corner move, a side move, or the best move.
possibleMoves = getValidMoves(board, tile)
random.shuffle(possibleMoves) # Randomize the order of the moves.
# Always go for a corner if available.
for x, y in possibleMoves:
if isOnCorner(x, y):
return [x, y]
# If there is no corner move to make, return a side move.
for x, y in possibleMoves:
if isOnSide(x, y):
return [x, y]
return getCornerBestMove(board, tile) # Do what the normal AI
would do.
def printScore(board, playerTile, computerTile):
--snip--
move = getCornerBestMove(board, playerTile)
--snip--
move = getWorstMove(board, computerTile)

##### 模拟 3 中 AI 的工作原理

getCornerBestMove()getWorstMove()getRandomMove()getCornerSideBestMove()这些函数彼此相似，但使用略有不同的策略来玩游戏。其中一个使用新的isOnSide()函数。这类似于我们的isOnCorner()函数，但它在选择最高得分的移动之前检查棋盘边缘的空格。

###### 角落最佳 AI

def getCornerBestMove(board, computerTile):

move = getCornerBestMove(board, playerTile)

###### 最差移动 AI

def getWorstMove(board, tile):
# Return the move that flips the least number of tiles.
possibleMoves = getValidMoves(board, tile)
random.shuffle(possibleMoves) # Randomize the order of the moves.
# Find the lowest-scoring move possible.
worstScore = 64
for x, y in possibleMoves:
boardCopy = getBoardCopy(board)
makeMove(boardCopy, tile, x, y)
score = getScoreOfBoard(boardCopy)[tile]
if score < worstScore:
worstMove = [x, y]
worstScore = score
return worstMove

getWorstMove()的算法从第 186 和 187 行开始与原始算法相同，但是在第 190 行开始有所不同。我们设置一个变量来保存worstScore，而不是bestScore，并将其设置为64，因为这是棋盘上的总位置数，也是整个棋盘填满时可能获得的最高分数。第 191 到 194 行与原始算法相同，但是第 195 行检查score是否小于worstScore，而不是检查score是否更高。如果score更小，则worstMove被替换为算法当前正在测试的棋盘上的移动，并且worstScore也会更新。然后函数返回worstMove

move = getWorstMove(board, computerTile)

###### 随机移动 AI

def getRandomMove(board, tile):
possibleMoves = getValidMoves(board, tile)
return random.choice(possibleMoves)

###### 检查边缘移动

def isOnSide(x, y):
return x == 0 or x == WIDTH - 1 or y == 0 or y == HEIGHT - 1

###### 角落-边缘最佳 AI

def getCornerSideBestMove(board, tile):
# Return a corner move, a side move, or the best move.
possibleMoves = getValidMoves(board, tile)
random.shuffle(possibleMoves) # Randomize the order of the moves.
# Always go for a corner if available.
for x, y in possibleMoves:
if isOnCorner(x, y):
return [x, y]
# If there is no corner move to make, return a side move.
for x, y in possibleMoves:
if isOnSide(x, y):
return [x, y]
return getCornerBestMove(board, tile) # Do what the normal AI
would do.

 函数 描述 getCornerBestMove() 如果有角落可用，则进行角落移动。如果没有角落，则找到得分最高的移动。 getCornerSideBestMove() 如果有角落可用，则进行角落移动。如果没有角落，则在侧面占用空间。如果没有侧面可用，则使用常规的getCornerBestMove()算法。 getRandomMove() 随机选择一个有效的移动。 getWorstMove() 采取导致翻转瓷砖最少的位置。

##### 比较 AI

###### 最差的移动 AI vs. 角落最佳 AI

X wins: 206 (82.4%)
O wins: 41 (16.4%)
Ties:   3 (1.2%)

###### 随机移动 AI vs. 角落最佳 AI

move = getRandomMove(board, computerTile)

Welcome to Reversegam!
#1: X scored 32 points. O scored 32 points.
#2: X scored 44 points. O scored 20 points.
#3: X scored 31 points. O scored 33 points.
#4: X scored 45 points. O scored 19 points.
#5: X scored 49 points. O scored 15 points.
--snip--
#249: X scored 20 points. O scored 44 points.
#250: X scored 38 points. O scored 26 points.
X wins: 195 (78.0%)
O wins: 48 (19.2%)
Ties:   7 (2.8%)

###### 角落侧面最佳 AI vs. 角落最佳 AI

move = getCornerSideBestMove(board, computerTile)

Welcome to Reversegam!
#1: X scored 27 points. O scored 37 points.
#2: X scored 39 points. O scored 25 points.
#3: X scored 41 points. O scored 23 points.
--snip--
#249: X scored 48 points. O scored 16 points.
#250: X scored 38 points. O scored 26 points.
X wins: 152 (60.8%)
O wins: 89 (35.6%)
Ties:   9 (3.6%)

## 十七、创建图形

• 安装pygame
• pygame中的颜色和字体
• 锯齿和抗锯齿图形
• 属性
• pygame.font.Fontpygame.Surfacepygame.Rectpygame.PixelArray数据类型
• 构造函数
• pygame的绘图功能
• 表面对象的blit()方法
• 事件

#### 安装 pygame

pygame模块帮助开发者通过在计算机屏幕上更容易绘制图形或向程序添加音乐来创建游戏。该模块不随 Python 一起提供，但与 Python 一样，可以免费下载。在www.nostarch.com/inventwithpython/下载pygame，并按照你的操作系统的说明进行操作。

>>> import pygame

#### pygame 中的 Hello World

pygame模块与交互式 shell 不兼容，所以你只能在文件编辑器中编写使用pygame的程序；你不能通过交互式 shell 逐条发送指令。

#### pygame Hello World 的源代码

pygame HelloWorld.py

import pygame, sys
from pygame.locals import *
# Set up pygame.
pygame.init()
# Set up the window.
windowSurface = pygame.display.set_mode((500, 400), 0, 32)
pygame.display.set_caption('Hello world!')
# Set up the colors.
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# Set up the fonts.
basicFont = pygame.font.SysFont(None, 48)
# Set up the text.
text = basicFont.render('Hello world!', True, WHITE, BLUE)
textRect = text.get_rect()
textRect.centerx = windowSurface.get_rect().centerx
textRect.centery = windowSurface.get_rect().centery
# Draw the white background onto the surface.
windowSurface.fill(WHITE)
# Draw a green polygon onto the surface.
pygame.draw.polygon(windowSurface, GREEN, ((146, 0), (291, 106),
(236, 277), (56, 277), (0, 106)))
# Draw some blue lines onto the surface.
pygame.draw.line(windowSurface, BLUE, (60, 60), (120, 60), 4)
pygame.draw.line(windowSurface, BLUE, (120, 60), (60, 120))
pygame.draw.line(windowSurface, BLUE, (60, 120), (120, 120), 4)
# Draw a blue circle onto the surface.
pygame.draw.circle(windowSurface, BLUE, (300, 50), 20, 0)
# Draw a red ellipse onto the surface.
pygame.draw.ellipse(windowSurface, RED, (300, 250, 40, 80), 1)
# Draw the text's background rectangle onto the surface.
pygame.draw.rect(windowSurface, RED, (textRect.left - 20,
textRect.top - 20, textRect.width + 40, textRect.height + 40))
# Get a pixel array of the surface.
pixArray = pygame.PixelArray(windowSurface)
pixArray[480][380] = BLACK
del pixArray
# Draw the text onto the surface.
windowSurface.blit(text, textRect)
# Draw the window onto the screen.
pygame.display.update()
# Run the game loop.
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()

#### 导入 pygame 模块

import pygame, sys
from pygame.locals import *

#### 初始化 pygame

# Set up pygame.
pygame.init()

#### 设置 pygame 窗口

# Set up the window.
windowSurface = pygame.display.set_mode((500, 400), 0, 32)
pygame.display.set_caption('Hello world!')

##### 元组

>>> spam = ('Life', 'Universe', 'Everything', 42)
➊ >>> spam[0]
'Life'
>>> spam[3]
42
➋ >>> spam[1:3]
('Universe', 'Everything')
➌ >>> spam[3] = 'Hello'
➍ Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

##### 表面对象

set_mode()函数返回一个pygame.Surface对象（我们将其简称为Surface对象）。对象只是数据类型的值，具有方法的引用。例如，字符串在 Python 中是对象，因为它们有数据（字符串本身）和方法（如lower()split()）。Surface对象表示窗口。

#### 设置颜色变量

# Set up the colors.
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

**表 17-1：**颜色及其 RGB 值

 颜色 RGB 值 黑色 (0, 0, 0) 蓝色 (0, 0, 255) 灰色 (128, 128, 128) 绿色 (0, 128, 0) 酸橙色 (0, 255, 0) 紫色 (128, 0, 128) 红色 (255, 0, 0) 青色 (0, 128, 128) 白色 (255, 255, 255) 黄色 (255, 255, 0)

#### 在 pygame 窗口上写文本

##### 使用字体来设置文本样式

# Set up the fonts.
basicFont = pygame.font.SysFont(None, 48)

##### 渲染字体对象

# Set up the text.
text = basicFont.render('Hello world!', True, WHITE, BLUE)

##### 使用 Rect 属性设置文本位置

pygame.Rect数据类型（简称Rect）表示特定大小和位置的矩形区域。这是我们用来设置窗口上对象位置的方法。

textRect = text.get_rect()
textRect.centerx = windowSurface.get_rect().centerx
textRect.centery = windowSurface.get_rect().centery

 pygame.Rect属性 描述 myRect.left 矩形左侧的 x 坐标的整数值 myRect.right 矩形右侧的 x 坐标的整数值 myRect.top 矩形顶部的 y 坐标的整数值 myRect.bottom 矩形底部的 y 坐标的整数值 myRect.centerx 矩形中心的 x 坐标的整数值 myRect.centery 矩形中心的 y 坐标的整数值 myRect.width 矩形的宽度的整数值 myRect.height 矩形的高度的整数值 myRect.size 两个整数的元组：(width, height) myRect.topleft 两个整数的元组：(left, top) myRect.topright 两个整数的元组：(right, top) myRect.bottomleft 两个整数的元组：(left, bottom) myRect.bottomright 一个包含两个整数的元组：(right, bottom) myRect.midleft 一个包含两个整数的元组：(left, centery) myRect.midright 一个包含两个整数的元组：(right, centery) myRect.midtop 一个包含两个整数的元组：(centerx, top) myRect.midbottom 一个包含两个整数的元组：(centerx, bottom)

Rect对象的好处是，如果您修改了这些属性中的任何一个，所有其他属性也将自动修改。例如，如果您创建一个宽 20 像素，高 20 像素，并且左上角坐标为(30, 40)Rect对象，那么右侧的 x 坐标将自动设置为50（因为 20 + 30 = 50）。

pygame模块内部有fontsurface模块，而在这些模块内部有FontSurface数据类型。pygame程序员以小写字母开头命名模块，以大写字母开头命名数据类型，以便更容易区分数据类型和模块。

#### 用颜色填充 Surface 对象

# Draw the white background onto the surface.
windowSurface.fill(WHITE)

#### pygame 的绘图函数

##### 绘制多边形

pygame.draw.polygon()函数可以绘制任何多边形形状。多边形是一个具有直线边的多边形。圆和椭圆不是多边形，因此我们需要使用不同的函数来绘制这些形状。

1. 要在上面绘制多边形的Surface对象。
2. 多边形的颜色。
3. 一个包含 x 和 y 坐标的点的元组的元组，以便按顺序绘制。最后一个元组将自动连接到第一个元组以完成形状。
4. 可选的多边形线宽的整数。如果没有这个，多边形将被填充。

# Draw a green polygon onto the surface.
pygame.draw.polygon(windowSurface, GREEN, ((146, 0), (291, 106),
(236, 277), (56, 277), (0, 106)))

##### 绘制线条

pygame.draw.line()函数只是从屏幕上的一个点到另一个点绘制一条线。pygame.draw.line()的参数依次为：

1. 要在其上绘制线的Surface对象。
2. 线的颜色。
3. 一个包含两个整数的元组，表示线的一端的 x 和 y 坐标。
4. 一个包含两个整数的元组，表示线的另一端的 x 和 y 坐标。
5. 可选的，表示线的宽度的整数。

# Draw some blue lines onto the surface.
pygame.draw.line(windowSurface, BLUE, (60, 60), (120, 60), 4)
pygame.draw.line(windowSurface, BLUE, (120, 60), (60, 120))
pygame.draw.line(windowSurface, BLUE, (60, 120), (120, 120), 4)

##### 绘制圆

pygame.draw.circle()函数在Surface对象上绘制圆。它的参数依次为：

1. 要在其上绘制圆的Surface对象。
2. 圆的颜色。
3. 一个包含两个整数的元组，表示圆的中心的 x 和 y 坐标。
4. 圆的半径的整数（即大小）。
5. 可选的，表示线的宽度的整数。宽度为0意味着圆将被填充。

# Draw a blue circle onto the surface.
pygame.draw.circle(windowSurface, BLUE, (300, 50), 20, 0)

##### 绘制椭圆

pygame.draw.ellipse()函数类似于pygame.draw.circle()函数，但它绘制的是一个椭圆，类似于一个扁平的圆。pygame.draw.ellipse()函数的参数依次为：

1. 要在其上绘制椭圆的Surface对象。
2. 椭圆的颜色。
3. 一个包含四个整数的元组，表示椭圆的Rect对象的左上角和宽度和高度。
4. 可选的，表示线的宽度的整数。宽度为0意味着椭圆将被填充。

# Draw a red ellipse onto the surface.
pygame.draw.ellipse(windowSurface, RED, (300, 250, 40, 80), 1)

##### 绘制矩形

pygame.draw.rect()函数将绘制一个矩形。pygame.draw.rect()函数的参数依次为：

1. 要在其上绘制矩形的Surface对象。
2. 矩形的颜色。
3. 一个包含四个整数的元组，分别表示左上角的 x 和 y 坐标以及矩形的宽度和高度。作为第三个参数的四个整数的元组，也可以传递一个Rect对象。

# Draw the text's background rectangle onto the surface.
pygame.draw.rect(windowSurface, RED, (textRect.left - 20,
textRect.top - 20, textRect.width + 40, textRect.height + 40))

##### 着色像素

PixelArray对象为您提供了高像素级别的控制，因此如果您需要向屏幕绘制非常详细或自定义的图像，而不仅仅是大形状，这是一个不错的选择。

# Get a pixel array of the surface.
pixArray = pygame.PixelArray(windowSurface)
pixArray[480][380] = BLACK

pygame 模块将自动修改 windowSurface 对象以进行此更改。

PixelArray 对象中的第一个索引是 x 坐标。第二个索引是 y 坐标。PixelArray 对象使得可以轻松地将 Surface 对象上的单个像素设置为特定颜色。

del pixArray

#### Surface 对象的 blit()方法

blit()方法将一个 Surface 对象的内容绘制到另一个 Surface 对象上。由render()方法创建的所有文本对象都存在于它们自己的 Surface 对象上。pygame 绘图方法都可以指定要在其上绘制形状或线条的 Surface 对象，但我们的文本存储在 text 变量中，而不是绘制到 windowSurface 上。为了在我们希望出现的 Surface 上绘制文本，我们必须使用 blit()方法：

# Draw the text onto the surface.
windowSurface.blit(text, textRect)

blit()的第二个参数指定了 text 表面应该在 windowSurface 上绘制的位置。在第 23 行调用 text.get_rect()得到的 Rect 对象被传递给这个参数。

#### 将 Surface 对象绘制到屏幕上

# Draw the window onto the screen.
pygame.display.update()

#### 事件和游戏循环

# Run the game loop.
while True:

while 语句的条件设置为 True，以便它永远循环。循环退出的唯一时间是如果事件导致程序终止。

##### 获取事件对象

for event in pygame.event.get():
if event.type == QUIT:

##### 退出程序

pygame.quit()
sys.exit()

pygame.quit()函数有点像init()的相反。在退出程序之前，您需要调用它。如果您忘记了，可能会导致 IDLE 在程序结束后挂起。第 62 和 63 行退出pygame并结束程序。

## 十八、让图形动起来

• 使用游戏循环来使对象动画化
• 改变对象的方向

#### 动画程序的源代码

animation.py

import pygame, sys, time
from pygame.locals import *
# Set up pygame.
pygame.init()
# Set up the window.
WINDOWWIDTH = 400
WINDOWHEIGHT = 400
windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),
0, 32)
pygame.display.set_caption('Animation')
# Set up direction variables.
DOWNLEFT = 'downleft'
DOWNRIGHT = 'downright'
UPLEFT = 'upleft'
UPRIGHT = 'upright'
MOVESPEED = 4
# Set up the colors.
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# Set up the box data structure.
b1 = {'rect':pygame.Rect(300, 80, 50, 100), 'color':RED, 'dir':UPRIGHT}
b2 = {'rect':pygame.Rect(200, 200, 20, 20), 'color':GREEN, 'dir':UPLEFT}
b3 = {'rect':pygame.Rect(100, 150, 60, 60), 'color':BLUE, 'dir':DOWNLEFT}
boxes = [b1, b2, b3]
# Run the game loop.
while True:
# Check for the QUIT event.
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
# Draw the white background onto the surface.
windowSurface.fill(WHITE)
for b in boxes:
# Move the box data structure.
if b['dir'] == DOWNLEFT:
b['rect'].left -= MOVESPEED
b['rect'].top += MOVESPEED
if b['dir'] == DOWNRIGHT:
b['rect'].left += MOVESPEED
b['rect'].top += MOVESPEED
if b['dir'] == UPLEFT:
b['rect'].left -= MOVESPEED
b['rect'].top -= MOVESPEED
if b['dir'] == UPRIGHT:
b['rect'].left += MOVESPEED
b['rect'].top -= MOVESPEED
# Check whether the box has moved out of the window.
if b['rect'].top < 0:
# The box has moved past the top.
if b['dir'] == UPLEFT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = DOWNRIGHT
if b['rect'].bottom > WINDOWHEIGHT:
# The box has moved past the bottom.
if b['dir'] == DOWNLEFT:
b['dir'] = UPLEFT
if b['dir'] == DOWNRIGHT:
b['dir'] = UPRIGHT
if b['rect'].left < 0:
# The box has moved past the left side.
if b['dir'] == DOWNLEFT:
b['dir'] = DOWNRIGHT
if b['dir'] == UPLEFT:
b['dir'] = UPRIGHT
if b['rect'].right > WINDOWWIDTH:
# The box has moved past the right side.
if b['dir'] == DOWNRIGHT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = UPLEFT
# Draw the box onto the surface.
pygame.draw.rect(windowSurface, b['color'], b['rect'])
# Draw the window onto the screen.
pygame.display.update()
time.sleep(0.02)

#### 设置常量变量

import pygame, sys, time
from pygame.locals import *
# Set up pygame.
pygame.init()
# Set up the window.
WINDOWWIDTH = 400
WINDOWHEIGHT = 400
windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),
0, 32)
pygame.display.set_caption('Animation')

##### 方向的常量变量

# Set up direction variables.
DOWNLEFT = 'downleft'
DOWNRIGHT = 'downright'
UPLEFT = 'upleft'
UPRIGHT = 'upright'

MOVESPEED = 4

##### 颜色的常量变量

# Set up the colors.
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

#### 设置框数据结构

# Set up the box data structure.
b1 = {'rect':pygame.Rect(300, 80, 50, 100), 'color':RED, 'dir':UPRIGHT}

b2 = {'rect':pygame.Rect(200, 200, 20, 20), 'color':GREEN, 'dir':UPLEFT}
b3 = {'rect':pygame.Rect(100, 150, 60, 60), 'color':BLUE, 'dir':DOWNLEFT}
boxes = [b1, b2, b3]

#### 游戏循环

##### 处理玩家退出时

# Run the game loop.
while True:
# Check for the QUIT event.
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()

# Draw the white background onto the surface.
windowSurface.fill(WHITE)

##### 移动每个框

for b in boxes:

for循环内，你将把当前的框称为b，以使代码更容易输入。我们需要根据框已经移动的方向来改变每个框，因此我们将使用if语句来通过检查框数据结构内的dir键来确定框的方向。然后我们将根据框移动的方向改变框的位置。

# Move the box data structure.
if b['dir'] == DOWNLEFT:
b['rect'].left -= MOVESPEED
b['rect'].top += MOVESPEED
if b['dir'] == DOWNRIGHT:
b['rect'].left += MOVESPEED
b['rect'].top += MOVESPEED
if b['dir'] == UPLEFT:
b['rect'].left -= MOVESPEED
b['rect'].top -= MOVESPEED
if b['dir'] == UPRIGHT:
b['rect'].left += MOVESPEED
b['rect'].top -= MOVESPEED

##### 弹跳框

# Check whether the box has moved out of the window.
if b['rect'].top < 0:
# The box has moved past the top.
if b['dir'] == UPLEFT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = DOWNRIGHT

if b['rect'].bottom > WINDOWHEIGHT:
# The box has moved past the bottom.
if b['dir'] == DOWNLEFT:
b['dir'] = UPLEFT
if b['dir'] == DOWNRIGHT:
b['dir'] = UPRIGHT

if b['rect'].left < 0:
# The box has moved past the left side.
if b['dir'] == DOWNLEFT:
b['dir'] = DOWNRIGHT
if b['dir'] == UPLEFT:
b['dir'] = UPRIGHT
if b['rect'].right > WINDOWWIDTH:
# The box has moved past the right side.
if b['dir'] == DOWNRIGHT:
b['dir'] = DOWNLEFT
if b['dir'] == UPRIGHT:
b['dir'] = UPLEFT

##### 在它们的新位置上在窗口上绘制方块

# Draw the box onto the surface.
pygame.draw.rect(windowSurface, b['color'], b['rect'])

##### 在屏幕上绘制窗口

for循环之后，boxes列表中的每个方块都将被绘制，因此你需要调用pygame.display.update()来在屏幕上绘制windowSurface

# Draw the window onto the screen.
pygame.display.update()
time.sleep(0.02)

#### 总结

|
6天前
|

1193: 最简单的计算机(python)
1193: 最简单的计算机(python)
15 0
|
6天前
|

Python中的装饰器是一种强大而又优雅的编程工具，它能够在不改变原有代码结构的情况下，为函数或类添加新的功能和行为。本文将深入解析Python装饰器的原理、用法和实际应用，帮助读者更好地理解和利用这一技术，提升代码的可维护性和可扩展性。
22 4
|
22天前
|

【Python 基础教程 01 全面介绍】 Python编程基础全攻略：一文掌握Python语法精髓，从C/C++ 角度学习Python的差异
【Python 基础教程 01 全面介绍】 Python编程基础全攻略：一文掌握Python语法精髓，从C/C++ 角度学习Python的差异
152 0
|
2天前
|

Python网络编程基础（Socket编程）多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中，随着客户端数量的增加，服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求，我们通常需要采用多线程或多进程的方式。在本章中，我们将探讨多线程/多进程服务器编程的概念，并通过一个多线程服务器的示例来演示其实现。
18 0
|
2天前
|

Python网络编程基础（Socket编程） 错误处理和异常处理的最佳实践
【4月更文挑战第11天】在网络编程中，错误处理和异常管理不仅是为了程序的健壮性，也是为了提供清晰的用户反馈以及优雅的故障恢复。在前面的章节中，我们讨论了如何使用try-except语句来处理网络错误。现在，我们将深入探讨错误处理和异常处理的最佳实践。
17 2
|
3天前
|

22 2
|
16天前
|

Python列表推导式：简洁与高效的编程利器

21 5
|
22天前
|
Java 编译器 Shell
【Python 基础教程 04】超详细Python编程教程：初学者入门至全面了解Python 解析器( CPython、IPython、Jython和PyPy)
【Python 基础教程 04】超详细Python编程教程：初学者入门至全面了解Python 解析器( CPython、IPython、Jython和PyPy)
42 0
|
23天前
|

Python语言的函数编程模块
Python语言的函数编程模块
12 0
|
25天前
|

21 0