Created
September 9, 2018 06:20
This program is a Connect four game that is powered by python. The premise around this program is to have the computer face the user in a game of connect four. Each player makes a move after each other while trying to produce a sequence of 4 tokens side by side, either vertically, horizontally, or diagonally. First one to create a four token lin…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Connect four | |
# By Abhishek Deorukhkar | |
import random, copy, sys, pygame | |
from pygame.locals import * | |
BOARDWIDTH = 7 # The number of spaces widthwise | |
BOARDHEIGHT = 6 # The number of spaces lengthwise | |
assert BOARDWIDTH >= 4 and BOARDHEIGHT >= 4, 'Board must be at least 4x4.' | |
DIFFICULTY = 2 # how many steps should the computer forsee | |
SPACESIZE = 50 # how big the tokens and the spaces where the tokens lie should be in pixels | |
FPS = 60 # Frames per second | |
WINDOWWIDTH = 640 # window width | |
WINDOWHEIGHT = 480 # window height | |
XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * SPACESIZE) / 2) | |
YMARGIN = int((WINDOWHEIGHT - BOARDHEIGHT * SPACESIZE) / 2) | |
BRIGHTBLUE = (0, 50, 255) | |
WHITE = (255, 255, 255) | |
BGCOLOR = BRIGHTBLUE | |
TEXTCOLOR = WHITE | |
RED = 'red' | |
BLACK = 'black' | |
EMPTY = None | |
HUMAN = 'human' | |
COMPUTER = 'computer' | |
def main(): | |
global FPSCLOCK, DISPLAYSURF, REDPILERECT, BLACKPILERECT, REDTOKENIMG | |
global BLACKTOKENIMG, BOARDIMG, ARROWIMG, ARROWRECT, HUMANWINNERIMG | |
global COMPUTERWINNERIMG, WINNERRECT, TIEWINNERIMG | |
pygame.init() | |
FPSCLOCK = pygame.time.Clock() | |
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) | |
pygame.display.set_caption('Four in a Row') | |
REDPILERECT = pygame.Rect(int(SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE) | |
BLACKPILERECT = pygame.Rect(WINDOWWIDTH - int(3 * SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE) | |
REDTOKENIMG = pygame.image.load('4row_red.png') | |
REDTOKENIMG = pygame.transform.smoothscale(REDTOKENIMG, (SPACESIZE, SPACESIZE)) | |
BLACKTOKENIMG = pygame.image.load('4row_black.png') | |
BLACKTOKENIMG = pygame.transform.smoothscale(BLACKTOKENIMG, (SPACESIZE, SPACESIZE)) | |
BOARDIMG = pygame.image.load('4row_board.png') | |
BOARDIMG = pygame.transform.smoothscale(BOARDIMG, (SPACESIZE, SPACESIZE)) | |
HUMANWINNERIMG = pygame.image.load('4row_humanwinner.png') | |
COMPUTERWINNERIMG = pygame.image.load('4row_computerwinner.png') | |
TIEWINNERIMG = pygame.image.load('4row_tie.png') | |
WINNERRECT = HUMANWINNERIMG.get_rect() | |
WINNERRECT.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)) | |
ARROWIMG = pygame.image.load('4row_arrow.png') | |
ARROWRECT = ARROWIMG.get_rect() | |
ARROWRECT.left = REDPILERECT.right + 10 | |
ARROWRECT.centery = REDPILERECT.centery | |
isFirstGame = True | |
while True: | |
runGame(isFirstGame) | |
isFirstGame = False | |
def runGame(isFirstGame): | |
if isFirstGame: | |
# Let the computer go first on the first game, so the player | |
# can see how the tokens are dragged from the token piles. | |
turn = COMPUTER | |
showHelp = True | |
else: | |
# Randomly choose who goes first. | |
if random.randint(0, 1) == 0: | |
turn = COMPUTER | |
else: | |
turn = HUMAN | |
showHelp = False | |
# Set up a blank board data structure. | |
mainBoard = getNewBoard() | |
while True: # main game loop | |
if turn == HUMAN: | |
# Human player's turn. | |
getHumanMove(mainBoard, showHelp) | |
if showHelp: | |
# turn off help arrow after the first move | |
showHelp = False | |
if isWinner(mainBoard, RED): | |
winnerImg = HUMANWINNERIMG | |
break | |
turn = COMPUTER # switch to other player's turn | |
else: | |
# Computer player's turn. | |
column = getComputerMove(mainBoard) | |
animateComputerMoving(mainBoard, column) | |
makeMove(mainBoard, BLACK, column) | |
if isWinner(mainBoard, BLACK): | |
winnerImg = COMPUTERWINNERIMG | |
break | |
turn = HUMAN # switch to other player's turn | |
if isBoardFull(mainBoard): | |
# A completely filled board means it's a tie. | |
winnerImg = TIEWINNERIMG | |
break | |
while True: | |
# Keep looping until player clicks the mouse or quits. | |
drawBoard(mainBoard) | |
DISPLAYSURF.blit(winnerImg, WINNERRECT) | |
pygame.display.update() | |
FPSCLOCK.tick() | |
for event in pygame.event.get(): # event handling loop | |
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE): | |
pygame.quit() | |
sys.exit() | |
elif event.type == MOUSEBUTTONUP: | |
return | |
def makeMove(board, player, column): | |
lowest = getLowestEmptySpace(board, column) | |
if lowest != -1: | |
board[column][lowest] = player | |
def drawBoard(board, extraToken=None): | |
DISPLAYSURF.fill(BGCOLOR) | |
# draw tokens | |
spaceRect = pygame.Rect(0, 0, SPACESIZE, SPACESIZE) | |
for x in range(BOARDWIDTH): | |
for y in range(BOARDHEIGHT): | |
spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE)) | |
if board[x][y] == RED: | |
DISPLAYSURF.blit(REDTOKENIMG, spaceRect) | |
elif board[x][y] == BLACK: | |
DISPLAYSURF.blit(BLACKTOKENIMG, spaceRect) | |
# draw the extra token | |
if extraToken != None: | |
if extraToken['color'] == RED: | |
DISPLAYSURF.blit(REDTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE)) | |
elif extraToken['color'] == BLACK: | |
DISPLAYSURF.blit(BLACKTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE)) | |
# draw board over the tokens | |
for x in range(BOARDWIDTH): | |
for y in range(BOARDHEIGHT): | |
spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE)) | |
DISPLAYSURF.blit(BOARDIMG, spaceRect) | |
# draw the red and black tokens off to the side | |
DISPLAYSURF.blit(REDTOKENIMG, REDPILERECT) # red on the left | |
DISPLAYSURF.blit(BLACKTOKENIMG, BLACKPILERECT) # black on the right | |
def getNewBoard(): | |
board = [] | |
for x in range(BOARDWIDTH): | |
board.append([EMPTY] * BOARDHEIGHT) | |
return board | |
def getHumanMove(board, isFirstMove): | |
draggingToken = False | |
tokenx, tokeny = None, None | |
while True: | |
for event in pygame.event.get(): # event handling loop | |
if event.type == QUIT: | |
pygame.quit() | |
sys.exit() | |
elif event.type == MOUSEBUTTONDOWN and not draggingToken and REDPILERECT.collidepoint(event.pos): | |
# start of dragging on red token pile. | |
draggingToken = True | |
tokenx, tokeny = event.pos | |
elif event.type == MOUSEMOTION and draggingToken: | |
# update the position of the red token being dragged | |
tokenx, tokeny = event.pos | |
elif event.type == MOUSEBUTTONUP and draggingToken: | |
# let go of the token being dragged | |
if tokeny < YMARGIN and tokenx > XMARGIN and tokenx < WINDOWWIDTH - XMARGIN: | |
# let go at the top of the screen. | |
column = int((tokenx - XMARGIN) / SPACESIZE) | |
if isValidMove(board, column): | |
animateDroppingToken(board, column, RED) | |
board[column][getLowestEmptySpace(board, column)] = RED | |
drawBoard(board) | |
pygame.display.update() | |
return | |
tokenx, tokeny = None, None | |
draggingToken = False | |
if tokenx != None and tokeny != None: | |
drawBoard(board, {'x':tokenx - int(SPACESIZE / 2), 'y':tokeny - int(SPACESIZE / 2), 'color':RED}) | |
else: | |
drawBoard(board) | |
if isFirstMove: | |
# Show the help arrow for the player's first move. | |
DISPLAYSURF.blit(ARROWIMG, ARROWRECT) | |
pygame.display.update() | |
FPSCLOCK.tick() | |
def animateDroppingToken(board, column, color): | |
x = XMARGIN + column * SPACESIZE | |
y = YMARGIN - SPACESIZE | |
dropSpeed = 1.0 | |
lowestEmptySpace = getLowestEmptySpace(board, column) | |
while True: | |
y += int(dropSpeed) | |
dropSpeed += 0.5 | |
if int((y - YMARGIN) / SPACESIZE) >= lowestEmptySpace: | |
return | |
drawBoard(board, {'x':x, 'y':y, 'color':color}) | |
pygame.display.update() | |
FPSCLOCK.tick() | |
def animateComputerMoving(board, column): | |
x = BLACKPILERECT.left | |
y = BLACKPILERECT.top | |
speed = 1.0 | |
# moving the black tile up | |
while y > (YMARGIN - SPACESIZE): | |
y -= int(speed) | |
speed += 0.5 | |
drawBoard(board, {'x':x, 'y':y, 'color':BLACK}) | |
pygame.display.update() | |
FPSCLOCK.tick() | |
# moving the black tile over | |
y = YMARGIN - SPACESIZE | |
speed = 1.0 | |
while x > (XMARGIN + column * SPACESIZE): | |
x -= int(speed) | |
speed += 0.5 | |
drawBoard(board, {'x':x, 'y':y, 'color':BLACK}) | |
pygame.display.update() | |
FPSCLOCK.tick() | |
# dropping the black tile | |
animateDroppingToken(board, column, BLACK) | |
def getComputerMove(board): | |
potentialMoves = getPotentialMoves(board, BLACK, DIFFICULTY) | |
# get the best fitness from the potential moves | |
bestMoveFitness = -1 | |
for i in range(BOARDWIDTH): | |
if potentialMoves[i] > bestMoveFitness and isValidMove(board, i): | |
bestMoveFitness = potentialMoves[i] | |
# find all potential moves that have this best fitness | |
bestMoves = [] | |
for i in range(len(potentialMoves)): | |
if potentialMoves[i] == bestMoveFitness and isValidMove(board, i): | |
bestMoves.append(i) | |
return random.choice(bestMoves) | |
def getPotentialMoves(board, tile, lookAhead): | |
if lookAhead == 0 or isBoardFull(board): | |
return [0] * BOARDWIDTH | |
if tile == RED: | |
enemyTile = BLACK | |
else: | |
enemyTile = RED | |
# Figure out the best move to make. | |
potentialMoves = [0] * BOARDWIDTH | |
for firstMove in range(BOARDWIDTH): | |
dupeBoard = copy.deepcopy(board) | |
if not isValidMove(dupeBoard, firstMove): | |
continue | |
makeMove(dupeBoard, tile, firstMove) | |
if isWinner(dupeBoard, tile): | |
# a winning move automatically gets a perfect fitness | |
potentialMoves[firstMove] = 1 | |
break # don't bother calculating other moves | |
else: | |
# do other player's counter moves and determine best one | |
if isBoardFull(dupeBoard): | |
potentialMoves[firstMove] = 0 | |
else: | |
for counterMove in range(BOARDWIDTH): | |
dupeBoard2 = copy.deepcopy(dupeBoard) | |
if not isValidMove(dupeBoard2, counterMove): | |
continue | |
makeMove(dupeBoard2, enemyTile, counterMove) | |
if isWinner(dupeBoard2, enemyTile): | |
# a losing move automatically gets the worst fitness | |
potentialMoves[firstMove] = -1 | |
break | |
else: | |
# do the recursive call to getPotentialMoves() | |
results = getPotentialMoves(dupeBoard2, tile, lookAhead - 1) | |
potentialMoves[firstMove] += (sum(results) / BOARDWIDTH) / BOARDWIDTH | |
return potentialMoves | |
def getLowestEmptySpace(board, column): | |
# Return the row number of the lowest empty row in the given column. | |
for y in range(BOARDHEIGHT-1, -1, -1): | |
if board[column][y] == EMPTY: | |
return y | |
return -1 | |
def isValidMove(board, column): | |
# Returns True if there is an empty space in the given column. | |
# Otherwise returns False. | |
if column < 0 or column >= (BOARDWIDTH) or board[column][0] != EMPTY: | |
return False | |
return True | |
def isBoardFull(board): | |
# Returns True if there are no empty spaces anywhere on the board. | |
for x in range(BOARDWIDTH): | |
for y in range(BOARDHEIGHT): | |
if board[x][y] == EMPTY: | |
return False | |
return True | |
def isWinner(board, tile): | |
# check horizontal spaces | |
for x in range(BOARDWIDTH - 3): | |
for y in range(BOARDHEIGHT): | |
if board[x][y] == tile and board[x+1][y] == tile and board[x+2][y] == tile and board[x+3][y] == tile: | |
return True | |
# check vertical spaces | |
for x in range(BOARDWIDTH): | |
for y in range(BOARDHEIGHT - 3): | |
if board[x][y] == tile and board[x][y+1] == tile and board[x][y+2] == tile and board[x][y+3] == tile: | |
return True | |
# check / diagonal spaces | |
for x in range(BOARDWIDTH - 3): | |
for y in range(3, BOARDHEIGHT): | |
if board[x][y] == tile and board[x+1][y-1] == tile and board[x+2][y-2] == tile and board[x+3][y-3] == tile: | |
return True | |
# check \ diagonal spaces | |
for x in range(BOARDWIDTH - 3): | |
for y in range(BOARDHEIGHT - 3): | |
if board[x][y] == tile and board[x+1][y+1] == tile and board[x+2][y+2] == tile and board[x+3][y+3] == tile: | |
return True | |
return False | |
if __name__ == '__main__': | |
main() |
You will need the recommended plugins (From pygame.locals) in order for the client to pull different commands such as "random, copy, sys, pygame".
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is my first attempt at publishing code. Although it is not an original idea, the code and the process of writing this code is genuinely of my own actions. I do not take credit for this game at all, yet I do take credit for my code.