Returns a generator that produces (x, y) tuples for a grid.

    The `gridLeft` and `gridTop` arguments are the x and y coordinates for the topleft corner of the grid.

    The `numBoxesWide` and `numBoxesHigh` arguments are the number of boxes (or, cells) in the grid.

    The `boxWidth` and `boxHeight` are the size of each box (or, cell). This is the size of the box's
    interior, and doesn't include the lines of the grid.

    The `thickness` argument is how the thick the grid lines are.

    The width of the grid is `(numBoxesWide * boxWidth) + (thickness * (numBoxesWide + 1))`.

    The height of the grid is `(numBoxesHeight * boxheight) + (thickness * (numBoxesHeight + 1))`.


    >>> list(grid(0, 0, 3, 2, 5, 4))
    [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0), (8, 0), (9, 0), (10, 0), (11, 0), (12, 0), (13, 0), (14, 0), (15, 0), (16, 0), (17, 0), (18, 0), (0, 5), (1, 5), (2, 5), (3, 5), (4, 5), (5, 5), (6, 5), (7, 5), (8, 5), (9, 5), (10, 5), (11, 5), (12, 5), (13, 5), (14, 5), (15, 5), (16, 5), (17, 5), (18, 5), (0, 10), (1, 10), (2, 10), (3, 10), (4, 10), (5, 10), (6, 10), (7, 10), (8, 10), (9, 10), (10, 10), (11, 10), (12, 10), (13, 10), (14, 10), (15, 10), (16, 10), (17, 10), (18, 10), (0, 1), (0, 2), (0, 3), (0, 4), (0, 6), (0, 7), (0, 8), (0, 9), (6, 1), (6, 2), (6, 3), (6, 4), (6, 6), (6, 7), (6, 8), (6, 9), (12, 1), (12, 2), (12, 3), (12, 4), (12, 6), (12, 7), (12, 8), (12, 9), (18, 1), (18, 2), (18, 3), (18, 4), (18, 6), (18, 7), (18, 8), (18, 9)]

    >>> drawPoints(grid(0, 0, 3, 2, 5, 4))
    OOOOOOOOOOOOOOOOOOO
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    OOOOOOOOOOOOOOOOOOO
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    OOOOOOOOOOOOOOOOOOO
    >>> drawPoints(grid(0, 0, 3, 2, 5, 4, thickness=2))
    OOOOOOOOOOOOOOOOOOOOOOO
    OOOOOOOOOOOOOOOOOOOOOOO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OOOOOOOOOOOOOOOOOOOOOOO
    OOOOOOOOOOOOOOOOOOOOOOO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OOOOOOOOOOOOOOOOOOOOOOO
    OOOOOOOOOOOOOOOOOOOOOOO

