我开发了一个python 4游戏,它只使用sys & numpy,其他的都是常规python!我所要求的只是一个更强的代码。我就是这么做的:
import sys
import numpy
BOARD_SIZE_X = 7
BOARD_SIZE_Y = 6
SEARCH_DEPTH = 4
COMPUTER_PLAYER = 1
HUMAN_PLAYER = -1
#
# Method that runs the minimax algorithm and returns
# the move and score of each call.
#
def minimax(gameState, depth, player, opponent):
availableMoves = BOARD_SIZE_X
for i in range(0, BOARD_SIZE_X):
if gameState[0][i] != 0:
availableMoves -= 1
if depth == 0 or availableMoves == 0:
score = evaluateScore(gameState, player, opponent)
return None, score
bestScore = None
bestMove = None
for i in range(0, BOARD_SIZE_X):
# If moves cannot be made on column, skip it
if gameState[0][i] != 0:
continue
currentMove = [0, i]
for j in range(0, BOARD_SIZE_Y - 1):
if gameState[j + 1][i] != 0:
gameState[j][i] = player
currentMove[0] = j
break
elif j == BOARD_SIZE_Y - 2:
gameState[j+1][i] = player
currentMove[0] = j+1
# Recursive minimax call, with reduced depth
move, score = minimax(gameState, depth - 1, opponent, player)
gameState[currentMove[0]][currentMove[1]] = 0
if player == COMPUTER_PLAYER:
if bestScore == None or score > bestScore:
bestScore = score
bestMove = currentMove
else:
if bestScore == None or score < bestScore:
bestScore = score
bestMove = currentMove
return bestMove, bestScore
#
# Method that calculates the heuristic value of a given
# board state. The heuristic adds a point to a player
# for each empty slot that could grant a player victory.
#
def evaluateScore(gameState, player, opponent):
# Return infinity if a player has won in the given board
score = checkWin(gameState)
if score == player:
return float("inf")
elif score == opponent:
return float("-inf")
else:
score = 0
for i in range(0, BOARD_SIZE_Y):
for j in range(0, BOARD_SIZE_X):
if gameState[i][j] == 0:
score += scoreOfCoordinate(gameState, i, j, player, opponent)
return score
#
# Method that evaluates if a given coordinate has a possible win
# for any player. Each coordinate evaluates if a possible win can be
# found vertically, horizontally or in both diagonals.
#
def scoreOfCoordinate(gameState, i, j, player, opponent):
score = 0
# Check vertical line
score += scoreOfLine(
gameState=gameState,
i=i,
j=j,
rowIncrement=-1,
columnIncrement=0,
firstRowCondition=-1,
secondRowCondition=BOARD_SIZE_Y,
firstColumnCondition=None,
secondColumnCondition=None,
player=player,
opponent=opponent
)
# Check horizontal line
score += scoreOfLine(
gameState=gameState,
i=i,
j=j,
rowIncrement=0,
columnIncrement=-1,
firstRowCondition=None,
secondRowCondition=None,
firstColumnCondition=-1,
secondColumnCondition=BOARD_SIZE_X,
player=player,
opponent=opponent
)
# Check diagonal /
score += scoreOfLine(
gameState=gameState,
i=i,
j=j,
rowIncrement=-1,
columnIncrement=1,
firstRowCondition=-1,
secondRowCondition=BOARD_SIZE_Y,
firstColumnCondition=BOARD_SIZE_X,
secondColumnCondition=-1,
player=player,
opponent=opponent
)
# Check diagonal \
score += scoreOfLine(
gameState=gameState,
i=i,
j=j,
rowIncrement=-1,
columnIncrement=-1,
firstRowCondition=-1,
secondRowCondition=BOARD_SIZE_Y,
firstColumnCondition=-1,
secondColumnCondition=BOARD_SIZE_X,
player=player,
opponent=opponent
)
return score
#
# Method that searches through a line (vertical, horizontal or
# diagonal) to get the heuristic value of the given coordinate.
#
def scoreOfLine(
gameState,
i,
j,
rowIncrement,
columnIncrement,
firstRowCondition,
secondRowCondition,
firstColumnCondition,
secondColumnCondition,
player,
opponent
):
score = 0
currentInLine = 0
valsInARow = 0
valsInARowPrev = 0
# Iterate in one side of the line until a move from another
# player or an empty space is found
row = i + rowIncrement
column = j + columnIncrement
firstLoop = True
while (
row != firstRowCondition and
column != firstColumnCondition and
gameState[row][column] != 0
):
if firstLoop:
currentInLine = gameState[row][column]
firstLoop = False
if currentInLine == gameState[row][column]:
valsInARow += 1
else:
break
row += rowIncrement
column += columnIncrement
# Iterate on second side of the line
row = i - rowIncrement
column = j - columnIncrement
firstLoop = True
while (
row != secondRowCondition and
column != secondColumnCondition and
gameState[row][column] != 0
):
if firstLoop:
firstLoop = False
# Verify if previous side of line guaranteed a win on the
# coordinate, and if not, continue counting to see if the
# given coordinate can complete a line from in between.
if currentInLine != gameState[row][column]:
if valsInARow == 3 and currentInLine == player:
score += 1
elif valsInARow == 3 and currentInLine == opponent:
score -= 1
else:
valsInARowPrev = valsInARow
valsInARow = 0
currentInLine = gameState[row][column]
if currentInLine == gameState[row][column]:
valsInARow += 1
else:
break
row -= rowIncrement
column -= columnIncrement
if valsInARow + valsInARowPrev >= 3 and currentInLine == player:
score += 1
elif valsInARow + valsInARowPrev >= 3 and currentInLine == opponent:
score -= 1
return score
#
# Method that executes the first call of the minimax method and
# returns the move to be executed by the computer. It also verifies
# if any immediate wins or loses are present.
#
def bestMove(gameState, player, opponent):
for i in range(0, BOARD_SIZE_X):
# If moves cannot be made on column, skip it
if gameState[0][i] != 0:
continue
currentMove = [0, i]
for j in range(0, BOARD_SIZE_Y - 1):
if gameState[j + 1][i] != 0:
gameState[j][i] = player
currentMove[0] = j
break
elif j == BOARD_SIZE_Y - 2:
gameState[j+1][i] = player
currentMove[0] = j+1
winner = checkWin(gameState)
gameState[currentMove[0]][currentMove[1]] = 0
if winner == COMPUTER_PLAYER:
return currentMove[1]
for i in range(0, BOARD_SIZE_X):
# If moves cannot be made on column, skip it
if gameState[0][i] != 0:
continue
currentMove = [0, i]
for j in range(0, BOARD_SIZE_Y - 1):
if gameState[j + 1][i] != 0:
gameState[j][i] = opponent
currentMove[0] = j
break
elif j == BOARD_SIZE_Y - 2:
gameState[j+1][i] = opponent
currentMove[0] = j+1
winner = checkWin(gameState)
gameState[currentMove[0]][currentMove[1]] = 0
if winner == HUMAN_PLAYER:
return currentMove[1]
move, score = minimax(gameState, SEARCH_DEPTH, player, opponent)
return move[1]
#
# Method that verifies if the current board is in a winning state
# for any player, returning infinity if that is the case.
#
def checkWin(gameState):
current = 0
currentCount = 0
computer_wins = 0
opponent_wins = 0
# Check horizontal wins
for i in range(0, BOARD_SIZE_Y):
for j in range(0, BOARD_SIZE_X):
if currentCount == 0:
if gameState[i][j] != 0:
current = gameState[i][j]
currentCount += 1
elif currentCount == 4:
if current == COMPUTER_PLAYER:
computer_wins += 1
else:
opponent_wins += 1
currentCount = 0
break
elif gameState[i][j] != current:
if gameState[i][j] != 0:
current = gameState[i][j]
currentCount = 1
else:
current = 0
currentCount = 0
else:
currentCount += 1
if currentCount == 4:
if current == COMPUTER_PLAYER:
computer_wins += 1
else:
opponent_wins += 1
current = 0
currentCount = 0
# Check vertical wins
for j in range(0, BOARD_SIZE_X):
for i in range(0, BOARD_SIZE_Y):
if currentCount == 0:
if gameState[i][j] != 0:
current = gameState[i][j]
currentCount += 1
elif currentCount == 4:
if current == COMPUTER_PLAYER:
computer_wins += 1
else:
opponent_wins += 1
currentCount = 0
break
elif gameState[i][j] != current:
if gameState[i][j] != 0:
current = gameState[i][j]
currentCount = 1
else:
current = 0
currentCount = 0
else:
currentCount += 1
if currentCount == 4:
if current == COMPUTER_PLAYER:
computer_wins += 1
else:
opponent_wins += 1
current = 0
currentCount = 0
# Check diagonal wins
np_matrix = numpy.array(gameState)
diags = [np_matrix[::-1,:].diagonal(i) for i in range(-np_matrix.shape[0]+1,np_matrix.shape[1])]
diags.extend(np_matrix.diagonal(i) for i in range(np_matrix.shape[1]-1,-np_matrix.shape[0],-1))
diags_list = [n.tolist() for n in diags]
for i in range(0, len(diags_list)):
if len(diags_list[i]) >= 4:
for j in range(0, len(diags_list[i])):
if currentCount == 0:
if diags_list[i][j] != 0:
current = diags_list[i][j]
currentCount += 1
elif currentCount == 4:
if current == COMPUTER_PLAYER:
computer_wins += 1
else:
opponent_wins += 1
currentCount = 0
break
elif diags_list[i][j] != current:
if diags_list[i][j] != 0:
current = diags_list[i][j]
currentCount = 1
else:
current = 0
currentCount = 0
else:
currentCount += 1
if currentCount == 4:
if current == COMPUTER_PLAYER:
computer_wins += 1
else:
opponent_wins += 1
current = 0
currentCount = 0
if opponent_wins > 0:
return HUMAN_PLAYER
elif computer_wins > 0:
return COMPUTER_PLAYER
else:
return 0
#
# Function that prints the game board, representing the player
# as a O and the computer as an X
#
def printBoard(gameState):
for i in range(1, BOARD_SIZE_X + 1):
sys.stdout.write(" %d " % i)
print("")
print("_" * (BOARD_SIZE_X * 3))
for i in range(0, BOARD_SIZE_Y):
for j in range(0, BOARD_SIZE_X):
if gameState[i][j] == 1:
sys.stdout.write("|X|")
elif gameState[i][j] == -1:
sys.stdout.write("|O|")
else:
sys.stdout.write("|-|")
print("")
print("_" * (BOARD_SIZE_X * 3))
print("")
#
# Method that provides the main flow of the game, prompting the user
# to make moves, and then allowing the computer to execute a move.
# After each turn, the method checks if the board is full or if a player
# has won.
#
def playGame():
gameState = [[0 for col in range(BOARD_SIZE_X)] for row in range(BOARD_SIZE_Y)]
moveHeights = [0] * BOARD_SIZE_X
player = COMPUTER_PLAYER
opponent = HUMAN_PLAYER
winner = 0
gameOver = False
remainingColumns = BOARD_SIZE_X
print("=========================")
print("= WELCOME TO CONNECT 4! =")
print("=========================\n")
printBoard(gameState)
while True:
while True:
try:
move = int(input("What is your move? (Choose from 1 to %d): " % BOARD_SIZE_X))
except ValueError:
print("That wasn't a number! Try again.")
continue
if move < 1 or move > BOARD_SIZE_X:
print("That is not a valid move. Try again.")
elif moveHeights[move - 1] == BOARD_SIZE_Y:
print("The chosen column is already full. Try again.")
else:
break
moveHeights[move - 1] += 1
gameState[BOARD_SIZE_Y - moveHeights[move - 1]][move - 1] = opponent
printBoard(gameState)
if moveHeights[move - 1] == BOARD_SIZE_Y:
remainingColumns -= 1
if remainingColumns == 0:
gameOver = True
if gameOver:
break
score = checkWin(gameState)
if score == player:
winner = player
break
elif score == opponent:
winner = opponent
break
else:
score = 0
print("Now it's the computer's turn!")
move = bestMove(gameState, player, opponent)
if move == None:
break
moveHeights[move] += 1
gameState[BOARD_SIZE_Y - moveHeights[move]][move] = player
printBoard(gameState)
if moveHeights[move] == BOARD_SIZE_Y:
remainingColumns -= 1
if remainingColumns == 0:
gameOver = True
if gameOver:
break
score = checkWin(gameState)
if score == player:
winner = player
break
elif score == opponent:
winner = opponent
break
else:
score = 0
return winner
#
# Main execution of the game. Plays the game until the user
# wishes to stop.
#
if __name__ == "__main__":
playing = True
while playing:
winner = playGame()
if winner == COMPUTER_PLAYER:
print("Sad! You lost!")
elif winner == HUMAN_PLAYER:
print("Congratulations! You won!")
else:
print("The board is full. This is a draw!")
while True:
try:
option = input("Do you want to play again? (Y/N): ")
except ValueError:
print("Please input a correct value. Try again.")
continue
if option == 'Y' or option == 'y':
break
elif option == 'N' or option == 'n':
playing = False
break
else:
print("Please enter Y or N.")
```发布于 2021-06-11 23:07:29
PythonPEP-8:
的样式指南
佩普-8定义了许多有助于使更多的用户更容易阅读和维护Python程序的约定。比如:
snake_case应该用于函数、参数和变量。gameState和availableMoves应该是game_state和available_moves等。j+1应该写成j + 1。(请注意,除非使用类型提示,否则不应在带有关键字参数的=令牌周围使用空格。)请看奈德·巴奇尔德的循环就像本地的演讲。
代码如下:
for i in range(0, BOARD_SIZE_X):
if gameState[0][i] != 0:
availableMoves -= 1可以更有效地重写,而无需索引如下:
for cell in gameState[0]:
if cell != 0:
availableMoves -= 1如果索引和该索引中列表的内容都是必需的,那么enumerate()是首选。而不是这样:
for i in range(0, BOARD_SIZE_Y):
for j in range(0, BOARD_SIZE_X):
if gameState[i][j] == 0:
score += scoreOfCoordinate(gameState, i, j, player, opponent)用途:
for i, row in enumerate(gameState):
for j, cell in row:
if cell == 0:
score += scoreOfCoordinate(gameState, i, j, player, opponent)您正在交替使用i和j。minimax()使用gameState[j][i],evaluateScore()使用gameState[i][j]。这很让人困惑。使用更多的描述性索引名称,比如row_idx和col_idx。
代码中有太多神奇的数字。0和1通常是OK的,例如当用作行/列偏移量时。我不知道2在BOARD_SIZE_Y - 2中意味着什么,但我可以猜到。3 in valsInARow == 3也许应该有个名字,但也许不值得。
真正严重的错误是:
if gameState[i][j] == 1:
sys.stdout.write("|X|")
elif gameState[i][j] == -1:
sys.stdout.write("|O|")在文件的顶部定义COMPUTER_PLAYER和HUMAN_PLAYER。为什么不在这里用呢?
另外,定义一个EMPTY常量,并对它而不是0进行测试,将澄清意图。
scoreOfCoordinate()占用了太多的参数。其中一些参数是多余的。
当行或列增量为0时,第一和第二条件为None。当增量为-1时,第一个条件是-1,第二个条件是相应方向的板大小。如果增量为+1,则反转条件,第一个为板大小,第二个为-1。
您可以删除条件参数,并在函数内根据行和列增量参数设置内部变量。
但这可能还是太复杂了。row和column值仅限于一个简单的常量范围。简单地测试一下!
while (0 <= row < BOARD_SIZE_Y and 0 <= column < BOARD_SIZE_X and gameState[row][column] != 0):
...甚至:
VALID_ROWS = range(BOARD_SIZE_Y)
VALID_COLUMNS = range(BOARD_SIZE_X)
...
while (row in VALID_ROWS and column in VALID_COLUMNS and gameState[row][column] != 0):
...我讨厌看到numpy被用来把对角线从矩阵中取出来。这太过分了。
如果你想要的是矮胖,你也可以使用它。例如,行中的4个相同值可以写入如下:
matrix = numpy.array(gameState)
# Compare all elements with their neighbour in the adjacent column
match = (matrix[:, :-1] != 0) & (matrix[:, :-1] == matrix[:, 1:])
# four-in-a-row would be three adjacent matching elements
four_in_a_row = (match[:, :-2] & match[:, 1:-1] & match[:, 2:]).any()在一列中检查4个相同的值也同样容易。当然,您只需旋转或转置矩阵并重复使用上面的检查。
检查对角线是相似的:matrix[:-1, :-1] == matrix[1:, 1:]在一个对角线方向上对相邻元素进行元素级比较,而matrix[1:, :-1] == matrix[:-1, 1:]则在另一个方向上进行比较。
你应该把你的代码分成几个模块
colorama ANSI控制台版本,而无需更改代码的其他部分。通过将代码分解成单独的模块,可以提高理解性。例如,您可以询问游戏状态的可用移动是什么,如果一个特定的移动是有效的,或者如果游戏处于“赢”状态。该代码不需要在minimax策略代码中重复,这将使代码更有针对性,更易于阅读。
https://codereview.stackexchange.com/questions/262897
复制相似问题