Do you know what it is?
Yes, this is a log-or-something text file, but more importantly – this is something that I had in mind when starting this project. It’s a legit football game generated by Football Manager, presented in a debug form. Pretty cool if you have been wanting it for the last 3 months, believe me.
Why it’s cool
From this point, it’s really easy to add to the game generation. As mentioned before, a game generates actions (dubbed
#2, etc.), and actions generate phases (
There is some logic related to generating phases (eg. which run ends with a tackle and which one ends with a touchdown), but more general computations (eg. which attempt is it going to be) are calculated on the action or game level. Therefore, If I want to add a new phase to the game or change an existing one, I should be able to do this inside only that phase generator.
Why so late
This time it took 28 commits across 2 weeks, really. Some commits are big, being solid, but much needed refactoring, outcomes of many-hour sittings.
The part that took much time was “progress” with the ball on the pitch. Namely, the progress can mean moving the ball to the left, or to the right. This depends on which team is facing where at any given moment, and that in turn changes constantly during the game. And it’s important to do it right because core functionalities like touchdown or first-down decisions depend on it.
The problem couldn’t have been postponed, because during kickoff – the first phase of each game – the ball is received and already now progress is being flipped. So very early in the game, we should be ready for flipping progress. And bear in mind that the progress flips with every turnover.
I’m fairly happy about the solution to this problem. It has been delegated to Markers and Progress (and Pseudomarkers) classes, each of them existing in two formats – Countup (where progress means more yards) and Countdown (where progress means fewer yards). For example:
class MarkerCountdown def initialize(first_down_yards:) @first_down_yards = first_down_yards @target = first_down_yards.from_left - 10 end attr_reader :first_down_yards, :target def crossed?(yards) yards.from_left < target end def starting_progress ProgressCountdown.new end end
class ProgressCountdown def to_yards(progress_num) Yards.new(-progress_num) end def to_range(first, last, yards) YardsFromRange.new(-last, -first, -yards) end def flip ProgressCountup.new end def touchdown?(yards_in_pitch) yards_in_pitch <= 0 end def ==(o) self.class == o.class end end
Tests do pay off. I did a lot of redesign like extracting
MatchState out of
MatchGenerator class. That would be virtually impossible without rewriting from scratch both of them if I didn’t have tests.
My tests have gaps, but most importantly – I can trust them. Whenever I feel I might need more of the safety net, I do add tests, simple as that.
Some interesting pieces of my current testing env:
- Factory for default fixtures written from scratch
- Test for generating a match for the purpose of this blog (I think I prefer to comment out this whole file rather than using
xit– due to an annoying yellow warning in the console every time I run tests)
Stay tunes, it’s exciting time for all the (American) football fans!