Instantly share code, notes, and snippets.

Dan-Q/grimesweeper.rb

Created March 18, 2013 15:51
Show Gist options
• 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]]