11
\$\begingroup\$

This is a program I made to practice using turtle graphics. I am looking for pointers specifically on how I handle the game board. Currently it holds all of the grid objects in a nested list. Sorry if the documentation in the program is unconventional. This is one of my first projects and I'm excited to see what I can do to improve.

#import needed functions
import turtle
from datetime import datetime
from random import randint
gameTitle = "pyCheckers v4.17"
#setup output window
wn = turtle.Screen()
wn.tracer(0,0)
wn.title(gameTitle)
#checks to see if coordinates (x, y) are on the board
def onGrid(x,y):
 if((x in range(8)) and (y in range(8))):
 return True
 else:
 return False
#prints a log string to the console
def logToConsole(*strings):
 dt = datetime.today()
 print("<%02i/%02i/%04i" % (dt.month,dt.day,dt.year),end=" ")
 print("%02i:%02i:%02i>" % (dt.hour,dt.minute,dt.second),end=" ")
 for string in strings:
 print(string,end="")
 print()
#main game class
class checkers:
 #create game instance
 def __init__(self,screen):
 self.screen = screen
 self.resetGame()
 self.createTitles()
 #resets the matrix that stores game data
 def resetGame(self):
 self.turn = randint(1,2)
 logToConsole("\tStarting Player: %s" % (self.turn))
 self.createGrid()
 #create the matrix that stores the game data
 def createGrid(self):
 #create empty matrix
 self.matrix = [[None]*8,[None]*8,[None]*8,[None]*8,[None]*8,[None]*8,[None]*8,[None]*8]
 #populate matrix with grid objects and assign them attributes
 for x in range(8):
 for y in range(8):
 self.matrix[x][y] = grid(self.screen)
 self.matrix[x][y].moveGrid(x,y)
 if(((x + y) % 2) == 1):
 self.matrix[x][y].colored = True
 if(y in [0,1,2]):
 self.matrix[x][y].pawn = True
 self.matrix[x][y].player = 1
 if(y in [5,6,7]):
 self.matrix[x][y].pawn = True
 self.matrix[x][y].player = 2
 self.matrix[x][y].draw()
 #create list to hold values of highlighted spaces
 self.highlightedSpaces = []
 self.spaceSelected = False
 #creates text to display game information such as the current turn
 def createTitles(self):
 self.text0 = titles(self.screen)
 self.text1 = titles(self.screen)
 self.text0.writeTitle()
 self.text1.writeTurn(self.turn)
 #this funcion is called whenever the window is clicked
 def mouseEvent(self,pixelX,pixelY):
 x = int((pixelX + 4*grid.gridSize) // grid.gridSize)
 y = int((pixelY + 4*grid.gridSize) // grid.gridSize)
 if(onGrid(x,y) == True):
 logToConsole("Mouse Event at coords (%s,%s)" % (x,y))
 #if grid clicked contains a pawn and the current player it, highlight it's possible moves
 if((self.matrix[x][y].pawn == True) and (self.matrix[x][y].player == self.turn)):
 if(self.spaceSelected != 0):
 self.deselectAll()
 moves = self.findMoves(x,y)
 jumps = self.findJumps(x,y)
 for move in moves:
 self.matrix[move[0]][move[1]].selected = 1
 self.highlightedSpaces.append((move[0],move[1]))
 self.matrix[move[0]][move[1]].draw()
 for move in jumps:
 self.matrix[move[0]][move[1]].selected = 2
 self.highlightedSpaces.append((move[0],move[1]))
 self.matrix[move[0]][move[1]].draw()
 self.spaceSelected = (x,y)
 logToConsole("\tMoves Highlighted for pawn at (%s,%s)" % (x,y))
 #if grid clicked can be moved too, move the selected pawn
 elif(self.matrix[x][y].selected == 1):
 self.movePawn(self.spaceSelected,(x,y))
 self.deselectAll()
 self.endTurn()
 #if grid clicked can be jumped too, jump the selected pawn
 elif(self.matrix[x][y].selected == 2):
 self.jumpPawn(self.spaceSelected,(x,y))
 jumps = self.findJumps(x,y)
 if(jumps != []):
 self.deselectAll()
 self.spaceSelected = (x,y)
 for move in jumps:
 self.matrix[move[0]][move[1]].selected = 2
 self.highlightedSpaces.append((move[0],move[1]))
 self.matrix[move[0]][move[1]].draw()
 else:
 self.deselectAll()
 self.endTurn()
 else:
 self.deselectAll()
 logToConsole("\tMouse Event completed\n")
 #deselects all of the selected grid spaces
 def deselectAll(self):
 for space in self.highlightedSpaces:
 self.matrix[space[0]][space[1]].selected = 0
 self.matrix[space[0]][space[1]].draw()
 self.spaceSelected = False
 logToConsole("\tAll Spaces Un-highlited")
 #returns moves available to a pawn at (x, y)
 def findMoves(self,x,y):
 if(self.matrix[x][y].player == 1):
 moves = [(-1,1),(1,1)]
 if(self.matrix[x][y].king == 1):
 moves += [(-1,-1),(1,-1)]
 elif(self.matrix[x][y].player == 2):
 moves = [(-1,-1),(1,-1)]
 if(self.matrix[x][y].king == 1):
 moves += [(-1,1),(1,1)]
 coords = []
 for move in moves:
 x1 = x + move[0]
 y1 = y + move[1]
 if((onGrid(x1,y1) == True) and (self.matrix[x1][y1].pawn == False)):
 coords.append((x1,y1))
 return coords
 #returns coords of jumps available to a pawn at coords (x, y)
 def findJumps(self,x,y):
 if(self.matrix[x][y].player == 1):
 moves = [(-1,1),(1,1)]
 if(self.matrix[x][y].king == 1):
 moves += [(-1,-1),(1,-1)]
 elif(self.matrix[x][y].player == 2):
 moves = [(-1,-1),(1,-1)]
 if(self.matrix[x][y].king == 1):
 moves += [(-1,1),(1,1)]
 coords = []
 for move in moves:
 x1 = x + move[0]
 y1 = y + move[1]
 x2 = x + 2*move[0]
 y2 = y + 2*move[1]
 if((onGrid(x2,y2) == True) and (self.matrix[x2][y2].pawn == False)):
 if((self.matrix[x1][y1].pawn == True)):
 if((self.matrix[x][y].player == 1) and (self.matrix[x1][y1].player == 2)):
 coords.append((x2,y2))
 elif((self.matrix[x][y].player == 2) and (self.matrix[x1][y1].player == 1)):
 coords.append((x2,y2))
 return coords
 #moves a pawn from gridA to gridB
 def movePawn(self,gridA,gridB):
 self.matrix[gridB[0]][gridB[1]].importPawn(self.matrix[gridA[0]][gridA[1]])
 self.matrix[gridA[0]][gridA[1]].clearPawn()
 self.kingPawn(gridB[0],gridB[1])
 logToConsole("\tMoved pawn at %s to %s" % (gridA,gridB))
 #moves a pawn from gridA to gridC by jumping over the pawn in gridB
 def jumpPawn(self,gridA,gridC):
 gridB = (int((gridC[0]+gridA[0])/2),int((gridC[1]+gridA[1])/2))
 self.matrix[gridC[0]][gridC[1]].importPawn(self.matrix[gridA[0]][gridA[1]])
 self.matrix[gridB[0]][gridB[1]].clearPawn()
 self.matrix[gridA[0]][gridA[1]].clearPawn()
 self.kingPawn(gridC[0],gridC[1])
 logToConsole("\tPawn at %s jumped over pawn at %s to coords %s" % (gridA,gridB,gridC))
 #kings the pawn at coords (x, y) if it has reached it's kings row
 def kingPawn(self,x,y):
 if((self.matrix[x][y].player == 1) and (y == 7)):
 self.matrix[x][y].king = True
 self.matrix[x][y].draw()
 logToConsole("\tPawn at (%s,%s) was Kinged" % (x,y))
 elif((self.matrix[x][y].player == 2) and (y == 0)):
 self.matrix[x][y].king = True
 self.matrix[x][y].draw()
 logToConsole("\tPawn at (%s,%s) was Kinged" % (x,y))
 #ends the current turn
 def endTurn(self):
 if(self.turn == 1):
 logToConsole("\tRed Player's turn has ended")
 self.turn = 2
 elif(self.turn == 2):
 logToConsole("\tBlue Player's turn has ended")
 self.turn = 1
 self.text1.writeTurn(self.turn)
#class that writes text on the screen
class titles(turtle.RawTurtle):
 def __init__(self,screen):
 self.screen = screen
 self.createPen()
 #creates the turtle that will write the text
 def createPen(self):
 super(titles,self).__init__(self.screen)
 self.hideturtle()
 self.speed(0)
 self.width(3)
 self.up()
 #writes the game title and how to play
 def writeTitle(self):
 line0 = gameTitle
 line1 = "Click a pawn to see its possible moves"
 line2 = "Capture enemy pawns by jumping over them"
 line3 = "The player with the last pawn Wins"
 self.clear()
 self.color("black")
 self.goto(-4*grid.gridSize,4.8*grid.gridSize)
 self.write(line0,align="left",font=("Arial",20,"normal"))
 self.goto(4*grid.gridSize,5*grid.gridSize)
 self.write(line1,align="right",font=("Arial",14,"normal"))
 self.goto(4*grid.gridSize,4.75*grid.gridSize)
 self.write(line2,align="right",font=("Arial",14,"normal"))
 self.goto(4*grid.gridSize,4.5*grid.gridSize)
 self.write(line3,align="right",font=("Arial",14,"normal"))
 #writes the current turn on the screen
 def writeTurn(self,turn):
 if(turn == 1):
 string = "It's Red Player's Turn"
 self.color("red")
 elif(turn == 2):
 string = "It's Blue Player's Turn"
 self.color("blue")
 elif(turn == 0):
 string = "Turns are disabled"
 self.clear()
 self.goto(0,-5*grid.gridSize)
 self.write(string,align="center",font=("Arial",20,"normal"))
#class that defines a grid space and its properties
class grid(turtle.RawTurtle):
 #variables that set the grids size
 gridSize = 60
 pawnRadius = 20
 crownRadius = 10
 #create grid space and give it default attributes
 def __init__(self,screen):
 self.screen = screen
 self.defaultAttributes()
 self.createPen()
 #creates the turtle that will draw the grid space
 def createPen(self):
 super(grid,self).__init__(self.screen)
 self.hideturtle()
 self.speed(0)
 self.width(3)
 self.up()
 #sets the grid's attributes
 def defaultAttributes(self):
 self.gridX = 0
 self.gridY = 0
 self.colored = False #True if the square is shaded, False if white
 self.selected = 0 #0 if space is normal, 1 or 2 if space is highlighted
 self.pawn = False #True if there is a pawn on the grid space
 self.player = 0 #1 if pawn is player1 (red), 2 if pawn is player2 (blue)
 self.king = False #True if pawn has been kinged
 #removes the pawn from the grid
 def clearPawn(self):
 self.selected = False
 self.pawn = False
 self.player = 0
 self.king = False
 self.draw()
 #imports all attributes from another grid object
 def importPawn(self,gridObj):
 self.colored = gridObj.colored
 self.pawn = gridObj.pawn
 self.player = gridObj.player
 self.king = gridObj.king
 self.draw()
 #places the grid at a new set of coords
 def moveGrid(self,gX,gY):
 self.gridX = gX
 self.gridY = gY
 #draws the grid
 def draw(self):
 pixleX = int(self.gridX*grid.gridSize - 4*grid.gridSize)
 pixleY = int(self.gridY*grid.gridSize - 4*grid.gridSize)
 self.clear()
 self.goto(pixleX,pixleY)
 self.seth(0)
 self.down()
 if(self.colored == True):
 if(self.selected in [1,2]):
 self.color((0,0,0),(0.5,1,0.5))
 else: 
 self.color((0,0,0),(0.75,0.75,0.75))
 self.begin_fill()
 for f in range(4):
 self.fd(grid.gridSize)
 self.left(90)
 self.end_fill()
 self.up()
 if(self.pawn == True):
 self.goto(pixleX + 0.5*grid.gridSize,pixleY + 0.5*grid.gridSize - grid.pawnRadius)
 if(self.player == 1):
 self.color((0,0,0),(1,0.5,0.5))
 elif(self.player == 2):
 self.color((0,0,0),(0.5,0.5,1))
 else:
 self.color((0,0,0),(1,0.5,1))
 self.down()
 self.begin_fill()
 self.circle(grid.pawnRadius,360,16)
 self.end_fill()
 self.up()
 if(self.king == True):
 self.goto(pixleX + 0.5*grid.gridSize,pixleY + 0.5*grid.gridSize - grid.crownRadius)
 self.color((0,0,0),(1,0.85,0))
 self.down()
 self.begin_fill()
 self.circle(grid.crownRadius,360,16)
 self.end_fill()
 self.up()
#creates game instance
logToConsole("Program Starting...")
logToConsole("Running Game: ",gameTitle)
game = checkers(wn)
#attach mouseEvent to click
wn.onclick(game.mouseEvent)
#begin main program loop
logToConsole("Mouse Event attatched to window")
logToConsole("Beginning Main Program Loop...\n")
wn.mainloop()
#announce end of main program loop
logToConsole("Main Program Loop Ended")
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Oct 19, 2016 at 3:01
\$\endgroup\$
0

1 Answer 1

4
\$\begingroup\$

Performance

This is very inefficient:

def onGrid(x,y):
 if((x in range(8)) and (y in range(8))):
 return True
 else:
 return False

The range(8) creates a generator of values 0..7, which will be iterated until a match is found. For example, whenever this function gets called with an invalid x value, x in range(8) will iterate over 0..7 before the condition is evaluated false.

The fix is easy, with a simple range condition:

def onGrid(x,y):
 return 0 <= x < 8 and 0 <= y < 8

Notice that I removed the if-else, boolean expressions can be returned directly like this, and this compact writing style is recommended too.

Simplify

This, on the other hand, is a good opportunity to improve using range:

self.matrix = [[None]*8,[None]*8,[None]*8,[None]*8,[None]*8,[None]*8,[None]*8,[None]*8]

Like this:

self.matrix = [[None]*8 for _ in range(8)]

Instead of if something == True, write simply if something.

Instead of if somelist != [], write simply if somelist.

Coding style

Python has a coding style guide called PEP8. I suggest to read it carefully.

answered Oct 19, 2016 at 19:55
\$\endgroup\$
1
  • \$\begingroup\$ At least in Python 3 using in range is fast. You can try 10**99 in range(1, 10**100) it will run in an instant. Also do not micro-optimizise as range could go at most up to 8 so 16 elements more would be trivial. \$\endgroup\$ Commented Oct 24, 2016 at 20:26

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.