Created
March 18, 2013 15:51
-
-
Save Dan-Q/5188177 to your computer and use it in GitHub Desktop.
Calculates the probabilities of victory using unusual combinations of non-transitive dice.
Does not make any optimisations (e.g. ruling out always-lose/always-win combinations on a DiceSet) at all, so it quickly slows down if, for example, two players each throw six dice.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# Grimesweeper | |
# Calculates the victory chances if different combinations of Grime Dice (non-transitive dice) against one another | |
# Inspired by https://protonsforbreakfast.wordpress.com/2013/01/08/amazing-dice-rediscovering-surprise/ | |
# Discussed on http://www.reddit.com/r/tabletopgamedesign/comments/1aibz9/grime_dice_might_make_a_good_basis_for/ | |
# More info: http://grime.s3-website-eu-west-1.amazonaws.com/ | |
class DiceSet < Array | |
# Returns an array of all of the possible outcome totals of throwing this DiceSet | |
def outcomes | |
# Calculate recursively by getting the outcomes for all dice except the first, in combination | |
# with all of the possible states of the first. | |
return self[0] if self.length == 1 # if only one die, outcomes are its face values (exit condition) | |
DiceSet::new(self[1..-1]).outcomes.collect{|o| self[0].collect{|i| i + o }}.flatten | |
end | |
end | |
class Die < Array | |
end | |
DICE = { | |
# A normal 6-sided die | |
:normal => Die::new([ 1, 2, 3, 4, 5, 6]), | |
# Three common non-transitive dice: red beats green beats blue beats red; the cycle is reversed if they're doubled up | |
:red => Die::new([ 3, 3, 3, 3, 3, 6]), | |
:green => Die::new([ 2, 2, 2, 5, 5, 5]), | |
:blue => Die::new([ 1, 4, 4, 4, 4, 4]), | |
# Four dice, in the 'Efron' model: blue beats magenta beats olive beats red beats blue; also magenta beats red (across the circle) | |
:efron_blue => Die::new([ 3, 3, 3, 3, 3, 3]), | |
:efron_magenta => Die::new([ 2, 2, 2, 2, 6, 6]), | |
:efron_olive => Die::new([ 1, 1, 1, 5, 5, 5]), | |
:efron_red => Die::new([ 0, 0, 4, 4, 4, 4]), | |
# Seven dice, numbered up to 21, in the 'Deventer' model: each beats three others, so that you can try to beat two opponents simultaneously. | |
# Deventer dice have six sides, but they're in pairs so we can simplify to "three sides" in this model: | |
:deventer_red => Die::new([ 7, 10, 16]), # beats orange, yellow, blue | |
:deventer_orange => Die::new([ 5, 13, 15]), # beats yellow, green, indigo | |
:deventer_yellow => Die::new([ 3, 9, 21]), # beats green, blue, violet | |
:deventer_green => Die::new([ 1, 12, 20]), # beats blue, indigo, red | |
:deventer_blue => Die::new([ 6, 8, 19]), # beats indigo, violet, orange | |
:deventer_indigo => Die::new([ 4, 11, 18]), # beats violet, red, yellow | |
:deventer_violet => Die::new([ 2, 14, 17]), # beats red, orange, green | |
# Grime fives | |
:five_blue => Die::new([ 2, 2, 2, 7, 7, 7]), # beats olive and magenta | |
:five_olive => Die::new([ 0, 5, 5, 5, 5, 5]), # beats yellow and red | |
:five_yellow => Die::new([ 3, 3, 3, 3, 8, 8]), # beats magenta and blue | |
:five_magenta => Die::new([ 1, 1, 6, 6, 6, 6]), # beats red and olive | |
:five_red => Die::new([ 4, 4, 4, 4, 4, 9]), # beats blue and yellow | |
} | |
# Given two dice, or two sets of dice, returns the chance that the former beating the latter | |
# Return value is in the form of a { :wins => ..., :loses => ..., :draws => ...} hash | |
def compete(alpha, beta) | |
# Convert inputs to a standard format, whether gives arrays, symbols, dice or sets | |
alpha = [alpha] if alpha.is_a?(Die) || !alpha.is_a?(Array) | |
beta = [beta] if beta.is_a?(Die) || !beta.is_a?(Array) | |
alpha = DiceSet::new(alpha.collect{|a| Die::new(a)}) | |
beta = DiceSet::new(beta.collect{|b| Die::new(b)}) | |
# Set up storage for wins, loses, draws | |
result = {:wins => 0, :loses => 0, :draws => 0, :trials => 0} | |
# compare each result to each other result | |
alpha.outcomes.sort.each do |a| | |
beta.outcomes.sort.each do |b| | |
result[:trials] += 1 | |
#printf "%3i. %2i %2i", result[:trials], a, b | |
result[:wins] += 1 if a > b | |
#printf ' %2i', result[:wins] if a > b | |
result[:loses] += 1 if a < b | |
#printf ' %2i', result[:loses] if a < b | |
result[:draws] += 1 if a == b | |
#print "\n" | |
end | |
end | |
result | |
end | |
# Returns human-readable results of a comparison | |
def compare(alpha_desc, alpha, beta_desc, beta) | |
result = compete(alpha, beta) | |
if result[:wins] == result[:loses] | |
puts "#{alpha_desc} ties with #{beta_desc} (#{result[:draws]} draws, #{result[:wins] + result[:loses]} wins/loses)" | |
elsif result[:wins] > result[:loses] | |
victory_percentage = ((result[:wins].to_f / result[:trials]) * 100).round(2) | |
draw_percentage = ((result[:draws].to_f / result[:trials]) * 100).round(2) | |
puts "#{alpha_desc} beats #{beta_desc} #{victory_percentage}% of the time (draws #{draw_percentage}%)" | |
else | |
victory_percentage = ((result[:loses].to_f / result[:trials]) * 100).round(2) | |
draw_percentage = ((result[:draws].to_f / result[:trials]) * 100).round(2) | |
puts "#{beta_desc} beats #{alpha_desc} #{victory_percentage}% of the time (draws #{draw_percentage}%)" | |
end | |
end | |
# Classic comparisons | |
compare 'Red', DICE[:red], 'Green', DICE[:green] | |
compare 'Green', DICE[:green], 'Blue', DICE[:blue] | |
compare 'Blue', DICE[:blue], 'Red', DICE[:red] | |
# Colours versus normal | |
#puts '-'*78 | |
#compare 'Red', DICE[:red], 'Normal', DICE[:normal] | |
#compare 'Green', DICE[:green], 'Normal', DICE[:normal] | |
#compare 'Blue', DICE[:blue], 'Normal', DICE[:normal] | |
# Pairs versus | |
puts '-'*78 | |
redset, greenset, blueset = [DICE[:red], DICE[:red]], [DICE[:green], DICE[:green]], [DICE[:blue], DICE[:blue]] | |
compare '2Red', redset, '2Green', greenset | |
compare '2Green', greenset, '2Blue', blueset | |
compare '2Blue', blueset, '2Red', redset | |
# Trios versus | |
puts '-'*78 | |
redset << DICE[:red] | |
greenset << DICE[:green] | |
blueset << DICE[:blue] | |
compare '3Red', redset, '3Green', greenset | |
compare '3Green', greenset, '3Blue', blueset | |
compare '3Blue', blueset, '3Red', redset | |
# Quads versus | |
puts '-'*78 | |
redset << DICE[:red] | |
greenset << DICE[:green] | |
blueset << DICE[:blue] | |
compare '4Red', redset, '4Green', greenset | |
compare '4Green', greenset, '4Blue', blueset | |
compare '4Blue', blueset, '4Red', redset | |
# Quints versus - warning: takes a long time | |
#puts '-'*78 | |
#redset << DICE[:red] | |
#greenset << DICE[:green] | |
#blueset << DICE[:blue] | |
#compare '5Red', redset, '5Green', greenset | |
#compare '5Green', greenset, '5Blue', blueset | |
#compare '5Blue', blueset, '5Red', redset | |
# Sexts versus - warning: takes a LUDICROUSLY long time | |
#puts '-'*78 | |
#redset << DICE[:red] | |
#greenset << DICE[:green] | |
#blueset << DICE[:blue] | |
#compare '6Red', redset, '6Green', greenset | |
#compare '6Green', greenset, '6Blue', blueset | |
#compare '6Blue', blueset, '6Red', redset | |
# Septs versus - warning: eats processors for breakfast | |
#puts '-'*78 | |
#redset << DICE[:red] | |
#greenset << DICE[:green] | |
#blueset << DICE[:blue] | |
#compare '6Red', redset, '6Green', greenset | |
#compare '6Green', greenset, '6Blue', blueset | |
#compare '6Blue', blueset, '6Red', redset | |
# A comparison of unusual pairings | |
#compare 'Red+Green', [DICE[:red], DICE[:green]], '2Green', [DICE[:green], DICE[:green]] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment