Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- from numbers import Number
- from turtle import Turtle
- from TicTacToe import TicTacToeBoardBase
- win = TicTacToeBoardBase.winCase
- class BoardSpaceOccupied(Exception):
- pass
- class TicTacToeBoard:
- """The board for Tic-Tac-Toe. Can only store TicTacToeBoard.X, TicTacToeBoard.O, and TicTacToeBoard.Empty."""
- # Don't change these. It is expected behavior that Empty is falsey, X and O are truthy and that Empty, X, and O can
- # all be stored in a bytearray. To fit these conditions, Empty MUST be 0. X and O can be any number from 1-255 as
- # long as they are not the same.middle
- Empty = 0
- X = 1
- O = 2
- NamesX = {0: "left", 1: "middle", 2: "right"}
- NamesY = {0: "top", 1: "center", 2: "bottom"}
- def _getBoard(self):
- return self._board
- def _setBoard(self, board):
- if self._board and len(self._board) != 9:
- raise ValueError("You cannot add or remove elements from the board!")
- self._board = board
- board = property(_getBoard, _setBoard, doc="The bytearray containing the actual board items.")
- def __init__(self):
- self._board = bytearray(self.Empty for _ in range(9)) # Initialize the board with self.Empty.
- def won(self):
- """Returns which player won (Board.X or Board.O) or False if neither player won."""
- b = self._board # This is going to be really long...
- won = b[0] == b[3] == b[6] != self.Empty and (b[0], win.vertLeft) or \
- b[1] == b[4] == b[7] != self.Empty and (b[1], win.vertMiddle) or \
- b[2] == b[5] == b[8] != self.Empty and (b[2], win.vertRight) or \
- b[0] == b[1] == b[2] != self.Empty and (b[0], win.horizTop) or \
- b[3] == b[4] == b[5] != self.Empty and (b[3], win.horizMiddle) or \
- b[6] == b[7] == b[8] != self.Empty and (b[6], win.horizBottom) or \
- b[0] == b[4] == b[8] != self.Empty and (b[0], win.diagonalTopLeft) or \
- b[2] == b[4] == b[6] != self.Empty and (b[2], win.diagonalTopRight)
- if won:
- return won
- elif self.isTie():
- return None
- return False
- # Yes, the above will work the way it should. b is there for code cleanup and cutting down on lookups.
- def isTie(self):
- """Returns if the board has a tie."""
- for line in ((0, 1, 2), (3, 4, 5), (6, 7, 8), (0, 3, 6), (1, 4, 7), (2, 5, 8), (0, 4, 8), (2, 4, 6)):
- totalScore = 0
- for space in line:
- if self.getSpace(space % 3, space // 3) != self.Empty:
- totalScore += 10 ^ (self.getSpace(space % 3, space // 3) - 1)
- if totalScore <= 10 or totalScore % 10 == 0:
- return False
- return True
- @staticmethod
- def _checkIndices(xIndex, yIndex):
- """Throws an exception if invalid board indices are passed to it."""
- if 0 >= xIndex > 2 or 0 >= yIndex > 2:
- raise IndexError("Board indices must be between 0 and 2.")
- elif not isinstance(xIndex, int) or not isinstance(yIndex, int):
- raise ValueError("Board indices can only be ints!")
- def _setBoardItem(self, xIndex, yIndex, val):
- """Makes sure the board is set properly. Throws an error otherwise."""
- self._checkIndices(xIndex, yIndex)
- if val != self.X and val != self.O and val != self.Empty:
- raise ValueError("The board can only store Empty, X, or O!")
- elif self._board[int(3 * yIndex + xIndex)]:
- raise BoardSpaceOccupied("The {} {} space is already occupied by an {}!".format(self.NamesX[xIndex],
- self.NamesY[yIndex], self._board[3 * yIndex + xIndex] == self.X and "X" or "O"))
- self._board[int(3 * yIndex + xIndex)] = val
- def putX(self, xIndex, yIndex):
- """Puts an X at the given X index and Y index, or errors if something is already there."""
- self._setBoardItem(xIndex, yIndex, self.X)
- def putO(self, xIndex, yIndex):
- """Puts an O at the given X index and Y index, or errors if something is already there."""
- self._setBoardItem(xIndex, yIndex, self.O)
- def clearSpace(self, xIndex, yIndex):
- """Clears the space at the given X index and Y index."""
- self._setBoardItem(xIndex, yIndex, self.Empty)
- def getSpace(self, xIndex, yIndex):
- """Gets what is at the space with the provided xIndex and yIndex."""
- self._checkIndices(xIndex, yIndex)
- return self._board[3 * yIndex + xIndex]
- def getNameForSpace(self, xIndex, yIndex):
- """Gets the name for the given space, like bottom right or top left."""
- self._checkIndices(xIndex, yIndex)
- return "{} {}".format(self.NamesX[xIndex], self.NamesY[yIndex])
- # Aliases, in case you prefer these names.
- setX = putX
- setO = putO
- setEmpty = clearSpace
- class TicTacToeBoardTurtle(TicTacToeBoardBase):
- """The component of Tic-Tac-Toe used to draw the board, implemented with Python's Turtle API."""
- def _getBoard(self):
- """The getter, in case you need to run something when this value is accessed."""
- return self._board
- def _setBoard(self, board):
- if self._board:
- raise ValueError("You can't change the board once it's been initialized! Use the board class methods!")
- self._board = board
- board = property(_getBoard, _setBoard,
- doc="The board, stored as a 1D array of length 9. Can only store Board.X, Board.O, and Board.Empty.")
- def getSpace(self, xIndex, yIndex):
- if xIndex > 2 or yIndex > 2:
- raise IndexError("Board indices must be between 0-2.")
- return self.board.getSpace(int(xIndex), int(yIndex))
- def getBoardPosition(self):
- """The getter, in case you need to run something when this value is accessed."""
- return self._boardPosition
- def _setBoardPosition(self, boardPosition):
- if len(boardPosition) != 2:
- raise ValueError("The board position must only contain two numbers!")
- elif not isinstance(boardPosition[0], Number) or isinstance(boardPosition[1], Number):
- raise ValueError("The board positions must only be numbers!")
- oldPos = self._boardPosition
- self._boardPosition = boardPosition
- if oldPos and oldPos != boardPosition: # If it's initializing, don't keep redrawing!
- self.redrawBoard()
- boardPosition = property(getBoardPosition, _setBoardPosition,
- doc="The position of the board, stored as a tuple.")
- def getX(self):
- return self._boardPosition[0]
- def setX(self, x):
- """Set the X coordinate of the board."""
- if not isinstance(x, Number):
- raise ValueError("X must be a number!")
- self._boardPosition[0] = x
- x = property(getX, setX)
- def getY(self):
- return self._boardPosition[1]
- def setY(self, y):
- """Set the Y coordinate of the board."""
- if not isinstance(y, Number):
- raise ValueError("Y must be a number!")
- self._boardPosition[1] = y
- y = property(getY, setY)
- def getBoardColor(self):
- """The getter, in case you need to run something when this value is accessed."""
- return self._boardColor
- def _setBoardColor(self, boardColor):
- if isinstance(self._boardColor, (tuple, set, list)):
- if 3 >= len(self._boardColor) >= 4:
- for i in self._boardColor:
- if not isinstance(i, Number):
- raise ValueError("The board color must have only numbers in it!")
- oldColor = self._boardColor
- self._boardColor = boardColor
- else:
- raise ValueError("The board color must be of length 3 or 4.")
- else:
- raise ValueError("The board color must be a tuple, set, or list!")
- if oldColor and oldColor != boardColor: # If it's initializing, don't keep redrawing!
- self.redrawBoard()
- boardColor = property(getBoardColor, _setBoardColor,
- doc="The color of the board, stored as a 3 or 4 item tuple containing ints between 0 and 255.")
- def getBoardWidth(self):
- """The getter, in case you need to run something when this value is accessed."""
- return self._boardWidth
- def _setBoardWidth(self, boardWidth):
- if not isinstance(boardWidth, Number):
- raise ValueError("The board width must be a number!")
- elif boardWidth <= 0:
- raise ValueError("The board width must be greater than 0!")
- oldWidth = self._boardWidth
- self._boardWidth = boardWidth
- if oldWidth and oldWidth != boardWidth: # If it's initializing, don't keep redrawing!
- self.redrawBoard()
- boardWidth = property(getBoardWidth, _setBoardWidth,
- doc="The width of the lines of the board, in pixels.")
- def getXColor(self):
- """The getter, in case you need to run something when this value is accessed."""
- return self._xColor
- def _setXColor(self, xColor):
- if isinstance(self._xColor, (tuple, set, list)):
- if 3 >= len(self._xColor) >= 4:
- for i in self._xColor:
- if not isinstance(i, Number):
- raise ValueError("The X color must have only numbers in it!")
- oldColor = self._xColor
- self._xColor = xColor
- else:
- raise ValueError("The X color must be of length 3 or 4.")
- else:
- raise ValueError("The X color must be a tuple, set, or list!")
- if oldColor and oldColor != xColor: # If it's initializing, don't keep redrawing!
- self.redrawBoard()
- xColor = property(getXColor, _setXColor,
- doc="The color of the X, stored as a 3 or 4 item tuple containing ints between 0 and 255.")
- def getOColor(self):
- """The getter, in case you need to run something when this value is accessed."""
- return self._oColor
- def _setOColor(self, oColor):
- if self._oColor:
- raise ValueError("You can't change the O color once it's been initialized!")
- if isinstance(self._oColor, (tuple, set, list)):
- if 3 >= len(self._oColor) >= 4:
- for i in self._oColor:
- if not isinstance(i, Number):
- raise ValueError("The O color must have only numbers in it!")
- oldColor = self._oColor
- self._oColor = oColor
- else:
- raise ValueError("The O color must be of length 3 or 4.")
- else:
- raise ValueError("The O color must be a tuple, set, or list!")
- if oldColor and oldColor != oColor: # If it's initializing, don't keep redrawing!
- self.redrawBoard()
- oColor = property(getOColor, _setOColor,
- doc="The color of the O, stored as a 3 or 4 item tuple containing ints between 0 and 255.")
- def getBoardSize(self):
- """The getter, in case you need to run something when this value is accessed."""
- return self._boardSize
- def _setBoardSize(self, boardSize):
- if self._boardSize:
- raise ValueError("You can't change the board size once it's been initialized!")
- if isinstance(boardSize, Number):
- if boardSize >= 5:
- oldSize = self._boardSize
- self._boardSize = boardSize
- else:
- raise ValueError("The board size cannot be less than 5px by 5px!")
- else:
- raise ValueError("The board size must be a number!")
- if oldSize and oldSize != boardSize: # If it's initializing, don't keep redrawing!
- self.redrawBoard()
- boardSize = property(getBoardSize, _setBoardSize,
- doc="The length of the (square) board.")
- def __init__(self, x, y, boardSize, boardColor=None, xColor=None, oColor=None, boardWidth=None, pen=None):
- """Create the canvas, and anything else needed to start the program initially."""
- super(TicTacToeBoardBase, self).__init__()
- self._board = TicTacToeBoard()
- self._boardPosition = [x, y]
- self._boardSize = boardSize
- self._boardWidth = boardWidth or self.defaultBoardWidth
- self._boardColor = boardColor or self.defaultBoardColor
- self._xColor = xColor or self.defaultXColor
- self._oColor = oColor or self.defaultOColor
- self._pen = pen or Turtle()
- self._mouseX, self._mouseY = None, None
- self._mouseMovedCallbacks = []
- self._mouseClickedCallbacks = []
- # You shouldn't need anything else in here, but if you need to do something on board init, do it here.
- def drawBoard(self):
- """Draw the board at the given coordinates, with the given width and height.
- Assume (0,0) is in the top left, and the positive direction is right in the X axis, and down in the Y axis.
- The width and height should be the total size of the board, and the minimum size should be 5px by 5px."""
- BoxSize = boxSize(self._boardSize, self._boardWidth)
- drawRect = lambda x, y, width, height: rect(self._pen, x, y, width, height, self._boardColor)
- drawRect(self.x + BoxSize, self.y, self._boardWidth, self._boardSize)
- drawRect(self.x + 2 * BoxSize + self._boardWidth, self.y, self._boardWidth, self._boardSize)
- drawRect(self.x, self.y + BoxSize, self._boardSize, self._boardWidth)
- drawRect(self.x, self.y + 2 * BoxSize + self._boardWidth, self._boardSize, self._boardWidth)
- for i in range(9):
- if self.board.getSpace(i % 3, i // 3) == self.X:
- self.drawX(i % 3, i // 3)
- elif self.board.getSpace(i % 3, i // 3) == self.O:
- self.drawO(i % 3, i // 3)
- def redrawBoard(self):
- """Redraw the board after a value change."""
- self._pen.clear()
- self.drawBoard()
- def drawX(self, xIndex, yIndex):
- """Draw an X at the given x index or y index, treating it as a 3x3, zero-indexed array.
- Raise an exception when an X is drawn over an existing X or existing O."""
- currentLetter = self.getSpace(xIndex, yIndex)
- if currentLetter == self.X:
- raise BoardSpaceOccupied("There's already an X in the {} {}!".format(TicTacToeBoard.NamesY[yIndex],
- TicTacToeBoard.NamesX[xIndex]))
- elif currentLetter == self.O:
- raise BoardSpaceOccupied("There's an O in the {} {}!".format(TicTacToeBoard.NamesY[yIndex],
- TicTacToeBoard.NamesX[xIndex]))
- BoxSize = boxSize(self._boardSize, self._boardWidth)
- goto = lambda x, y: self._pen.goto(*windowToTurtleCoordinates(x + (BoxSize + self._boardWidth) * xIndex,
- y + (BoxSize + self._boardWidth) * yIndex,
- self._pen.getscreen().window_width(),
- self._pen.getscreen().window_height()))
- oldColor = self._pen.pencolor()
- oldSize = self._pen.pensize()
- self._pen.pencolor(*self.xColor)
- self._pen.pensize(self.boardWidth / 5)
- self._pen.penup()
- goto(self.x + BoxSize * .15, self.y + BoxSize * .15)
- self._pen.pendown()
- goto(self.x + BoxSize * .85, self.y + BoxSize * .85)
- self._pen.penup()
- goto(self.x + BoxSize * .85, self.y + BoxSize * .15)
- self._pen.pendown()
- goto(self.x + BoxSize * .15, self.y + BoxSize * .85)
- self._pen.penup()
- self._board.putX(xIndex, yIndex)
- self._pen.pencolor(*oldColor)
- self._pen.pensize(oldSize)
- def drawO(self, xIndex, yIndex):
- """Draw an X at the given x index or y index, treating it as a 3x3, zero-indexed array.
- Raise an exception when an X is drawn over an existing X or existing O."""
- currentLetter = self.getSpace(xIndex, yIndex)
- if currentLetter == self.X:
- raise BoardSpaceOccupied("There's already an X in the {} {}!".format(TicTacToeBoard.NamesY[yIndex],
- TicTacToeBoard.NamesX[xIndex]))
- elif currentLetter == self.O:
- raise BoardSpaceOccupied("There's an O in the {} {}!".format(TicTacToeBoard.NamesY[yIndex],
- TicTacToeBoard.NamesX[xIndex]))
- BoxSize = boxSize(self._boardSize, self._boardWidth)
- goto = lambda x, y: self._pen.goto(*windowToTurtleCoordinates(x, y, self._pen.getscreen().window_width(),
- self._pen.getscreen().window_height()))
- oldColor = self._pen.pencolor()
- oldSize = self._pen.pensize()
- self._pen.pencolor(*self.oColor)
- self._pen.pensize(self.boardWidth / 5)
- self._pen.penup()
- goto(self.x + BoxSize / 2 + (BoxSize + self._boardWidth) * xIndex,
- self.y + BoxSize * .15 + (BoxSize + self._boardWidth) * yIndex)
- self._pen.pendown()
- self._pen.setheading(180)
- self._pen.circle(BoxSize * .35)
- self._pen.penup()
- self._board.putO(xIndex, yIndex)
- self._pen.pencolor(*oldColor)
- self._pen.pensize(oldSize)
- _boardPositions = {
- win.vertLeft: lambda self, BoxSize: (
- self.x + BoxSize / 2., self.y + BoxSize / 2., self.x + BoxSize / 2.,
- self.y + self._boardSize - BoxSize / 2.),
- win.vertMiddle: lambda self, BoxSize: (
- self.x + BoxSize * 3 / 2. + self._boardWidth, self.y + BoxSize / 2.,
- self.x + BoxSize * 3 / 2. + self._boardWidth, self.y + self._boardSize - BoxSize / 2.),
- win.vertRight: lambda self, BoxSize: (
- self.x + BoxSize * 5 / 2. + 2 * self._boardWidth, self.y + BoxSize / 2.,
- self.x + BoxSize * 5 / 2. + 2 * self._boardWidth, self.y + self._boardSize - BoxSize / 2.),
- win.horizTop: lambda self, BoxSize: (
- self.x + BoxSize / 2., self.y + BoxSize / 2., self.x + self._boardSize - BoxSize / 2.,
- self.y + BoxSize / 2.),
- win.horizMiddle: lambda self, BoxSize: (
- self.x + BoxSize / 2., self.y + BoxSize * 3 / 2. + self._boardWidth,
- self.x + self._boardSize - BoxSize / 2., self.y + BoxSize * 3 / 2. + self._boardWidth),
- win.horizBottom: lambda self, BoxSize: (
- self.x + BoxSize / 2., self.y + BoxSize * 5 / 2. + 2 * self._boardWidth,
- self.x + self._boardSize - BoxSize / 2., self.y + BoxSize * 5 / 2. + 2 * self._boardWidth),
- win.diagonalTopLeft: lambda self, BoxSize: (
- self.x + BoxSize / 2., self.y + BoxSize / 2., self.x + self._boardSize - BoxSize / 2.,
- self.y + self._boardSize - BoxSize / 2.),
- win.diagonalTopRight: lambda self, BoxSize: (
- self.x + self._boardSize - BoxSize / 2., self.y + BoxSize / 2., self.x + BoxSize / 2.,
- self.y + self._boardSize - BoxSize / 2.)
- }
- def drawLine(self, winCase):
- """Draw the victory line for the given winCase."""
- pos = self._boardPositions[winCase]
- BoxSize = boxSize(self.boardSize, self.boardWidth)
- self._pen.penup()
- goto = lambda x, y: self._pen.goto(*windowToTurtleCoordinates(x, y, self._pen.getscreen().window_width(),
- self._pen.getscreen().window_height()))
- goto(*pos(self, BoxSize)[0:2])
- self._pen.pendown()
- goto(*pos(self, BoxSize)[2:])
- self._pen.penup()
- def onClick(self, x, y, button=1):
- """Run when the mouse is clicked."""
- winX, winY = turtleToWindowCoordinates(x, y, self._pen.getscreen().window_width(),
- self._pen.getscreen().window_height())
- for i in self._mouseClickedCallbacks:
- i(winX, winY, button)
- def addMouseClickedCallback(self, callback):
- self._mouseClickedCallbacks.append(callback)
- def clearMouseClickedCallbacks(self):
- self._mouseClickedCallbacks = []
- def isWon(self):
- return self._board.won()
- def turtleToWindowCoordinates(x, y, width, height):
- """Convert turtle coordinates to window coordinates."""
- return x + width / 2., -(y - height / 2.)
- def windowToTurtleCoordinates(x, y, width, height):
- """Convert window coordinates to turtle coordinates"""
- return x - width / 2., -y + height / 2.
- def turtleXToWindowX(x, width):
- """Convert X coord in turtle coordinates to window coordinates"""
- return x + width / 2.
- def turtleYToWindowY(y, height):
- """Convert Y coord in turtle coordinates to window coordinates"""
- return -(y - height / 2.)
- def boxSize(boardSize, boardWidth):
- """The size of each box on the board."""
- return (boardSize - boardWidth * 2) / 3.
- def boxIndexAtCoordinates(boardSize, boardWidth, boardX, boardY, x, y):
- """Returns the index of the box at the given x and y coordinates, or False if it is not on a box."""
- if boardX <= x <= boardX + boardSize and boardY <= y <= boardY + boardSize: # Check if it's on the board
- localX, localY, BoxSize = x - boardX, y - boardY, boxSize(boardSize, boardWidth) # Relative coords to the board
- if localX % (BoxSize + boardWidth) > BoxSize or localY % (BoxSize + boardWidth) > BoxSize: # If it's on a line
- return False
- else:
- return int(localX // (BoxSize + boardWidth)), int(
- localY // (BoxSize + boardWidth)) # "Magic!" Return the index.
- return False # It's not on the board. No need for an else, since it's the last statement of the function.
- def rect(pen, x, y, width, height, color): # Draws a rectangle of x,y,width,height and color.
- winX, winY = windowToTurtleCoordinates(x, y, pen.getscreen().window_width(), pen.getscreen().window_height())
- pen.penup()
- pen.goto(winX, winY)
- pen.pendown()
- pen.pencolor(color)
- pen.fillcolor(color)
- if width == 1 and height == 1:
- pen.dot()
- return
- elif width == 1:
- pen.sety(winY - height)
- return
- elif height == 1:
- pen.setx(winX + width)
- return
- pen.begin_fill()
- pen.setheading(90)
- for i in range(2):
- pen.right(90)
- pen.forward(width)
- pen.right(90)
- pen.forward(height)
- pen.end_fill()
Add Comment
Please, Sign In to add comment