def grid(gridLeft, gridTop, numBoxesWide, numBoxesHigh, boxWidth, boxHeight, thickness=1):
    """
    Returns a generator that produces (x, y) tuples for a grid.

    The `gridLeft` and `gridTop` arguments are the x and y coordinates for the topleft corner of the grid.

    The `numBoxesWide` and `numBoxesHigh` arguments are the number of boxes (or, cells) in the grid.

    The `boxWidth` and `boxHeight` are the size of each box (or, cell). This is the size of the box's
    interior, and doesn't include the lines of the grid.

    The `thickness` argument is how the thick the grid lines are.

    The width of the grid is `(numBoxesWide * boxWidth) + (thickness * (numBoxesWide + 1))`.

    The height of the grid is `(numBoxesHeight * boxheight) + (thickness * (numBoxesHeight + 1))`.


    >>> list(grid(0, 0, 3, 2, 5, 4))
    [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0), (8, 0), (9, 0), (10, 0), (11, 0), (12, 0), (13, 0), (14, 0), (15, 0), (16, 0), (17, 0), (18, 0), (0, 5), (1, 5), (2, 5), (3, 5), (4, 5), (5, 5), (6, 5), (7, 5), (8, 5), (9, 5), (10, 5), (11, 5), (12, 5), (13, 5), (14, 5), (15, 5), (16, 5), (17, 5), (18, 5), (0, 10), (1, 10), (2, 10), (3, 10), (4, 10), (5, 10), (6, 10), (7, 10), (8, 10), (9, 10), (10, 10), (11, 10), (12, 10), (13, 10), (14, 10), (15, 10), (16, 10), (17, 10), (18, 10), (0, 1), (0, 2), (0, 3), (0, 4), (0, 6), (0, 7), (0, 8), (0, 9), (6, 1), (6, 2), (6, 3), (6, 4), (6, 6), (6, 7), (6, 8), (6, 9), (12, 1), (12, 2), (12, 3), (12, 4), (12, 6), (12, 7), (12, 8), (12, 9), (18, 1), (18, 2), (18, 3), (18, 4), (18, 6), (18, 7), (18, 8), (18, 9)]

    >>> drawPoints(grid(0, 0, 3, 2, 5, 4))
    OOOOOOOOOOOOOOOOOOO
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    OOOOOOOOOOOOOOOOOOO
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    O,,,,,O,,,,,O,,,,,O
    OOOOOOOOOOOOOOOOOOO
    >>> drawPoints(grid(0, 0, 3, 2, 5, 4, thickness=2))
    OOOOOOOOOOOOOOOOOOOOOOO
    OOOOOOOOOOOOOOOOOOOOOOO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OOOOOOOOOOOOOOOOOOOOOOO
    OOOOOOOOOOOOOOOOOOOOOOO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OO,,,,,OO,,,,,OO,,,,,OO
    OOOOOOOOOOOOOOOOOOOOOOO
    OOOOOOOOOOOOOOOOOOOOOOO
    """

    # Validate arguments.
    _checkForIntOrFloat(gridLeft)
    _checkForIntOrFloat(gridTop)
    _checkForIntOrFloat(numBoxesWide)
    _checkForIntOrFloat(numBoxesHigh)
    _checkForIntOrFloat(boxWidth)
    _checkForIntOrFloat(boxHeight)
    _checkForIntOrFloat(thickness)

    numBoxesWide = int(numBoxesWide)
    numBoxesHigh = int(numBoxesHigh)
    boxWidth = int(boxWidth)
    boxHeight = int(boxHeight)
    thickness = int(thickness)

    if numBoxesWide < 1:
        raise PyBresenhamException('numBoxesWide must be 1 or greater')
    if numBoxesHigh < 1:
        raise PyBresenhamException('numBoxesHigh must be 1 or greater')
    if boxWidth < 1:
        raise PyBresenhamException('boxWidth must be 1 or greater')
    if boxHeight < 1:
        raise PyBresenhamException('boxHeight must be 1 or greater')
    if thickness < 1:
        raise PyBresenhamException('thickness must be 1 or greater')


    # Record the y coordinates so we don't repeat the points at the intersections
    # of the grid.
    intersectiony = set()

    """Generate the points for the horizontal lines of the grid.
    i.e. the - characters in this diagram:
    ----------
    |  |  |  |
    |  |  |  |
    ----------
    |  |  |  |
    |  |  |  |
    ----------
    |  |  |  |
    |  |  |  |
    ----------"""
    for gridRow in range(numBoxesHigh + 1):
        for thicknessIndex in range(thickness): # thicknessIndex isn't a great name, but each "grid row" can be multiple points tall if thickness > 1
            y = (boxHeight * gridRow) + (thickness * gridRow) + thicknessIndex
            intersectiony.add(y)
            for x in range(numBoxesWide * boxWidth + (thickness * (numBoxesWide + 1))):
                yield (x + gridLeft, y + gridTop)

    """Generate the points for the vertical lines in between the horizontal lines of the grid.
    i.e. the | characters in this diagram:
    ----------
    |  |  |  |
    |  |  |  |
    ----------
    |  |  |  |
    |  |  |  |
    ----------
    |  |  |  |
    |  |  |  |
    ----------"""
    for gridColumn in range(numBoxesWide + 1):
        for thicknessIndex in range(thickness): # thicknessIndex isn't a great name, but each "grid row" can be multiple points tall if thickness > 1
            x = (boxWidth * gridColumn) + (thickness * gridColumn) + thicknessIndex
            for y in range(numBoxesHigh * boxHeight + (thickness * (numBoxesHigh + 1))):

                # Additionally, we don't want to yield xy points we've yielded before.
                """ i.e. These would be the points at the intersections of the grid,
                at the + characters in this diagram:
                    +--+--+--+
                    |  |  |  |
                    |  |  |  |
                    +--+--+--+
                    |  |  |  |
                    |  |  |  |
                    +--+--+--+
                    |  |  |  |
                    |  |  |  |
                    +--+--+--+"""
                if y not in intersectiony:
                    yield (x + gridLeft, y + gridTop)