Advertisement
Guest User

Untitled

a guest
Dec 10th, 2019
161
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.23 KB | None | 0 0
  1. import random
  2. from tkinter import *
  3.  
  4. class Connect4:
  5.     def __init__(self, rowCount, colCount, circleSize, window):
  6.         self.margin = 25
  7.         self.rowCount = rowCount
  8.         self.colCount = colCount
  9.         self.circleSize = circleSize
  10.         self.circleSpacing = 10
  11.         self.messageSize = 15
  12.         self.width = self.colCount * (self.circleSize + self.circleSpacing)
  13.         self.height = self.rowCount * (self.circleSize + self.circleSpacing) + self.messageSize*4
  14.         self.window = window
  15.         self.data = []
  16.         self.frame = Frame(window)
  17.         self.frame.pack()
  18.         self.diameter = self.circleSize #self.rowCount * self.colCount
  19.         self.initialColor = 'white'
  20.         self.resetButton = Button(self.frame, text='Restart', command=self.restartGame)
  21.         self.resetButton.place(x=340, y=0)
  22.         self.quitButton = Button(self.frame, text='Quit', command=self.quitGame)
  23.         self.quitButton.pack(side=TOP)
  24.         self.draw = Canvas(self.frame, height=self.height, width=self.width)
  25.         self.draw.bind('<Button-1>', self.mouseInput)
  26.         self.circles = []
  27.         self.colors = []
  28.         self.ox = 'X'
  29.         y = self.circleSpacing
  30.         for row in range(self.rowCount):
  31.             circleRow = []
  32.             colorRow = []
  33.             x = self.circleSpacing
  34.             for col in range(self.colCount):
  35.                 circleRow += [self.draw.create_oval(x, y, x + self.diameter, y + self.diameter, fill=self.initialColor)]
  36.                 colorRow += [self.initialColor]
  37.                 x += self.diameter + self.circleSpacing
  38.             self.circles += [circleRow]
  39.             self.colors += [colorRow]
  40.             y += self.diameter + self.circleSpacing
  41.         self.message = self.draw.create_text(self.messageSize, self.height-self.messageSize, text=f'{self.ox}\'s turn!', anchor='w', font='Courier 27')
  42.         for row in range(self.rowCount):
  43.             boardRow = []
  44.             for col in range(self.colCount):
  45.                 boardRow += [' ']
  46.             self.data += [boardRow]
  47.         self.draw.pack(padx = self.margin, pady = self.margin)
  48.  
  49.     def __repr__(self): # Sets the literal structure of the board so that it's easy to interpret for the players
  50.         s = ''
  51.         for row in range(self.height):
  52.             s += '|'
  53.             for col in range(self.width):
  54.                 s += self.data[row][col] + '|'
  55.             s += '\n'
  56.         s += '--'*self.width + '-\n'
  57.         for col in range(self.width):
  58.             s += ' ' + str(col%10)
  59.         s += '\n'
  60.         return s
  61.  
  62.     def mouseInput(self, event):
  63.         col = int(event.x/(self.diameter+self.circleSpacing))
  64.         self.hostGame(aiPlayer)
  65.         self.updateBoard(col)
  66.    
  67.     def quitGame(self):
  68.         self.window.destroy()
  69.  
  70.     def restartGame(self):
  71.         self.clear()
  72.         for row in range(self.rowCount):
  73.             for col in range(self.colCount):
  74.                 self.draw.itemconfig(self.circles[row][col], fill='white')
  75.  
  76.     def clear(self): # Clears the current state of the board
  77.         for row in range(self.rowCount):
  78.             for col in range(self.colCount): # For every value in the board
  79.                 self.data[row][col] = ' ' # Override whatever is there with a space (the original value each spot starts with)
  80.  
  81.     def setChecker(self, row, col, color): # Where the user clicked
  82.         self.data[row][col] = self.ox
  83.         self.draw.itemconfig(self.circles[row][col], fill=color)
  84.  
  85.     def setText(self, text):
  86.         self.draw.itemconfig(self.message, text=text)
  87.  
  88.     def lowestRow(self, col):
  89.         if self.allowsMove(col):
  90.             for row in range(self.rowCount):
  91.                 if self.data[row][col] != ' ': # If the spot is not empty...
  92.                     return row - 1
  93.             return self.rowCount - 1
  94.  
  95.     def hostGame(self, aiPlayer):
  96.         if self.winsFor(self.ox):
  97.             # The move they just did let them win
  98.             self.setText(f'{self.ox} won!')
  99.         else:
  100.             O = aiPlayer.nextMove(self)
  101.             self.placeChecker(O, color='black')
  102.             self.addMove(O, self.ox)
  103.             #self.ox = 'O' if self.ox == 'X' else 'X'
  104.             self.setText(f'{self.ox}\'s turn!')
  105.  
  106.     def placeChecker(self, col, color = None):
  107.         if color is None:
  108.             color = 'red' if self.ox == 'X' else 'black'
  109.         row = self.lowestRow(col)
  110.         self.setChecker(row, col, color)
  111.  
  112.     def updateBoard(self, col): # Adds a "checker" to the column passed
  113.         self.placeChecker(col)
  114.         self.addMove(col, self.ox)
  115.  
  116.     def addMove(self, col, ox):
  117.         if self.allowsMove(col): # Uses allowsMove to check if the column is available
  118.             for row in range(self.rowCount):
  119.                 if self.data[row][col] != ' ': # If the spot is not empty...
  120.                     self.data[row-1][col] = ox # Add the checker to the spot above it
  121.                     return
  122.             self.data[self.rowCount-1][col] = ox # Otherwise but the checker there
  123.  
  124.     def allowsMove(self, col): # Tests to see if a column has room for another move
  125.         if 0 <= col < self.colCount:
  126.             return self.data[0][col] == ' '
  127.         else:
  128.             return False
  129.  
  130.     def delMove(self, col): # Deletes the last move played in column passed to the function
  131.         for row in range(self.rowCount): # For each row..
  132.             if self.data[row][col] != ' ': # If the indexed spot is not empty
  133.                 self.data[row][col] = ' ' # Override whatever is there with a space
  134.                 return
  135.         return
  136.  
  137.     def isFull(self): # Checks if the board is full or not
  138.         for values in self.data: # For each value in the entire board, if there's a space anywhere then it's not full (False). If there isn't a space, then the board is full (True).
  139.             if ' ' in values:
  140.                 return False
  141.         return True
  142.  
  143.     def winsFor(self, ox): # Checks to see if a checker ('X' or 'O') won the Connect 4.
  144.         for row in range(self.rowCount): # Horizontal check
  145.             for col in range(self.colCount - 3): # Uses an anchor point (subtracting 3 from the width and so forth) from where to start checking so it doesn't go out of range.
  146.                 if self.data[row][col] == ox and self.data[row][col+1] == ox and self.data[row][col+2] == ox and self.data[row][col+3] == ox:
  147.                     return True
  148.         for row in range(self.rowCount - 3): # Vertical check
  149.             for col in range(self.colCount):
  150.                 if self.data[row][col] == ox and self.data[row+1][col] == ox and self.data[row+2][col] == ox and self.data[row+3][col] == ox:
  151.                     return True
  152.         for row in range(self.rowCount - 3): # SW>NE check
  153.             for col in range(self.colCount - 3):
  154.                 if self.data[row][col] == ox and self.data[row+1][col+1] == ox and self.data[row+2][col+2] == ox and self.data[row+3][col+3] == ox:
  155.                     return True
  156.         for row in range(3, self.rowCount): # SE>NW check
  157.             for col in range(self.colCount - 3):
  158.                 if self.data[row][col] == ox and self.data[row-1][col+1] == ox and self.data[row-2][col+2] == ox and self.data[row-3][col+3] == ox:
  159.                     return True
  160.         return False
  161.  
  162. class Player:
  163.     def __init__(self, ox, tbt, ply):
  164.         self.ox = ox
  165.         self.tbt = tbt
  166.         self.ply = ply
  167.  
  168.     def __repr__(self):
  169.         intro = 'Tiebreak Type' + self.tbt
  170.         intro += '\n' + 'Ply:' + str(self.ply)
  171.         return intro
  172.  
  173.     def opponent(self):
  174.         if self.ox == 'X':
  175.             return 'O'
  176.         else:
  177.             return 'X'
  178.  
  179.     def scoreBoard(self, b):
  180.         if b.winsFor(self.opponent()) == True:
  181.             return 0
  182.         elif b.winsFor(self.ox) == True:
  183.             return 100
  184.         else:
  185.             return 50
  186.  
  187.     def tieBreakType(self, scores):
  188.         eachCol = []
  189.         t = 0
  190.         for col in range(len(scores)):
  191.             if scores[col] == max(scores):
  192.                 t += 1
  193.                 eachCol += [col]
  194.         if t > 1:
  195.             if self.tbt == 'Random':
  196.                 return random.choice(eachCol)
  197.             elif self.tbt == 'Right':
  198.                 return max(eachCol)
  199.             elif self.tbt == 'Left':
  200.                 return min(eachCol)
  201.         else:
  202.             return eachCol[0]
  203.  
  204.     def scoresFor(self, b, ox, ply):
  205.         score = [50]*b.colCount # Pre-seeds a list with the default value of 50, uses list multiplication to get the width of the board to determine how many columns are needed
  206.         for col in range(b.colCount): # For each column...
  207.             if b.allowsMove(col) == False: # Base Case, if the column is full it should always have the value of -1
  208.                 score[col] = -1
  209.             elif b.winsFor(self.opponent()) or b.winsFor(self.ox): # Base Case, the column is either valued as a loss (0) or a win(100)
  210.                 score[col] = self.scoreBoard(b)
  211.             elif self.ply == 0: # Base Case, ply 0 will always value every column (except full columns) at 50
  212.                 score[col] = 50
  213.             elif self.ply > 0: # Evaluates higher plys through recursion
  214.                 b.addMove(col, self.ox) # Add a move to every column and evaluate
  215.                 if self.scoreBoard == 0 or self.scoreBoard == 100: # Evaluate if the scoreBoard is a loss or a win
  216.                     score[col] = self.scoreBoard(b)
  217.                 else:
  218.                     p = Player(self.opponent(), self.tbt, self.ply-1) # Sets up the parameters of the game, subtracts 1 from ply to avoid loop
  219.                     score[col] = 100 - max(p.scoresFor(b, self.ox, self.ply)) # This is the recursive step that uses scoresFor to evaluate the board based on the opponent, tiebreak type, and ply number.
  220.                 b.delMove(col) # Delete the move from every column after done evaluating
  221.         return score # Return the best decision(s)
  222.  
  223.     def nextMove(self, b):
  224.         score = self.scoresFor(b, self.ox, self.ply)
  225.         return self.tieBreakType(score)
  226.  
  227. root = Tk()
  228. root.title('Connect 4')
  229. game = Connect4(6, 7, 100, root)
  230. aiPlayer = Player('O', 'Random', 3)
  231. game.hostGame(aiPlayer)
  232. root.mainloop()
  233.  
  234. def main():
  235.     pass
  236.  
  237. if __name__ == '__main__':
  238.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement