Tic Tac Toe (2)

In today’s lecture, we will continue our discussion on building a simple text-based board game (Tic Tac Toe). In the process, we will learn about the benefits of inheritance, encapsulation and more generally of object-oriented design.

Tic Tac Toe Board

We discussed the Board class last time which is the base layer of our game. We start by importing the graphics package and Board class, which will now just use. At this point we don’t need to worry too much about the details of their implementation!

from graphics import *
from board import Board

Using the Board Class

We can use the Board class by defining an instance (object) of the class.

win = GraphWin("Tic Tac Toe", 400, 400)
board = Board(win) # create the board using default values
win.close()

Extending the Board Class

We can now inherit and extend the Board class to create a Tic Tac Toe Board.

A tic-tac-toe board is composed of a grid of tic-tac-toe letters.

We will define tic-tac-toe letters it their own class TTTLetter to separate out the functionality of a single later. Let’s ignore those details for now (abstraction!).

from tttletter import TTTLetter
from graphics import *
# let's just see what methods we have to work with when using TTTLetters
help(TTTLetter)
# Tic Tac Toe board class
"""Extends the Board class with specific features
required for Tic Tac Toe"""

# import modules and classes
from graphics import *
from tttletter import TTTLetter
from board import Board

class TTTBoard(Board):
    """TTT Board class implements the functionality of a
    Tic Tac Toe board. It inherits from the Board class and
    extends it by creating a grid of TTTLetters."""

    __slots__ = ['_grid']

    def __init__(self, win):
        super().__init__(win)

        # initialize a grid of TTTLetters (list of lists)
        self._grid = []
        for col in range(self._cols):
            grid_col = []
            # next part could be a list comprehension!
            for row in range(self._rows):
                # create new TTTLetter, specifying grid coord
                letter = TTTLetter(self.getBoard(), col, row)

                # add TTTLetter to column
                grid_col.append(letter)

            # add column to grid
            self._grid.append(grid_col)

    def getTTTLetterAtPoint(self, point):
        """Returns the TTTLetter at point (a screen coord tuple)"""
        if self.inGrid(point):
            (col, row) = self.getPosition(point)
            return self._grid[col][row]
        else:
            return None

    # reset all letter objects in grid
    def reset(self):
        """Clears the TTT board by clearing letters."""
        for x in range(self._cols):
            for y in range(self._rows):
                # get letter out of grid and reset it
                let = self._grid[x][y]
                let.setLetter("")


    # checking for win methods:
    def _checkRows(self, letter):
        """Check rows for a win (3 in a row)."""
        for row in range(self._rows):
            count = 0
            for col in range(self._cols):
                tttLet = self._grid[col][row]

                # check how many times letter appears
                if tttLet.getLetter() == letter:
                    count +=1

            # if this is a winning row
            if count == self._rows:
                return True

        # no winning row found
        return False

    def _checkCols(self, letter):
        """Check columns for a win (3 in a row)."""
        for col in range(self._cols):
            count = 0
            for row in range(self._rows):
                tttLet = self._grid[col][row]

                # check how many times letter appears
                if tttLet.getLetter() == letter:
                    count +=1

            # if this is a winning row
            if count == self._cols:
                return True

        # if no winning rows
        return False

    def _checkDiagonals(self, letter):
        """Check diagonals for a win (3 in a row)."""
        # counts for primary and secondary diagonal
        countPrimary, countSecond = 0, 0

        for col in range(self._cols):
            for row in range(self._rows):
                tttLet = self._grid[col][row]

                # update count for primary diagonal
                if (row == col and tttLet.getLetter() == letter):
                    countPrimary += 1

                # update count for secondary diagonal
                if (row + col == self._rows - 1 and tttLet.getLetter() == letter):
                    countSecond += 1

        # return true if either return in win
        return countPrimary == self.getRows() or countSecond == self.getRows()


    def checkForWin(self, letter):
        """Check board for a win."""
        rowWin = self._checkRows(letter)
        colWin = self._checkCols(letter)
        diagWin = self._checkDiagonals(letter)

        return rowWin or colWin or diagWin
win = GraphWin("Tic Tac Toe", 400, 400)
tttboard = TTTBoard(win)
tttboard.getTTTLetterAtPoint(Point(75, 75)).setLetter("X")
tttboard.getTTTLetterAtPoint(Point(150, 150)).setLetter("O")
tttboard.getRows(), tttboard.getCols()
# calls str of TTTLetter
print(tttboard.getTTTLetterAtPoint(Point(75,75)))
tttboard.reset()
# example board state
#  X  O  O
#     X  
#  O  O  O
tttboard.getTTTLetterAtPoint(Point(75,75)).setLetter("X")
tttboard.getTTTLetterAtPoint(Point(125,75)).setLetter("O")
tttboard.getTTTLetterAtPoint(Point(175,75)).setLetter("O")
tttboard.getTTTLetterAtPoint(Point(75,125)).setLetter("")
tttboard.getTTTLetterAtPoint(Point(125,125)).setLetter("X")
tttboard.getTTTLetterAtPoint(Point(175,125)).setLetter("")
tttboard.getTTTLetterAtPoint(Point(75,175)).setLetter("O")
tttboard.getTTTLetterAtPoint(Point(125,175)).setLetter("O")
tttboard.getTTTLetterAtPoint(Point(175,175)).setLetter("O")
tttboard._checkRows("O")
tttboard._checkCols("O")
tttboard._checkDiagonals("X")
tttboard._checkDiagonals("O")
tttboard.checkForWin("X")
tttboard.checkForWin("O")
tttboard.reset()
win.close()