gambit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. data/AUTHORS +2 -0
  2. data/CHANGELOG +7 -0
  3. data/COPYING +340 -0
  4. data/INSTALL +23 -0
  5. data/LICENSE +7 -0
  6. data/README +25 -0
  7. data/Rakefile +87 -0
  8. data/TODO +6 -0
  9. data/examples/bird_wars/game.rb +434 -0
  10. data/examples/bird_wars/html/chest.rhtml +37 -0
  11. data/examples/bird_wars/html/combat.rhtml +38 -0
  12. data/examples/bird_wars/html/fight.rhtml +71 -0
  13. data/examples/bird_wars/html/fridge.rhtml +31 -0
  14. data/examples/bird_wars/html/help.rhtml +30 -0
  15. data/examples/bird_wars/html/house.rhtml +36 -0
  16. data/examples/bird_wars/html/images/albatross.jpg +0 -0
  17. data/examples/bird_wars/html/images/alka_seltzer_small.png +0 -0
  18. data/examples/bird_wars/html/images/bazooka_small.png +0 -0
  19. data/examples/bird_wars/html/images/bb_gun_small.png +0 -0
  20. data/examples/bird_wars/html/images/cat_small.png +0 -0
  21. data/examples/bird_wars/html/images/combat.png +0 -0
  22. data/examples/bird_wars/html/images/crossbow_small.png +0 -0
  23. data/examples/bird_wars/html/images/cuckoo.jpg +0 -0
  24. data/examples/bird_wars/html/images/fridge.png +0 -0
  25. data/examples/bird_wars/html/images/gull.jpg +0 -0
  26. data/examples/bird_wars/html/images/house.jpg +0 -0
  27. data/examples/bird_wars/html/images/items.png +0 -0
  28. data/examples/bird_wars/html/images/not_found.png +0 -0
  29. data/examples/bird_wars/html/images/note.jpg +0 -0
  30. data/examples/bird_wars/html/images/oil_small.png +0 -0
  31. data/examples/bird_wars/html/images/one_stone_small.png +0 -0
  32. data/examples/bird_wars/html/images/owl.jpg +0 -0
  33. data/examples/bird_wars/html/images/poisoned_seeds_small.png +0 -0
  34. data/examples/bird_wars/html/images/rice_small.png +0 -0
  35. data/examples/bird_wars/html/images/shotgun_small.png +0 -0
  36. data/examples/bird_wars/html/images/slingshot_small.png +0 -0
  37. data/examples/bird_wars/html/images/soda_rings_small.png +0 -0
  38. data/examples/bird_wars/html/images/spear_small.png +0 -0
  39. data/examples/bird_wars/html/images/stork.jpg +0 -0
  40. data/examples/bird_wars/html/images/weapons.png +0 -0
  41. data/examples/bird_wars/html/images/woodpecker.jpg +0 -0
  42. data/examples/bird_wars/html/images/you.jpg +0 -0
  43. data/examples/bird_wars/html/index.rhtml +13 -0
  44. data/examples/bird_wars/html/weapons.rhtml +35 -0
  45. data/examples/cli/blackjack.rb +86 -0
  46. data/examples/cli/go.rb +93 -0
  47. data/examples/cli/space_trader2/planet_data +12280 -0
  48. data/examples/cli/space_trader2/space_trader2.rb +248 -0
  49. data/examples/cli/space_trader2/world.rb +127 -0
  50. data/examples/cli/spacetrader.rb +262 -0
  51. data/examples/cli/yahtzee.rb +161 -0
  52. data/examples/galactic_courier/Rakefile +9 -0
  53. data/examples/galactic_courier/bin/galactic_courier.rb +44 -0
  54. data/examples/galactic_courier/html/console.rhtml +15 -0
  55. data/examples/galactic_courier/html/images/barren.jpg +0 -0
  56. data/examples/galactic_courier/html/images/industrial.jpg +0 -0
  57. data/examples/galactic_courier/html/images/jungle.jpg +0 -0
  58. data/examples/galactic_courier/html/images/oceanic.jpg +0 -0
  59. data/examples/galactic_courier/html/images/tundra.jpg +0 -0
  60. data/examples/galactic_courier/html/images/volcanic.jpg +0 -0
  61. data/examples/galactic_courier/lib/galaxy.rb +64 -0
  62. data/examples/galactic_courier/lib/planet.rb +30 -0
  63. data/examples/galactic_courier/lib/sector.rb +84 -0
  64. data/examples/galactic_courier/lib/station.rb +18 -0
  65. data/examples/galactic_courier/test/tc_galaxy.rb +24 -0
  66. data/examples/galactic_courier/test/tc_planet.rb +22 -0
  67. data/examples/galactic_courier/test/tc_sector.rb +55 -0
  68. data/examples/galactic_courier/test/ts_all.rb +12 -0
  69. data/examples/gambit_mail/html/compose.rhtml +39 -0
  70. data/examples/gambit_mail/html/index.rhtml +3 -0
  71. data/examples/gambit_mail/html/mailbox.rhtml +47 -0
  72. data/examples/gambit_mail/html/message.rhtml +34 -0
  73. data/examples/gambit_mail/mail.rb +75 -0
  74. data/lib/gambit.rb +10 -0
  75. data/lib/gambit/server.rb +269 -0
  76. data/lib/gambit/server/gambiterror.rb +28 -0
  77. data/lib/gambit/server/game.rb +185 -0
  78. data/lib/gambit/server/game/eventform.rb +174 -0
  79. data/lib/gambit/server/message.rb +85 -0
  80. data/lib/gambit/server/player.rb +40 -0
  81. data/lib/gambit/tools.rb +13 -0
  82. data/lib/gambit/tools/board.rb +275 -0
  83. data/lib/gambit/tools/cards.rb +11 -0
  84. data/lib/gambit/tools/cards/card.rb +158 -0
  85. data/lib/gambit/tools/cards/deck.rb +99 -0
  86. data/lib/gambit/tools/cards/hand.rb +86 -0
  87. data/lib/gambit/tools/cards/pile.rb +107 -0
  88. data/lib/gambit/tools/currency.rb +148 -0
  89. data/lib/gambit/tools/dice.rb +219 -0
  90. data/lib/gambit/tools/movehistory.rb +164 -0
  91. data/lib/gambit/tools/scorecard.rb +333 -0
  92. data/lib/gambit/viewable.rb +71 -0
  93. data/setup.rb +1360 -0
  94. data/test/tc_board.rb +167 -0
  95. data/test/tc_cards.rb +182 -0
  96. data/test/tc_currency.rb +99 -0
  97. data/test/tc_dice.rb +83 -0
  98. data/test/tc_error.rb +34 -0
  99. data/test/tc_event.rb +367 -0
  100. data/test/tc_game.rb +29 -0
  101. data/test/tc_message.rb +35 -0
  102. data/test/tc_movehistory.rb +38 -0
  103. data/test/tc_player.rb +60 -0
  104. data/test/tc_scorecard.rb +112 -0
  105. data/test/tc_views.rb +353 -0
  106. data/test/test_game.rb +19 -0
  107. data/test/test_view.rhtml +2 -0
  108. data/test/ts_all.rb +12 -0
  109. data/test/ts_server.rb +14 -0
  110. data/test/ts_tools.rb +14 -0
  111. metadata +183 -0
