Archive for the ‘C#’ Category.

Battleship AI contest on Stack Overflow

Recently there was a contest running on Stack Overflow to design the best Battleship AI. Here is a link to the page:

What is the best Battleship AI?

I must admit that I am still smarting over this contest, as my BP7 contest entry did awesomely well and incredibly poor at the same time. Here is the code:

namespace Battleship
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
 
    public class BP7 : IBattleshipOpponent
    {
        public string Name { get { return "BP7"; } }
        public Version Version { get { return this.version; } }
 
        Random rand = new Random();
        Version version = new Version(0, 7);
        Size gameSize;
        List<Point> scanShots;
        List<NextShot> nextShots;
        int wins, losses;
        int totalWins = 0;
        int totalLosses = 0;
        int maxWins = 0;
        int maxLosses = 0;
        int matchWins = 0;
        int matchLosses = 0;
 
        public enum Direction { VERTICAL = -1, UNKNOWN = 0, HORIZONTAL = 1 };
        Direction hitDirection, lastShotDirection;
 
        enum ShotResult { UNKNOWN, MISS, HIT };
        ShotResult[,] board;
 
        public struct NextShot
        {
            public Point point;
            public Direction direction;
            public NextShot(Point p, Direction d)
            {
                point = p;
                direction = d;
            }
        }
 
        public struct ScanShot
        {
            public Point point;
            public int openSpaces;
            public ScanShot(Point p, int o)
            {
                point = p;
                openSpaces = o;
            }
        }
 
        public void NewGame(Size size, TimeSpan timeSpan)
        {
            this.gameSize = size;
            scanShots = new List<Point>();
            nextShots = new List<NextShot>();
            fillScanShots();
            hitDirection = Direction.UNKNOWN;
            board = new ShotResult[size.Width, size.Height];
        }
 
        private void fillScanShots()
        {
            int x;
            for (x = 0; x < gameSize.Width - 1; x++)
            {
                scanShots.Add(new Point(x, x));
            }
 
            if (gameSize.Width == 10)
            {
                for (x = 0; x < 3; x++)
                {
                    scanShots.Add(new Point(9 - x, x));
                    scanShots.Add(new Point(x, 9 - x));
                }
            }
        }
 
        public void PlaceShips(System.Collections.ObjectModel.ReadOnlyCollection<Ship> ships)
        {
            foreach (Ship s in ships)
            {
                s.Place(
                    new Point(
                        rand.Next(this.gameSize.Width),
                        rand.Next(this.gameSize.Height)),
                    (ShipOrientation)rand.Next(2));
            }
        }
 
        public Point GetShot()
        {
            Point shot;
 
            if (this.nextShots.Count > 0)
            {
                if (hitDirection != Direction.UNKNOWN)
                {
                    if (hitDirection == Direction.HORIZONTAL)
                    {
                        this.nextShots = this.nextShots.OrderByDescending(x => x.direction).ToList();
                    }
                    else
                    {
                        this.nextShots = this.nextShots.OrderBy(x => x.direction).ToList();
                    }
                }
 
                shot = this.nextShots.First().point;
                lastShotDirection = this.nextShots.First().direction;
                this.nextShots.RemoveAt(0);
                return shot;
            }
 
            List<ScanShot> scanShots = new List<ScanShot>();
            for (int x = 0; x < gameSize.Width; x++)
            {
                for (int y = 0; y < gameSize.Height; y++)
                {
                    if (board[x, y] == ShotResult.UNKNOWN)
                    {
                        scanShots.Add(new ScanShot(new Point(x, y), OpenSpaces(x, y)));
                    }
                }
            }
            scanShots = scanShots.OrderByDescending(x => x.openSpaces).ToList();
            int maxOpenSpaces = scanShots.FirstOrDefault().openSpaces;
 
            List<ScanShot> scanShots2 = new List<ScanShot>();
            scanShots2 = scanShots.Where(x => x.openSpaces == maxOpenSpaces).ToList();
            shot = scanShots2[rand.Next(scanShots2.Count())].point;
 
            return shot;
        }
 
        int OpenSpaces(int x, int y)
        {
            int ctr = 0;
            Point p;
 
            // spaces to the left
            p = new Point(x - 1, y);
            while (p.X >= 0 && board[p.X, p.Y] == ShotResult.UNKNOWN)
            {
                ctr++;
                p.X--;
            }
 
            // spaces to the right
            p = new Point(x + 1, y);
            while (p.X < gameSize.Width && board[p.X, p.Y] == ShotResult.UNKNOWN)
            {
                ctr++;
                p.X++;
            }
 
            // spaces to the top
            p = new Point(x, y - 1);
            while (p.Y >= 0 && board[p.X, p.Y] == ShotResult.UNKNOWN)
            {
                ctr++;
                p.Y--;
            }
 
            // spaces to the bottom
            p = new Point(x, y + 1);
            while (p.Y < gameSize.Height && board[p.X, p.Y] == ShotResult.UNKNOWN)
            {
                ctr++;
                p.Y++;
            }
 
            return ctr;
        }
 
        public void NewMatch(string opponenet)
        {
            wins = 0;
            losses = 0;
        }
 
        public void OpponentShot(Point shot) { }
 
        public void ShotHit(Point shot, bool sunk)
        {
            board[shot.X, shot.Y] = ShotResult.HIT;
 
            if (!sunk)
            {
                hitDirection = lastShotDirection;
                if (shot.X != 0)
                {
                    this.nextShots.Add(new NextShot(new Point(shot.X - 1, shot.Y), Direction.HORIZONTAL));
                }
 
                if (shot.Y != 0)
                {
                    this.nextShots.Add(new NextShot(new Point(shot.X, shot.Y - 1), Direction.VERTICAL));
                }
 
                if (shot.X != this.gameSize.Width - 1)
                {
                    this.nextShots.Add(new NextShot(new Point(shot.X + 1, shot.Y), Direction.HORIZONTAL));
                }
 
                if (shot.Y != this.gameSize.Height - 1)
                {
                    this.nextShots.Add(new NextShot(new Point(shot.X, shot.Y + 1), Direction.VERTICAL));
                }
            }
            else
            {
                hitDirection = Direction.UNKNOWN;
                this.nextShots.Clear();     // so now this works like gangbusters ?!?!?!?!?!?!?!?!?
            }
        }
 
        public void ShotMiss(Point shot)
        {
            board[shot.X, shot.Y] = ShotResult.MISS;
        }
 
        public void GameWon()
        {
            wins++;
        }
 
        public void GameLost()
        {
            losses++;
        }
 
        public void MatchOver()
        {
            if (wins > maxWins)
            {
                maxWins = wins;
            }
 
            if (losses > maxLosses)
            {
                maxLosses = losses;
            }
 
            totalWins += wins;
            totalLosses += losses;
 
            if (wins >= losses)
            {
                matchWins++;
            }
            else
            {
                matchLosses++;
            }
        }
 
        public void FinalStats()
        {
            Console.WriteLine("Games won: " + totalWins.ToString());
            Console.WriteLine("Games lost: " + totalLosses.ToString());
            Console.WriteLine("Game winning percentage: " + (totalWins * 1.0 / (totalWins + totalLosses)).ToString("P"));
            Console.WriteLine("Game losing percentage: " + (totalLosses * 1.0 / (totalWins + totalLosses)).ToString("P"));
            Console.WriteLine();
            Console.WriteLine("Matches won: " + matchWins.ToString());
            Console.WriteLine("Matches lost: " + matchLosses.ToString());
            Console.WriteLine("Match winning percentage: " + (matchWins * 1.0 / (matchWins + matchLosses)).ToString("P"));
            Console.WriteLine("Match losing percentage: " + (matchLosses * 1.0 / (matchWins + matchLosses)).ToString("P"));
            Console.WriteLine("Match games won high: " + maxWins.ToString());
            Console.WriteLine("Match games lost high: " + maxLosses.ToString());
            Console.WriteLine();
        }
    }
}

