parsing robocode results

In previous posts I've been exploring acceptance testing my robocode robot and I'm at a stage where I can run a robocode battle and produce a results file. I therefore need to begin parsing the robocode results file.
Here's what that file looks like:

battle_results

This text file has a line per robot and many fields that span to the right which I'll explore as I develop the parser.
Most of the file is tab delimited so I should be able to do much of my parsing easily.

So in thinking about my first specification for a BattleResultParser I would like to work against a static string rather than repeatedly read a text file so I'm going to abstract the reading of the file from the BattleResultsParser. I also know that I need to integrate it into the BattleRunner so I'll need an interface seam on the BattleResultsParser itself:

    public class when_parsing_a_battle_result
    {
        Establish context = () =>
        {
            battle = new Battle("kjbasd8908");

            battleResultFileReader = MockRepository.GenerateStub<IBattleResultFileReader>();
            battleResultParser = new BattleResultParser(battleResultFileReader);
        };

        Because of = () => battleResultParser.ReadResultsFrom(battle);

        It should_ask_the_battle_result_file_reader_to_read_the_results_file = () =>
            battleResultFileReader.AssertWasCalled(r =>
                 r.ReadResultFrom(battle.BattleResultFile));

        static IBattleResultParser battleResultParser;
        static Battle battle;
        static IBattleResultFileReader battleResultFileReader;
    }

I'm creating a stub of this new file reader and have declared the BattleResultParser as an interface type in the specification so I can ensure the BattleRunner can use it at an abstract level. Here is the first part of the implementation:

    public interface IBattleResultParser
    {
        IEnumerable<BattleResult> ReadResultsFrom(Battle battle);
    }

    public class BattleResultParser : IBattleResultParser
    {
        IBattleResultFileReader battleResultFileReader;

        public BattleResultParser(IBattleResultFileReader battleResultFileReader)
        {
            this.battleResultFileReader = battleResultFileReader;
        }

        public IEnumerable<BattleResult> ReadResultsFrom(Battle battle)
        {
            var resultLines = battleResultFileReader.ReadResultFrom(battle.BattleResultFile);
            return null;
        }
    }

    public interface IBattleResultFileReader
    {
        string[] ReadResultFrom(Uri file);
    }

I can now stub the content returned by the file reader:

    public class when_parsing_a_battle_result
    {
        Establish context = () =>
        {
            var resultLines = new string[4]
            {
                "Results for 10 rounds",
                "Robot Name\t    Total Score    \tSurvival\tSurv Bonus\tBullet Dmg\tBullet",
                "1st: sample.SittingDuck\t600 (100%)\t500\t100\t0\t0\t0\t0\t10\t0\t0\t",
                "2nd: TeamSlayer.Adapter.SlayerRobot\t0 (0%)\t0\t0\t0\t0\t0\t0\t0\t10\t0\t"
            };

            battle = new Battle("kjbasd8908");

            battleResultFileReader = MockRepository.GenerateStub<IBattleResultFileReader>();
            battleResultFileReader.Stub(r => r.ReadResultFrom(null)).IgnoreArguments()
.Return(resultLines); battleResultParser = new BattleResultParser(battleResultFileReader); }; Because of = () => results = battleResultParser.ReadResultsFrom(battle); It should_ask_the_battle_result_file_reader_to_read_the_results_file = () => battleResultFileReader.AssertWasCalled(r =>
r.ReadResultFrom(battle.BattleResultFile)); It should_ignore_the_first_two_lines_and_bring_back_only_two_results = () => results.Count().ShouldEqual(2); static IBattleResultParser battleResultParser; static Battle battle; static IBattleResultFileReader battleResultFileReader; static IEnumerable<BattleResult> results; }

I can make this pass easily by creating a list of two battle results, so now I need a new specification to force the reading of the number of robots in the file. I therefore create a similar specification with results for five robots in the string array and I'll skip showing that here.
I now have the following implementation:

    public class BattleResultParser : IBattleResultParser
    {
        IBattleResultFileReader battleResultFileReader;

        public BattleResultParser(IBattleResultFileReader battleResultFileReader)
        {
            this.battleResultFileReader = battleResultFileReader;
        }

        public IEnumerable<BattleResult> ReadResultsFrom(Battle battle)
        {
            var resultLines = battleResultFileReader.ReadResultFrom(battle.BattleResultFile);

            var battleResults = resultLines.Where(x => !x.StartsWith("Results for") 
&& !x.StartsWith("Robot Name")); return battleResults.Select(x => new BattleResult()); } }

I'm now ready to parse each individual line, but it occurs to me that rather than treat the BattleResult as a simple value object I could pass it one line from the robot file and it would be responsible for hydrating itself.
So I move over to specifying the BattleResult class:

    public class when_creating_a_battle_result_from_a_result_line
    {
        Establish context = () => 
        {
              robotName = "asdknoij08asd";
              battleResultLine = string.Format(
                  "3rd: {0}\t600 (100%)\t500\t100\t2\t0\t0\t0\t10\t0\t0\t", robotName);
        };

        Because of = () => battleResult = new BattleResult(battleResultLine);

        It should_have_the_correct_robot_name = () => 
battleResult.RobotName.ShouldEqual(robotName); static string battleResultLine; static string robotName; static BattleResult battleResult; }

I've set up a line in the expected format from a real results file and create a new BattleResult. I then assert against the robot name. To get this to pass, the implementation is:

    public class BattleResult
    {
        public BattleResult(string battleResultLine)
        {
            RobotName = battleResultLine.Split('\t')[0].Split(':')[1].Trim();
        }

        public string RobotName { get; private set; }
    }

I continue to add to the specification and iteratively build each part of the results up. The final specification is shown below:

    public class when_creating_a_battle_result_from_a_result_line
    {
        Establish context = () => 
        {
            position = 8;
            robotName = "asdknoij08asd";
            totalScore = 546;
            totalScorePercent = 99;
            survival = 88;
            survivalBonus = 12;
            bulletDamage = 54;
            bulletBonus = 35;
            ramDamage = 91;
            ramBonus = 28;
            firsts = 2;
            seconds = 1;
            thirds = 12;

            battleResultLine = string.Format(
                  "{0}rd: {1}\t{2} ({3}%)\t{4}\t{5}\t{6}\t{7}\t{8}\t{9}\t{10}\t{11}\t{12}\t",
                  position, robotName, totalScore, totalScorePercent, survival, survivalBonus,
                  bulletDamage, bulletBonus, ramDamage, ramBonus, firsts, seconds, thirds);
        };

        Because of = () => battleResult = new BattleResult(battleResultLine);

        It should_have_the_correct_position = () => 
battleResult.Position.ShouldEqual(position); It should_have_the_correct_robot_name = () =>
battleResult.RobotName.ShouldEqual(robotName); It should_have_the_correct_total_score = () =>
battleResult.TotalScore.ShouldEqual(totalScore); It should_have_the_correct_total_score_percentage = () => battleResult.TotalScorePercentage.ShouldEqual(totalScorePercent); It should_have_the_correct_survival = () =>
battleResult.Survival.ShouldEqual(survival); It should_have_the_correct_survival_bonus = () =>
battleResult.SurvivalBonus.ShouldEqual(survivalBonus); It should_have_the_correct_bullet_damage = () =>
battleResult.BulletDamage.ShouldEqual(bulletDamage); It should_have_the_correct_bullet_bonus = () =>
battleResult.BulletBonus.ShouldEqual(bulletBonus); It should_have_the_correct_ram_damage = () =>
battleResult.RamDamage.ShouldEqual(ramDamage); It should_have_the_correct_ram_bonus = () =>
battleResult.RamBonus.ShouldEqual(ramBonus); It should_have_the_correct_number_of_first_places = () => battleResult.NumberOfFirstPlaces.ShouldEqual(firsts); It should_have_the_correct_number_of_second_places = () => battleResult.NumberOfSecondPlaces.ShouldEqual(seconds); It should_have_the_correct_number_of_third_places = () => battleResult.NumberOfThirdPlaces.ShouldEqual(thirds); static string battleResultLine; static string robotName; static BattleResult battleResult; static int totalScore, position, totalScorePercent, survival, survivalBonus; static int bulletDamage, bulletBonus, ramDamage, ramBonus, firsts, seconds, thirds; }

And I'll end this post with the final implementation to pass these specifications:

    public class BattleResult
    {
        public BattleResult(string battleResultLine)
        {
            var results = battleResultLine.Split('\t');

            Position = GetPositionFrom(results[0]);
            RobotName = GetRobotNameFrom(results[0]);
            TotalScore = GetTotalScoreFrom(results[1]);
            TotalScorePercentage = GetTotalScorePercentageFrom(results[1]);
            Survival = Convert.ToInt32(results[2]);
            SurvivalBonus = Convert.ToInt32(results[3]);
            BulletDamage = Convert.ToInt32(results[4]);
            BulletBonus = Convert.ToInt32(results[5]);
            RamDamage = Convert.ToInt32(results[6]);
            RamBonus = Convert.ToInt32(results[7]);
            NumberOfFirstPlaces = Convert.ToInt32(results[8]);
            NumberOfSecondPlaces = Convert.ToInt32(results[9]);
            NumberOfThirdPlaces = Convert.ToInt32(results[10]);
        }

        public int Position { get; private set; }
        public string RobotName { get; private set; }
        public int TotalScore { get; private set; }
        public int TotalScorePercentage { get; private set; }
        public int Survival { get; private set; }
        public int SurvivalBonus { get; private set; }
        public int BulletDamage { get; private set; }
        public int BulletBonus { get; private set; }
        public int RamDamage { get; private set; }
        public int RamBonus { get; private set; }
        public int NumberOfFirstPlaces { get; private set; }
        public int NumberOfSecondPlaces { get; private set; }
        public int NumberOfThirdPlaces { get; private set; }

        int GetPositionFrom(string place)
        {
            //assuming no more than 9 robots
            return Convert.ToInt32(place.Substring(0, 1));
        }

        string GetRobotNameFrom(string result)
        {
            //comes in as "1st: sample.Tracker"
            return result.Split(':')[1].Trim();
        }

        int GetTotalScoreFrom(string result)
        {
            //comes in as "3600 (88%)"
            return Convert.ToInt32(result.Split('(')[0]);
        }

        int GetTotalScorePercentageFrom(string result)
        {
            //comes in as "3600 (88%)"
            return Convert.ToInt32(result.Split('(')[1].Split(')')[0].Replace("%", ""));
        }
    }



0 comments: