cardlike 0.0.1
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.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +278 -0
- data/Rakefile +4 -0
- data/cardlike.gemspec +21 -0
- data/examples/go_fish.rb +120 -0
- data/lib/cardlike/card.rb +73 -0
- data/lib/cardlike/deck.rb +103 -0
- data/lib/cardlike/hand.rb +28 -0
- data/lib/cardlike/score.rb +47 -0
- data/lib/cardlike/turn.rb +27 -0
- data/lib/cardlike/version.rb +3 -0
- data/lib/cardlike.rb +154 -0
- data/spec/integration/card_construction_spec.rb +105 -0
- data/spec/integration/dealing_spec.rb +42 -0
- data/spec/integration/deck_construction_spec.rb +143 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/units/cardlike_spec.rb +9 -0
- data/spec/units/deck_spec.rb +43 -0
- data/spec/units/hand_spec.rb +50 -0
- data/spec/units/score_spec.rb +73 -0
- data/spec/units/turn_spec.rb +24 -0
- metadata +110 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Payton Swick
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
# Cardlike
|
2
|
+
|
3
|
+
A DSL to design and test card games.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'cardlike'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install cardlike
|
18
|
+
|
19
|
+
Include the gem in your code to use it.
|
20
|
+
|
21
|
+
require 'cardlike'
|
22
|
+
|
23
|
+
## Summary
|
24
|
+
|
25
|
+
Cardlike provides a [DSL](http://en.wikipedia.org/wiki/Domain-specific_language)
|
26
|
+
for creating a card game. It's a layer of abstraction for making Cards (with
|
27
|
+
various properties), putting them into Decks, dealing them into Hands, and then
|
28
|
+
taking turns while keeping a running score.
|
29
|
+
|
30
|
+
Cardlike is agnostic about what kind of game you want to create (it may not have
|
31
|
+
turns or a score) and how you want to interact with the game. Here's a few
|
32
|
+
possibilities for how you could use it:
|
33
|
+
|
34
|
+
* Write tests for a physical card game to see if it will work.
|
35
|
+
* Write a text-based card game.
|
36
|
+
* Use as a backend for a web card game (backbone.js, anyone?).
|
37
|
+
* Use as a backend for a native app card game.
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
There are various ways to use the methods in Cardlike. Probably the easiest is
|
42
|
+
to define your whole game in a `Cardlike.game` block.
|
43
|
+
|
44
|
+
Cardlike.game do
|
45
|
+
card "Ace of Diamonds"
|
46
|
+
card "Ace of Hearts"
|
47
|
+
card "Ace of Spades"
|
48
|
+
card "Ace of Clubs"
|
49
|
+
|
50
|
+
deck "My Deck" do
|
51
|
+
copy_card "Ace of Diamonds"
|
52
|
+
copy_card "Ace of Hearts"
|
53
|
+
copy_card "Ace of Clubs"
|
54
|
+
copy_card "Ace of Spades"
|
55
|
+
end
|
56
|
+
|
57
|
+
the_deck("My Deck").shuffle!
|
58
|
+
|
59
|
+
hand "Player 1"
|
60
|
+
|
61
|
+
puts "Find the Ace of Spades!"
|
62
|
+
|
63
|
+
the_deck("My Deck").draw_into(the_hand("Player 1"))
|
64
|
+
|
65
|
+
if the_hand("Player 1").first.name == "Ace of Spades"
|
66
|
+
puts "Congratulations, you win!"
|
67
|
+
else
|
68
|
+
puts "Oh, well, try again."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
You can also prefix the class methods with Cardlike.
|
73
|
+
|
74
|
+
Cardlike.type_of_card :playing_card do
|
75
|
+
has :suit
|
76
|
+
end
|
77
|
+
|
78
|
+
Cardlike.new_playing_card "Six of Spades" do
|
79
|
+
suit 'spades'
|
80
|
+
end
|
81
|
+
|
82
|
+
@deck = Cardlike.deck "My Deck" do
|
83
|
+
include_card "Six of Spades"
|
84
|
+
end
|
85
|
+
|
86
|
+
puts "Drawing #{@deck.draw}" # @deck.draw == Cardlike.the_deck("My Deck").draw
|
87
|
+
|
88
|
+
### Creating Cards
|
89
|
+
|
90
|
+
The `card` method can be used to create a new Card object. Created cards are
|
91
|
+
stored globally and can be accessed with `the_card` and the card's name.
|
92
|
+
|
93
|
+
Cardlike.card "Draw one Play one" # => creates (and returns) a new Card object
|
94
|
+
Cardlike.the_card "Draw one Play one" # => the Card object created above (NOT a copy)
|
95
|
+
|
96
|
+
A block passed to the `card` method will be executed in the new Card's context.
|
97
|
+
Card objects can have properties, but it's best to create a custom type of card
|
98
|
+
to do this.
|
99
|
+
|
100
|
+
Cardlike.type_of_card :action_card do
|
101
|
+
has :power_level
|
102
|
+
has :color
|
103
|
+
end
|
104
|
+
|
105
|
+
Cardlike.new_action_card "Magic Spell" do
|
106
|
+
power_level 5
|
107
|
+
color :red
|
108
|
+
end # => new Card object (actually a new ActionCard object)
|
109
|
+
|
110
|
+
Cardlike.the_card("Magic Spell")[:color] # => :red
|
111
|
+
Cardlike.the_card("Magic Spell")[:power_level] # => 5
|
112
|
+
|
113
|
+
The `type_of_card` method creates a new subclass of Card. Use the `has` method in
|
114
|
+
a `card` block to add a property. A new card creation method is added to the DSL
|
115
|
+
with the type of card prefixed by `new_`.
|
116
|
+
|
117
|
+
To assign a property to a new card in the card block, use a method of the same
|
118
|
+
name as the property. To retrieve that property, treat the card as a hash and
|
119
|
+
reference the property name as the key.
|
120
|
+
|
121
|
+
### Creating Decks
|
122
|
+
|
123
|
+
But what you really want to do is create a Deck of cards.
|
124
|
+
|
125
|
+
Cardlike.game do
|
126
|
+
|
127
|
+
type_of_card :action_card do
|
128
|
+
has :power_level
|
129
|
+
has :cost
|
130
|
+
end
|
131
|
+
|
132
|
+
deck "Action Deck" do
|
133
|
+
|
134
|
+
new_action_card "Magic Spell" do
|
135
|
+
power_level 5
|
136
|
+
cost 3
|
137
|
+
end
|
138
|
+
|
139
|
+
new_action_card "Subtle Strike" do
|
140
|
+
power_level 2
|
141
|
+
cost 2
|
142
|
+
end
|
143
|
+
|
144
|
+
new_action_card "Wide Swing" do
|
145
|
+
power_level 1
|
146
|
+
cost 1
|
147
|
+
end
|
148
|
+
|
149
|
+
3.times { copy_card "Magic Spell" }
|
150
|
+
3.times { copy_card "Subtle Strike" }
|
151
|
+
3.times { copy_card "Wide Swing" }
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
You can use the `deck` method to name a new deck and pass it a block which will
|
158
|
+
be evauluated in the context of the Deck. In this case, we put a bunch of `card`
|
159
|
+
creation methods in the block, or more specifically, `new_action_card` (the
|
160
|
+
method that was created by `type_of_card`).
|
161
|
+
|
162
|
+
Cards defined within the `deck` method will automatically be added to the Deck.
|
163
|
+
You can also add cards by treating the Deck as an Array (which it is).
|
164
|
+
|
165
|
+
Cardlike.game do
|
166
|
+
type_of_card :action_card
|
167
|
+
@card1 = new_action_card "Magic Spell"
|
168
|
+
@deck = deck "Action Deck"
|
169
|
+
|
170
|
+
@deck << @card1
|
171
|
+
end
|
172
|
+
|
173
|
+
Decks can be accessed using `the_deck` method, which takes the name of a Deck.
|
174
|
+
The cards within are inside an Array, so most Array methods will work
|
175
|
+
(particularly `Deck#shuffle!`).
|
176
|
+
|
177
|
+
Cardlike.game do
|
178
|
+
...
|
179
|
+
the_deck("Action Deck").shuffle!
|
180
|
+
the_deck("Action Deck").first.name # => could be "Magic Spell"
|
181
|
+
end
|
182
|
+
|
183
|
+
### Drawing into Hands
|
184
|
+
|
185
|
+
Often you probably want to draw the top card of the deck. Cardlike Decks have a
|
186
|
+
`draw` method and a `draw_into` method. Both remove and return the top Card from
|
187
|
+
that Deck. `draw_into` takes an additional argument which should be an Array,
|
188
|
+
Deck, or Hand (or something that responds to `<<`).
|
189
|
+
|
190
|
+
A Hand is a subclass of a Deck with some additional methods. Creating a Hand is
|
191
|
+
easiest with the `hand` method (which works like the `Deck` method).
|
192
|
+
|
193
|
+
Cardlike.game do
|
194
|
+
type_of_card :action_card
|
195
|
+
@card1 = new_action_card "Magic Spell"
|
196
|
+
@deck = deck "Action Deck" do
|
197
|
+
2.times { copy_card "Magic Spell" }
|
198
|
+
end
|
199
|
+
|
200
|
+
the_deck("Action Deck").draw # => <Card, :name => 'Magic Spell'>
|
201
|
+
|
202
|
+
@player1 = hand "Player 1"
|
203
|
+
|
204
|
+
the_deck("Action Deck").draw_into @player1 # => <Card, :name => 'Magic Spell'>
|
205
|
+
end
|
206
|
+
|
207
|
+
Hands can be accessed by `the_hand`, just like the other Cardlike objects.
|
208
|
+
|
209
|
+
Cardlike.game do
|
210
|
+
...
|
211
|
+
hand "Player 1"
|
212
|
+
the_deck("Action Deck").draw_into the_hand("Player 1")
|
213
|
+
the_hand("Player 1").size # => 1
|
214
|
+
end
|
215
|
+
|
216
|
+
### Defining a Turn
|
217
|
+
|
218
|
+
As most games have turns, you can define a block of code as a turn using the
|
219
|
+
`define_turn` method and then call it using `begin_new_turn`. Any number of
|
220
|
+
arguments can be passed to the block.
|
221
|
+
|
222
|
+
Cardlike.game do
|
223
|
+
...
|
224
|
+
|
225
|
+
define_turn do |current_hand|
|
226
|
+
the_deck("Action Deck").draw_into current_hand # Draw a card
|
227
|
+
puts "#{current_hand.name}'s Hand: #{current_hand}"
|
228
|
+
end
|
229
|
+
|
230
|
+
hands = []
|
231
|
+
hands << hand "Player 2"
|
232
|
+
hands << hand "Player 1"
|
233
|
+
|
234
|
+
begin
|
235
|
+
hands.rotate!
|
236
|
+
begin_new_turn(hands.first)
|
237
|
+
end while victory == false
|
238
|
+
end
|
239
|
+
|
240
|
+
### Keeping Score
|
241
|
+
|
242
|
+
Chances are there's scoring in your game too. You can use the `score` method to
|
243
|
+
add a score to a particular key. The key can be anything, so players can have
|
244
|
+
several scores or the game could keep score of several objects at once.
|
245
|
+
|
246
|
+
You can retrieve the score using `the_score` followed by the key or get a Hash
|
247
|
+
of all the scores with `scores`.
|
248
|
+
|
249
|
+
If you need to decrement the score or set it to a particular number, use
|
250
|
+
`set_score`.
|
251
|
+
|
252
|
+
Cardlike.game do
|
253
|
+
...
|
254
|
+
score "Player 1" # sets the score to 1
|
255
|
+
score "Player 2" # sets the score to 1
|
256
|
+
score "Player 1" # sets the score to 2
|
257
|
+
|
258
|
+
the_score "Player 1" # => 2
|
259
|
+
|
260
|
+
scores # => {"Player 1" => 2, "Player 2" => 1}
|
261
|
+
|
262
|
+
set_score "Player 2", 5
|
263
|
+
|
264
|
+
scores # => {"Player 1" => 2, "Player 2" => 5}
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
### Examples
|
269
|
+
|
270
|
+
See the examples in the `examples/` directory for some more good ideas.
|
271
|
+
|
272
|
+
## Contributing
|
273
|
+
|
274
|
+
1. Fork it
|
275
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
276
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
277
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
278
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/cardlike.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cardlike/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "cardlike"
|
8
|
+
gem.version = Cardlike::VERSION
|
9
|
+
gem.authors = ["Payton Swick"]
|
10
|
+
gem.email = ["payton@foolord.com"]
|
11
|
+
gem.summary = 'A library for developing and testing card games.'
|
12
|
+
gem.homepage = "https://github.com/sirbrillig/cardlike"
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($/)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
|
19
|
+
gem.add_development_dependency 'rspec'
|
20
|
+
gem.add_dependency 'activesupport'
|
21
|
+
end
|
data/examples/go_fish.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'cardlike'
|
2
|
+
require 'highline/import'
|
3
|
+
|
4
|
+
Cardlike.game do
|
5
|
+
puts "Setting up..."
|
6
|
+
|
7
|
+
type_of_card :playing_card do
|
8
|
+
has :face
|
9
|
+
has :value
|
10
|
+
has :suit
|
11
|
+
end
|
12
|
+
|
13
|
+
deck "Go Fish" do
|
14
|
+
['Spades', 'Clubs', 'Diamonds', 'Hearts'].each do |card_suit|
|
15
|
+
['Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Jack', 'Queen', 'King', 'Ace'].each_with_index do |name, index|
|
16
|
+
new_playing_card("#{name} of #{card_suit}") do
|
17
|
+
value index
|
18
|
+
face name
|
19
|
+
suit card_suit
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
shuffle!
|
25
|
+
end
|
26
|
+
|
27
|
+
players = []
|
28
|
+
players << hand("Player 1")
|
29
|
+
players << hand("Player 2")
|
30
|
+
|
31
|
+
players.each do |player|
|
32
|
+
7.times { the_deck("Go Fish").draw_into player }
|
33
|
+
end
|
34
|
+
|
35
|
+
current_player = players.first
|
36
|
+
|
37
|
+
define_turn do
|
38
|
+
target_player = players.last
|
39
|
+
if players.size > 2
|
40
|
+
target_name = choose do |menu|
|
41
|
+
menu.prompt = "#{current_player.name}, who are you asking? "
|
42
|
+
menu.choices(*players.map { |p| p.name })
|
43
|
+
end
|
44
|
+
target_player = players.select { |p| p.name == target_name }.first
|
45
|
+
end
|
46
|
+
|
47
|
+
target_card_name = choose do |menu|
|
48
|
+
menu.prompt = "#{current_player.name}, what are you looking for? "
|
49
|
+
menu.choices(*current_player.map {|c| c.name })
|
50
|
+
end
|
51
|
+
target_card = current_player.select { |c| c.name == target_card_name }.first
|
52
|
+
puts "#{target_player.name}, do you have any #{target_card[:face]}s?\n"
|
53
|
+
|
54
|
+
another_turn = false
|
55
|
+
removed = target_player.remove_card_if { |c| c[:value] == target_card[:value] }
|
56
|
+
unless removed.empty?
|
57
|
+
current_player += removed
|
58
|
+
puts "Got one! Yes indeed, #{target_player.name} had #{removed.size} #{removed.first[:face]}#{removed.size == 1 ? '' : 's'}."
|
59
|
+
puts "You get another turn."
|
60
|
+
another_turn = true
|
61
|
+
|
62
|
+
# Check for matches.
|
63
|
+
matching_sets = current_player.group_by { |c| c[:value] }.select { |k, v| v.size >= 4 }
|
64
|
+
matching_sets.each do |matching_value, cards|
|
65
|
+
matching_card = cards.first
|
66
|
+
puts "You have a matching set of #{matching_card[:face]}s!"
|
67
|
+
current_player.remove_card_if { |c| c[:value] == matching_value }
|
68
|
+
score current_player.name
|
69
|
+
end
|
70
|
+
|
71
|
+
else
|
72
|
+
puts "Go Fish!\n"
|
73
|
+
drawn = the_deck("Go Fish").draw_into current_player
|
74
|
+
puts "You drew a #{drawn.name}."
|
75
|
+
|
76
|
+
# Check for matches.
|
77
|
+
matching_sets = current_player.group_by { |c| c[:value] }.select { |k, v| v.size >= 4 }
|
78
|
+
matching_sets.each do |matching_value, cards|
|
79
|
+
matching_card = cards.first
|
80
|
+
puts "Good fortune! You have a matching set of #{matching_card[:face]}s!"
|
81
|
+
puts "You get another turn."
|
82
|
+
another_turn = true
|
83
|
+
current_player.remove_card_if { |c| c[:value] == matching_value }
|
84
|
+
score current_player.name
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
another_turn
|
90
|
+
end
|
91
|
+
|
92
|
+
begin
|
93
|
+
puts "\nBeginning a new turn!"
|
94
|
+
|
95
|
+
begin
|
96
|
+
another_turn = begin_new_turn
|
97
|
+
|
98
|
+
if the_deck("Go Fish").empty?
|
99
|
+
puts "\nThe deck is empty!\n"
|
100
|
+
break
|
101
|
+
end
|
102
|
+
break if current_player.empty?
|
103
|
+
|
104
|
+
end while another_turn
|
105
|
+
players.rotate!
|
106
|
+
current_player = players.first
|
107
|
+
|
108
|
+
if the_deck("Go Fish").empty?
|
109
|
+
break
|
110
|
+
end
|
111
|
+
break if current_player.empty?
|
112
|
+
|
113
|
+
end until players.any? { |p| p.size < 1 }
|
114
|
+
|
115
|
+
puts "\n\nThe game has ended!"
|
116
|
+
|
117
|
+
winner = scores.max_by { |p, s| s }.first
|
118
|
+
puts "#{winner} is the winner!"
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#
|
2
|
+
# Represents a game card. Best used with the Card and Deck DSL. See Cardlike.
|
3
|
+
#
|
4
|
+
class Cardlike::Card
|
5
|
+
attr_accessor :name, :text
|
6
|
+
|
7
|
+
#
|
8
|
+
# Create an instance of a Card. Arguments are a hash that should include
|
9
|
+
# +:name+ at a minimum. Optionally all cards have a +text+ accessor that can
|
10
|
+
# be set in the constructor by including the option +:text+. Perhaps a better
|
11
|
+
# idea is to use Card.create or Cardlike.card.
|
12
|
+
#
|
13
|
+
def initialize(options={})
|
14
|
+
self.name = options[:name]
|
15
|
+
self.text = options[:text]
|
16
|
+
@properties = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Factory method to create an instance of a Card. This sets the name of the
|
21
|
+
# card and its properties using the Card DSL in the corresponding block. You
|
22
|
+
# can also use Cardlike.card, which does the same thing. Probably more useful
|
23
|
+
# is to create custom card types using Cardlike.type_of_card and create them
|
24
|
+
# using the +new_+ methods.
|
25
|
+
#
|
26
|
+
# Card.create "Big Monster" do
|
27
|
+
# text "Spend 1 Mana to Attack"
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# Preferred over Card.new.
|
31
|
+
#
|
32
|
+
def self.create(name, &block)
|
33
|
+
c = self.new(name: name)
|
34
|
+
c.instance_eval(&block) if block_given?
|
35
|
+
c
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# DSL method for setting the card text. Still works as a getter if no args are
|
40
|
+
# passed.
|
41
|
+
#
|
42
|
+
def text(card_text=nil)
|
43
|
+
return @text unless card_text
|
44
|
+
@text = card_text
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Return custom properties of this Card. Custom properties can be set using
|
49
|
+
# the Card.has method, ideally inside a Cardlike.type_of_card block.
|
50
|
+
#
|
51
|
+
# @king_of_diamonds[:suit] # => 'Diamonds'
|
52
|
+
#
|
53
|
+
def [](prop)
|
54
|
+
@properties[prop]
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Class DSL method for setting custom properties for a Card. See
|
59
|
+
# Cardlike.type_of_card.
|
60
|
+
#
|
61
|
+
def self.has(prop)
|
62
|
+
define_method(prop, lambda { |arg| raise "Cards are immutable." if @properties.has_key? prop; @properties[prop] = arg })
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
t = []
|
67
|
+
t << "Name: #{name}"
|
68
|
+
t << "Text: #{text}" if text
|
69
|
+
@properties.each { |p,v| t << "#{p}: #{v}" }
|
70
|
+
t.join("\n")
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
#
|
2
|
+
# Represents a game deck. Best used with the Card and Deck DSL. See Cardlike.
|
3
|
+
#
|
4
|
+
class Cardlike::Deck < Array
|
5
|
+
attr_accessor :name
|
6
|
+
|
7
|
+
#
|
8
|
+
# Create a new Deck. Options should always include +:name+ and may optionally
|
9
|
+
# include +:cards+, which is an array of Card objects (ideally) to keep in
|
10
|
+
# this deck. It's better to use Cardlike.deck to create decks, though.
|
11
|
+
#
|
12
|
+
def initialize(options={})
|
13
|
+
self.name = options[:name]
|
14
|
+
options[:cards].each { |c| self << c } if options[:cards]
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Draw the top card from this deck and return it.
|
19
|
+
#
|
20
|
+
def draw
|
21
|
+
self.pop
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Shuffle this deck and return a copy Deck. Probably more useful is
|
26
|
+
# Deck#shuffle! which will shuffle this deck in place.
|
27
|
+
#
|
28
|
+
def shuffle
|
29
|
+
self.dup.shuffle!
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Append an array (or Deck) of cards to this Deck.
|
34
|
+
#
|
35
|
+
def +(ary)
|
36
|
+
ary.each { |a| self << a }
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# DSL method to add a pre-defined card to this Deck. This inserts the unique
|
42
|
+
# card into the deck. If you want to put a copy into the deck (or several
|
43
|
+
# copies), use Deck#copy_card instead.
|
44
|
+
#
|
45
|
+
# Cardlike.game do
|
46
|
+
# card "Super Strike"
|
47
|
+
#
|
48
|
+
# deck "My Deck" do
|
49
|
+
# include_card "Super Strike"
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
def include_card(name)
|
54
|
+
raise "Card '#{name}' not found." unless card = Cardlike.the_card(name)
|
55
|
+
self << card
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# DSL method to add a duplicate of a pre-defined card to this Deck.
|
60
|
+
#
|
61
|
+
# Cardlike.game do
|
62
|
+
# card "Super Strike"
|
63
|
+
#
|
64
|
+
# deck "My Deck" do
|
65
|
+
# 4.times { copy_card "Super Strike" }
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
def copy_card(name)
|
70
|
+
raise "Card '#{name}' not found." unless card = Cardlike.the_card(name)
|
71
|
+
copy = card.dup
|
72
|
+
self << copy
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# DSL method to create a new card inside this deck. Also check out the +new_+
|
77
|
+
# methods created by Cardlike.type_of_card. This works just like Cardlike.card
|
78
|
+
# except that it automatically adds the card to this deck.
|
79
|
+
#
|
80
|
+
# Cardlike.deck "Options Deck" do
|
81
|
+
# card "Magic Spell"
|
82
|
+
# card "Arcane Mark"
|
83
|
+
# card "Simple Attack"
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
def card(name, &block)
|
87
|
+
c = Cardlike.card(name, &block)
|
88
|
+
self << c
|
89
|
+
c
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# Like Deck#draw except that it draws a card and then adds it to a Deck or
|
94
|
+
# Hand. Also returns the card drawn.
|
95
|
+
#
|
96
|
+
# Cardlike.the_deck("Poker Deck").draw_into(Cardlike.the_hand("Player 1"))
|
97
|
+
#
|
98
|
+
def draw_into(deck)
|
99
|
+
card = draw
|
100
|
+
deck << card
|
101
|
+
card
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#
|
2
|
+
# Represents a game hand. Best used with the Card and Deck DSL. See Cardlike and
|
3
|
+
# Cardlike::Deck, from which this inherits.
|
4
|
+
#
|
5
|
+
class Cardlike::Hand < Cardlike::Deck
|
6
|
+
#
|
7
|
+
# Remove and return a Card from this hand by name.
|
8
|
+
#
|
9
|
+
def remove_card(card_name)
|
10
|
+
self.delete(self.select { |card| card.name == card_name }.first)
|
11
|
+
end
|
12
|
+
|
13
|
+
#
|
14
|
+
# Remove cards for which the block evaluates to true, returning removed cards
|
15
|
+
# in an Array. If no cards are found, returns an empty array.
|
16
|
+
#
|
17
|
+
def remove_card_if(&block)
|
18
|
+
matches = self.select { |card| yield(card) }
|
19
|
+
matches.collect { |match| self.delete(match) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
puts "Hand: #{name}"
|
24
|
+
self.each do |card|
|
25
|
+
puts "-> #{card}\n"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|