Strikes Tournament Simulation Results

This real-world data is very useful for comparison. I immediately notice some discrepancies between real tournaments and my simulation.

For example, looking at the Group Knockout, 10 progressive strikes category, with a number of players between 11 and 20, the data shows two tournaments ending in 9 rounds and eight tournaments ending in 10 rounds. My simulation says that 11-player 10-progressive-strike tournaments end anywhere between 11 and 16 rounds, and that 9 or 10 rounds never occurs (0 results in 5000 simulations). So either the TDs applied some ‘sudden death’ rules, or players dropped out of the tournament, or the pairing type has a bigger effect than I think it does, or my simulation is bugged.

What an odd rule. Does it apply to strikes tournaments, or only other types of tournaments?

With that few tournaments who knows what happened. Sudden death seems likely, but could also be abandoned tournaments. I probably wouldn’t put too much faith in numbers until there are 50+ tournaments to compare (so you know the tournaments are coming from multiple organizers in multiple locations)

I think it technically applies to matchplay events too. Remember. If a tournament is not taking long enough, IFPA takes away some of the value.

Does your simulation take into account the fact that almost every progressive strikes event I know about changes to 2 strikes for the loser once it’s down to two people?

Awesome! You’ll want to repeat this across a range of simulation parameters and observe the outcome. You can just record the mean of the 5000 runs. For example, what is the difference in mean Kendall Tau between 3 strikes vs 10 strikes? or maybe 10 strikes and 11 strikes. The goal here determine how well different tournament formats correctly sort players according to their true skill. (If you want to really ruffle feathers, simulate Flip Frenzy and compare the outcome to other formats.)

If Tau is too much work you could also record the proportion of the simulation runs in which the highest skilled player finished first in the tournament. Sorry, not trying to make more work for you – feel free to ignore these suggestions!

This makes sense – when all the skill levels are equivalent there shouldn’t be any correlation, as the result of the tournament is pretty much random. Also there’s no variability in skill, so I’m surprised it just didn’t spit out an error. I thought you were sampling from a distribution of skills though, right? We never know what the Tau is for a real tournament, as we have no way of knowing a players true skill level. I guess you could use ratings or something, but the beauty of simulations is that you have a known ground truth value.

1 Like

No, it does not. That may be the source of the discrepancy in number of rounds between the real-world data and my simulation.

Using my previously described player model, a progressive strikes tournament with 47 players and N strikes gives this average Kendall Tau coefficient (using 5000 simulations again):

  • 3 strikes: 0.297
  • 4 strikes: 0.341
  • 5 strikes: 0.376
  • 6 strikes: 0.403
  • 7 strikes: 0.423
  • 8 strikes: 0.443
  • 9 strikes: 0.461
  • 10 strikes: 0.475
  • 11 strikes: 0.491

This seems like a lower Kendall Tau value than we would see in the real world, where we consistently see the same few players in the finals. So I decided to modify my game score model - the second parameter of the log-normal distribution now changes from 0.5 to 0.25, meaning the best players are now highly favored over even average players. Re-running the same simulation with this tighter game score distribution produces these average Kendall Tau values:

  • 3 strikes: 0.463
  • 4 strikes: 0.518
  • 5 strikes: 0.551
  • 6 strikes: 0.574
  • 7 strikes: 0.596
  • 8 strikes: 0.614
  • 9 strikes: 0.628
  • 10 strikes: 0.638
  • 11 strikes: 0.648

Here is the updated game score distribution for players of various skills.

1 Like

Just chiming in to note that I dislike this custom rule, as it can negate the competitive advantage that one player earned over the other. I use a slightly different method to speed things up while maintaining the earned position to that point.

For example, 9 strike progressive. Down to 2 players who have 5 strikes and 6 strikes. Clearly nobody wants to deal with a possible 6 games of head to head play here, we can all(?) agree on that. But instead of 2 strikes per loss which would put them both 2 losses from elimination, I manually add the same number of strikes to both players so that the one with more strikes is one loss away from elimination. In this example they would move to 7 and 8 strikes respectively, which maintains the advantage but still speeds up the end of the tournament. Why isn’t this the more common approach?

8 Likes

I think the trailing player will always prefer the 2 strike solution, especially in situations like your hypothetical where each player has 3 or more strikes remaining. This way they can survive at least one loss, using the cushion they “earned,” as opposed to sudden death. TDs are only human, and probably don’t want to deal with the inevitable whining.

Good players generally want as much play as possible. As a speed zealot I prefer the sudden death option.

1 Like

Yeah, if time isn’t an immediate issue I’m also cool with putting the player with more strikes 2 losses from elimination. As long as the solution is in the rules ahead of time and minimizes a potentially long and tedious 1v1 slog without throwing off any earned advantage between the two players I’m good with it!

1 Like

We tried Progressive a few times, but I’ve moved to Fair Strike when I want to run a Strikes event. Players stay active longer, but then it gets (purposely) brutal at the end where you basically need to win to move on. Progressive unfortunately suffers from the long-tail problem.