@@ -0,0 +1,99 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # deck.rb
4
+ #
5
+ # Created by James Edward Gray II on 2005-05-30.
6
+ # Copyright 2005 Gray Productions. All rights reserved.
7
+
8
+ require "gambit/viewable"
9
+
10
+ module Gambit
11
+ module Tools
12
+ module Cards
13
+ # A Deck manager for any class inheriting from Card.
14
+ class Deck
15
+ include Gambit::Viewable
16
+ register_view(:gambit_html, <<-END_HTML_VIEW.gsub(/\t{4}/, ""))
17
+ <ol class="deck">
18
+ % each do |card|
19
+ <li><%= card.view(:gambit_text) %></li>
20
+ % end
21
+ </ol>
22
+ END_HTML_VIEW
23
+ register_view(:gambit_text, <<-END_TEXT_VIEW.gsub(/\t+/, ""))
24
+ % each do |card|
25
+ <%= card.view(:gambit_text) + "\\n" %>
26
+ % end
27
+ END_TEXT_VIEW
28
+
29
+ #
30
+ # Create a new Deck filled with _card_type_ (StandardCard by
31
+ # default).
32
+ #
33
+ def initialize( card_type = StandardCard )
34
+ @deck = Array.new
35
+ card_type.each { |card| @deck << card }
36
+ end
37
+
38
+ # Returns the Deck size.
39
+ def count( )
40
+ @deck.size
41
+ end
42
+
43
+ # Cuts the deck at the provided _count_ or a random default.
44
+ def cut!( count = rand(@deck.size / 2) + @deck.size / 4 )
45
+ @deck = @deck.values_at(count..-1, 0...count)
46
+ end
47
+
48
+ #
49
+ # Deals cards to _hands_ in round robin fashion. If a Fixnum is
50
+ # included in the argument list, that many cards will be dealt
51
+ # to each hand. Otherwise, the entire deck is dealt.
52
+ #
53
+ # You may use Hand objects in _hands_ or anything that supports
54
+ # the << operator.
55
+ #
56
+ def deal( *hands )
57
+ if count = hands.find { |hand| hand.is_a? Fixnum }
58
+ hands.delete(count)
59
+ else
60
+ count = @deck.size
61
+ end
62
+ count.times do
63
+ hands.each do |hand|
64
+ hand << @deck.shift
65
+ end
66
+ end
67
+ end
68
+
69
+ # Removes and returns a card from the top of the deck.
70
+ def draw( )
71
+ @deck.shift
72
+ end
73
+
74
+ include Enumerable
75
+
76
+ # Iterate through the cards.
77
+ def each( &block )
78
+ @deck.each(&block)
79
+ end
80
+
81
+ # Randomize the deck.
82
+ def shuffle!( )
83
+ @deck = @deck.sort_by { rand }
84
+ end
85
+
86
+ # Sort the deck by card value.
87
+ def sort!( &block )
88
+ @deck.sort!(&block)
89
+ end
90
+
91
+ # Replace _card_ on the bottom of the deck.
92
+ def replace( card )
93
+ @deck << card
94
+ end
95
+ alias_method :<<, :replace
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,86 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # hand.rb
4
+ #
5
+ # Created by James Edward Gray II on 2005-05-30.
6
+ # Copyright 2005 Gray Productions. All rights reserved.
7
+
8
+ require "gambit/viewable"
9
+
10
+ module Gambit
11
+ module Tools
12
+ module Cards
13
+ # An object for managing a Hand of cards.
14
+ class Hand
15
+ include Gambit::Viewable
16
+ register_view(:gambit_html, <<-END_HTML_VIEW.gsub(/^\t{4}/, ""))
17
+ <ol class="hand">
18
+ % each do |card|
19
+ <li><%= card.view(:gambit_text) %></li>
20
+ % end
21
+ </ol>
22
+ END_HTML_VIEW
23
+ register_view(:gambit_text, <<-END_TEXT_VIEW.gsub(/^\t+/, ""))
24
+ % if @hand.size.zero?
25
+ an empty pile
26
+ % elsif @hand.size == 1
27
+ <%= @hand.first.view(:gambit_text) %>
28
+ % else
29
+ % first_cards = @hand[0..-2].map { |c| c.view(:gambit_text) }
30
+ % last_card = @hand[-1].view(:gambit_text)
31
+ <%= first_cards.join(', ') + " and " + last_card %>
32
+ % end
33
+ END_TEXT_VIEW
34
+
35
+ # Create an empty Hand of cards.
36
+ def initialize( )
37
+ @hand = Array.new
38
+ end
39
+
40
+ # Fetch a card by _index_.
41
+ def []( index )
42
+ @hand[index]
43
+ end
44
+
45
+ #
46
+ # Add a card to this Hand.
47
+ #
48
+ # Returns +self+ for method chaining.
49
+ #
50
+ def <<( card )
51
+ @hand << card
52
+
53
+ self
54
+ end
55
+
56
+ # Returns the Hand size.
57
+ def count( )
58
+ @hand.size
59
+ end
60
+
61
+ include Enumerable
62
+
63
+ # Iterate through the cards.
64
+ def each( &block )
65
+ @hand.each(&block)
66
+ end
67
+
68
+ # Removes and returns the card at the given _index_.
69
+ def discard( index )
70
+ @hand.delete_at(index)
71
+ end
72
+
73
+ #
74
+ # Sort the Hand by card value.
75
+ #
76
+ # Returns +self+ for method chaining.
77
+ #
78
+ def sort!( &block )
79
+ @hand.sort!(&block)
80
+
81
+ self
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,107 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # Pile.rb
4
+ #
5
+ # Created by James Edward Gray II on 2005-05-31.
6
+ # Copyright 2005 Gray Productions. All rights reserved.
7
+
8
+ require "gambit/viewable"
9
+
10
+ module Gambit
11
+ module Tools
12
+ module Cards
13
+ # A tool for managing Piles of cards.
14
+ class Pile
15
+ include Gambit::Viewable
16
+ register_view(:gambit_html, <<-END_HTML_VIEW.gsub(/^\t+/, ""))
17
+ <ol class="pile">
18
+ % @pile.each_with_index do |card, index|
19
+ % if @face_up[index]
20
+ <%= "\\t" %><li><%= card.view(:gambit_text) %></li>
21
+ % else
22
+ <%= "\\t" %><li>A Face-down Card</li>
23
+ % end
24
+ % end
25
+ </ol>
26
+ END_HTML_VIEW
27
+ register_view(:gambit_text, <<-END_TEXT_VIEW.gsub(/^\t+/, ""))
28
+ % cards = to_a
29
+ % if cards.size.zero?
30
+ an empty pile
31
+ % elsif cards.size == 1
32
+ <%= cards.first.view(:gambit_text) %>
33
+ % else
34
+ % first_cards = cards[0..-2].map { |c| c.view(:gambit_text) }
35
+ % last_card = cards[-1].view(:gambit_text)
36
+ <%= first_cards.join(', ') + " and " + last_card %>
37
+ % end
38
+ END_TEXT_VIEW
39
+
40
+ # Creates an empty Pile of cards.
41
+ def initialize( )
42
+ @pile = Array.new
43
+
44
+ @add_to_top = true
45
+ @remove_from_top = true
46
+
47
+ @face_up = lambda { |index| true }
48
+ end
49
+
50
+ #
51
+ # When +true+ (default), add() will place cards on the top of
52
+ # the pile. Otherwise, they go to the bottom.
53
+ #
54
+ attr_accessor :add_to_top
55
+ #
56
+ # When +true+ (default), remove() will pull cards from the top
57
+ # of the pile. Otherwise, they come from the bottom.
58
+ #
59
+ attr_accessor :remove_from_top
60
+ #
61
+ # This method contains a Proc object that is passed card indices
62
+ # when rendering the default views. The Proc is excepted to
63
+ # return +true+ or +false+ indicating if the card at that index
64
+ # is face up or face down.
65
+ #
66
+ attr_accessor :face_up
67
+
68
+ # Fetch a card by _index_.
69
+ def []( index )
70
+ @pile[index]
71
+ end
72
+
73
+ # Add _card_ to this Pile.
74
+ def add( card )
75
+ if @add_to_top
76
+ @pile.unshift(card)
77
+ else
78
+ @pile << card
79
+ end
80
+
81
+ self
82
+ end
83
+ alias_method :<<, :add
84
+
85
+ include Enumerable
86
+
87
+ # Iterate through the cards.
88
+ def each( )
89
+ @pile.each_index do |index|
90
+ next unless @face_up[index]
91
+
92
+ yield @pile[index]
93
+ end
94
+ end
95
+
96
+ # Remove and return a card from this Pile.
97
+ def remove( )
98
+ if @remove_from_top
99
+ @pile.shift
100
+ else
101
+ @pile.pop
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,148 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # currency.rb
4
+ #
5
+ # Created by Gregory Brown on 2005-05-30
6
+ # Copyright 2005 smtose.org. All rights reserved
7
+
8
+ require "gambit/viewable"
9
+
10
+ module Gambit
11
+ module Tools
12
+ #
13
+ # A general money tool for managing various denominations and the
14
+ # conversions between them.
15
+ #
16
+ class Currency
17
+ include Gambit::Viewable
18
+ register_view(:gambit_html, <<-END_HTML_VIEW.gsub(/^\t{3}/, ""))
19
+ <dl class="currency">
20
+ % resources = @holdings.keys
21
+ % resources = resources.sort_by { |res| res.to_s }
22
+ % resources.each do |resource|
23
+ <dt><%= resource.to_s.capitalize %></dt>
24
+ <dd><%= @holdings[resource] %></dd>
25
+ % if resource != resources.last
26
+
27
+ % end
28
+ % end
29
+ </dl>
30
+ END_HTML_VIEW
31
+ register_view(:gambit_text, <<-END_TEXT_VIEW.gsub(/^\t+/, ""))
32
+ % resources = @holdings.keys
33
+ % resources = resources.sort_by { |res| res.to_s }
34
+ % res_width = resources.max { |a, b| a.to_s.length <=>
35
+ % b.to_s.length }.to_s.length
36
+ % amt_width = @holdings.values.max { |a, b| a.to_s.length <=>
37
+ % b.to_s.length }.
38
+ % to_s.length
39
+ % resources.each do |resource|
40
+ <%= "%\#{res_width}s" % resource.to_s.capitalize %>: <%=
41
+ "%\#{amt_width}d" % @holdings[resource] %>
42
+ % end
43
+ END_TEXT_VIEW
44
+
45
+ @@values = Hash.new(0)
46
+
47
+ #
48
+ # Used to set a standard _ratio_ for a _resource_ Currency objects
49
+ # can use.
50
+ #
51
+ def self.[]=( resource, ratio )
52
+ @@values[resource] = ratio.to_f
53
+ end
54
+
55
+ #
56
+ # Fetch a standard ratio for a _resource_ Currency objects can use.
57
+ #
58
+ def self.[]( resource )
59
+ return @@values[resource]
60
+ end
61
+
62
+ # Calculates a relative_value _from_resource_ _to_resource_.
63
+ def self.relative_value( from_resource, to_resource )
64
+ if @@values.key?(from_resource) && @@values.key?(to_resource)
65
+ @@values[from_resource] / @@values[to_resource]
66
+ else
67
+ raise ArgumentError, "Can't find conversion ratio!"
68
+ end
69
+ end
70
+
71
+ #
72
+ # Creates a new Currency object. Optionally, you may provide a Hash
73
+ # of _initial_holdings_.
74
+ #
75
+ def initialize( initial_holdings = nil )
76
+ if initial_holdings.is_a? Hash
77
+ @holdings = initial_holdings
78
+ else
79
+ @holdings = Hash.new(0)
80
+ end
81
+ end
82
+
83
+ # The holdings Hash.
84
+ attr_reader :holdings
85
+
86
+ # Delegates to the holdings Hash.
87
+ def []( resource )
88
+ return @holdings[resource]
89
+ end
90
+
91
+ #
92
+ # Remove _amount_ of _resource_ from these holdings, optionally
93
+ # _as_resource_.
94
+ #
95
+ def withdraw( amount, resource, as_resource = nil )
96
+ if (@holdings[resource] - amount >= 0 && as_resource.nil?)
97
+ @holdings[resource] -= amount
98
+ return amount
99
+ elsif (@holdings[resource] - amount >= 0)
100
+ @holdings[resource] -= amount
101
+ return amount *
102
+ self.class.relative_value(resource, as_resource)
103
+ else
104
+ raise ArgumentError, "Insufficiant Funds"
105
+ end
106
+ end
107
+
108
+ # Add _amount_ of _resource_ to these holdings.
109
+ def deposit( amount, resource )
110
+ if @@values.key?(resource)
111
+ @holdings[resource] += amount
112
+ return amount
113
+ end
114
+ end
115
+
116
+ # Combine holding between this Currency object and _other_currency_.
117
+ def +( other_currency )
118
+ new_holdings = Hash.new(0)
119
+ @holdings.each do |key,value|
120
+ new_holdings[key] += value
121
+ end
122
+ other_currency.holdings.each do |key,value|
123
+ new_holdings[key] += value
124
+ end
125
+ return Currency.new(new_holdings)
126
+ end
127
+
128
+ # Remove holdings in _other_currency_ from this Currency object.
129
+ def -( other_currency )
130
+ new_holdings = Hash.new(0)
131
+ other_currency.holdings.each do |key,value|
132
+ new_holdings[key] = @holdings[key] - value
133
+ end
134
+ if new_holdings.values.min < 0
135
+ raise ArgumentError, "Detected negative value"
136
+ end
137
+ return Currency.new(new_holdings)
138
+ end
139
+
140
+ include Enumerable
141
+
142
+ # Delegates to the holdings Hash.
143
+ def each(&block)
144
+ @holdings.each(&block)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,219 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # dice.rb
4
+ #
5
+ # Created by James Edward Gray II on 2005-05-29.
6
+ # Copyright 2005 Gray Productions. All rights reserved.
7
+
8
+ require "gambit/viewable"
9
+
10
+ module Gambit
11
+ module Tools
12
+ # An object for managing a roll of dice.
13
+ class Dice
14
+ include Gambit::Viewable
15
+ register_view(:gambit_html, <<-END_HTML_VIEW.gsub(/^\t{3}/, ""))
16
+ <ul class="dice">
17
+ % each do |die|
18
+ <li><%= die %></li>
19
+ % end
20
+ </ul>
21
+ END_HTML_VIEW
22
+ register_view(:gambit_text, <<-END_TEXT_VIEW.gsub(/^\t+/, ""))
23
+ % if @dice == 1
24
+ <%= "a \#{@roll.first}" %>
25
+ % else
26
+ <%= "\#{@roll[0..-2].join(', ')} and \#{@roll[-1]}" %>
27
+ % end
28
+ END_TEXT_VIEW
29
+
30
+ #
31
+ # Creates a new Dice object, rolling the indicated _count_ of the
32
+ # indicated _sided_ dice. The sides on a die need not be realistic,
33
+ # so this object can also be used to manage a coin toss (a two-sided
34
+ # die).
35
+ #
36
+ # :call-seq:
37
+ # Dice.new(count, sides) -> count_d_sides_roll
38
+ # Dice.new(sides) -> one_d_sides_roll
39
+ # Dice.new() -> one_d_six_roll
40
+ #
41
+ def initialize( *args )
42
+ @dice, @sides = if args.size == 2
43
+ args
44
+ elsif args.size == 1
45
+ [1, args.first]
46
+ else
47
+ [1, 6]
48
+ end
49
+
50
+ @roll = Array.new(@dice) { rand(@sides) + 1 }
51
+ end
52
+
53
+ # Fetch the die at the provided _index_.
54
+ def []( index )
55
+ @roll[index]
56
+ end
57
+
58
+ #
59
+ # Returns a count of all dice in the roll matching the provided
60
+ # _pips_.
61
+ #
62
+ def count( pips )
63
+ @roll.inject(0) do |total, die|
64
+ if die == pips then total + 1 else total end
65
+ end
66
+ end
67
+
68
+ include Enumerable
69
+
70
+ # Iterate over each die in the roll.
71
+ def each( &block )
72
+ @roll.each(&block)
73
+ end
74
+
75
+ #
76
+ # Count the number of "failure" dice in this roll. A die is
77
+ # a failure if it is less than or equal to the provided _maximum_.
78
+ #
79
+ def fail( maximum )
80
+ @roll.inject(0) do |total, die|
81
+ if die <= maximum then total + 1 else total end
82
+ end
83
+ end
84
+
85
+ #
86
+ # A call to matches?() allows _pattern_ matching for dice rolls. A
87
+ # _pattern_ may contain single-letter Strings and/or Integers.
88
+ #
89
+ # Strings are used to locate recurring die patterns. For example,
90
+ # one could check for a full house with
91
+ # <tt>dice.matches?(*w{x x x y y})</tt>.
92
+ #
93
+ # Integers can be used to locate sequences of dice. For example,
94
+ # one could check for a small straight with
95
+ # <tt>dice.matches?(*(1..4).to_a)</tt>.
96
+ #
97
+ def matches?( *pattern )
98
+ digits, letters = pattern.partition { |e| e.is_a?(Integer) }
99
+ matches_digits?(digits) and matches_letters?(letters)
100
+ end
101
+
102
+ #
103
+ # A call to this method will reroll the indicated dice. Dice can be
104
+ # selected by block or _indices_.
105
+ #
106
+ # :call-seq:
107
+ # reroll(*indices) -> dice
108
+ # reroll() { |die| ... } -> dice
109
+ #
110
+ def reroll( *indices )
111
+ if block_given?
112
+ @roll.each_with_index do |die, i|
113
+ @roll[i] = rand(6) + 1 if yield(die)
114
+ end
115
+ elsif indices.empty?
116
+ @roll = Array.new(@dice) { rand(@sides) + 1 }
117
+ else
118
+ indices.each { |i| @roll[i] = rand(6) + 1 }
119
+ end
120
+
121
+ self
122
+ end
123
+
124
+ #
125
+ # Count the number of "success" dice in this roll. A die is
126
+ # a success if it is greater than or equal to the provided
127
+ # _minimum_.
128
+ #
129
+ def success( minimum )
130
+ @roll.inject(0) do |total, die|
131
+ if die >= minimum then total + 1 else total end
132
+ end
133
+ end
134
+
135
+ #
136
+ # Returns a total sum of all dice in the roll matching the provided
137
+ # _pips_. If _pips_ is +nil+ (the default), all dice are totaled.
138
+ #
139
+ def sum( pips = nil )
140
+ if pips
141
+ @roll.inject(0) do |total, die|
142
+ if die == pips then total + die else total end
143
+ end
144
+ else
145
+ @roll.inject(0) { |total, die| total + die }
146
+ end
147
+ end
148
+
149
+ # Add the sum() of this roll to an Integer.
150
+ def +( other ) sum + other end
151
+ # Subtract the sum() of this roll from an Integer.
152
+ def -( other ) sum - other end
153
+ # Multiply the sum() of this roll to an Integer.
154
+ def *( other ) sum * other end
155
+ # Divide the sum() of this roll by an Integer.
156
+ def /( other ) sum / other end
157
+
158
+ #
159
+ # Support for using Dice objects in ordinary Ruby math. Rolls are
160
+ # added to an Integer or Float by sum().
161
+ #
162
+ def coerce( other )
163
+ if other.is_a? Integer
164
+ [other, self.sum]
165
+ else
166
+ [Float(other), Float(self.sum)]
167
+ end
168
+ end
169
+
170
+ private
171
+
172
+ # The "number pattern" half of matches?().
173
+ def matches_digits?( digits )
174
+ return true if digits.size < 2
175
+
176
+ digits.sort!
177
+ test = @roll.uniq.sort
178
+ loop do
179
+ (0..(@roll.length - digits.length)).each do |index|
180
+ return true if test[index, digits.length] == digits
181
+ end
182
+
183
+ digits.collect! { |d| d + 1 }
184
+ break if digits.last > 6
185
+ end
186
+
187
+ false
188
+ end
189
+
190
+ # The "letter pattern" half of matches?().
191
+ def matches_letters?( letters )
192
+ return true if letters.size < 2
193
+
194
+ counts = Hash.new(0)
195
+ letters.each { |l| counts[l] += 1 }
196
+ counts = counts.values.sort.reverse
197
+
198
+ pips = @roll.uniq
199
+ counts.each do |c|
200
+ return false unless match = pips.find { |p| count(p) >= c }
201
+ pips.delete(match)
202
+ end
203
+
204
+ true
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ class Integer
211
+ #
212
+ # A shortcut for rolling dice in a program. For example:
213
+ #
214
+ # str = 3.d(6) + 2
215
+ #
216
+ def d( sides )
217
+ Gambit::Tools::Dice.new(self, sides)
218
+ end
219
+ end