【发布时间】:2021-04-09 04:27:33
【问题描述】:
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, 0, 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, font=(None, 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, font=(None, 15))
p1.grid(row=3, column=1)
tk.Button(root, text="Play Computer!", command=lambda: play_computer(colorChoice.get(), p1.get(), root, screen)).grid(row=10, column=1)
tk.mainloop()
def play_computer(colorChoice, playerName, root, 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, root, 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], 80, 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.LEFT, self.TOP, self.RECWIDTH, 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, color, (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, self.board):
self.drop_piece_animation(position)
if self.who_won(self.board, 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)
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)
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", font=(None, 15, 'underline'), anchor='w', justify='left').grid(row=0, column=1, sticky="NSEW")
tk.Label(self.root, text=self.player1.name + ": " + str(self.scoreboard.get(self.player1.name)), font=(None, 15), anchor='w', justify='left').grid(row=1, column=1, sticky = "NSEW")
tk.Label(self.root, text=self.player2.name + ": " + str(self.scoreboard.get(self.player2.name)), font=(None, 15), anchor='w', justify='left').grid(row=2, column=1, sticky="NSEW")
tk.Label(self.root, text="Ties: " + str(self.scoreboard.get("ties")), font=(None, 15), anchor='w', justify='left').grid(row=3, column=1, sticky="NSEW")
# if isAi == True:
# # tk.Button(self.root, text='Rematch!', command=self.playAi, font=(None, 12), fg="blue").grid(row=4, column=1, sticky=tk.W)
# else:
tk.Button(self.root, text='Rematch!', command=self.play, font=(None, 12), fg="blue").grid(row=4, column=1, sticky=tk.W)
# tk.Button(self.root, text='Rematch with Swap!', command= lambda: self.swapPlayers(isAi), font=(None, 12), 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.COLUMNS):
for row 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, userRect = self.display_player_name(self.player1.name, colors.blue)
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 == humanPlayer and position != self.EMPTY:
if self.is_legal_move(position, self.board):
self.drop_piece_animation(position)
if self.who_won(self.board, humanPlayer):
self.gameOver = True
self.scoreboard[humanName] = self.scoreboard.get(humanName) + 1
userText, userRect = self.display_player_name(humanName + " " + "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)
if self.moveNumber % 2 == computerPlayer and self.gameOver == False:
move = self.generate_move(self.board, 4, computerPlayer, humanPlayer, True, 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, userRect = self.display_player_name("It is a TIE!!!", 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, player1, player2, screen):
super().__init__(player1, player2, 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, board, depth, computerPlayer, humanPlayer, 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, computerPlayer, 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, computerPlayer, humanPlayer, False, moveNumber + 1)
if result >= maxValue:
maxValue = result
bestMove = move
return bestMove
else:
minValue = 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, humanPlayer, humanPlayer, True, 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, position, board, 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, board, computerPlayer, humanPlayer):
totalScore = 0
totalScore += self.get_hori_score(board, computerPlayer, humanPlayer)
# totalScore += self.get_vert_score(board, computerPlayer, humanPlayer)
# totalScore += self.get_upright_score(board, computerPlayer, humanPlayer)
# totalScore += self.get_upleft_score(board, computerPlayer, humanPlayer)
return totalScore
def get_hori_score(self, board, computerPlayer, 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.COLUMNS - 3):
for row 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, board, computerPlayer, 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.COLUMNS - 3):
for row 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, 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_upleft_score(self, board, computerPlayer, 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.COLUMNS):
for row 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, 1)
humanPieces = self.count_player_pieces(groupingFourList, humanPlayer)
emptyPieces = self.count_player_pieces(groupingFourList, self.EMPTY)
score += self.score_metric(computerPieces, humanPieces, emptyPieces)
groupingFourList = []
return score
def get_vert_score(self, board, computerPlayer, 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.COLUMNS):
for row in range(0, 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, humanPlayer)
emptyPieces = self.count_player_pieces(groupingFourList, self.EMPTY)
score += self.score_metric(computerPieces, humanPieces, 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, humanPieces, 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
colors.py
"""
Valid colors to use got it from this link : https://python-forum.io/Thread-PyGame-PyGame-Colors
"""
realBlue = (0,0,255)
white = (255,255,255)
green = (0,255,0)
black = (0,0,0)
orange = (255,100,10)
blue_green = (0,255,170)
marroon = (115,0,0)
lime = (180,255,100)
pink = (255,100,180)
purple = (240,0,255)
magenta = (255,0,230)
brown = (100,40,0)
forest_green = (0,50,0)
navy_blue = (0,0,100)
rust = (210,150,75)
dandilion_yellow = (255,200,0)
highlighter = (255,255,100)
sky_blue = (0,255,255)
light_gray = (200,200,200)
dark_gray = (50,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, 255, 212)
#Colors used for menu
blue = (135, 206, 250)
yellow = (255, 255, 0)
red = (255,0,0)
gray = (128, 128, 128)
播放器.py
class Player():
def __init__(self, name):
self.name = name
解决方案已解决,但 stackoverflow 不允许我删除问题。我希望删除问题的原因是因为提供的答案不是解决方案,所以它只会让其他人失望。
【问题讨论】:
-
@rabbid76 所以你想让我告诉你解决方法是什么但让它成为答案?
-
@Rabbid76 希望这是有道理的。
标签: python-3.x recursion pygame minimax