问题描述
main.py
from Player import Player
import tkinter as tk
import pygame
import pygame_menu
import time
import colors
import Connect4 as cFour
import Minimax as mx
def text_format(option,textSize,textColor):
"""
Creates a text object to show in the main menu
"""
newFont = pygame.font.Font(pygame_menu.font.FONT_FRANCHISE,textSize)
newText = newFont.render(option,textColor)
return newText
def load_screen():
"""
This initializes the window for pygame to use
"""
screen = pygame.display.set_mode((600,600))
pygame.display.set_caption("Connect4")
return screen
def get_player_details(screen):
"""
Creates a tkinter object(button) that gets players names
"""
root = tk.Tk()
root.title("Player Names!")
tk.Label(root,text="Player One",fg="blue").grid(row=0)
tk.Label(root,text="Player Two",fg="red").grid(row=1)
p1 = tk.Entry(root,font=(None,15))
p2 = tk.Entry(root,15))
p1.grid(row=0,column=1)
p2.grid(row=1,column=1)
tk.Button(root,text='Play!',command= lambda: play_game(p1.get(),p2.get(),root,screen)).grid(row=10,column=1,sticky=tk.W)
tk.mainloop()
def get_player_ai_details(screen):
"""
Creating the panel to allow the user to select a color and go against the AI
"""
options = ["Player 1","Player 2"]
root = tk.Tk()
root.title("Player 1(Blue) or 2(Red)?")
colorChoice= tk.StringVar(root)
colorChoice.set(options[0])
tk.OptionMenu(root,colorChoice,*options).grid(row=3)
p1 = tk.Entry(root,15))
p1.grid(row=3,text="Play Computer!",command=lambda: play_computer(colorChoice.get(),p1.get(),column=1)
tk.mainloop()
def play_computer(colorChoice,playerName,screen):
"""
Connect4 play function (human v computer)
"""
root.destroy()
if colorChoice == "Player 1":
mx.Minimax(Player(playerName),Player("Ed"),screen).play_computer()
else:
mx.Minimax(Player("Ed"),Player(playerName),screen).play_computer()
def play_game(p1Name,p2Name,screen):
"""
Connect4 play function (human v human)
"""
root.destroy()
game = cFour.Connect4(Player(p1Name.strip()),Player(p2Name.strip()),screen).play()
if __name__ == "__main__":
pygame.init()
screen = load_screen()
features = [
("Player Vs Player",colors.yellow),("Player Vs AI",colors.red),("Quit",colors.gray)
]
iterator = 0
menu = True
while menu:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
#This if block makes it where the user doesnt have to click arrow key up/down if they have exhausted the possible options,it will loop you throughout options
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_DOWN:
iterator += 1
if iterator == len(features):
iterator = 0
if event.key == pygame.K_UP:
iterator -= 1
if iterator < 0:
iterator = len(features) - 1
if event.key == pygame.K_RETURN:
if selected == "Player Vs Player":
get_player_details(screen)
if selected == "Player Vs AI":
get_player_ai_details(screen)
if selected == "Quit":
pygame.quit()
quit()
selected = features[iterator][0]
screen.fill(colors.blue)
screen_rect = screen.get_rect()
for i in range(0,len(features)):
counter = -50 + (i * 90) # Equation that sets distance between each choice in main menu
if i == iterator:
text = text_format(features[i][0],80,features[i][1])
else:
text = text_format(features[i][0],colors.black)
player_rect = text.get_rect(center=screen_rect.center)
player_rect[1] = player_rect[1] + counter
screen.blit(text,player_rect)
pygame.display.update()
Connect4.py
import pygame
import colors
import tkinter as tk
import pygame_menu
# import pandas as pd
import random
class Connect4:
"""
Class used to represent connect4 game
"""
def __init__(self,player1,player2,screen):
# Use 1 version of the screen instead of trying to create a new one
self.screen = screen
# Circle Radius and Width
self.WIDTH = 0
self.CIRCLERADIUS = 25
# Game-Time Variables
self.player1 = player1
self.player2 = player2
self.moveNumber = 0
self.gameOver = False
self.COLUMNS = 7
self.ROWS = 6
self.EMPTY = 99
self.board = [[self.EMPTY for x in range(self.COLUMNS)] for y in range(self.ROWS)]
# The distance between where the window starts and the game board is placed
self.disTANCE = 90
# Space between each circle
self.disTANCEGAP = 70
# Setting rectangle default
self.LEFT = 50
self.TOP = 70
self.HEIGHT = 470
self.RECWIDTH = 500
#Creating new tkinterobject
self.root = tk.Tk()
self.scoreboard = {self.player1.name: 0,self.player2.name: 0,"ties": 0}
# Storing locations of available moves given a user clicks the window -- Tuple of locations
self.POSITIONS = [
(
self.disTANCE + (self.disTANCEGAP*column) - self.CIRCLERADIUS,self.disTANCE + (self.disTANCEGAP*column) + self.CIRCLERADIUS
)
for column in range(0,self.COLUMNS)
]
def who_won(self,board,piece):
"""
Determines the state of the game and finds if there is a winner
"""
# Horizontal
for col in range(0,self.COLUMNS - 3):
for row in range(0,self.ROWS):
if board[row][col] == piece and board[row][col + 1] == piece and board[row][col + 2] == piece and board[row][col + 3] == piece:
return True
# Vertical
for col in range(0,self.COLUMNS):
for row in range(0,self.ROWS - 3):
if board[row][col] == piece and board[row + 1][col] == piece and board[row + 2][col] == piece and board[row + 3][col] == piece:
return True
# Up-Left/Down-Right
for col in range(3,self.COLUMNS):
for row in range(3,self.ROWS):
if board[row][col] == piece and board[row - 1][col - 1] == piece and board[row - 2][col - 2] == piece and board[row - 3][col - 3] == piece:
return True
# Up-Right/Down-Left
for col in range(0,self.COLUMNS - 3):
for row in range(3,self.ROWS):
if board[row][col] == piece and board[row - 1][col + 1] == piece and board[row - 2][col + 2] == piece and board[row - 3][col + 3] == piece:
return True
# A winning move is not found
return False
def is_legal_move(self,position,board):
"""
Validates if a move is available/legal
"""
if board[0][position] == self.EMPTY:
return True
return False
def display_board(self):
"""
displaying the game board to the user
"""
# Function: rect(surface,color,rectangle object,optional width) -- First one forms the outline of the board
pygame.draw.rect(self.screen,colors.salmon,(self.LEFT,self.TOP,self.RECWIDTH,self.HEIGHT),13)
# This forms inner-most rectangle that users play on
pygame.draw.rect(self.screen,colors.burlywood,self.HEIGHT))
for column in range(0,self.COLUMNS):
colEq = self.disTANCE + (self.disTANCEGAP * column)
for row in range(0,self.ROWS):
# 125 is used here to make a the board placed in the center of the board and helps finding a value for self.TOP easier
rowEq = 125 + (self.disTANCEGAP * row)
if self.board[row][column] == self.EMPTY:
color = colors.white
elif self.board[row][column] == 0:
color = colors.realBlue
elif self.board[row][column] == 1:
color = colors.red
pygame.draw.circle(self.screen,(colEq,rowEq),self.CIRCLERADIUS,self.WIDTH)
pygame.display.flip()
def play(self):
"""
This is the game-loop
"""
while not self.gameOver:
self.display_board()
if self.moveNumber % 2 == 0:
userText,userRect = self.display_player_name(self.player1.name,colors.realBlue)
elif self.moveNumber % 2 == 1:
userText,userRect = self.display_player_name(self.player2.name,colors.red)
self.screen.blit(userText,userRect)
for event in pygame.event.get():
self.screen.fill(colors.aquamarine) # Set up background color
if event.type == pygame.QUIT:
self.gameOver = True
elif event.type == pygame.MOUSEBUTTONDOWN:
x,y = pygame.mouse.get_pos()
position = self.get_column_position(x)
if self.moveNumber % 2 == 0 and position != self.EMPTY:
if self.is_legal_move(position,self.board):
self.drop_piece_animation(position)
if self.who_won(self.board,0):
self.gameOver = True
self.scoreboard[self.player1.name] = self.scoreboard.get(self.player1.name) + 1
userText,userRect = self.display_player_name(self.player1.name + " " + "Wins!!!",colors.dark_gray)
elif self.check_if_tie(self.board):
self.gameOver = True
self.scoreboard["ties"] = self.scoreboard.get("ties") + 1
userText,userRect = self.display_player_name("It is a TIE!!!",colors.dark_gray)
elif self.moveNumber % 2 == 1 and position != self.EMPTY:
if self.is_legal_move(position,1):
self.gameOver = True
self.scoreboard[self.player2.name] = self.scoreboard.get(self.player2.name) + 1
userText,userRect = self.display_player_name(self.player2.name + " " + "Wins!!!",colors.dark_gray)
self.display_board()
self.screen.blit(userText,userRect)
pygame.display.flip()
self.display_scoreboard(False)
def display_scoreboard(self,isAi):
"""
This enables the tkinter object so I can display the user options after : Victory/Loss/Tie
"""
self.root.geometry('460x150+300+0')
self.reset()
self.root.title("Choices")
# This creates the Feedback information screen that the user sees after a game
tk.Label(self.root,text="Close window to go to main menu",15,'underline'),anchor='w',justify='left').grid(row=0,sticky="NSEW")
tk.Label(self.root,text=self.player1.name + ": " + str(self.scoreboard.get(self.player1.name)),15),justify='left').grid(row=1,sticky = "NSEW")
tk.Label(self.root,text=self.player2.name + ": " + str(self.scoreboard.get(self.player2.name)),justify='left').grid(row=2,text="Ties: " + str(self.scoreboard.get("ties")),justify='left').grid(row=3,sticky="NSEW")
# if isAi == True:
# # tk.Button(self.root,text='Rematch!',command=self.playAi,12),fg="blue").grid(row=4,sticky=tk.W)
# else:
tk.Button(self.root,command=self.play,sticky=tk.W)
# tk.Button(self.root,text='Rematch with Swap!',command= lambda: self.swapPlayers(isAi),fg="red").grid(row=4,column=2,sticky=tk.W)
tk.Entry(self.root)
self.root.mainloop()
def check_if_tie(self,board):
"""
A possible game state : Checking for a tie
"""
totalPieces = 0
for col in range(0,self.ROWS):
if board[row][col] == 0 or board[row][col] == 1:
totalPieces += 1
if totalPieces == 42:
return True
else:
return False
def display_player_name(self,name,color):
"""
A feature to help users kNow who's turn it is that gets displayed
"""
font = pygame.font.Font(pygame_menu.font.FONT_FRANCHISE,60)
text = font.render(name,True,color)
textRect = text.get_rect()
textRect.center = (len(name) * 30,20)
return text,textRect
def drop_piece_animation(self,position):
"""
Inserting a piece at a given position with the animation of a piece drop
"""
tmpRow = 5
while self.board[tmpRow][position] == 1 or self.board[tmpRow][position] == 0:
tmpRow -= 1
for i in range(0,tmpRow + 1):
self.board[i][position] = self.moveNumber % 2
self.display_board()
pygame.time.delay(200)
pygame.display.flip()
self.board[i][position] = self.EMPTY
self.board[tmpRow][position] = self.moveNumber % 2
self.moveNumber += 1
def get_column_position(self,position):
"""
Takes a X coordinate value dependent on a click and determines what column user clicked
"""
index = 0
for i in self.POSITIONS:
if position + self.CIRCLERADIUS/2 >= i[0] and position - self.CIRCLERADIUS/2 <= i[1]:
return index
index += 1
return self.EMPTY
def reset(self):
"""
Restoring the game in its original state
"""
self.moveNumber = 0
self.board = [[self.EMPTY for x in range(self.COLUMNS)] for y in range(self.ROWS)]
self.gameOver = False
def play_computer(self):
"""
This is the game-loop used for AI play
"""
# If/else block to distinguish the human/Ai because the ai cant mouse click events
if self.player1.name == "Ed": # Ed Watkins (Staten Island)
humanPlayer = 1
computerPlayer = 0
humanName = self.player2.name
computerName = self.player1.name
elif self.player2.name == "Ed":
humanPlayer = 0
computerPlayer = 1
humanName = self.player1.name
computerName = self.player2.name
while not self.gameOver:
self.display_board()
if self.moveNumber % 2 == 0:
userText,colors.blue)
elif self.moveNumber % 2 == 1:
userText,y = pygame.mouse.get_pos()
position = self.get_column_position(x)
if self.moveNumber % 2 == humanPlayer and position != self.EMPTY:
if self.is_legal_move(position,humanPlayer):
self.gameOver = True
self.scoreboard[humanName] = self.scoreboard.get(humanName) + 1
userText,userRect = self.display_player_name(humanName + " " + "Wins!!!",colors.dark_gray)
if self.moveNumber % 2 == computerPlayer and self.gameOver == False:
move = self.generate_move(self.board,4,computerPlayer,humanPlayer,self.moveNumber)
self.drop_piece_animation(move)
if self.who_won(self.board,computerPlayer):
self.gameOver = True
self.scoreboard[computerName] = self.scoreboard.get(computerName) + 1
userText,userRect = self.display_player_name(computerName + " " + "Wins!!!",colors.dark_gray)
elif self.check_if_tie(self.board):
self.gameOver = True
self.scoreboard["ties"] = self.scoreboard.get("ties") + 1
userText,colors.dark_gray)
self.display_board()
self.screen.blit(userText,userRect)
pygame.display.flip()
Minimax.py
from Connect4 import Connect4
import random
from copy import copy,deepcopy
import pygame
class Minimax(Connect4):
def __init__(self,screen):
super().__init__(player1,screen)
def is_game_over(self,board):
if self.who_won(board,1) or self.who_won(board,0):
return True
return False
def generate_move(self,depth,maximizingPlayer,moveNumber):
if depth == 0 or self.is_game_over(board) or self.check_if_tie(board):
if self.is_game_over(board):
if self.who_won(board,computerPlayer):
return 1000000
elif self.who_won(board,humanPlayer):
return -1000000
elif self.check_if_tie(board):
return 0
else:
return self.get_game_score(board,humanPlayer)
if maximizingPlayer:
maxValue = -1000000
for move in range(0,self.COLUMNS):
tmpBoard = self.copyBoard(board)
if self.is_legal_move(move,tmpBoard):
self.drop_piece_computer(move,tmpBoard,moveNumber)
result = self.generate_move(tmpBoard,depth - 1,False,moveNumber + 1)
if result >= maxValue:
maxValue = result
bestMove = move
return bestMove
else:
minValue = 1000000
for move in range(0,moveNumber + 1)
if result <= minValue:
minValue = result
thismove = move
return thismove
def copyBoard(self,board):
tmpList = [[self.EMPTY for x in range(self.COLUMNS)] for y in range(self.ROWS)]
for row in range(0,self.ROWS):
for col in range(0,self.COLUMNS):
tmpList[row][col] = board[row][col]
return tmpList
def drop_piece_computer(self,moveNumber):
"""
Inserting a piece at a given position with the animation of a piece drop
"""
tmpRow = 5
while board[tmpRow][position] == 1 or board[tmpRow][position] == 0:
tmpRow -= 1
board[tmpRow][position] = moveNumber % 2
# moveNumber += 1
def get_game_score(self,humanPlayer):
totalscore = 0
totalscore += self.get_hori_score(board,humanPlayer)
# totalscore += self.get_vert_score(board,humanPlayer)
# totalscore += self.get_upright_score(board,humanPlayer)
# totalscore += self.get_upleft_score(board,humanPlayer)
return totalscore
def get_hori_score(self,humanPlayer):
score = 0
# List to collect all the groupings of 4(Horizontally) out of the current game state
groupingFourList = []
for col in range(0,self.ROWS):
groupingFourList.append(board[row][col])
groupingFourList.append(board[row][col + 1])
groupingFourList.append(board[row][col + 2])
groupingFourList.append(board[row][col + 3])
computerPieces = self.count_player_pieces(groupingFourList,1)
humanPieces = self.count_player_pieces(groupingFourList,0)
emptyPieces = self.count_player_pieces(groupingFourList,self.EMPTY)
score += self.score_metric(computerPieces,humanPieces,emptyPieces)
groupingFourList = []
return score
def get_upright_score(self,self.ROWS):
groupingFourList.append(board[row][col])
groupingFourList.append(board[row - 1][col + 1])
groupingFourList.append(board[row - 2][col + 2])
groupingFourList.append(board[row - 3][col + 3])
computerPieces = self.count_player_pieces(groupingFourList,emptyPieces)
groupingFourList = []
return score
def get_upleft_score(self,humanPlayer):
score = 0
# List to collect all the groupings of 4(Horizontally) out of the current game state
groupingFourList = []
for col in range(3,self.ROWS):
groupingFourList.append(board[row][col])
groupingFourList.append(board[row - 1][col - 1])
groupingFourList.append(board[row - 2][col - 2])
groupingFourList.append(board[row - 3][col - 3])
computerPieces = self.count_player_pieces(groupingFourList,humanPlayer)
emptyPieces = self.count_player_pieces(groupingFourList,emptyPieces)
groupingFourList = []
return score
def get_vert_score(self,self.ROWS -3):
groupingFourList.append(board[row][col])
groupingFourList.append(board[row + 1][col])
groupingFourList.append(board[row + 2][col])
groupingFourList.append(board[row + 3][col])
computerPieces = self.count_player_pieces(groupingFourList,computerPlayer)
humanPieces = self.count_player_pieces(groupingFourList,emptyPieces)
groupingFourList = []
return score
def count_player_pieces(self,groupingFourList,playerPiece):
totalPieces = 0
for piece in groupingFourList:
if piece == playerPiece:
totalPieces += 1
return totalPieces
def score_metric(self,computerPieces,emptyPieces):
score = 0
# Making bot prioritize playing defense than offense
# Thats why the score is lower when regarding the enemy: AI chooses highest scoring move
if (computerPieces == 4):
score += 100
elif (computerPieces == 3 and emptyPieces == 1):
score += 20
elif (computerPieces == 2 and emptyPieces == 2):
score += 10
if (humanPieces == 3 and emptyPieces == 1):
score -= 100
return score
颜色.py
"""
Valid colors to use got it from this link : https://python-forum.io/Thread-PyGame-PyGame-Colors
"""
realBlue = (0,255)
white = (255,255,255)
green = (0,0)
black = (0,0)
orange = (255,100,10)
blue_green = (0,170)
marroon = (115,0)
lime = (180,100)
pink = (255,180)
purple = (240,255)
magenta = (255,230)
brown = (100,40,0)
forest_green = (0,50,0)
navy_blue = (0,100)
rust = (210,150,75)
dandilion_yellow = (255,200,0)
Highlighter = (255,100)
sky_blue = (0,255)
light_gray = (200,200)
dark_gray = (50,50)
tan = (230,220,170)
coffee_brown = (200,190,140)
moon_glow = (235,245,255)
burlywood = (255,211,155)
salmon = (139,76,57)
aquamarine = (127,212)
#Colors used for menu
blue = (135,206,250)
yellow = (255,0)
red = (255,0)
gray = (128,128,128)
播放器.py
class Player():
def __init__(self,name):
self.name = name
解决方案已解决,但 stackoverflow 不允许我删除问题。我想删除这个问题的原因是因为提供的答案不是解决方案,所以它只会让其他人望而却步。
解决方法
好的解决方法如下:
在这部分代码中,将遍历搜索空间的一部分,并根据每个玩家的最佳动作评估游戏状态。通过这个算法,AI 知道每个玩家的最佳走法,并且可以做出“好的”走法。
if maximizingPlayer:
maxValue = -1000000
for move in range(0,self.COLUMNS):
tmpBoard = self.copyBoard(board)
if self.is_legal_move(move,tmpBoard):
self.drop_piece_computer(move,tmpBoard,moveNumber)
result = self.generate_move(tmpBoard,depth - 1,computerPlayer,humanPlayer,False,moveNumber + 1)
if result >= maxValue:
maxValue = result
bestMove = move
return bestMove
else:
minValue = 1000000
for move in range(0,True,moveNumber + 1)
if result <= minValue:
minValue = result
thismove = move
return thismove
但是,如果您仔细观察,当我在 !maximizing player 函数中递归地将函数调用回 AI 时:
result = self.generate_move(tmpBoard,moveNumber + 1)
换句话说,当模拟人类玩家在通用棋盘上玩以生成 ai 移动时,它假设有 2 个人类玩家,这就是为什么会发生暂停的原因,因为您不能与 2 个相同的玩家进行游戏.
如此变化:
result = self.generate_move(tmpBoard,moveNumber + 1)
为此:
result = self.generate_move(tmpBoard,moveNumber + 1)
,
将 False
代替 True
放在 if maximizingPlayer
块中,将 True
代替 False
放在 else 子句中作为递归的倒数第二个参数调用 generate_move
似乎可以解决问题。
即将 Minimax.py 第 33 行的 False 改为 True,将第 44 行的 True 改为 False。