ace_of_spades 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ .DS_Store
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
@@ -0,0 +1 @@
1
+ 1.9.3-p327
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec' do
5
+ watch(%r{^spec/lib/.+_spec\.rb$})
6
+ watch(%r{^lib/ace_of_spades/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('lib/ace_of_spades.rb') { "spec" }
8
+ watch('spec/spec_helper.rb') { "spec" }
9
+ end
10
+
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Dom Kiva-Meyer
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.
@@ -0,0 +1,188 @@
1
+ # Ace Of Spades
2
+
3
+ A simple and flexible playing cards library.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'ace_of_spades'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install ace_of_spades
18
+
19
+ ## Usage
20
+
21
+ require "ace_of_spades"
22
+ include AceOfSpades
23
+
24
+ ### Deck
25
+
26
+ Deck accepts an optional hash for initialization. Defaults are: `{aces_high: true, shuffle: true, jokers: false}`
27
+
28
+ deck = Deck.new(shuffle: false, jokers: true)
29
+ => [Two of Clubs, Three of Clubs, Four of Clubs, Five of Clubs, . . . Jack of Spades, Queen of Spades, King of Spades, Ace of Spades, Joker, Joker]
30
+
31
+ Deck also accepts `false` to initialize without initializing cards.
32
+
33
+ deck = Deck.new(false)
34
+ => []
35
+
36
+ Cards can be initialized later with `#initialize_cards`
37
+
38
+ deck.initialize_cards(shuffle: false, jokers: true)
39
+ => [Two of Clubs, Three of Clubs, Four of Clubs, Five of Clubs, . . . Jack of Spades, Queen of Spades, King of Spades, Ace of Spades, Joker, Joker]
40
+
41
+ Deck is array-like and can be manipulated like an array. All Array and Enumerable methods are available.
42
+
43
+ deck.first
44
+ => Two of Clubs
45
+
46
+ Ace ranking can be retrieved and set with `#aces_high?` and `#aces_high=`
47
+
48
+ deck.aces_high?
49
+ => true
50
+ deck.aces_high = false
51
+ => false
52
+ deck.aces_high?
53
+ => false
54
+
55
+ Cards can be added with `#add_card`
56
+
57
+ deck.add_card(:ace, :spades)
58
+ deck.last
59
+ => Ace of Spades
60
+
61
+ Any card-related methods on Deck accept valid arguments for Card.new (outlined below).
62
+
63
+ Specific cards can be found with `#find_cards`. It can accept arguments and/or a block. It will return all cards that match the arguments or for which the block evaluates to true.
64
+
65
+ deck.find_cards(:ace) { |card| card.joker? }
66
+ =>[Ace of Clubs, Ace of Diamonds, Ace of Hearts, Ace of Spades, Joker, Joker]
67
+
68
+ Similarly, `#find_card` will return the first card that the arguments match or for which the block evaluates to true.
69
+
70
+ deck.find_card(:spade) { |card| card.ace? }
71
+ => Ace of Clubs
72
+
73
+ `Ace of Clubs` was returned because the block evaluated to true. If no matching cards are found, `#find_cards` will return `nil`.
74
+
75
+ `#remove_cards` works just like `#find_cards`, but will remove all matching cards from the deck.
76
+
77
+ deck.jokers?
78
+ => true
79
+ deck.remove_cards(:joker)
80
+ => [Joker, Joker]
81
+ deck.jokers?
82
+ => false
83
+
84
+ `#deal` will remove the specified number of cards from the deck, store them separately, and return them. The default argument is 1.
85
+
86
+ deck.deal
87
+ => Two of Clubs
88
+ deck.count
89
+ => 51
90
+ deck.deal(9)
91
+ => [Three of Clubs, Four of Clubs, Five of Clubs, Six of Clubs, Seven of Clubs, Eight of Clubs, Nine of Clubs, Ten of Clubs, Jack of Clubs]
92
+ deck.count
93
+ => 42
94
+
95
+ `#shuffle`, `#shuffle!`, `#unshuffle`, `#unshuffle!`, do exactly what you'd think. The first two shuffle the deck and the last two unshuffle it (sorted from lowest to highest card). The bang versions are destructive.
96
+
97
+ `#valid?` returns true if the deck consists of 13 different cards from each suit and 0 or 2 jokers.
98
+
99
+ deck.valid?
100
+ => true
101
+ deck.remove_cards(:ace, :spades)
102
+ deck.valid?
103
+ => false
104
+
105
+ ### Card
106
+
107
+ Card accepts a value and a suit for initialization. The value can be an integer (1-14), a face card name, or the first letter of a face card name. The latter two can be symbols or strings and are case insensitive. The suit can be the plural or singular name of a suit or the first letter of the name of the suit. Again, symbols or strings are accepted and are case insensitive. Example: `Card.new` with `(:ace, :spades)`, `("ACE", :spades)`, `(:ace, "Spade")`, `(1, :spades)`, `(14, :spades)`, `(:A, :S)`, `("a", :spade)`, etc., would all produce the same card.
108
+
109
+ card = Card.new(:ace, :spades)
110
+ => Ace of Spades
111
+
112
+ A joker is a card with neither a value or suit and can be initialized by passing in `joker`.
113
+
114
+ joker = Card.new(:joker)
115
+ => Joker
116
+
117
+ `.wrap` will return the same instance of Card that is passed in if a Card instance is passed in, a new Card instance if valid arguments are passed in, or nil if invalid arguments are passed.
118
+
119
+ Card.wrap(card)
120
+ => Ace of Spades
121
+ Card.wrap('ace', 'spade')
122
+ => Ace of Spades
123
+ Card.wrap('foo')
124
+ => nil
125
+
126
+ Both suit and value have readers and writers defined. Setting a suit or value will first wrap the argument in the proper class.
127
+
128
+ card.value
129
+ => Ace
130
+ card.value = 5
131
+ card.value
132
+ => Five
133
+
134
+ Cards can be compared.
135
+
136
+ ace = Card.new(:ace, :spades)
137
+ king = Card.new(:king, :spades)
138
+ ace > king
139
+ => true
140
+
141
+ Cards are compared by suit first and, in the event of a tie, by value. Aces respect the `aces_high` boolean value of the parent deck.
142
+
143
+ `#to_s` and `#to_sym` convert a card to a String and Symbol respectively.
144
+
145
+ ace.to_s
146
+ => "Ace of Spades"
147
+ ace.to_sym
148
+ => :"Ace of Spades"
149
+
150
+ Cards respond to valid predicate methods that are dynamically defined based on valid values and suits.
151
+
152
+ ace.joker? || ace.heart?
153
+ => false
154
+ ace.ace? && ace.spade? && ace.spades? && ace.ace_of_spades? && ace.a? && ace.s?
155
+ => true
156
+ ace.foo?
157
+ => NoMethodError: undefined method 'foo?'
158
+
159
+ `#valid` checks whether a card is valid. A card is considered valid if it has a valid value and suit or if both the value and suit are `nil` (joker).
160
+
161
+ ace.valid?
162
+ => true
163
+ ace.suit = :foo
164
+ ace.valid?
165
+ => false
166
+
167
+ ### Value & Suit
168
+
169
+ In general you shouldn't have to touch Value and Suit since Deck and Card handle this for you. However, their interfaces are very similar to Card, if you need to use them directly.
170
+
171
+ ## Roadmap
172
+
173
+ * Abstract Deck functionality into a container class (Cardtainer?) from which Deck, Metadeck (deck of decks), Hand, and Game will inherit
174
+ * Implement Hand, Metadeck, and Game
175
+ * Implement customizable logic for joker comparison, Hand comparison, Suit ranking, and Deck validation
176
+ * Extract shared parsing functionality into a Parser module
177
+ * Extract shared functionality of Value and Suit into a module
178
+ * Separate unit and integration tests
179
+ * Add YARD documentation
180
+
181
+
182
+ ## Contributing
183
+
184
+ 1. Fork it
185
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
186
+ 3. Commit your changes and tests (`git commit -am 'Add some feature'`)
187
+ 4. Push to the branch (`git push origin my-new-feature`)
188
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "ace_of_spades"
5
+ gem.version = "0.0.1.pre"
6
+ gem.authors = ["Dom Kiva-Meyer"]
7
+ gem.email = ["hello@domkm.com"]
8
+ gem.description = "A simple and flexible playing cards library."
9
+ gem.summary = "A simple and flexible playing cards library."
10
+ gem.homepage = "https://github.com/domkm/ace_of_spades"
11
+
12
+ gem.files = `git ls-files`.split($/)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.require_paths = ["lib"]
16
+
17
+ gem.add_development_dependency "rspec", "~> 2.11.0"
18
+ gem.add_development_dependency "guard", "~> 1.5.3"
19
+ gem.add_development_dependency "guard-rspec", "~> 2.1.1"
20
+ end
@@ -0,0 +1,8 @@
1
+ module AceOfSpades
2
+ VALUES = [:Ace, :Two, :Three, :Four, :Five, :Six, :Seven, :Eight, :Nine, :Ten, :Jack, :Queen, :King, :Ace]
3
+ SUITS = [:Clubs, :Diamonds, :Hearts, :Spades]
4
+ end
5
+
6
+ %w[deck card value suit].each do |file|
7
+ require "ace_of_spades/#{file}"
8
+ end
@@ -0,0 +1,88 @@
1
+ class AceOfSpades::Card
2
+ include Comparable
3
+ attr_reader :value, :suit
4
+ attr_accessor :deck
5
+
6
+ def initialize(*args)
7
+ first, second = args[0] || false, args[1] || false
8
+ @value, @suit = Value.wrap(first), Suit.wrap(second)
9
+ @value, @suit = first, second if @value.nil? && @suit.nil?
10
+ @value = @suit = nil if args.count == 1 && /\Ajoker\z/i === first
11
+ @value.card = @suit.card = self unless joker?
12
+ validate!
13
+ end
14
+
15
+ def self.wrap(*args)
16
+ return args.first if args.first.instance_of?(Card)
17
+ Card.new(*args)
18
+ rescue
19
+ nil
20
+ end
21
+
22
+ def value=(v)
23
+ @value = Value.wrap(v)
24
+ end
25
+
26
+ def suit=(s)
27
+ @suit = Suit.wrap(s)
28
+ end
29
+
30
+ def <=>(card)
31
+ return nil if !card.instance_of?(Card) || card.joker?
32
+ suit_comparison = suit <=> card.suit
33
+ return suit_comparison unless suit_comparison == 0
34
+ value <=> card.value
35
+ end
36
+
37
+ def aces_high?
38
+ return true if deck.nil?
39
+ deck.aces_high?
40
+ end
41
+
42
+ def joker?
43
+ value.nil? && suit.nil?
44
+ end
45
+
46
+ def to_s
47
+ return 'Joker' if joker?
48
+ "#{value.to_s} of #{suit.to_s}"
49
+ end
50
+
51
+ def to_sym
52
+ to_s.to_sym
53
+ end
54
+
55
+ def valid?
56
+ return true if joker?
57
+ value.instance_of?(Value) && suit.instance_of?(Suit)
58
+ end
59
+
60
+ def validate!
61
+ valid? ? self : invalidate!
62
+ end
63
+
64
+ def method_missing(method, *args, &block)
65
+ return super unless match = %r(.+\?\z).match(method).to_s.chop.split('_')
66
+ v, s = match.first, match.last
67
+ valuable, suitable = Value.valuable?(v), Suit.suitable?(s)
68
+ if joker? && (suitable || valuable)
69
+ false
70
+ elsif valuable && suitable
71
+ value.send("#{v}?") && suit.send("#{s}?")
72
+ elsif match.count >= 2
73
+ super
74
+ elsif valuable
75
+ value.send("#{v}?")
76
+ elsif suitable
77
+ suit.send("#{s}?")
78
+ else
79
+ super
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def invalidate!
86
+ raise "Invalid Card: @value = #{value}, @suit = #{suit}"
87
+ end
88
+ end
@@ -0,0 +1,95 @@
1
+ class AceOfSpades::Deck < Array
2
+ attr_reader :dealt
3
+ attr_writer :aces_high
4
+
5
+ def initialize(opts = {})
6
+ super()
7
+ @dealt, @aces_high = [], true
8
+ return self unless opts
9
+ opts = {shuffle: true, jokers: false, aces_high: true, jokers: false}.merge(opts)
10
+ @aces_high = opts[:aces_high]
11
+ initialize_cards(opts)
12
+ end
13
+
14
+ def find_card(*args, &block)
15
+ find_cards(*args, &block).first
16
+ end
17
+
18
+ def find_cards(*args, &block)
19
+ args.map! { |arg| Value.parse(arg) || Suit.parse(arg) || (:joker if %r(\Ajoker\z)i === arg) }.compact!
20
+ cards.each_with_object([]) do |card, arr|
21
+ arr.push(card) if ( !args.empty? && args.all? { |arg| card.send("#{arg}?") } ) || ( block.call(card) if block_given? )
22
+ end
23
+ end
24
+
25
+ def add_card(*args)
26
+ card = Card.new(*args)
27
+ card.deck = self
28
+ push(card)
29
+ end
30
+
31
+ def remove_cards(*args, &block)
32
+ to_be_removed = find_cards(*args, &block)
33
+ delete_if { |card| to_be_removed.include?(card) }
34
+ @dealt.delete_if { |card| to_be_removed.include?(card) }
35
+ to_be_removed
36
+ end
37
+
38
+ def aces_high?
39
+ !!@aces_high
40
+ end
41
+
42
+ def jokers?
43
+ cards.any? { |card| card.joker? }
44
+ end
45
+
46
+ def deal(num = 1)
47
+ dealt = shift(num)
48
+ @dealt.concat(dealt)
49
+ num > 1 ? dealt : dealt.first
50
+ end
51
+
52
+ def shuffle!
53
+ concat(@dealt)
54
+ @dealt.clear
55
+ super
56
+ end
57
+
58
+ def shuffle
59
+ dup.shuffle!
60
+ end
61
+
62
+ def unshuffle!
63
+ concat(@dealt)
64
+ @dealt.clear
65
+ jokers = remove_cards(:joker)
66
+ sort!.concat(jokers)
67
+ end
68
+
69
+ def unshuffle
70
+ dup.unshuffle!
71
+ end
72
+
73
+ def initialize_cards(opts = {})
74
+ VALUES[1..-1].product(SUITS) { |value, suit| add_card(value, suit) }
75
+ 2.times { add_card(:joker) } if opts[:jokers]
76
+ opts[:shuffle] ? shuffle! : unshuffle!
77
+ end
78
+
79
+ def valid?
80
+ deck = unshuffle
81
+ return true if deck.remove_cards(:joker).count == 2 && deck.valid?
82
+ deck.reduce do |memo, card|
83
+ return false unless memo < card
84
+ card
85
+ end
86
+ return false unless deck.count == 52
87
+ true
88
+ end
89
+
90
+ private
91
+
92
+ def cards
93
+ self + @dealt
94
+ end
95
+ end