The thing I find odd about this was that against the winning entry, Dreadnought, my AI was far and away the most successful, winning about 40% of the individual games. The next closest competitor to Dreadnought was BSKiller, crafted by my compatriot in crime, John Boker, winning 20% in the initial round robin and 12% and 14% in the knockout round.

And yet, BP7 stunk against most of the other real submissions, which is a bit mysterious to me. I understand that there is some random fortune involved here (for example, in the round robin, BSKiller defeated BP7 121 games to 80, even though in my own testing, BP7 was winning about 54% of the games over BSKiller), but to be defeated at almost every turn?

Oh well. Here is my VS2008 solution and class files, if you would care to try it out for yourself:

Battleship solution

Also, when I was first looking into this problem, I created some graph paper to help me visualize certain situations. Here is the PDF file of this graph paper:

Battleship Graph Paper

Happy Thanksgiving everyone! (For those outside the U.S., Happy November 26th.)

Deuces wild???

As a baseball loving youth, I remember with great fondness watching and listening to baseball as called by the voice of baseball, Vin Scully, who is unquestionably one of the greatest baseball announcers.

One thing I can always remember him saying during his calls was “deuces wild”, which he used to describe a situation that occurs with a count of 2 balls, 2 strikes, and 2 outs.

So, I figured I would take a look at the raw statistics and see how often the deuces wild situation actually came up.  Here is the code from my Program.cs file:

using System;
using System.Collections.Generic;
using System.Linq;
 
namespace RetrosheetReader
{
    class Program
    {
        static List<Team> teamList;
        static List<Player> playerList;
        static List<Event> eventList;
 
        const string DATA = "c:\\baseball_data\\";
 
        static void Main(string[] args)
        {
            Console.WriteLine("Retrosheet Reader");
            Console.WriteLine();
 
            teamList = Team.GetTeamList(DATA);
            playerList = Player.GetPlayerList(DATA);
            eventList = Event.GetEventList(DATA);
 
            Console.WriteLine("Number of teams: " + teamList.Count().ToString());
            Console.WriteLine("Number of players: " + playerList.Count().ToString());
            Console.WriteLine("Number of events: " + eventList.Count().ToString());
 
            int[, ,] pitchCount = new int[4, 3, 3];
            int balls, strikes, totalPitches;
 
            totalPitches = 0;
            foreach (var ev in eventList)
            {
                balls = 0;
                strikes = 0;
                foreach (char c in ev.pitchSequence)
                {
                    if (Functions.IsBallOrStrike(c))
                    {
                        if (balls < 0 || balls > 3)
                        {
                            Console.WriteLine("Illegal number of balls (" + balls.ToString() + ") in pitch sequence " + ev.pitchSequence);
                        }
                        else
                        {
                            pitchCount[balls, strikes, ev.outs]++;
                            totalPitches++;
                            if (Functions.IsStrike(c))
                            {
                                if (strikes == 2)
                                {
                                    if (!Functions.IsFoul(c))
                                        strikes++;
                                }
                                else
                                    strikes++;
                            }
                            else
                            {
                                balls++;
                            }
                        }
                    }
                }
            }
 
            Console.WriteLine("Total pitches: " + totalPitches.ToString());
            for (int o = 0; o < 3; o++)
                for (int s = 0; s < 3; s++)
                    for (int b = 0; b < 4; b++)
                        Console.WriteLine(String.Format("Total pitches on B{0}-S{1}-O{2}: {3,8}  ({4,6:P})", b, s, o, pitchCount[b, s, o],
                                                    pitchCount[b, s, o] * 1.0 / totalPitches));
 
            Console.WriteLine();
            Console.Write("Strike any key to end...");
            Console.ReadKey();
        }
    }
}

By the way, there is a new Functions.cs file in the project that contains utility functions, and I had to modify the Event.cs file to take into account the fact that the BEVENT application creates duplicate records for a batter if there is some kind of on-base event that happens in the middle of the at-bat, such as a stolen base or pick off.

The results? I ran the application with the 2008 season data, and found that there were 700,242 total pitches. The deuces wild situation happened on only 17,141 pitches, or 2.45%. Of course, the highest percentage occurred with 0 balls, 0 strikes, and 0 outs (the first pitch to any batters that bat in an inning before the first out is recorded, including the first batter of each inning) with 65,050 pitches, or 9.29%.

Here is the zipped up solution:

RetrosheetReader.zip

Best way to not use an enumeration, ever!

So here is some more interesting stuff found in some code that needed some emergency modifications.

The enumeration of this code starts on line 26 of the class file:

public enum City
{
    CLINTON = 78,
    FRANKLIN = 1,
    FAIRVIEW = 59,
    GREENVILLE = 55,
    SALEM = 83,
    MADISON = 81,
    SPRINGFIELD = 14,
    ARLINGTON = 84,
    CLAYTON = 63,
    GEORGETOWN = 85,
    MARION = 15,
    OXFORD = 65,
    BURLINGTON = 5
}

And instead of using the MADISON enumeration, the if statement of this code starts on line 118 of the very same class file:

if (cityID == 0x51)
{
    actual = expected - actual;
}

The names were changed to protect the innocent, thanks to Wikipedia for having an easy to find list of common place names.

Because you never know how many days could be between Monday and Friday

Why do a couple of subtractions when two loops can do the same work?

while (processDate.DayOfWeek != DayOfWeek.Monday)
    processDate = processDate.AddDays(-1);
 
string mondayDate = processDate.ToShortDateString();
 
while (processDate.DayOfWeek != DayOfWeek.Friday)
    processDate = processDate.AddDays(1);
 
string fridayDate = processDate.ToShortDateString();

Retrosheet event record mapping completed

The Retrosheet project has kind of sat around for a while untouched, so I figured I would finish up the reading of the event records. I have decided to break the project into separate files, as if I kept it in one file, it would be obscenely long.

So, here is the main Program.cs file:

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
 
namespace RetrosheetReader
{
    class Program
    {
        static List<Team> teamList;
        static List<Player> playerList;
        static List<Event> eventList;
 
        const string DATA = "c:\\baseball_data\\";
 
        static void Main(string[] args)
        {
            Console.WriteLine("Retrosheet Reader");
            Console.WriteLine();
 
            teamList = Team.GetTeamList(DATA);
            playerList = Player.GetPlayerList(DATA);
            eventList = Event.GetEventList(DATA);
 
            Console.WriteLine("Number of teams: " + teamList.Count().ToString());
            Console.WriteLine("Number of players: " + playerList.Count().ToString());
            Console.WriteLine("Number of events: " + eventList.Count().ToString());
 
            Console.WriteLine();
            Console.Write("Strike any key to end...");
            Console.ReadKey();
        }
    }
}

There are now 3 class files, one each for a team, player, and event. The event file is the one with the most new stuff going on.

Team.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
 
namespace RetrosheetReader
{
    class Team
    {
        int year;
        string city;
        string nickname;
        string abbreviation;
        string league;
 
        public Team(int y, string[] a)
        {
            year = y;
            abbreviation = a[0];
            league = a[1];
            city = a[2];
            nickname = a[3];
        }
 
        public static List<Team> GetTeamList(string dir)
        {
            int y;
            string s;
            string[] splitLine;
            List<Team> teamList = new List<Team>();
 
            List<string> tfs = Directory.GetFiles(dir, "team*").ToList();
            foreach (string tf in tfs)
            {
                y = Convert.ToInt32(Path.GetFileName(tf).Substring(4));
                StreamReader sr = new StreamReader(tf);
                while ((s = sr.ReadLine()) != null)
                {
                    splitLine = s.Split(',');
                    if (splitLine.Count() == 4)
                    {
                        teamList.Add(new Team(y, splitLine));
                    }
                }
            }
 
            return teamList;
        }
    }
}

Player.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
 
namespace RetrosheetReader
{
    class Player
    {
        int year;
        string team;
        string playerID;
        string firstName;
        string lastName;
        string bats;
        string throws;
        string position;
 
        public Player(int y, string[] a)
        {
            year = y;
            playerID = a[0];
            lastName = a[1];
            firstName = a[2];
            bats = a[3];
            throws = a[4];
            team = a[5];
            position = a[6];
        }
 
        public static List<Player> GetPlayerList(string dir)
        {
            int y;
            string s;
            string[] splitLine;
            List<Player> playerList = new List<Player>();
 
            List<string> rfs = Directory.GetFiles(dir, "*.ros").ToList();
            foreach (string rf in rfs)
            {
                y = Convert.ToInt32(Path.GetFileName(rf).Substring(3).Split('.')[0]);
                StreamReader sr = new StreamReader(rf);
                while ((s = sr.ReadLine()) != null)
                {
                    splitLine = s.Split(',');
                    if (splitLine.Count() == 7)
                    {
                        playerList.Add(new Player(y, splitLine));
                    }
                }
            }
 
            return playerList;
        }
    }
}

Event.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
 
