cardlike 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *~
19
+ .*.s[a-w][a-z]
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cardlike.gemspec
4
+ gemspec
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
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
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
@@ -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