1 Like

When did this change? News to me

Keefer’s simulator changed how we handled it. Strikes tournaments were overvalued the old way.

1 Like

What about the 17 player x number rounds of match play, I assume that is only 1.5x multiplier? Even though match play tried to do groups of four?

image

Match Play Qualifying

  • 1 game added to TGP per game played for Head-to-Head matches
  • 1.5 games added to TGP per game played for 3-player style matches (if a majority of groups are 3-player groups)
  • 2 games added to TGP per game played for 4-player style matches (if a majority of groups are 4-player groups)
  • If there are an equal number of 2-player and 3-player matches there is no multiplier
  • If there are an equal number of 3-player and 4-player matches there is a 1.5X multiplier
  • Please note this format does not require a finals component as the qualifying portion already consists of DIRECT play. This is often found in Swiss style formats where there is no separate distinct finals component.
  • Please note the qualifying portion of the tournament can be added to the TGP calculation only if that qualifying portion reduces the field of participants by 50% or more

In case anyone’s interested in seeing the Mathematica code I used, here it is (including the narrower game score distribution to better separate skill levels, as well as sudden death for progressive strikes).

(* create a new player with a random skill level, 0 strikes/games *)
(* {playerID, skill, {round opponents}, strikes} *)
MakePlayer[ID_] := {ID, RandomVariate[LogNormalDistribution[0, 0.25]], {}, 0}

