bool Board::isValidMove (StoneColor stoneColor, const PointCoords & coords) const
{
    LOG_FUNCTION(cout, "Board::isValidMove");

    const Point & thePoint = m_points.at(coords.row).at(coords.column);

    // Easy Case: If there is already a stone there, then this cannot be a
    // valid move.
    //
    if (!thePoint.canPlayStone())
    {
        return false;
    }


    // Hard Case: If the spot we intend to play at would result in an
    // immediate capture of our stones, then the move is disallowed.
    // This is called the suicide rule. The one exception is in the case
    // where we would also capture one or more stones from the opponent.
    // Of course, that exception is also governed by the 'Ko' rule
    //

    // Caclulate what the chain would look like if the stone were placed
    // at the desired location
    //
    Chain potentialChain {stoneColor, thePoint, *this, nullptr};

    // A set of points that we've already examined. This helps prevents some
    // bad recursion and keeps a chain from being "found" once for each stone
    // in the chain.
    //
    ConstPointSet alreadyVisited;

    bool wouldCaptureOpponentChain = false;

    // For each point on the border of the potential chain that is occupied
    // by an opponent's stone, calculate it's chain.
    //
    // If the chain only has a single liberty (which would promptly be
    // taken by this move) and the 'Ko' rule does not apply, then we would
    // in fact capture at least one opponent stone.
    //
    potentialChain.forEachSurroundingPoint(getOpposingColor(stoneColor),
                                           [this, &alreadyVisited, &wouldCaptureOpponentChain](const Point & point)
    {
        LOG_FUNCTION(cout, "Board::lambda_wouldCapture");

        bool opponentThreatened = false;

        // If the point we are considering has already been visited,
        // then Chain's ctor will throw a PointVisitedAlreadyException
        // object. We can safely swallow that exception and move on
        // to the next point.
        //
        try
        {
            Chain opponentChain {point, *this, &alreadyVisited};

            gLogger.log(LogLevel::kMedium, cout, "Discovered chain"); // " : ", chain);

            // If this move would take the last liberty from a chain of the opponent and
            // if the 'Ko' rule doesn't apply, then we would indeed capture
            // their stones
            //
            if (opponentChain.libertyCount() == 1)
            {
                if (!doesKoRuleApply(opponentChain))
                    wouldCaptureOpponentChain = true;
            }
        }
        catch (const Chain::PointVisitedAlreadyException & ex)
        {
            gLogger.log(LogLevel::kFirehose, cout, "Skipping ", point);
        }
    });

    // If making this move would result in our chain not having any liberties,
    // it is only valid if we end up capturing an opposing chain (per the
    // restrictions from above); otherwise, it is a valid move.
    //
    if (potentialChain.libertyCount() == 0)
    {
        return wouldCaptureOpponentChain;
    }

    return true;
}