Skip to content

Instantly share code, notes, and snippets.

@Dan-Q
Created March 18, 2013 15:51
Show Gist options
  • Save Dan-Q/5188177 to your computer and use it in GitHub Desktop.
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.
#!/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