A simple race tracking class for LFSLib.NET

Continuing in providing some usage examples for LFSLib.NET, I've created a very simple race tracking class. I figure it covers a lot of ground, since most people want to keep track of race progress, and it does show the basic use of the InSimHandler.

This class only tracks players entering and leaving and reports their split and lap times. It ignores player renames and other race information, and just spits that info out to the console. There is a static RaceTracker.Test() method that's just to be able to run the class from your Main, i.e. it's not how you'd likely use it in your actual tracking code.

using System;
using System.Collections.Generic;
using System.Text;
using FullMotion.LiveForSpeed.InSim;
using FullMotion.LiveForSpeed.InSim.EventHandlers;
using FullMotion.LiveForSpeed.InSim.Events;

namespace FullMotion.LiveForSpeed
{
  /// <summary>
  /// Simple Race Tracking example. Keeps track of player names
  /// and reports the split and lap times for each player
  /// </summary>
  public class RaceTracker
  {
    /// <summary>
    /// Just a static test call for running RaceTracker against a local LFS instance
    /// </summary>
    public static void Test()
    {
      RaceTracker tracker = new RaceTracker("127.0.0.1", 30000);
      tracker.Start();
      Console.WriteLine("Press Enter to Exit");
      Console.ReadLine();
      tracker.Stop();
    }

    InSimHandler handler;

    // state variables to make sure we don't start and stop
    // the handler multiple times
    bool started = false;
    bool running = false;

    // keep track of player names, since race tracking only gives us IDs
    Dictionary<byte, string> players = new Dictionary<byte, string>();

    /// <summary>
    /// Set up an insim handler for tracking basic race info
    /// </summary>
    /// <param name="host"></param>
    /// <param name="port"></param>
    public RaceTracker(string host, int port)
    {
      handler = new InSimHandler();
      handler.Configuration.LFSHost = host;
      handler.Configuration.LFSHostPort = port;
      handler.Configuration.UseTCP = true;
      handler.RaceTrackPlayer 
        += new RaceTrackPlayerHandler(handler_RaceTrackPlayer);
      handler.RaceTrackPlayerLeave 
        += new RaceTrackPlayerLeaveHandler(handler_RaceTrackPlayerLeave);
      handler.RaceTrackPlayerSplitTime 
        += new RaceTrackPlayerSplitTimeHandler(handler_RaceTrackPlayerSplitTime);
      handler.RaceTrackPlayerLap 
        += new RaceTrackPlayerLapHandler(handler_RaceTrackPlayerLap);
    }

    /// <summary>
    /// Connect to LFS and start tracking
    /// </summary>
    public void Start()
    {
      if (started)
      {
        throw new InvalidOperationException("RaceTracker cannot be started multiple times");
      }
      handler.Initialize();
      started = true;
      running = true;

      // make sure we get all players in the race
      handler.RequestPlayerInfo();
    }

    /// <summary>
    /// Close down the connection
    /// </summary>
    public void Stop()
    {
      if (running)
      {
        handler.Close();
        running = false;
      }
    }

    private void handler_RaceTrackPlayer(InSimHandler sender, RaceTrackPlayer e)
    {
      if (!players.ContainsKey(e.PlayerId))
      {
        players.Add(e.PlayerId, e.Playername);
        Console.WriteLine("Player joined: {0} ({1})", e.Playername, e.PlayerId);
      }
    }

    private void handler_RaceTrackPlayerLeave(InSimHandler sender, RaceTrackPlayerLeave e)
    {
      Console.WriteLine("Player left: {0} ({1})", players[e.PlayerId], e.PlayerId);
      players.Remove(e.PlayerId);
    }

    void handler_RaceTrackPlayerLap(InSimHandler sender, RaceTrackPlayerLap e)
    {
      Console.WriteLine(
        "Player '{0}': Lap {1} @ {2}:{3}.{4}",
        players[e.PlayerId],
        e.LapsDone,
        e.LapTime.Minutes,
        e.LapTime.Seconds,
        e.LapTime.Milliseconds);
    }

    void handler_RaceTrackPlayerSplitTime(InSimHandler sender, RaceTrackPlayerSplitTime e)
    {
      Console.WriteLine(
        "Player '{0}': Split {1} @ {2:00}:{3:00}.{4:000}",
        players[e.PlayerId],
        e.Split,
        e.SplitTime.Minutes,
        e.SplitTime.Seconds,
        e.SplitTime.Milliseconds);
    }
  }
}

Sample output of this running against a race of three AIs on Aston Cadet Reverse looks like this:

Press Enter to Exit
Player joined: AI 2 (6)
Player joined: AI 3 (8)
Player joined: AI 4 (10)
Player 'AI 2': Split 1 @ 00:29.870
Player 'AI 4': Split 1 @ 00:31.280
Player 'AI 3': Split 1 @ 00:31.800
Player 'AI 2': Lap 1 @ 1:5.960
Player 'AI 4': Lap 1 @ 1:9.180
Player 'AI 3': Lap 1 @ 1:10.220
Player left: AI 2 (6)
Player left: AI 3 (8)
Player 'AI 4': Split 1 @ 00:26.410
Player left: AI 4 (10)

The Player left are me manual throwing the AIs into spectate mode.

The players dictionary could easily be changed to hold a Player class instead of string as the value slot and then more data can be tracked and even a continuous log of the race results could be kept. But this should be a place to start building a race tracking application