Returns a generator that produces all of the points in the lines connecting the (x, y) tuples in `points`.

    If `closed` is `True`, then the last point will connect to the first point.

    (Note: The `thickness` and `endcap` parameters are not yet implemented.)

    >>> list(lines([(0, 0), (10, 3), (5, 5)]))
    [(0, 0), (1, 0), (2, 1), (3, 1), (4, 1), (5, 1), (6, 2), (7, 2), (8, 2), (9, 3), (10, 3), (9, 4), (8, 4), (7, 4), (6, 5), (5, 5)]
    >>> drawPoints(lines([(0, 0), (10, 3), (5, 5)]))
    OO,,,,,,,,,
    ,,OOOO,,,,,
    ,,,,,,OOO,,
    ,,,,,,,,,OO
    ,,,,,,,OOO,
    ,,,,,OO,,,,

def lines(points, closed=False, thickness=1, endcap=None, _skipFirst=False):
    """
    Returns a generator that produces all of the points in the lines connecting the (x, y) tuples in `points`.

    If `closed` is `True`, then the last point will connect to the first point.

    (Note: The `thickness` and `endcap` parameters are not yet implemented.)

    >>> list(lines([(0, 0), (10, 3), (5, 5)]))
    [(0, 0), (1, 0), (2, 1), (3, 1), (4, 1), (5, 1), (6, 2), (7, 2), (8, 2), (9, 3), (10, 3), (9, 4), (8, 4), (7, 4), (6, 5), (5, 5)]
    >>> drawPoints(lines([(0, 0), (10, 3), (5, 5)]))
    OO,,,,,,,,,
    ,,OOOO,,,,,
    ,,,,,,OOO,,
    ,,,,,,,,,OO
    ,,,,,,,OOO,
    ,,,,,OO,,,,
    """

    if thickness != 1 or endcap is not None:
        raise NotImplementedError('The pybresenham module is under development and the filled, thickness, and endcap parameters are not implemented. You can contribute at https://github.com/asweigart/pybresenham')

    # Validate points argument
    try:
        iter(points)
    except TypeError:
        raise PyBresenhamException('points must be an iterable')

    # Check that all the xy coordinates are either ints or floats.
    for i, point in enumerate(points):
        try:
            _checkForIntOrFloat(point[0])
            _checkForIntOrFloat(point[1])
        except:
            raise PyBresenhamException('point at index %s is not a tuple of two int/float values' % (i))

    if closed:
        # Validate the points argument for a closed multi-line shape.
        try:
            points[2] # Make sure there are at least three points for this closed irregular polygon.
            points = list(points)
            points.append(points[0]) # The final point connects back to the starting point.
        except:
            raise PyBresenhamException('points argument must have at least three points if closed==True')
        _skipFirst=True
    else:
        # Validate that there are at least two points.
        try:
            points[1]
        except:
            raise PyBresenhamException('points argument must have at least two points')

    # Technically, we're allocating all of the iterators for each line segment
    # at once, which isn't the most efficient way to do it but is the most
    # direct. And even this direct way still produces kind of unreadable code.
    # ("Line segment" means the line between two adjacent points in the points
    # parameter.)
    # We are using itertools.chain() to create one iterator from several
    # iterables (one iterable per line segment).
    if _skipFirst:
        return itertools.chain.from_iterable([line(points[i][0], points[i][1], points[i+1][0], points[i+1][1], _skipFirst=True) for i in range(len(points) - 1)])
    else:
        return itertools.chain([(points[0][0], points[0][1])], # the first point in points
                               itertools.chain.from_iterable([line(points[i][0], points[i][1], points[i+1][0], points[i+1][1], _skipFirst=True) for i in range(len(points) - 1)]))