namespace RetrosheetReader
{
    class Event
    {
        string gameID;          // 0, A
        string visitingTeam;    // 1, B
        int inning;             // 2, C
        int battingTeam;        // 3, D
        int outs;               // 4, E
        int balls;              // 5, F
        int strikes;            // 6, G
        string pitchSequence;   // 7, H
        int visitorScore;       // 8, I
        int homeScore;          // 9, J
        string batter;          // 10, K
        string batterHand;      // 11, L
        string resBatter;       // 12, M
        string resBatterHand;   // 13, N
        string pitcher;         // 14, O
        string pitcherHand;     // 15, P
        string resPitcher;      // 16, Q
        string resPitcherHand;  // 17, R
        string catcher;         // 18, S
        string firstBase;       // 19, T
        string secondBase;      // 20, U
        string thirdBase;       // 21, V
        string shortstop;       // 22, W
        string leftField;       // 23, X
        string centerField;     // 24, Y
        string rightField;      // 25, Z
        string firstRunner;     // 26, AA
        string secondRunner;    // 27, AB
        string thirdRunner;     // 28, AC
        string eventText;       // 29, AD
        bool leadoffFlag;       // 30, AE
        bool pinchHitFlag;      // 31, AF
        int defensivePosition;  // 32, AG
        int lineupPosition;     // 33, AH
        int eventType;          // 34, AI
        bool batterEventFlag;   // 35, AJ
        bool abFlag;            // 36, AK
        int hitValue;           // 37, AL
        bool shFlag;            // 38, AM
        bool sfFlag;            // 39, AN
        int outsOnPlay;         // 40, AO
        bool doublePlayFlag;    // 41, AP
        bool triplePlayFlag;    // 42, AQ
        int rbiOnPlay;          // 43, AR
        bool wildPitchFlag;     // 44, AS
        bool passedBallFlag;    // 45, AT
        int fieldedBy;          // 46, AU
        string battedBallType;  // 47, AV
        bool buntFlag;          // 48, AW
        bool foulFlag;          // 49, AX
        string hitLocation;     // 50, AY
        int numErrors;          // 51, AZ
        int firstErrorPlayer;   // 52, BA
        string firstErrorType;  // 53, BB
        int secondErrorPlayer;  // 54, BC
        string secondErrorType; // 55, BD
        int thirdErrorPlayer;   // 56, BE
        string thirdErrorType;  // 57, BF
        int batterDest;         // 58, BG
        int runner1Dest;        // 59, BH
        int runner2Dest;        // 60, BI
        int runner3Dest;        // 61, BJ
        string playOnBatter;    // 62, BK
        string playOnRunner1;   // 63, BL
        string playOnRunner2;   // 64, BM
        string playOnRunner3;   // 65, BN
        bool sbRunner1Flag;     // 66, BO
        bool sbRunner2Flag;     // 67, BP
        bool sbRunner3Flag;     // 68, BQ
        bool csRunner1Flag;     // 69, BR
        bool csRunner2Flag;     // 70, BS
        bool csRunner3Flag;     // 71, BT
        bool poRunner1Flag;     // 72, BU
        bool poRunner2Flag;     // 73, BV
        bool poRunner3Flag;     // 74, BW
        string respPitcher1;    // 75, BX
        string respPitcher2;    // 76, BY
        string respPitcher3;    // 77, BZ
        bool newGameFlag;       // 78, CA
        bool endGameFlag;       // 79, CB
        bool pinchRunner1;      // 80, CC
        bool pinchRunner2;      // 81, CD
        bool pinchRunner3;      // 82, CE
        string removedForPR1;   // 83, CF
        string removedForPR2;   // 84, CG
        string removedForPR3;   // 85, CH
        string removedForPH;    // 86, CI
        int posRemovedForPH;    // 87, CJ
        int fielderWithPO1;     // 88, CK
        int fielderWithPO2;     // 89, CL
        int fielderWithPO3;     // 90, CM
        int fielderWithA1;      // 91, CN
        int fielderWithA2;      // 92, CO
        int fielderWithA3;      // 93, CP
        int fielderWithA4;      // 94, CQ
        int fielderWithA5;      // 95, CR
        int eventNum;           // 96, CS
 
        public Event(string[] a)
        {
            gameID = a[0].Replace("\"", "");
            visitingTeam = a[1].Replace("\"", "");
            inning = Convert.ToInt32(a[2]);
            battingTeam = Convert.ToInt32(a[3]);
            outs = Convert.ToInt32(a[4]);
            balls = Convert.ToInt32(a[5]);
            strikes = Convert.ToInt32(a[6]);
            pitchSequence = a[7].Replace("\"", "");
            visitorScore = Convert.ToInt32(a[8]);
            homeScore = Convert.ToInt32(a[9]);
            batter = a[10].Replace("\"", "");
            batterHand = a[11].Replace("\"", "");
            resBatter = a[12].Replace("\"", "");
            resBatterHand = a[13].Replace("\"", "");
            pitcher = a[14].Replace("\"", "");
            pitcherHand = a[15].Replace("\"", "");
            resPitcher = a[16].Replace("\"", "");
            resPitcherHand = a[17].Replace("\"", "");
            catcher = a[18].Replace("\"", "");
            firstBase = a[19].Replace("\"", "");
            secondBase = a[20].Replace("\"", "");
            thirdBase = a[21].Replace("\"", "");
            shortstop = a[22].Replace("\"", "");
            leftField = a[23].Replace("\"", "");
            centerField = a[24].Replace("\"", "");
            rightField = a[25].Replace("\"", "");
            firstRunner = a[26].Replace("\"", "");
            secondRunner = a[27].Replace("\"", "");
            thirdRunner = a[28].Replace("\"", "");
            eventText = a[29].Replace("\"", "");
            leadoffFlag = a[30].Contains('T');
            pinchHitFlag = a[31].Contains('T');
            defensivePosition = Convert.ToInt32(a[32]);
            lineupPosition = Convert.ToInt32(a[33]);
            eventType = Convert.ToInt32(a[34]);
            batterEventFlag = a[35].Contains('T');
            abFlag = a[36].Contains('T');
            hitValue = Convert.ToInt32(a[37]);
            shFlag = a[38].Contains('T');
            sfFlag = a[39].Contains('T');
            outsOnPlay = Convert.ToInt32(a[40]);
            doublePlayFlag = a[41].Contains('T');
            triplePlayFlag = a[42].Contains('T');
            rbiOnPlay = Convert.ToInt32(a[43]);
            wildPitchFlag = a[44].Contains('T');
            passedBallFlag = a[45].Contains('T');
            fieldedBy = Convert.ToInt32(a[46]);
            battedBallType = a[47].Replace("\"", "");
            buntFlag = a[48].Contains('T');
            foulFlag = a[49].Contains('T');
            hitLocation = a[50].Replace("\"", "");
            numErrors = Convert.ToInt32(a[51]);
            firstErrorPlayer = Convert.ToInt32(a[52]);
            firstErrorType = a[53].Replace("\"", "");
            secondErrorPlayer = Convert.ToInt32(a[54]);
            secondErrorType = a[55].Replace("\"", "");
            thirdErrorPlayer = Convert.ToInt32(a[56]);
            thirdErrorType = a[57].Replace("\"", "");
            batterDest = Convert.ToInt32(a[58]);
            runner1Dest = Convert.ToInt32(a[59]);
            runner2Dest = Convert.ToInt32(a[60]);
            runner3Dest = Convert.ToInt32(a[61]);
            playOnBatter = a[62].Replace("\"", "");
            playOnRunner1 = a[63].Replace("\"", "");
            playOnRunner2 = a[64].Replace("\"", "");
            playOnRunner3 = a[65].Replace("\"", "");
            sbRunner1Flag = a[66].Contains('T');
            sbRunner2Flag = a[67].Contains('T');
            sbRunner3Flag = a[68].Contains('T');
            csRunner1Flag = a[69].Contains('T');
            csRunner2Flag = a[70].Contains('T');
            csRunner3Flag = a[71].Contains('T');
            poRunner1Flag = a[72].Contains('T');
            poRunner2Flag = a[73].Contains('T');
            poRunner3Flag = a[74].Contains('T');
            respPitcher1 = a[75].Replace("\"", "");
            respPitcher2 = a[76].Replace("\"", "");
            respPitcher3 = a[77].Replace("\"", "");
            newGameFlag = a[78].Contains('T');
            endGameFlag = a[79].Contains('T');
            pinchRunner1 = a[80].Contains('T');
            pinchRunner2 = a[81].Contains('T');
            pinchRunner3 = a[82].Contains('T');
            removedForPR1 = a[83].Replace("\"", "");
            removedForPR2 = a[84].Replace("\"", "");
            removedForPR3 = a[85].Replace("\"", "");
            removedForPH = a[86].Replace("\"", "");
            posRemovedForPH = Convert.ToInt32(a[87]);
            fielderWithPO1 = Convert.ToInt32(a[88]);
            fielderWithPO2 = Convert.ToInt32(a[89]);
            fielderWithPO3 = Convert.ToInt32(a[90]);
            fielderWithA1 = Convert.ToInt32(a[91]);
            fielderWithA2 = Convert.ToInt32(a[92]);
            fielderWithA3 = Convert.ToInt32(a[93]);
            fielderWithA4 = Convert.ToInt32(a[94]);
            fielderWithA5 = Convert.ToInt32(a[95]);
            eventNum = Convert.ToInt32(a[96]);
        }
 
        public static List<Event> GetEventList(string dir)
        {
            string s;
            string[] splitLine;
            List<Event> eventList = new List<Event>();
 
            List<string> efs = Directory.GetFiles(dir, "*.csv").ToList();
            foreach (string ef in efs)
            {
                Console.WriteLine("Reading events in " + ef);
                StreamReader sr = new StreamReader(ef);
                while ((s = sr.ReadLine()) != null)
                {
                    splitLine = s.Split(',');
                    if (splitLine.Count() == 97)
                    {
                        eventList.Add(new Event(splitLine));
                    }
                }
            }
 
            return eventList;
        }
    }
}

I have zipped up the solution if you would like to work with it:

RetrosheetReader.zip

Next up will come some actual analysis of the data, now that it is completely being read in and stored.

NetCFSvcUtil.exe error in Windows 7

I have been using Windows 7 for a while now, and always had to keep my Windows XP dev machine handy to help out with my Windows Mobile software development.

It turns out that the NetCFSvcUtil application included in the .NET CF Power Toys did not get along with Windows 7 (neither the RC or the RTM bits), as it gave a cryptic error message of “An error occurred in the tool”.

So just for giggles, I decided to see if there were any updates, and lo and behold, I found a blog post from Habib Heydarian with an updated application:

NetCFSvcUtil.exe and Windows 7

Now I can happily generate the client code on my Windows 7 machine.

Retrosheet reader

OK, so you just got back from the Ohio LinuxFest 2009  (meh, not exactly a hoppin’ place this year, I was kind of disappointed), and you have your event files converted over to CSV files after you generated the BEVENT batch files per last weekend’s post.

