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 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