Suggested code

Hi. Here’s my version of the Bowl function.

public Action Bowl(int pins) {
    if (pins < 0 || pins > 10) {
        throw new UnityException("Invalid number of pins: " + pins);
    }

    bowls[bowl - 1] = pins;

    if (bowl == 21) {
        return Action.EndGame;
    }

    // Handle last-frame
    if (bowl == 19 && pins == 10) {
        bowl++;
        return Action.Reset;
    } else if (bowl == 20) {
        bowl++;
        if (Bowl21Awarded()) { // if there was a strike on 19 ball or spare on 20 - bonus ball is awarded
            return Bowl20PinsLeft() // if any pins left standing after ball 20
                ? Action.Tidy
                : Action.Reset;
        } else { // no bonus, ball 20 is the second ball in 10th frame and last ball in the game
            return Action.EndGame; 
        }
    }

    if (pins == 10) {
        bowl += 2;
        return Action.EndTurn;
    }

    if (bowl % 2 != 0) { // mid frame (or last frame)
        bowl++;
        return Action.Tidy;
    } else { // end of frame
        bowl++;
        return Action.EndTurn; 
    }

    throw new UnityException("WTF to do?");
}

// A Strike or a Spare on the 10th frame.
private bool Bowl21Awarded() {
    return (bowls[19 - 1] + bowls[20 - 1] >= 10);
}

// A Strike on the 10th frame but no Strike on second ball.
private bool Bowl20PinsLeft() {
    return (bowls[19 - 1] == 10 && bowls[20 - 1] < 10);
}

I think it’s a little bit cleaner - there are less code and condition to choose between Tidy and Reset on 20th ball is more logical. If you have pins left after 2 balls, THEN you need to tidy (that’s what tidy is for, to prepare for follow-up ball).

Also I think you don’t need bowls array because only last 2 elements are actually used…