Now, you would like to read in that data and start looking through it for anything useful. Well this weekend’s post will help you with the reading in part, and you can take it from there if you would like.

Here is the C# console code. I have put all of the CSV files, ROS (roster) files, and the TEAM???? file into a folder on my C: drive called baseball_data, if your folder is different then just change the constant defined in the code below:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
 
namespace RetrosheetReader
{
 
    class Team
    {
        int year;
        string city;
        string nickname;
        string abbreviation;
        string league;
 
        public Team(int y, string[] a)
        {
            year = y;
            abbreviation = a[0];
            league = a[1];
            city = a[2];
            nickname = a[3];
        }
    }
 
    class Player
    {
        int year;
        string team;
        string playerID;
        string firstName;
        string lastName;
        string bats;
        string throws;
        string position;
 
        public Player(int y, string[] a)
        {
            year = y;
            playerID = a[0];
            lastName = a[1];
            firstName = a[2];
            bats = a[3];
            throws = a[4];
            team = a[5];
            position = a[6];
        }
    }
 
    class Event
    {
        string gameID;
        string visitingTeam;
        int inning;
        string battingTeam;
        int outs;
        int balls;
        int strikes;
        // yeah, there is still some work left here to do
        // maybe next time
 
        public Event(string[] a)
        {
            gameID = a[0];
            visitingTeam = a[1];
            inning = Convert.ToInt32(a[2]);
            battingTeam = a[3];
            outs = Convert.ToInt32(a[4]);
            balls = Convert.ToInt32(a[5]);
            strikes = Convert.ToInt32(a[6]);
        }
    }
 
    class Program
    {
        const string DATA = "c:\\baseball_data\\";
 
        static void Main(string[] args)
        {
            List teamList = new List();
            List playerList = new List();
            List eventList = new List();
 
            string s;
            string[] splitLine;
            int y;
 
            Console.WriteLine("Retrosheet Reader");
            Console.WriteLine();
 
            List tfs = Directory.GetFiles(DATA, "team*").ToList();
            foreach (string tf in tfs)
            {
                y = Convert.ToInt32(Path.GetFileName(tf).Substring(4));
                StreamReader sr = new StreamReader(tf);
                while ((s = sr.ReadLine()) != null)
                {
                    splitLine = s.Split(',');
                    if (splitLine.Count() == 4)
                    {
                        teamList.Add(new Team(y, splitLine));
                    }
                }
            }
 
            List rfs = Directory.GetFiles(DATA, "*.ros").ToList();
            foreach (string rf in rfs)
            {
                y = Convert.ToInt32(Path.GetFileName(rf).Substring(3).Split('.')[0]);
                StreamReader sr = new StreamReader(rf);
                while ((s = sr.ReadLine()) != null)
                {
                    splitLine = s.Split(',');
                    if (splitLine.Count() == 7)
                    {
                        playerList.Add(new Player(y, splitLine));
                    }
                }
            }
 
            List efs = Directory.GetFiles(DATA, "*.csv").ToList();
            foreach (string ef in efs)
            {
                Console.WriteLine("Reading events in " + ef);
                StreamReader sr = new StreamReader(ef);
                while ((s = sr.ReadLine()) != null)
                {
                    splitLine = s.Split(',');
                    if (splitLine.Count() == 97)
                    {
                        eventList.Add(new Event(splitLine));
                    }
                }
            }
 
            Console.WriteLine("Number of teams: " + teamList.Count().ToString());
            Console.WriteLine("Number of players: " + playerList.Count().ToString());
            Console.WriteLine("Number of events: " + eventList.Count().ToString());
 
            Console.WriteLine();
            Console.Write("Strike any key to end...");
            Console.ReadKey();
        }
    }
}

I have an idea as to the first thing that I am going to look for in the Retrosheet data, so tune in next weekend and I will (hopefully) have some interesting insights.

The illusion…

I have heard it said that the greatest danger is the illusion that all is well. This is sort of pessimistic, and I am not a pessimistic person by nature.

And then I start looking through the code and see something like this:

public Guid AddSomething(Guid id1, Guid id2, bool useAlternateValue)
{
    // stuff removed from here
    // including the creation of the obj object, 
    // which conains a default and alternate value
 
    string s = "";
    if (useAlternateValue && (obj.alternateValue != null))
        s = obj.alternateValue;
    else if ((!useAlternateValue) && (obj.defaultValue != null))
        s = obj.defaultValue;
 
    // more stuff removed from here
}

One of the developers that I work with is confused by my hatred of compounding the not operator with ands, ors, and parentheses in decision statements, and of the use of the null object. This code appears to combine both of these things in a mishmash of non functional code.

Brute force solution to a birthday riddle

