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,164 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # movehistory.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
+ # An objectified move history object.
13
+ class MoveHistory
14
+ include Gambit::Viewable
15
+ register_view(:gambit_html, <<-END_HTML_VIEW.gsub(/^\t{3}/, ""))
16
+ <table class="movehistory">
17
+ % turns = @moves.keys.sort
18
+ <tr>
19
+ <th>&nbsp;</th>
20
+ % @players.each do |player|
21
+ <th id="<%= player %>"><%= player %></th>
22
+ % end
23
+ <%= "</tr>" %>
24
+ % even = true
25
+ % turns.each do |turn|
26
+ % even = (not even)
27
+ % if even
28
+ <tr class="even">
29
+ % else
30
+ <tr class="odd">
31
+ % end
32
+ <td class="turn"><%= turn %></td>
33
+ % @players.each do |player|
34
+ <td id="<%= player %><%= turn %>"><%= @moves[turn][player] ||
35
+ "&nbsp;" %></td>
36
+ % end
37
+ % if turn == turns.last
38
+ </tr>
39
+ % else
40
+ <%= "</tr>" %>
41
+ % end
42
+ % end
43
+ </table>
44
+ END_HTML_VIEW
45
+ register_view(:gambit_text, <<-END_TEXT_VIEW.gsub(/^\t+/, ""))
46
+ % turns = @moves.keys.sort
47
+ % len_cmp = lambda { |a, b| a.length <=> b.length }
48
+ % turn_width = turns.map { |t| t.to_s }.max(&len_cmp).length
49
+ % move_width = [ @moves.map { |(turn, moves)| moves.values }.
50
+ % flatten.max(&len_cmp).length,
51
+ % @players.max(&len_cmp).length ].max
52
+ <%= ("%\#{turn_width}s " + "%\#{move_width}s " * @players.size) %
53
+ ["", *@players] %>
54
+ % turns.each do |turn|
55
+ % unless @moves[turn][@players.first].nil?
56
+ <%= ("%\#{turn_width}d: " + "%\#{move_width}s " * @players.size) %
57
+ [turn, *@players.map { |p| @moves[turn][p] }] %>
58
+ % end
59
+ % end
60
+ END_TEXT_VIEW
61
+
62
+ #
63
+ # Creates a new MoveHistory object, optionally initialized with some
64
+ # _players_.
65
+ #
66
+ def initialize( *players )
67
+ @players = Array.new
68
+ @turn = 1
69
+ @next_player = 0
70
+ @moves = Hash.new { |moves, turn| moves[turn] = Hash.new }
71
+
72
+ add_players(*players)
73
+ end
74
+
75
+ # The current turn number or name.
76
+ attr_accessor :turn
77
+
78
+ #
79
+ # Returns the moves for the provided _turn_ (optionally for the
80
+ # given _player_.)
81
+ #
82
+ def []( turn, player = nil )
83
+ if player.nil?
84
+ @moves[turn]
85
+ else
86
+ @moves[turn][player]
87
+ end
88
+ end
89
+
90
+ #
91
+ # Add _move_ to the current player for the current turn.
92
+ #
93
+ # Returns +self+ for method chaining.
94
+ #
95
+ def <<( move )
96
+ @moves[@turn][@players[@next_player]] = move
97
+
98
+ find_next_player
99
+
100
+ self
101
+ end
102
+
103
+ #
104
+ # Add a _player_ to this MoveHistory.
105
+ #
106
+ # Returns +self+ for method chaining.
107
+ #
108
+ def add_player( player )
109
+ @players << player
110
+
111
+ self
112
+ end
113
+
114
+ #
115
+ # Add a group of _players_ to this MoveHistory.
116
+ #
117
+ # Returns +self+ for method chaining.
118
+ #
119
+ def add_players( *players )
120
+ players.each { |player| add_player(player) }
121
+
122
+ self
123
+ end
124
+
125
+ # Iterate over each move, by turn and then by player.
126
+ def each( )
127
+ @moves.each do |(turn, moves)|
128
+ @players.each { |player| yield moves[player] }
129
+ end
130
+ end
131
+
132
+ #
133
+ # Add's an _event_ to the given _player_ for the current turn. If
134
+ # the player already has and event for that turn, an Array is used
135
+ # to hold all added events in order.
136
+ #
137
+ # Returns +self+ for method chaining.
138
+ #
139
+ def record( player, event )
140
+ if @moves[@turn][player].nil?
141
+ @moves[@turn][player] = event
142
+ elsif @moves[@turn][player].is_a? Array
143
+ @moves[@turn][player] << event
144
+ else
145
+ @moves[@turn][player] = [@moves[@turn][player]]
146
+ @moves[@turn][player] << event
147
+ end
148
+
149
+ self
150
+ end
151
+
152
+ private
153
+
154
+ # Loops through players, circularly.
155
+ def find_next_player( )
156
+ @next_player += 1
157
+ if @next_player >= @players.size
158
+ @next_player = 0
159
+ @turn += 1
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,333 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # scorecard.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
+ # A tool for managing the score of just about any game.
13
+ class Scorecard
14
+ include Gambit::Viewable
15
+ register_view(:gambit_html, <<-END_HTML_VIEW.gsub(/^\t{3}/, ""))
16
+ <table class="scorecard">
17
+ <tr>
18
+ <th>&nbsp;</th>
19
+ % @players.each do |player|
20
+ <th id="<%= player %>"><%= player %></th>
21
+ % end
22
+ <%= "</tr>" %>
23
+ % even = true
24
+ % @order.each do |cat|
25
+ % even = (not even)
26
+ % if even
27
+ <tr class="even">
28
+ % else
29
+ <tr class="odd">
30
+ % end
31
+ <td class="category"><%= cat %></td>
32
+ % @players.each do |player|
33
+ % if @categories.assoc(cat)
34
+ <td id="<%= player %>--<%= cat %>"><%=
35
+ @categories_score[player][cat] || "&nbsp;" %></td>
36
+ % elsif subtotal = @totals.assoc(cat)
37
+ <td id="<%= player %>--<%= cat %>"><%=
38
+ figure_total(player, subtotal[1], subtotal[2]) ||
39
+ "&nbsp;" %></td>
40
+ % end
41
+ % end
42
+ % if cat == @order.last
43
+ </tr>
44
+ % else
45
+ <%= "</tr>" %>
46
+ % end
47
+ % end
48
+ </table>
49
+ END_HTML_VIEW
50
+ register_view(:gambit_text, <<-END_TEXT_VIEW.gsub(/^\t+/, ""))
51
+ % max_len = @players.max { |a, b| a.length <=> b.length }.length
52
+ % cat_len = @order.max { |a, b| a.length <=> b.length }.length
53
+ <%= "%\#{cat_len}s" % "" %> <%= "%\#{max_len}s " * @players.size %
54
+ @players %>
55
+ % last = nil
56
+ % @order.each do |cat|
57
+ % if @categories.assoc(cat)
58
+ % if @totals.assoc(last)
59
+
60
+ % end
61
+ <%= "%\#{cat_len}s" % cat %>: <%= "%\#{max_len}s " *
62
+ @players.size % @players.map { |player|
63
+ @categories_score[player][cat] } %>
64
+ % elsif subtotal = @totals.assoc(cat)
65
+ <%= "-" * cat_len %> <%=
66
+ (("-" * max_len) + " ") * @players.size %>
67
+ <%= "%\#{cat_len}s" % cat %>: <%= "%\#{max_len}s " *
68
+ @players.size % @players.map { |player|
69
+ figure_total(player, subtotal[1], subtotal[2]) } %>
70
+ % end
71
+ % last = cat
72
+ % end
73
+ END_TEXT_VIEW
74
+
75
+ #
76
+ # Creates a new Scorecard object, optionally initialized with some
77
+ # _players_.
78
+ #
79
+ def initialize( *players )
80
+ @score = Hash.new(0)
81
+ @players = Array.new
82
+
83
+ @use_categories = false
84
+ @categories = Array.new
85
+ @totals = Array.new
86
+ @order = Array.new
87
+ @categories_score = Hash.new do |names, cat|
88
+ names[cat] = Hash.new(0)
89
+ end
90
+ @win_category = nil
91
+
92
+ add_players(*players)
93
+ end
94
+
95
+ #
96
+ # Add a new _player_ to this Scorecard.
97
+ #
98
+ # Returns +self+ for method chaining.
99
+ #
100
+ def add_player( player )
101
+ @players << player
102
+
103
+ self
104
+ end
105
+
106
+ #
107
+ # Add a group of _players_ to this Scorecard.
108
+ #
109
+ # Returns +self+ for method chaining.
110
+ #
111
+ def add_players( *players )
112
+ players.each { |player| add_player(player) }
113
+
114
+ self
115
+ end
116
+
117
+ #
118
+ # Returns the losing player, as determined by the provided winning
119
+ # _condition_. If a _condition_ block is not given, scores will be
120
+ # compared numerically, with the highest score considered to be the
121
+ # best.
122
+ #
123
+ def loser( &condition )
124
+ if @use_categories
125
+ scores = @categories_score.map do |(name, scores)|
126
+ if @categories.assoc(@win_category)
127
+ @score[@win_category]
128
+ elsif total = @totals.assoc(@win_category)
129
+ figure_total(name, total[1], total[2])
130
+ end
131
+ end
132
+ best_score = scores.min(&condition)
133
+ @categories_score.find do |(name, score)|
134
+ if @categories.assoc(@win_category)
135
+ @score[@win_category] == best_score
136
+ elsif total = @totals.assoc(@win_category)
137
+ figure_total(name, total[1], total[2]) == best_score
138
+ end
139
+ end.first
140
+ else
141
+ best_score = @score.values.min(&condition)
142
+ @score.find { |(player, score)| score == best_score }.first
143
+ end
144
+ end
145
+
146
+ #
147
+ # Forces this Scorecard to score by category only.
148
+ #
149
+ # Returns +self+ for method chaining.
150
+ #
151
+ def require_categories( )
152
+ @use_categories = true
153
+
154
+ self
155
+ end
156
+
157
+ #
158
+ # Can be used to add _points_ to the score of the given _player_ or
159
+ # to fetch the score for a _player_, by omitting the _points_. If
160
+ # given, a score will be modified or fetched for the given
161
+ # _category_.
162
+ #
163
+ def score( player, points = 0, category = nil )
164
+ if @use_categories
165
+ cat = @categories.assoc(category) or
166
+ raise ArgumentError, "Invalid category."
167
+
168
+ total = @categories_score[player][category]
169
+ total += points
170
+
171
+ if cat.size == 2 and cat.last.is_a? Proc
172
+ unless cat.last[total]
173
+ raise ArgumentError, "Invalid score for " +
174
+ "#{cat.first} category."
175
+ end
176
+ elsif cat.size == 2 and cat.last.is_a? Array
177
+ unless cat.last.include?(total)
178
+ raise ArgumentError, "Invalid score for " +
179
+ "#{cat.first} category."
180
+ end
181
+ elsif cat.size == 3
182
+ unless cat[1] <= total and total <= cat[2]
183
+ raise ArgumentError, "Invalid score for " +
184
+ "#{cat.first} category."
185
+ end
186
+ end
187
+
188
+ @categories_score[player][category] = total
189
+
190
+ total(player, category)
191
+ else
192
+ @score[player] += points
193
+ @score[player] = yield(@score[player]) if block_given?
194
+
195
+ total(player)
196
+ end
197
+ end
198
+
199
+ #
200
+ # This method adds a "total" field to this Scorecard, by _name_.
201
+ # A total can be calculated _from_ any other fields on the card,
202
+ # including other totals.
203
+ #
204
+ # A _validation_ block can be given, if needed. The block will be
205
+ # called each time the total is display and passed the sum of all
206
+ # _from_ fields. The return value of the block will be the total
207
+ # displayed.
208
+ #
209
+ # Returns +self+ for method chaining.
210
+ #
211
+ def set_total( name, *from, &validation )
212
+ @order << name
213
+ @totals << [name, from, validation]
214
+
215
+ self
216
+ end
217
+
218
+ #
219
+ # Sets the score catrgory used to determine a winner.
220
+ #
221
+ # Returns +self+ for method chaining.
222
+ #
223
+ def set_win( name )
224
+ @win_category = name
225
+
226
+ self
227
+ end
228
+
229
+ #
230
+ # Sets a starting score for each field. *WARNING*: Should be
231
+ # called before any calls to score(), for predictable results.
232
+ #
233
+ def start_at=( minimum_score )
234
+ @score.default = minimum_score
235
+ end
236
+
237
+ #
238
+ # Returns the total score for the given _player_ or for the given
239
+ # _player_ in the optional _category_.
240
+ #
241
+ def total( player, category = nil )
242
+ if category.nil?
243
+ @score[player]
244
+ else
245
+ if @categories.assoc(category)
246
+ @categories_score[player][category]
247
+ elsif total = @totals.assoc(category)
248
+ figure_total(player, total[1], total[2])
249
+ else
250
+
251
+ end
252
+ end
253
+ end
254
+
255
+ #
256
+ # Adds a _category_ to this Scorecard.
257
+ #
258
+ # Returns +self+ for method chaining.
259
+ #
260
+ # :call-seq:
261
+ # use_category(cat) { |score| ... } -> self
262
+ # use_category(cat, ary_of_accepted_values) -> self
263
+ # use_category(cat, low, high) -> self
264
+ #
265
+ def use_category( category, *details, &validation )
266
+ @order << category
267
+
268
+ if not validation.nil?
269
+ @categories << [category, validation]
270
+ elsif details.size == 1 and details[0].is_a? Array
271
+ @categories << [category, details[0]]
272
+ elsif details.size == 2 and details.all? { |n| n.is_a? Integer }
273
+ @categories << [category, *details]
274
+ else
275
+ raise ArgumentError, "Invalid category definition."
276
+ end
277
+
278
+ self
279
+ end
280
+
281
+ #
282
+ # Returns the winning player, as determined by the provided winning
283
+ # _condition_. If a _condition_ block is not given, scores will be
284
+ # compared numerically, with the highest score considered to be the
285
+ # best.
286
+ #
287
+ def winner( &condition )
288
+ if @use_categories
289
+ scores = @categories_score.map do |(name, scores)|
290
+ if @categories.assoc(@win_category)
291
+ @score[@win_category]
292
+ elsif total = @totals.assoc(@win_category)
293
+ figure_total(name, total[1], total[2])
294
+ end
295
+ end
296
+ best_score = scores.max(&condition)
297
+ @categories_score.find do |(name, score)|
298
+ if @categories.assoc(@win_category)
299
+ @score[@win_category] == best_score
300
+ elsif total = @totals.assoc(@win_category)
301
+ figure_total(name, total[1], total[2]) == best_score
302
+ end
303
+ end.first
304
+ else
305
+ best_score = @score.values.max(&condition)
306
+ @score.find { |(player, score)| score == best_score }.first
307
+ end
308
+ end
309
+
310
+ private
311
+
312
+ #
313
+ # Calculates a total field as described in set_total() and returns
314
+ # the score.
315
+ #
316
+ def figure_total( player, from, validate )
317
+ total = 0
318
+
319
+ from.each do |cat_or_total|
320
+ if @categories.assoc(cat_or_total)
321
+ total += @categories_score[player][cat_or_total]
322
+ elsif subtotal = @totals.assoc(cat_or_total)
323
+ total += figure_total(player, subtotal[1], subtotal[2])
324
+ end
325
+ end
326
+
327
+ total = validate[total] unless validate.nil?
328
+
329
+ total
330
+ end
331
+ end
332
+ end
333
+ end