(* create a list of n new players *)
MakePlayers[n_] := MakePlayer[#] & /@ Range[n]

(* game score for a single player *)
GameScore[skill_] := RandomVariate[LogNormalDistribution[skill, 0.25]]

(* game scores for all players *)
GameScores[players_] := GameScore[#[[2]]] & /@ players

(* rank players by game score *)
GameRanking[players_] := Module[{scores, sortedScores, ranks},
  scores = GameScores[players];
  sortedScores = Reverse@Sort@scores;
  ranks = Flatten[FirstPosition[sortedScores, #] & /@ scores];
  Return@ranks
]

(* run game for multiple players, update records & strikes *)
RunGame[players_, suddenDeath_] := 
 Module[{playerIDs, skills, roundResults, strikes, ranks, updatedStrikes, newRoundResults, updatedRoundResults, updatedPlayers},
  {playerIDs, skills, roundResults, strikes} = Transpose@players;
  ranks = GameRanking[players];
  newRoundResults = DeleteCases[playerIDs, #] & /@ playerIDs;(* list of opponents for each player *)
  updatedRoundResults = MapThread[Append[#1, #2] &, {roundResults, newRoundResults}];
  updatedStrikes = strikes + Switch[
     1,
     1, If[suddenDeath, 2 (ranks - 1), ranks - 1],(* 1 strike for each loss, doubled if sudden death *)
     2, Switch[#, 1, 0, Length@players, 2, _, 1] & /@ ranks,(* fair strikes: 1st gets 0, last gets 2, else 1 *)
     3, Sign[ranks - 1](* everyone except 1st gets a strike *)
     ];
  updatedPlayers = Transpose@{playerIDs, skills, updatedRoundResults, updatedStrikes};
  Return@updatedPlayers
]

(* partition players into groups of up to n, avoiding singletons *)
PartitionPlayers[players_, n_] := Module[{nPlayers, remainingPlayers, groups, finalPartition},
  nPlayers = Length@players;
  remainingPlayers = players;
  groups = {};
  If[nPlayers <= n,(* if players fit on one game *)
   AppendTo[groups, remainingPlayers];
   remainingPlayers = {};
 ];
  While[Length@remainingPlayers > LCM[n, n - 1],(* while excess players exist *)
   AppendTo[groups, Take[remainingPlayers, n]];(* take n of them *)
   remainingPlayers = Drop[remainingPlayers, n]
 ];
  finalPartition = First@SortBy[Select[IntegerPartitions[Length@remainingPlayers], Max@# <= 4 &], Length@# - Min@# &];(* partition final players to maximize minimum group size *)
  Do[
   AppendTo[groups, Take[remainingPlayers, finalPartition[[i]]]];(* take up to n of them *)
   remainingPlayers = Drop[remainingPlayers, finalPartition[[i]]], {i, 1, Length@finalPartition}
 ];
  Return@groups
]

(* run tournament round with nPerGame players per game *)
RunRound[players_, nPerGame_, suddenDeath_] := 
 Module[{groups, updatedGroups},
  groups = PartitionPlayers[players, nPerGame];
  updatedGroups = RunGame[#, suddenDeath] & /@ groups;
  Return@Flatten[updatedGroups, 1]
]

(* shuffle players and sort by strikes *)
SortByStrikes[players_] := SortBy[RandomSample[players], Last]

(* run tournament with nStrikes to be ejected *)
RunTournament[players_, nPerGame_, nStrikes_] := 
 Module[{nRounds, remainingPlayers, removedPlayers, suddenDeath},
  nRounds = 0;
  remainingPlayers = players;
  removedPlayers = {};
  suddenDeath = False;(* flag set when 2 players remain *)
  While[Length@remainingPlayers > 1,
   nRounds++;
   If[Length@remainingPlayers < 3, suddenDeath = True];(* 2 players left \[Rule] sudden death *)
   remainingPlayers = RunRound[remainingPlayers, nPerGame, suddenDeath];
   removedPlayers = Join[removedPlayers, Reverse@Select[remainingPlayers, Last@# >= nStrikes &]];
   remainingPlayers = Select[remainingPlayers, Last@# < nStrikes &];
   remainingPlayers = SortByStrikes[remainingPlayers]
 ];
  Return@{nRounds, remainingPlayers, removedPlayers}
]

(* run Monte Carlo simulation of many tournaments to find duration (in rounds) *)
RunMonteCarloRounds[nPlayers_, nPerGame_, nStrikes_, nSims_] := 
 Module[{results},
  results = ParallelTable[First@RunTournament[MakePlayers[nPlayers], nPerGame, nStrikes], {i, 1, nSims}];
  Return[results]
]

(* run tournament and calculate Kendall Tau coefficient of final rankings *)
TournamentKendall[nPlayers_, nPerGame_, nStrikes_] := 
 Module[{nRounds, remainingPlayers, removedPlayers, IDs, skills, roundResults, strikes},
  {nRounds, remainingPlayers, removedPlayers} = RunTournament[MakePlayers[nPlayers], nPerGame, nStrikes];
  {IDs, skills, roundResults, strikes} = Transpose@Join[removedPlayers, remainingPlayers];
  Return@KendallTau[Range[nPlayers], skills]
]

Once these functions are loaded, simulations can be run like this:

(* generate statistics on a specific tournament config *)
nPlayers = 33;
nPlayersPerGame = 4;
nStrikes = 10;
nSims = 5000;
data = RunMonteCarloRounds[nPlayers, nPlayersPerGame, nStrikes, nSims];
mean = N@Mean@data
stdev = N@Sqrt@Variance@data;
cov = stdev/mean;
SortBy[Tally[data], First] // TableForm // Print
hist = Histogram[data, Automatic, "ProbabilityDensity"];
dist = EstimatedDistribution[data, JohnsonDistribution["SB", \[Gamma], \[Delta], \[Mu], \[Sigma]]]
Show[hist, Plot[PDF[dist, x], {x, 0, 30}, PlotRange -> All]]

Since I added ‘sudden death’ rules to the Progressive Strikes code (doubling the assigned strikes when only 2 players remain), I recalculated the average round lengths:

I also re-did the variance calculation. Here is the distribution of round numbers for the 47-player 10-progressive-strikes tournament:

Average duration = ~12.1888 rounds

  • 10 rounds: 25 sims
  • 11 rounds: 1451 sims
  • 12 rounds: 1836 sims
  • 13 rounds: 1111 sims
  • 14 rounds: 426 sims
  • 15 rounds: 122 sims
  • 16 rounds: 29 sims

Relative to Progressive Strikes without sudden death, the average duration decreased by only ~0.4 rounds, but the far end of the right tail now terminates at 16 rounds, instead of 21. Quite a large drop!

3 Likes

The results aren’t actually that different—I cross checked some of your values with the simulator the TGP guide is based on, and you’re getting pretty much the same numbers for rounds played. The TGP guide doesn’t list rounds played, though, only games towards TGP, which includes the time multiplier, but you can get the sim’s average rounds played from https://strikestgp.slapsave.com/.

While, as others have mentioned, the official rules say that the whole round gets counted as 1 / 1.5 / 2 games towards TGP, depending on whether the round’s games are a majority 2P / 3P / 4P games, the TGP guide’s simulator doesn’t actually look at that majority, but at each individual game.

I.e. it counts each individual 4P game as 2 games towards TGP, each 3P game as 1.5 games towards TGP, and so on, then calculates the average games towards TGP across all the games of all the rounds of all the simulator runs. That then is what the average number of total rounds played across all sim runs gets multiplied with in order to determine total games towards TGP for the format. Probably not that big of a difference to the majority thing, but might be interesting to look into.

This gives me a lot of confidence that my simulation was coded correctly. And it’s very interesting news that the TPG value of each game in a round is averaged together to determine the round’s TGP contribution. Is that how the official TPG table is generated?

I will try to add a TPG calculation to my simulation.

Yes, but you gotta be careful, the way you worded it is not quite correct: The simulator doesn’t ever determine any particular round’s TGP contribution, it averages across all games of all rounds of all sim runs. This probably makes a real difference for the results, because the late rounds will have a high relative but low absolute number of less-than-4P-games.

Simple example, 32 players play Oprah strikes for 1 strike total, i.e. 1st round has 32 players, 2nd has 8, 3rd has 2. By determining each round’s games towards TGP separately, you’ll get 5 exactly, but the simulator will get you 5.73, which the TGP guide will round to 6.