At one point a long time ago, one of my college professors asked our class how many people it would take to put in a room before the probability that two of the people had the same birthday was greater than or equal to 50 percent, took guesses from a few of us students, and then told us the answer was 12. Coming from a professor, this had to be true.

That answer has not sat well with me lo these many years since my drinking days, so sitting here with nothing else to do, I decided to try a little Monte Carlo problem solving.

Here is my VS 2008 C# console application code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Birthdays
{
    class Program
    {
        static int numberOfRuns;
        static int[] numberOfPeople;
        static List<int> birthdays;
        static Random rand;
 
        static int GetABirthday()
        {
            int b;
            int maxDays = (rand.Next(1, 5) == 1) ? 367 : 366;
 
            b = rand.Next(1, maxDays);
 
            return b;
        }
 
        static void Main(string[] args)
        {
            rand = new Random();
 
            Console.WriteLine("Birthdays application");
 
            Console.Write("How many times would you like to run a birthday search? ");
            string s = Console.ReadLine();
            numberOfRuns = Convert.ToInt32(s);
 
            numberOfPeople = new int[367];
            for (int i = 0; i < 367; i++)
            {
                numberOfPeople[i] = 0;
            }
 
            Boolean leaveLoop;
            int b;
            int leapDayMatches = 0;
            int leapDayBirthdays = 0;
            int totalBirthdays = 0;
            for (int i = 1; i <= numberOfRuns; i++)
            {
                birthdays = new List<int>();
                leaveLoop = false;
                while (!leaveLoop)
                {
                    b = GetABirthday();
                    totalBirthdays++;
                    if (b == 366)
                    {
                        leapDayBirthdays++;
                    }
                    if (birthdays.Contains(b))
                    {
                        numberOfPeople[birthdays.Count() + 1]++;
                        leaveLoop = true;
                        if (b == 366)
                        {
                            leapDayMatches++;
                        }
                    }
                    else
                    {
                        birthdays.Add(b);
                    }
                }
            }
 
            Console.WriteLine();
            Console.WriteLine("Breakdown of number of people required:");
            int ctr = 0;
            for (int i = 2; i <= 366; i++)
            {
                if (numberOfPeople[i] != 0)
                {
                    ctr += numberOfPeople[i];
                    Console.WriteLine(string.Format("{0} people: {1} ({2:P})", i, 
                                        numberOfPeople[i], ctr * 1.0 / numberOfRuns));
                }
            }
            Console.WriteLine(string.Format("Total birthdays generated: {0}", 
                                                totalBirthdays));
            Console.WriteLine(string.Format("Leap day birthdays: {0}", 
                                                leapDayBirthdays));
            Console.WriteLine(string.Format("Leap day matches: {0}", 
                                                leapDayMatches));
            Console.WriteLine();
            Console.WriteLine("Strike any key to end the program");
            Console.ReadKey();
        }
    }
}

The answer yielded by the above code is 23, as any meaningful sample size plugged into the program above will demonstrate.

Of course, we did not have the internet back then, but now a quick Google search yields plenty of discussion of the theory and math behind the puzzle. If you are interested, click this link.

Sentinel.v3.5Client error in ClickOnce app install

We have had bad experiences in the past deploying desktop applications based on the .Net Framework version 3.5, largely due to the huge payload required on the end user’s system to run our relatively simple application.

When SP1 of Visual Studio 2008 was announced to have an option to trim down the bits to be downloaded, we were naturally excited to put it into place on our newest product. In a VB.NET application, this option is called “Client-only Framework subset”, it is on the Advanced Compiler Settings window shown by clicking Advance Compile Options… on the Compile tab of My Project, and in a C#.NET application, the Client-only Framework subset check box is right under Target Framework on the Application tab shown by right clicking on the project file and selecting Properties.

Excited until, of course, we tried for the very first time to run the ClickOnce installer for our product on a clean Windows XP SP3 virtual machine and received the following error:

Unable to install or run the application. The application requires that
assembly Sentinel.v3.5Client Version 3.5.0.0 be installed in the Global
Assembly Cache (GAC) first.

I did some quick investigating and found that, for the most part, the suggested solution was to turn off the client only subset option. Bugger!

This did not sit well with me, so I did a little more digging. I did not think that there were any kind of items added to the code that would require namespaces not included in the client only framework, so I went through all of the property pages for the project.

On the Publish tab, I clicked on Preqrequisites, and noticed that my application was including prerequisites for Windows Installer 3.1, .NET Framework 3.5, and SQL Server Compact 3.5. On a lark, I turned off the prerequisite for .NET Framework 3.5 and turned on the prerequisite for .NET Framework 3.5 SP1, built the ClickOnce installer, and voila! It is now working like a champ.