I like playing CounterStrike Source (CS:S). I like playing against my friends at LAN parties (yes we actually still physically gather with our pc’s at some location and network them up and play against each other, we call it Lanfix).We then like to know how I did. We used to use to use Psychostats to generate a visual dashboard of what happened in game but it was no longer maintained and did not support the log format now coming out of CS:S.
@naiboss put out a request on the Lanfix mailing list to see if someone could tweak the Psyhcostats code to work with the log files I though I would have a look.
Instead of tweaking the Psychostats code I wrote a new application. After I had finished writing the app I then NoSQL Distilled: A Brief Guide to the Emerging World of Polyglot Persistence' target=_blank>read a good book about NoSql databases by Martin Fowler. At the end of the book he quickly covers other types of databases, one of them being an Event Sourcing databases and it occurred to me that is exactly what the CS:S log files were. The log files were events that had happened in that order in the game. My app essentially replayed these events to build up the state of the application. A fuller description of Event Sourcing can be found here. I didn’t know it at the time but I had built an app using an Event Sourcing database.
This post is a brief look at the the solution I came up with after I decided it would be easier to write an app from scratch rather than try and update the Psycostats codebase. The working version of the final application is up and running if you want to see what it does before reading how it does it. I finish the post with a summary of what I leant and
Why I didn’t tweak psyhcostats
I looked at the Psychostats code. It is written in php which is not my strong point and something I do not want to be a strong point, It required a mySql database which always cost some money and caused us problems with hosting and upgrades plus there was a process of uploading the files log files which then had to be interpreted and put the the correct database tables so the php webpages could run queries to display the webpages.
The interpretation engine was full of regex. I always forget how to regex stuff very quickly and find it hard to read. I didn’t relish trying to work out what regex I had to tweak to get the new style logs to load into the database properly. I also did not want to do a lot of re-work in a language that I personally don’t rate.
New Design Constraints
As I was starting from scratch it was a good opportunity to really think about the design. I came up with the following requirements:
- Must be able to run without a database. That was always a pain to manage in the old system
- Must be testable with automated unit tests
- Must be easy to understand and maintain the code when the log files change. This really meant not using regex.
- Must allow old logs to be interpreted side by side with new logs as and when the log format changes
Step 1 – Look at the raw log file
The most obvious place to start was the raw log file that had to be interpreted. It was interesting to see what I had to work with, every line had a timestamp, the lines ran in time order with each ‘cs:s server event’ being appended to the log file. Maps loading, players buying weapons, players shooting other players, in game chat, there were lots of things going on. The goal was to make sense of these things and display them in a nice dashboard.
Step 2 – Interpret the raw log file
I decided the first thing I needed to do was start writing something that parsed the file and populated an object I could then use in my application. I allowed the data I found in the log files drive the design of my domain objects.
I started by writing code and unit tests which picked out specific lines from the log file. After I got this working I wrote a ‘Line Processor’ for each line that was responsible to understanding what the data meant after the line had been identified. The processor extracted the data from the line and populated the appropriate domain objects.
Step 3 – Create a UI
The aim of the exercise is to create a nice dashboard for people to view the stats after a session. For this I used Sencha’s ExtJS framework. It has a very rich set of controls and excellent documentation. I delegated the layout to @naiboss and @krofunk. The UI development is what drove the requirements for the data that had to be delivered from the domain objects.
It was an interesting exercise because there were a number of ‘Server Events’ that were missing and the data had to be derived from what we had. An example of this was the ‘Successful’ bombing count, how to attribute that to a player. I solved this problem by keeping track of the last person to plant a bomb, when a round finished because of a bombing I attributed that to the personal who last planted the bomb. This required a context to be created for the Line Processors to work in so they could be aware of previous output.
Step 4 – Create a scoring system
People always like a bit of competition so @naiboss devised a scoring system so people were given points for various things, like ‘a kill’ or ‘a bombing’. The scores are weighted by weapon and bonus points given for awards, such as ‘most headshots’
The scoring system is developed against an interface allowing it to be swapped out for other scoring systems if required.
Step 5 – Mash it up - Steam Integration
As the UI and scoring system were progressing we were thinking of how we could make it easy for people to manage their profiles. The answer we came up with was to leverage their Steam profiles. The log provides the SteamId of the players so it was possible to query Steam for more information. Queries are made to get the logos and player names.
Step 6 File management – Mash it up – Dropbox integration
We were now thinking of how to allow the log files to be uploaded. As there was no database and the whole system runs on the fly from the log files I decided to look at integrating with Dropbox. A user could register their dropbox credentials and then sync their dropbox with the server to upload their log files.
This then raised another interesting possibility, how to allow sessions to be looked at in isolation or together. By using the ubiquitous analogy of file and folder management I updated the file parser to process all the log files in a folder and its sub folders. So when we had a lanparty with CS:S in the morning and afternoon we could create an ‘LanParty’ folder with AM and PM folders in it. By clicking on the LanParty folder you get the stats for all the logs on the day but by clicking on either the AM or PM folder you can see what happened in those individual sessions.
This just worked and I dread to think how hard it would have been to implement with something like the Pcyhostats database.
It was a really interesting little project and is not quiet finished yet. The dropbox integration does not work properly yet which is essential to allow other people to use the system.
I learnt a lot as I implemented design patterns that I read in the excellent book ‘Dependancy Injection in .NET’. I have a good suite of unit tests over the file parser so I will be able to easily and safely change the parser to handle updated file formants.