My first stab at ScoreMaster

I assume this is going to be done in depth in later videos but here is my first attempt (after a round of refactoring). It passes all the tests I have thrown at it, including checking the correct exceptions are thrown (e.g. too many rolls, values not between 0 and 10 etc…)

My logic took me down the road of converting the whole list first to strip out the ‘fake’ balls (namely ball 2 when ball 1 was a strike). This made it easier to just grab the bonuses. Thinking about how to do scoring by iterating the original List made my head hurt lol. It’s still a bit long-winded and nowhere near the target “19 extra lines” but it’s late and I need to get some sleep.

Please let me know what you think.

public class ScoreMaster
{
    public static List<int> ScoreFrames(List<int> rolls)
    {
        int thisRoll = 0;

        if (rolls.Count > 21) throw new UnityException("Invalid Roll List - too long!");
        if (rolls.Count < 1) return new List<int>();
        List<int> frameList = new List<int>();
        List<int> actualRolls = new List<int>();

        int i = 0;
        int currentFrame = 0;
        bool firstBallInFrame = true;
        int frameScore = 0;
        bool strikeBonusAvailable = false;
        bool spareBonusAvailable = false;

        // Convert 'Rolls' to Actual Rolls
        do {
            if(rolls[i]<0 || rolls[i] > 10) { throw new UnityException("Score out of range - bowl must be between 0 and 10"); }
            actualRolls.Add(rolls[i]);
            // For first 9 frames, skip virtual roll when STRIKE in first ball of frame 
            if (i < 18 && i % 2 == 0 && rolls[i] == 10) { i = i + 2; } 
            else i++;
        } while (i < rolls.Count);

        int rollCount = actualRolls.Count;

        // Iterate through ACTUAL rolls
        for (i = 0; currentFrame < 10; i++)
        {
            thisRoll = actualRolls[i];
            strikeBonusAvailable = (rollCount >= i + 3);
            spareBonusAvailable = (rollCount >= i + 2);

            if (firstBallInFrame)
            {
                //STRIKE - see if bonus points exist
                if (thisRoll >= 10 && strikeBonusAvailable)
                {
                    frameList.Add( thisRoll + actualRolls[i + 1] + actualRolls[i + 2]);
                    currentFrame++;
                }
                else if (thisRoll >= 10 && !strikeBonusAvailable) { return frameList; }

                //OPEN first roll - just store temporary score
                else
                {
                    frameScore = thisRoll;
                    firstBallInFrame = false;
                }
            }
            else
            {
                // SPARE - see if bonus points exist
                if (thisRoll + frameScore >= 10 && spareBonusAvailable)
                {
                    frameList.Add( frameScore + thisRoll + actualRolls[i + 1]);
                    firstBallInFrame = true;
                    currentFrame++;
                }
                else if (thisRoll + frameScore >= 10 && !spareBonusAvailable) { return frameList; }

                // OPEN second roll - just add temporary score to this roll
                else
                {
                    frameList.Add(frameScore + thisRoll);
                    firstBallInFrame = true;
                    currentFrame++;
                }
            }
        }
        return frameList;
    }
}

A new day and a fresh pair of eyes… My greatly trimmed down version… Not sure yet whether i need the two exception lines - probably not if ActionMaster is filtering correctly.
Please let me know what you think and of any tests that it may fail:

public class ScoreMaster
{
    public static List<int> ScoreFrames(List<int> rolls)
    {
        int rollCount = rolls.Count;
        if (rollCount > 21) throw new UnityException("Invalid Roll List - too long!"); // stop immediately if invalid rolls list
        List<int> frameList = new List<int>();
        if (rollCount < 1) return frameList; // If rolls is empty, return empty scorecard. Maybe remove if redundant.
        int thisRoll, strikeScore, spareScore, frameScore, frame10StrikeScore ;

        for (int i = 0; i < rolls.Count && i<20 ; i += 2)
        {
            thisRoll = rolls[i];
            if (thisRoll > 10 || thisRoll <0) throw new UnityException("Score out of range - bowl must be between 0 and 10"); // remove if redundant
            try { frameScore = thisRoll + rolls[i + 1]; } catch { frameScore = -1; }
            try { spareScore = thisRoll + rolls[i + 1] + rolls[i + 2]; } catch { spareScore = -1; }
            try { strikeScore = thisRoll + rolls[i + 2] + (rolls[i + 2] < 10 ? rolls[i + 3] : rolls[i + 4]); } catch { strikeScore = -1; } // Next two actual rolls
            try { frame10StrikeScore = thisRoll + rolls[i + 1] + rolls[i + 2]; } catch { frame10StrikeScore = -1; } // Next two actual rolls

            if (i == 18 && frame10StrikeScore > -1 && thisRoll == 10) { frameList.Add(frame10StrikeScore); } // STRIKE fr10 with bonus available
            else if (i<18 && strikeScore>-1 && thisRoll == 10) { frameList.Add(strikeScore); } // STRIKE fr1-9 with bonus available
            else if(spareScore>-1 && frameScore == 10) { frameList.Add(spareScore); } // SPARE with bonus available
            else if(frameScore>-1 && frameScore < 10) { frameList.Add(frameScore); } // OPEN and frame complete
            else { return frameList; }
        }
        return frameList;
    }
}

Hmmm… I looked at the test cases and my assumption that strikes were to be padded with zeroes was wrong apparently. I guess that came from the ActionManager section… The solution above still worked with the test cases modified accordingly…
Anyway, as well as a slightly refined version of the above, i have now also come up with a ScoreFramesNoPadding method which passes all of the bundled tests for anon-padded roll list :

    public static List<int> ScoreFramesNoPadding(List<int> rolls)
    {
        List<int> frameList = new List<int>();
        int thisRoll, frameScore, bonusScore;
        int currentFrame = 1;

        for (int i = 0; i < rolls.Count && currentFrame < 11; i++)
        {
            thisRoll = rolls[i];
            if (thisRoll > 10 || thisRoll < 0) throw new UnityException("Score out of range - bowl must be between 0 and 10"); // remove if redundant
            try { frameScore = thisRoll + rolls[i + 1]; } catch { frameScore = -1; }
            try { bonusScore = thisRoll + rolls[i + 1] + rolls[i + 2]; } catch { bonusScore = -1; }

            if (bonusScore > -1 && thisRoll == 10) frameList.Add(bonusScore); // STRIKE with bonus available
            else if (bonusScore > -1 && thisRoll<10 && thisRoll + rolls[i + 1] == 10) { // SPARE with bonus available
                frameList.Add(bonusScore);
                i++;
            }
            else if(frameScore > -1 && frameScore < 10) { // OPEN and frame complete
                frameList.Add(frameScore);
                i++;
            }
            else return frameList;
            currentFrame++;
        }
        return frameList;
    }

Privacy & Terms