AI Checkers to Chess
An Experiment in Adaptive Game AI
from graphics import *
import sys
import tkMessageBox
#+-added to be able to select a random number from a range of numbers
from random import randrange
'''
The revisions in the code are labelled by the prefix '#+-'
'''
class Checkers:
def init(s):
s.state = 'CustomSetup'
s.is1P = False
#+-added string
s.compIsColour = 'not playing' #computer not playing by default
s.placeColour = 'White'
s.placeRank = 'Pawn'
s.placeType = 'Place' #Place or Delete (piece)
s.pTurn = 'White' #White or Black (turn)
s.selectedTile = ''
s.selectedTileAt = []
s.hasMoved = False
s.pieceCaptured = False
s.BoardDimension = 8
s.numPiecesAllowed = 12
s.win = GraphWin('Checkers',600,600) #draws screen
s.win.setBackground('White')
s.win.setCoords(-1,-3,11,9) #creates a coordinate system for the window
s.ClearBoard()
s.tiles = [[Tile(s.win,i,j,False) for i in range(s.BoardDimension)] for j in range(s.BoardDimension)] #creates the 2D list and initializes all 8x8 entries to an empty tile
#+-added two lists
s.moves = []
s.badMoves = []
gridLetters = ['A','B','C','D','E','F','G','H',]
for i in range(s.BoardDimension):
Text(Point(-0.5,i+0.5),i+1).draw(s.win) #left and right numbers for grid
Text(Point(8.5,i+0.5),i+1).draw(s.win)
Text(Point(i+0.5,-0.5),gridLetters[i]).draw(s.win) #bottom and top letters
Text(Point(i+0.5,8.5),gridLetters[i]).draw(s.win)
s.SetButtons()
s.SetupBoard()
####
#handles the setup of the board (i.e. piece placement)
####
def SetupBoard(s):
while s.state == 'CustomSetup':
s.Click()
if s.state == 'Play':
s.Play()
####
#handles the general play of the game
####
def Play(s):
while s.state == 'Play':
#+-added if statement
if s.is1P and s.compIsColour == s.pTurn:
s.CompTurn()
else:
s.Click()
if s.state == 'CustomSetup':
s.SetupBoard()
####
#+-added to be able to control the computer's turn
####
def CompTurn(s):
s.moves = s.movesAvailable()
s.badMoves = []
#######consider making s.goodMoves which replaces s.badMoves for many uses (changes conditions a bit as well)
#To prevent leaving the back row (currently top priority)
for move in s.moves:
if s.movesFromBack(move):
s.badMoves.append(move)
s.removeBadMoves()
#This may not always work, it is not expected/designed to (hopefully it usually works)
#It is meant to promote the use of moves that allow capture of another piece afterwards
for move in s.moves:
if not s.PieceCanCapture(s.moveEndsAt(move)[0],s.moveEndsAt(move)[1]):
s.badMoves.append(move)
s.removeBadMoves()
#Promotes move which make kings
for move in s.moves:
if not (((s.moveEndsAt(move)[1]==7 and s.tiles[move[0]][move[1]].isWhite) or \
(s.moveEndsAt(move)[1]==0 and s.tiles[move[0]][move[1]].isBlack)) and \
s.tiles[move[0]][move[1]].isPawn):
s.badMoves.append(move)
s.removeBadMoves()
#Promotes moves which take kings for free
for move in s.moves:
if not ((s.tiles[move[2]][move[3]].isKing) and s.isMoveSafe(move)):
s.badMoves.append(move)
s.removeBadMoves()
#This may not always work, it is not expected/designed to (hopefully it usually works)
#Promotes moves which trade your pawn for opponent king
for move in s.moves:
if not ((s.tiles[move[0]][move[1]].isPawn) and (s.tiles[move[2]][move[3]].isKing) and not s.isMoveSafe(move)):
s.badMoves.append(move)
s.removeBadMoves()
#This may not always work, it is not expected/designed to (hopefully it usually works)
#Promotes moves which trade kings when ahead in ***pieces <--might want to change classification of being ahead
for move in s.moves:
if not ((s.tiles[move[0]][move[1]].isKing) and (s.tiles[move[2]][move[3]].isKing) and not s.isMoveSafe(move) and s.hasMorePieces()):
s.badMoves.append(move)
s.removeBadMoves()
#This may not always work, it is not expected/designed to (hopefully it usually works)
#Promotes moves which trade pawns when ahead in ***pieces <--might want to change classification of being ahead
for move in s.moves:
if not ((s.tiles[move[0]][move[1]].isPawn) and (s.tiles[move[2]][move[3]].isPawn) and not s.isMoveSafe(move) and s.hasMorePieces()):
s.badMoves.append(move)
s.removeBadMoves()
#Promotes moves which does not endanger itself needlessly
for move in s.moves:
if not s.isMoveSafe(move):
s.badMoves.append(move)
s.removeBadMoves()
#should implement the next part to pick at random from the remaining moves (does not do this)
#performs the select and move action for the computer
m = randrange(0,len(s.moves))
if s.selectedTileAt == []:
s.Action(s.moves[m][0],s.moves[m][1])
s.Action(s.moves[m][2],s.moves[m][3])
####
#+-added to determine whether the player whose turn it is has more pieces than opponent
####
def hasMorePieces(s):
return s.numColour(s.pTurn) > s.numColour(s.opposite(s.pTurn))
#+-added new method
#Returns true if a GIVEN piece cannot be taken immediately after ITS proposed move
#########Might want another method to know if a move exposes another piece
#Likely to not work (depends on how PieceCanCapturePiece method works)
def isMoveSafe(s,move):
X1,Y1 = [s.moveEndsAt(move)[0]-1,s.moveEndsAt(move)[0]+1],[s.moveEndsAt(move)[1]-1,s.moveEndsAt(move)[1]+1]
for i in range(2):
for j in range(2):
if s.SpecialPCCP(s.tiles[move[0]][move[1]].pieceColour,X1[i],Y1[j],s.moveEndsAt(move)[0],s.moveEndsAt(move)[1],move[0],move[1]):
return False
return True
#+-added new method
#modification to PieceCanCapturePiece such that it works with the isMoveSafe method
def SpecialPCCP(s,piece2Colour,x,y,X,Y,initX,initY):
X1,X2,Y1,Y2 = [x-1,x+1],[x-2,x+2],[y-1,y+1],[y-2,y+2]
if ((0<=X<8) and (0<=Y<8)) and ((0<=x<8) and (0<=y<8)):
if (piece2Colour == s.opposite(s.tiles[x][y].pieceColour)):
if s.CanDoWalk(x,y,X,Y,exception=False):
for i in range(2):
for j in range(2):
if X1[i]==X and Y1[j]==Y:
if (0<=X2[i]<8) and (0<=Y2[j]<8):
if not (s.tiles[X2[i]][Y2[j]].isPiece) or (X2[i]==initX and Y2[j]==initY):
return True
return False
#+-added new method
#Removes badMoves from the list of moves that can be made
#if all of the moves that can be made are badMoves, we still need to do something so it leaves all of them
#Note: at any time this is run, the moves that are considered bad should be considered bad for the same reason (i.e. they are equivalently bad)
def removeBadMoves(s): #assumes moves were added to badMoves in the same order as they appear in moves
if s.moves != s.badMoves:
for move in s.badMoves:
s.moves.remove(move)
s.badMoves = []
#+-added new method
def movesFromBack(s,move):
if (move[1]==0 and s.compIsColour=='White') or \
(move[1]==7 and s.compIsColour=='Black'):
return True
else:
return False
#+-added new method
def moveEndsAt(s,move): #takes 4 element array representing a move
if s.tiles[move[2]][move[3]].isPiece:
return [move[0]+(move[2]-move[0])*2,move[1]+(move[3]-move[1])*2]
else:
return [move[2],move[3]]
#+-added to calculate all the available valid moves
def movesAvailable(s):
moves=[]
for j in range(8):
for i in range(8):
X1,Y1 = [i-1,i+1],[j-1,j+1]
for a in range(2):
for b in range(2):
if 0<=X1[a]<8 and 0<=Y1[b]<8:
if s.moveIsValid(i,j,X1[a],Y1[b]):
moves.append([i,j,X1[a],Y1[b]])
return moves
#Resets the board to be empty
def ClearBoard(s):
s.tiles=[[Tile(s.win,i,j,False) for i in range(s.BoardDimension)] for j in range(s.BoardDimension)] #creates the 2D list and initializes all 8x8 entries to an empty tile
for i in range(s.BoardDimension):
for j in range(s.BoardDimension):
s.ColourButton(s.TileColour(i,j),i,j)
s.state = 'CustomSetup'
s.pTurn = 'White'
s.SetButtons()
def ColourButton(s,colour,X,Y,width=1,height=1): #function to create a rectangle with a given colour, size, and location
rect = Rectangle(Point(X,Y),Point(X+width,Y+height))
rect.setFill(colour)
rect.draw(s.win)
def TileColour(s,x,y):
if (x%2 == 0 and y%2 == 0) or (x%2 == 1 and y%2 == 1):
return 'Red' #sets every other square to red
else:
return 'White' #every non red square to white
##########
#Draws Buttons
##########
def SetButtons(s):
s.ColourButton('White',-1,-3,12,2)
s.ColourButton('White',9,-1,2,10)
if s.state == 'CustomSetup':
s.DrawStandard()
s.DrawStart()
s.DrawClear()
s.Draw1P()
s.Draw2P()
s.DrawLoad()
s.DrawSave()
s.DrawTurn()
s.DrawX()
s.DrawW()
s.DrawB()
s.DrawK()
s.DrawDel()
s.DrawScore() #not actually a button
elif s.state == 'Play':
#+-updated method name
s.DrawResign()
s.DrawSave()
s.DrawTurn()
s.DrawX()
s.DrawScore() #not actually a button
def DrawStandard(s):
s.ColourButton('White',-1,-2,2,1) #Standard Setup button
Text(Point(0,-1.3),'Standard').draw(s.win)
Text(Point(0,-1.7),'Setup').draw(s.win)
def DrawCustom(s):
s.ColourButton('White',-1,-3,2,1) #Custom Setup button
Text(Point(0,-2.3),'Custom').draw(s.win)
Text(Point(0,-2.7),'Setup').draw(s.win)
def DrawStart(s):
s.ColourButton('Yellow',1,-2) #Start! button
Text(Point(1.5,-1.5),'Start!').draw(s.win)
def DrawClear(s):
s.ColourButton('White',-1,-3,2,1) #Clear Board button
Text(Point(0,-2.3),'Clear').draw(s.win)
Text(Point(0,-2.7),'Board').draw(s.win)
def Draw1P(s):
col = 'Red'
if s.is1P:
s.DrawCompColour()
else:
s.ColourButton(col,3,-2,2,1) #1Player -- (1AI)
Text(Point(4,-1.3),'1Player').draw(s.win)
Text(Point(4,-1.7),'Game').draw(s.win)
def DrawCompColour(s):
s.ColourButton(s.compIsColour,3,-2,2,1)
txt1 = Text(Point(4,-1.3),'Comp Is')
txt2 = Text(Point(4,-1.7),s.compIsColour)
txt1.draw(s.win)
txt2.draw(s.win)
txt1.setFill(s.opposite(s.compIsColour))
txt2.setFill(s.opposite(s.compIsColour))
def Draw2P(s):#2Player
col = 'Green'
if s.is1P:
col = 'Red'
s.ColourButton(col,3,-3,2,1)
Text(Point(4,-2.3),'2Player').draw(s.win)
Text(Point(4,-2.7),'Game').draw(s.win)
def DrawLoad(s):#Load
s.ColourButton('White',6,-3,2,1)
Text(Point(7,-2.5),'Load').draw(s.win)
def DrawSave(s): #Save
s.ColourButton('White',8,-3,2,1)
Text(Point(9,-2.5),'Save').draw(s.win)
def DrawX(s):#X
s.ColourButton('Red',10,-3)
Exit_txt = Text(Point(10.5,-2.5),'X')
Exit_txt.draw(s.win)
Exit_txt.setFill('White')
def DrawW(s):#W
col = 'Green'
if s.placeColour != 'White':
col = 'Red'
s.ColourButton(col,6,-2)
Text(Point(6.5,-1.5),'W').draw(s.win)
def DrawB(s):#B
col = 'Red'
if s.placeColour != 'White':
col = 'Green'
s.ColourButton(col,7,-2)
Text(Point(7.5,-1.5),'B').draw(s.win)
def DrawK(s): #K
col = 'Red'
if s.placeRank == 'King':
col = 'Green'
s.ColourButton(col,8,-2)
Text(Point(8.5,-1.5),'K').draw(s.win)
def DrawDel(s):#Del
col1 = 'Black'#square colour
col2 = 'White'#text colour
if s.placeType == 'Delete':
col1 = 'Green'
col2 = 'Black'
s.ColourButton(col1,9,-2)
deleteTxt = Text(Point(9.5,-1.5),'Del')
deleteTxt.draw(s.win)
deleteTxt.setFill(col2)
def DrawResign(s):
s.ColourButton('White',6,-3,2,1) #Load
Text(Point(7,-2.5),'Resign').draw(s.win)
def DrawTurn(s):
col1 = 'White'
col2 = 'Black'
if s.pTurn == 'Black':
col1 = 'Black'
col2 = 'White'
s.ColourButton(col1,9,8,2,1) #Standard Setup button
txt1 = Text(Point(10,8.7),col1)
txt2 = Text(Point(10,8.3),'Turn')
txt1.draw(s.win)
txt2.draw(s.win)
txt1.setFill(col2)
txt2.setFill(col2)
def DrawScore(s): # draw score
Text(Point(10,7.5),'# White').draw(s.win)
Text(Point(10,7.1),'Pieces:').draw(s.win)
Text(Point(10,6.7),s.numColour('White')).draw(s.win)
Text(Point(10,5.9),'# Black').draw(s.win)
Text(Point(10,5.5),'Pieces').draw(s.win)
Text(Point(10,5.1),s.numColour('Black')).draw(s.win)
def Click(s):
click = s.win.getMouse() #Perform mouse click
X, Y = s.ClickedSquare(click) #Gets click coords
s.Action(X,Y)
def Action(s,X,Y): #performs action for the location X,Y --essentially means user clicked there or computer is 'clicked' there
if s.state == 'CustomSetup':
s.clickInCustom(X,Y)
elif s.state == 'Play':
s.clickInPlay(X,Y)
def clickInCustom(s,X,Y):
#+-added X button if statement
if (10<=X<11 and -3<=Y<-2): #X clicked
ExitGame(s.win)
elif (-1<=X<1 and -2<=Y<-1): #Standard clicked
s.StandardSetup()
elif (1<=X<2 and -2<=Y<-1): #Start! clicked
num_wh = s.numColour('White')
num_bl = s.numColour('Black')
if ((num_wh == 0) and (num_bl == 0)): #This means there are no pieces on the board; this is a pointless setup.
tkMessageBox.showinfo("Error", "No pieces have been placed!")
else:
s.state = 'Play'
s.SetButtons()
elif (-1<=X<1 and -3<=Y<-2): #Clear Board clicked
s.ClearBoard()
#+-added 1Player and Comp Is if statements
elif (3<=X<5 and -2<=Y<-1 and not s.is1P): #1Player clicked
s.is1P = True
s.compIsColour = 'White'
s.SetButtons()
elif (3<=X<5 and -2<=Y<-1 and s.is1P): #'Comp Is' button clicked --requires the user to have already designated it to be a 1 player game, else the option is not available
s.compIsColour = s.opposite(s.compIsColour)
s.SetButtons()
elif (3<=X<5 and -3<=Y<-2): #2Player clicked
s.is1P = False
s.SetButtons()
elif (8<=X<10 and -3<=Y<-2): #Save clicked
s.SaveSetupToFile()
elif (6<=X<8 and -3<=Y<-2): #Load clicked
s.LoadSetupFromFile()
elif (9<=X<11 and 8<=Y<9): #pTurn button clicked during CustomSetup
s.pTurn = s.opposite(s.pTurn)
s.SetButtons()
elif (6<=X<7 and -2<=Y<-1): #W clicked
s.placeColour = 'White'
s.placeType = 'Place'
s.SetButtons()
elif (7<=X<8 and -2<=Y<-1): #B clicked
s.placeColour = 'Black'
s.placeType = 'Place'
s.SetButtons()
elif (8<=X<9 and -2<=Y<-1): #K clicked
s.placeRank = s.opposite(s.placeRank)
s.placeType = 'Place'
s.SetButtons()
elif (9<=X<10 and -2<=Y<-1): #Del clicked
s.placeType = s.opposite(s.placeType)
s.SetButtons()
elif (0<=X<8 and 0<=Y<8): #Tile clicked in CustomSetup
if s.tiles[X][Y].TileColour(X,Y) == 'White': #Clicked tile is White
tkMessageBox.showinfo("Error", "Illegal Placement")
elif s.numColour(s.placeColour) >= s.numPiecesAllowed and s.placeType == 'Place': #clicked tile would result in too many of colour being placed
tkMessageBox.showinfo("Error", "Illegal Placement")
elif (Y == 7 and s.placeColour == 'White' and not(s.placeRank == 'King')) or (Y == 0 and s.placeColour == 'Black' and not(s.placeRank == 'King')): #placing a non-king on a king square
tkMessageBox.showinfo("Error", "Illegal Placement")
else: #Valid tile update action (i.e. piece placement or deletion)
s.tiles[X][Y] = Tile(s.win,X,Y,s.placeType == 'Place',s.placeColour,s.placeRank) #updates that square in array
s.SetButtons()
#Handles mouse clicks
def clickInPlay(s,X,Y):
#+-added X and Save clicked if statements
if (10<=X<11 and -3<=Y<-2): #X clicked
ExitGame(s.win)
elif (8<=X<10 and -3<=Y<-2): #Save clicked
s.SaveSetupToFile()
elif (6<=X<8 and -3<=Y<-2): #Resign clicked
#+-added message box indicating which player had quit/resigned
tkMessageBox.showinfo("Resignation", str(s.pTurn) + ' has resigned! ' + str(s.opposite(s.pTurn)) + ' wins!')
s.state = 'CustomSetup'
s.SetButtons()
elif (0<=X<8 and 0<=Y<8): #Tile Clicked in Play
if s.selectedTileAt != []: #move if able
if s.selectedTileAt[0] == X and s.selectedTileAt[1] == Y and not s.pieceCaptured: #Re-Selecting the already selected piece de-selects it
s.selectedTileAt = []
s.tiles[X][Y] = Tile(s.win,X,Y,s.tiles[X][Y].isPiece,s.tiles[X][Y].pieceColour,s.tiles[X][Y].pieceRank)
elif s.pTurn == s.tiles[X][Y].pieceColour and not s.pieceCaptured and (s.PieceCanCapture(X,Y) or not s.PlayerCanCapture()):
s.tiles[s.selectedTileAt[0]][s.selectedTileAt[1]] = Tile(s.win,s.selectedTileAt[0],s.selectedTileAt[1],s.tiles[s.selectedTileAt[0]][s.selectedTileAt[1]].isPiece,s.tiles[s.selectedTileAt[0]][s.selectedTileAt[1]].pieceColour,s.tiles[s.selectedTileAt[0]][s.selectedTileAt[1]].pieceRank)
s.selectedTileAt = [X,Y]
s.tiles[X][Y] = Tile(s.win,X,Y,s.tiles[X][Y].isPiece,s.tiles[X][Y].pieceColour,s.tiles[X][Y].pieceRank,isSelected=True)
elif s.moveIsValid(s.selectedTileAt[0],s.selectedTileAt[1],X,Y):
#####################+-added extra code here
if s.tiles[X][Y].isPiece:
X=X+(X-s.selectedTileAt[0])
Y=Y+(Y-s.selectedTileAt[1])
############################################################
s.move(s.selectedTileAt[0],s.selectedTileAt[1],X,Y)
if not (s.pieceCaptured and s.PieceCanCapture(X,Y)):
s.pieceCaptured = False
s.selectedTileAt = []
s.pTurn = s.opposite(s.pTurn)
s.SetButtons()
#+-added if statement to check defeat
if s.movesAvailable() == [] and s.numColour(s.pTurn):
tkMessageBox.showinfo("Defeat", str(s.pTurn) + ' has no available moves! ' + str(s.opposite(s.pTurn)) + ' wins!')
s.state = 'CustomSetup'
s.SetButtons()
else:
tkMessageBox.showinfo("Error", "Cannot perform that action.")
else: #Select a Piece to move
if s.pTurn != s.tiles[X][Y].pieceColour:
tkMessageBox.showinfo("Error", "Select a piece of current player's colour")
elif (not s.PieceCanCapture(X,Y)) and s.PlayerCanCapture():
tkMessageBox.showinfo("Error", "Invalid selection, current player must take a piece")
else:
s.selectedTileAt = [X,Y]
s.tiles[X][Y] = Tile(s.win,X,Y,s.tiles[X][Y].isPiece,s.tiles[X][Y].pieceColour,s.tiles[X][Y].pieceRank,isSelected=True)
#+-added to determine whether the tile attempting to be selected is valid
def validTileSelect(s,X,Y):
if (0<=X<8 and 0<=Y<8): #Tile Clicked in Play
if s.selectedTileAt != []: #move if able
if s.selectedTileAt[0] == X and s.selectedTileAt[1] == Y and not s.pieceCaptured: #Re-Selecting the already selected piece de-selects it
return False
elif s.pTurn == s.tiles[X][Y].pieceColour and not s.pieceCaptured and (s.PieceCanCapture(X,Y) or not s.PlayerCanCapture()):
return True
elif s.moveIsValid(s.selectedTileAt[0],s.selectedTileAt[1],X,Y):
return False
else:
return False
else: #Select a Piece to move
if s.pTurn != s.tiles[X][Y].pieceColour:
return False
elif (not s.PieceCanCapture(X,Y)) and s.PlayerCanCapture():
return False
else:
return True
else:
return False
#+-added to determine whether the tile attempting to be moved to is valid
def validTileMove(s,X,Y):
if (0<=X<8 and 0<=Y<8): #Tile Clicked in Play
if s.selectedTileAt != []: #move if able
if s.selectedTileAt[0] == X and s.selectedTileAt[1] == Y and not s.pieceCaptured: #Re-Selecting the already selected piece de-selects it
return False
elif s.pTurn == s.tiles[X][Y].pieceColour and not s.pieceCaptured and (s.PieceCanCapture(X,Y) or not s.PlayerCanCapture()):
return False
elif s.moveIsValid(s.selectedTileAt[0],s.selectedTileAt[1],X,Y):
return True
else:
return False
else: #Selecting a Piece to move
return False
else:
False
def moveIsValid(s,x,y,X,Y): #parameters -> self,starting x,starting y,final X,final Y
#+-added if statement to ensure it the selected piece's turn
if s.tiles[x][y].pieceColour == s.pTurn:
if s.tiles[X][Y].pieceColour == s.opposite(s.pTurn): #valid if can jump target piece
return s.PieceCanCapturePiece(x,y,X,Y)
elif s.PieceCanJumpTo(x,y,X,Y): #valid if can jump to target location
return True
elif s.CanDoWalk(x,y,X,Y) and not s.PlayerCanCapture(): #valid if piece can travel to X,Y normally and PlayerCanCapture==False
return True
else:
return False
########
# This Function Enables a Piece to move. Trace Back --> Requirement 1.3
########
def move(s,x,y,X,Y): #parameters -> self,starting x,starting y,final X,final Y assumes valid move as input
s.tiles[X][Y] = Tile(s.win,X,Y,True,s.tiles[x][y].pieceColour,s.tiles[x][y].pieceRank)
if (Y==7 and s.tiles[X][Y].isWhite) or \
(Y==0 and s.tiles[X][Y].isBlack):
s.tiles[X][Y].pieceRank = 'King'
s.tiles[X][Y] = Tile(s.win,X,Y,True,s.tiles[X][Y].pieceColour,s.tiles[X][Y].pieceRank)
s.tiles[x][y] = Tile(s.win,x,y,isPiece=False)
if X-x == 2 or X-x == -2:
if s.numColour(s.tiles[x+(X-x)/2][y+(Y-y)/2].pieceColour) == 1:
tkMessageBox.showinfo("Winner", str(s.tiles[X][Y].pieceColour) + ' Wins!')
#+-updated to allow another game to be played after a winner is declared
s.state = 'CustomSetup'
s.SetButtons()
s.tiles[x+(X-x)/2][y+(Y-y)/2] = Tile(s.win,x+(X-x)/2,y+(Y-y)/2,isPiece=False)
s.tiles[X][Y] = Tile(s.win,X,Y,True,s.tiles[X][Y].pieceColour,s.tiles[X][Y].pieceRank)
if s.PieceCanCapture(X,Y):
s.tiles[X][Y] = Tile(s.win,X,Y,True,s.tiles[X][Y].pieceColour,s.tiles[X][Y].pieceRank,isSelected=True)
s.selectedTileAt = [X,Y]
s.pieceCaptured = True
else:
s.selectedTileAt = []
s.tiles[X][Y] = Tile(s.win,X,Y,True,s.tiles[X][Y].pieceColour,s.tiles[X][Y].pieceRank)
s.pieceCaptured = False
#the below few functions need conditions added to handle out of bounds errors (for being off grid, i.e. 0<=X<8 or 0<=Y<8 doesn't hold) <--- I think this is handled in PieceCanCapturePiece
def PlayerCanCapture(s):
for i in range(s.BoardDimension):
for j in range(s.BoardDimension):
if s.pTurn == s.tiles[i][j].pieceColour: #Current piece belongs to current player
if s.PieceCanCapture(i,j):
return True
return False
def PieceCanCapture(s,x,y):
X1,X2,Y1,Y2 = [x-1,x+1],[x-2,x+2],[y-1,y+1],[y-2,y+2]
for i in range(2):
for j in range(2):
if s.PieceCanCapturePiece(x,y,X1[i],Y1[j]):
return True
return False
#########
# This Function enables a tile to capture and remove an opponent piece. Trace Back --> Requirement 1.5
#########
def PieceCanCapturePiece(s,x,y,X,Y):
X1,X2,Y1,Y2 = [x-1,x+1],[x-2,x+2],[y-1,y+1],[y-2,y+2]
#+-added first two if conditions
if ((0<=X<8) and (0<=Y<8)) and ((0<=x<8) and (0<=y<8)):
if (s.tiles[x][y].pieceColour == s.opposite(s.tiles[X][Y].pieceColour)):
if s.CanDoWalk(x,y,X,Y,exception=True):
for i in range(2):
for j in range(2):
if X1[i]==X and Y1[j]==Y:
if (0<=X2[i]<8) and (0<=Y2[j]<8):
if not (s.tiles[X2[i]][Y2[j]].isPiece):
return True
return False
############
# This Function enables a tile to jump over opponent piece. Trace Back --> Requirement 1.5
############
def PieceCanJumpTo(s,x,y,X,Y):
X1,X2,Y1,Y2 = [x-1,x+1],[x-2,x+2],[y-1,y+1],[y-2,y+2]
for i in range(2):
for j in range(2):
if X2[i]==X and Y2[j]==Y:
if s.PieceCanCapturePiece(x,y,X1[i],Y1[j]):
return True
return False
########
# This Function enables a king to move front and back. Trace Back --> Requirement 1.5
########
def CanDoWalk(s,x,y,X,Y,exception=False): #The final parameter is to add a special case such that PieceCanCapturePiece may use this method
X1,Y1 = [x-1,x+1],[y-1,y+1]
for i in range(2):
for j in range(2):
if X1[i]==X and Y1[j]==Y:
if (0<=X<8) and (0<=Y<8):
if(s.tiles[x][y].isWhite and j==1) or \
(s.tiles[x][y].isBlack and j==0) or \
(s.tiles[x][y].isKing):
if not(exception or s.tiles[X][Y].isPiece) or \
(exception and s.tiles[X][Y].isPiece and \
(s.pTurn != s.tiles[X][Y].pieceColour)):
return True
return False
##########
# This Function launches the standard/original setup of the board. Trace Back --> Requirement 1.1
##########
def StandardSetup(s): #defines the standard button
s.ClearBoard()
s.state = 'CustomSetup' #in custom mode
for i in range(s.BoardDimension):
for j in range(s.BoardDimension):
if s.tiles[i][j].TileColour(i,j) == 'Red' and (j < 3):
s.tiles[i][j] = Tile(s.win,i,j,True,'White','Pawn')
if s.tiles[i][j].TileColour(i,j) == 'Red' and (j > 4):
s.tiles[i][j] = Tile(s.win,i,j,True,'Black','Pawn')
#places all the pieces in default checkers postitions
def numColour(s,colour): #counts the number of pieces of a given colour
c = 0 #initiate counter
for i in range(s.BoardDimension):
for j in range(s.BoardDimension):
if colour=='White' and s.tiles[i][j].isWhite:
c += 1
elif colour=='Black' and s.tiles[i][j].isBlack:
c += 1
return c
def opposite(s,opp): #Returns the 'opposite' of a given parameter (only works for specific things)
if opp == 'White':
return 'Black'
elif opp == 'Black':
return 'White'
elif opp == 'King':
return 'Pawn'
elif opp == 'Pawn':
return 'King'
elif opp == 'Place':
return 'Delete'
elif opp == 'Delete':
return 'Place'
else:
#+-returns instead of printing error message
return opp
#####
#function returns the bottom left coordinate of the square clicked
#####
def ClickedSquare(s,click):
try:
clickX = click.getX()
clickY = click.getY()
if clickX < 0:
clickX = int(clickX)-1
else:
clickX = int(clickX)
if clickY < 0:
clickY = int(clickY)-1
else:
clickY = int(clickY)
return clickX, clickY
except IndexError: #some positions on the outskirts of the screen are invalid locations
s.Click()
#######
# This Function Saves the game to be resumed later. Trace back --> Requirement 1.6
#######
def SaveSetupToFile(s): #method writes the tiles array to file checkers.txt
# can have a dialog box to ask for the text file name to save to
saveFile = open ('checkers.txt' , 'w') #opens file to write
for i in range(s.BoardDimension):
for j in range(s.BoardDimension):
if (s.tiles[i][j].isPiece):
i_string = str(i)
j_string = str(j)
saveFile.write(i_string + j_string + str(s.tiles[i][j].pieceColour)[0] + \
str(s.tiles[i][j].pieceRank)[0] + "\n")
saveFile.write(s.pTurn[0]) #saves whose turn it is too
tkMessageBox.showinfo("Saved Complete", "Game setup was saved to checkers.txt")
saveFile.close()
##########################
#Function LoadSetupFromFile(s) corresponds to Requirement 1.2
##########################
def LoadSetupFromFile(s): #method gets the setup saved and places pieces accordingly
loadFile = open ('checkers.txt' , 'r') #opens file to read
piece_list = loadFile.readlines()
tkMessageBox.showinfo("Loading", "Will now clear the board and \nplace the saved setup")
s.ClearBoard()
for i in range(len(piece_list) - 1):
tot_string = piece_list[i]
x_var = int(tot_string[0])
y_var = int(tot_string[1])
#checks the text file, with these specific letters signifying what each piece is
#first letter - 'W' is white, 'B' is black
#second letter - 'K' is a King piece, 'P' is a pawn piece
if (tot_string[2] == 'W'): #it is a white piece
if (tot_string[3] == 'K'): #it is a white King piece
s.tiles[x_var][y_var] = Tile(s.win,x_var,y_var,True,'White','King')
else : #piece is a white pawn
assert(tot_string[3] == 'P')
s.tiles[x_var][y_var] = Tile(s.win,x_var,y_var,True,'White','Pawn')
else: #piece is black
assert(tot_string[2] == 'B')
if (tot_string[3] == 'K'): #piece is a black King
s.tiles[x_var][y_var] = Tile(s.win,x_var,y_var,True,'Black','King')
else: #piece is a black pawn
assert(tot_string[3] == 'P')
s.tiles[x_var][y_var] = Tile(s.win,x_var,y_var,True,'Black','Pawn')
#whose turn it was is restored
if (piece_list[len(piece_list)-1] == 'W'): #it is white's turn
s.pTurn = 'White'
else: #it is black's turn
s.pTurn = 'Black'
s.SetButtons()
loadFile.close()
'''
# We took out Class Piece from Checkers_v6 and implemented Class Tile in Checkers_v18.
This was done to ensure a seamless transition for when the game actually ran.
'''
##########
#defines a tile and holds its current state
##########
class Tile:
def init(s,win,X,Y,isPiece,pieceColour='',pieceRank='',isSelected=False):
s.win = win
s.x = X
s.y = Y
s.isPiece = isPiece
s.isWhite = ('White' == pieceColour) and s.isPiece
s.isBlack = ('Black' == pieceColour) and s.isPiece
s.isKing = ('King' == pieceRank) and s.isPiece
s.isPawn = ('Pawn' == pieceRank) and s.isPiece
s.pieceColour=''
s.pieceRank=''
if s.isWhite:
s.pieceColour = 'White'
elif s.isBlack:
s.pieceColour = 'Black'
if s.isKing:
s.pieceRank = 'King'
elif s.isPawn:
s.pieceRank = 'Pawn'
s.c = Point(s.x+.5,s.y+.5)
s.circ = Circle(s.c,0.4)
if isSelected:
s.circ.setOutline('Yellow')
else:
s.circ.setOutline('Black')
s.circ.undraw()
s.kingTxt = Text(s.c,'K')
s.kingTxt.undraw()
s.ColourButton(s.TileColour(s.x,s.y),s.x,s.y)
if s.isPiece:
s.DrawPiece()
##########
#function to create a rectangle with a given colour, size, and location
##########
def ColourButton(s,colour,X,Y,width=1,height=1):
rect = Rectangle(Point(X,Y),Point(X+width,Y+height))
rect.setFill(colour)
rect.draw(s.win)
##########
#Tiles on the board
##########
def TileColour(s,x,y):
if (x%2 == 0 and y%2 == 0) or (x%2 == 1 and y%2 == 1):
return 'Red' #sets every other square to red
else:
return 'White' #every non red square to white
##########
#Displays White or Black or King Pieces
##########
def DrawPiece(s):
s.circ.draw(s.win)
if s.isWhite:
col1,col2 = 'White','Black'
elif s.isBlack:
col1,col2 = 'Black','White'
s.circ.setFill(col1)
if s.isKing:
s.kingTxt.draw(s.win)
s.kingTxt.setFill(col2)
##########
#Quit
##########
def ExitGame(win):
win.close()
sys.exit()
game = Checkers()