I get a bit of a thrill when there’s a need to design a mini-language. I have one facing me now for a little project I’m responsible for, which is maintaining a scoring site for a bocce comp I’m involve in with friends.

How scoring works now is that the winner of a particular bocce match gets one point for the season. The winner for the season is the person with the most points. However, we recently discuss the idea of adding “final matches,” which will give the match winner 7 points, the runner up 2 points, and the person who came in third 1 point. At the same time I want to add the notion of “friendly matches” which won’t count to the season score.

It might have been that a simple solution was to encode these rules directly in the app, and have a flag indicating whether a match was normal, final or friendly. But this was suboptimal as there is another variant of the game we play which do not have the notion of finals, and if we did, we may eventually have different rule for it. So I opted for a design in which a new “match type” is added as a new database entity, which will have the scoring rules encoded as a PostgreSQL JSON column type. Using this as a mechanism of encoding free(ish) structured data when there’s no need to query it has worked for me in the past. There was no need to add the notion of seasons points as it was already present as an easy way to keep track of wins for a season.

For the scoring rules JSON structure, I’m considering the use of an array of conditions. When a player meets the conditions of a particular array element, they will be awarded the points associated with that condition. Each player will only be permitted to match one condition, and if they don’t match any, they won’t get any points. The fields of the condition that a player can be matched to can be made up of the following attributes:

  • rank: (int) the position the player has in the match just played in accordance with the scoring, with 1 being the player with the highest score, 2 being the player with the second highest score, and so on.
  • winner: (bool) whether the player is considered the winner of the match. The person with the highest score usually is, but this is treated as an independent field and so it should be possible to define rules accordingly.
  • draw: (bool) whether the player shares their rank with another player. When a draw occurs, both winning players will have a rank of 1, with the player of the second highest score having a rank of 2.

Using this structure, a possible scoring rules definition for a normal match may look like the following:

{ "season_score": [
  { "condition": { "winner": true }, "points": 1 }
]}

whereas a rules definition for the final match may look like the following:

{ "season_score": [
  { "condition": { "rank": 1 }, "points": 7 },
  { "condition": { "rank": 2 }, "points": 2 },
  { "condition": { "rank": 3 }, "points": 1 }
}]

Finally, for friendlies, the rules can simply look like the following:

{ "season_score": [] }

I think this provides a great deal of flexibility and extensibility without making the rules definition too complicated.