10
\$\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")
\$\endgroup\$
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.

| improve this answer | |
\$\endgroup\$
  • \$\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\$ – Caridorc Oct 24 '16 at 20:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.