[RPG] How to model this “Party Draft Pool” ability score generation method in AnyDice

ability-scoresanydicednd-5estatistics

I'm looking to model an alternate to the "4d6, drop the lowest" mechanic for determining ability scores.

The proposed system works as follows:

  1. 4 players roll 4d6, drop the lowest x 6 [24 total].
  2. Combine these results into a stat pool.
  3. Players then roll initiative (1d20).
  4. Players select stats in "snake order" [1, 2, 3, 4, 4, 3, 2, 1, 1, 2…]
  5. Players assign stats to ability scores as desired.

(I am thinking this is a good way to have fun rolling dice without the inter-party balance issues of straight 4d6, drop 1.)

Obviously for modeling, this can be simplified:

  1. Roll 24 d [highest 3 of 4d6]
  2. Rank the dice
  3. Loop Player 1..4 and assign values per "snake order", assuming highest available is always selected
  4. Output of 6 abilities per player (or of only player 1 vs player 4)

How do I model the probability distribution of the above in Anydice? I'm getting hung up on the syntax.

Best Answer

Overall this seems to benefit the first player, but it depends how you measure it.

I thought I would model this programatically as it will be a bit more flexible than using AnyDice. I've written a script which carries out the process a large number of times and averages the values for each player.

The main difficulty here is how you actually interpret the data: what makes one array of ability scores better than another one? There are multiple ways to judge this. I'll include some values for different methods.

Total of all ability scores
Player 1 has mean ability score value 72.95522.
Player 2 has mean ability score value 73.43975.
Player 3 has mean ability score value 73.64131.
Player 4 has mean ability score value 73.72761.
Verdict: Being later in the order is better

Total of all ability scores except the lowest one
Player 1 has mean ability score value 66.49585.
Player 2 has mean ability score value 65.71347.
Player 3 has mean ability score value 65.10333.
Player 4 has mean ability score value 64.57315.
Verdict: Being earlier in the order is better

Total points buy cost of all ability scores
(I've used invented points buy scores for numbers outside the normal allowed range: 18 = 19, 17 = 15, 16 = 12, 6-7 = -1, 4-5 = -2, 3 = -1)
Player 1 has mean ability score value 33.43838.
Player 2 has mean ability score value 31.80692.
Player 3 has mean ability score value 31.00389.
Player 4 has mean ability score value 30.64647.
Verdict: Being earlier in the order is better

Total points buy cost of all ability scores except the lowest one
Player 1 has mean ability score value 34.33727.
Player 2 has mean ability score value 31.88918.
Player 3 has mean ability score value 30.39722.
Player 4 has mean ability score value 29.45149.
Verdict: Being earlier in the order is much better

I've included my horrible amateur code here so you can try it out.

#!/usr/bin/perl

use strict;
use warnings;
use List::Util qw(sum);
use Data::Dumper;
use POSIX;
use 5.010;

my $die_size = 6;
my $number_of_dice = 4;
my $number_of_players = 4;
my $number_of_runs = 10000;

sub get_single_ability_score {
    my @rolls;
    for (1..$number_of_dice) {
        my $roll = 1 + int rand($die_size);
        push @rolls, $roll;
    }
    @rolls = sort {$a <=> $b} @rolls;
    for (1..$number_of_dice - 3) {
        shift @rolls;
    }
    my $ability_score = sum(@rolls);
    return $ability_score;
}

sub get_total_values {
    my @group_ability_scores;
    for (1..$number_of_players * 6) {
        push @group_ability_scores, get_single_ability_score();
    }
    @group_ability_scores = sort { $b <=> $a } @group_ability_scores;
    my @player_order = (1..$number_of_players);
    my @reverse_player_order = sort { $b <=> $a } @player_order;
    @player_order = (@player_order, @reverse_player_order, @player_order, @reverse_player_order, @player_order, @reverse_player_order);
    my @player_ability_scores;
    foreach my $player (@player_order) {
        my @ability_scores;
        my $chosen_ability_score = shift @group_ability_scores;
        push @ability_scores, $chosen_ability_score;
        push @{ $player_ability_scores[$player-1] }, @ability_scores;
    }
    my @total_values;
    foreach my $player (1..$number_of_players) {
        my @ability_scores = sort { $a <=> $b } @{ $player_ability_scores[$player-1] };
        my $total_value = 0;
        shift @ability_scores; # One dump stat is fine so discard the lowest ability score
        foreach my $ability_score (@ability_scores) {
            #$total_value += $ability_score;                    # Uses the score as the value
            #$total_value += floor(($ability_score - 10) / 2);  # Uses the modifier as the value
            given ($ability_score) {
                when ($_ == 18) {$total_value += 19}
                when ($_ == 17) {$total_value += 15}
                when ($_ == 16) {$total_value += 12}
                when ($_ == 15) {$total_value +=  9}
                when ($_ == 14) {$total_value +=  7}
                when ($_ == 13) {$total_value +=  5}
                when ($_ == 12) {$total_value +=  4}
                when ($_ == 11) {$total_value +=  3}
                when ($_ == 10) {$total_value +=  2}
                when ($_ ==  9) {$total_value +=  1}
                when ($_ ==  8) {$total_value +=  0}
                when ($_ ==  7) {$total_value += -1}
                when ($_ ==  6) {$total_value += -2}
                when ($_ ==  5) {$total_value += -3}
                when ($_ ==  4) {$total_value += -4}
                when ($_ ==  3) {$total_value += -5}
            }
        }
        push @total_values, $total_value;
    }
    return @total_values;
}

my @all_values;
for (1..$number_of_runs) {
    my @total_values = get_total_values();
    foreach my $player (1..$number_of_players) {
        push @{ $all_values[$player-1] }, $total_values[$player-1];
    }
}

for my $player (1..$number_of_players) {
    my $total_value;
    foreach my $value (@{ $all_values[$player-1] }) {
        $total_value += $value;
    }
    my $mean_value = $total_value / $number_of_runs;
    print "Player $player has mean ability score value $mean_value.\n";
}

Try it online!