patience 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 (56) hide show
  1. data/CHANGELOG.md +6 -0
  2. data/Gemfile +5 -0
  3. data/Gemfile.lock +17 -0
  4. data/LICENSE +19 -0
  5. data/README.md +111 -0
  6. data/Rakefile +11 -0
  7. data/bin/patience +5 -0
  8. data/lib/patience.rb +13 -0
  9. data/lib/patience/area.rb +62 -0
  10. data/lib/patience/card.rb +107 -0
  11. data/lib/patience/core_ext/class.rb +17 -0
  12. data/lib/patience/core_ext/core_ext.rb +1 -0
  13. data/lib/patience/core_ext/object.rb +37 -0
  14. data/lib/patience/core_ext/string.rb +33 -0
  15. data/lib/patience/cursor.rb +66 -0
  16. data/lib/patience/deck.rb +21 -0
  17. data/lib/patience/event_handlers/click.rb +99 -0
  18. data/lib/patience/event_handlers/drag.rb +36 -0
  19. data/lib/patience/event_handlers/drop.rb +147 -0
  20. data/lib/patience/foundation.rb +30 -0
  21. data/lib/patience/game.rb +10 -0
  22. data/lib/patience/pile.rb +78 -0
  23. data/lib/patience/processable.rb +87 -0
  24. data/lib/patience/rank.rb +56 -0
  25. data/lib/patience/scenes/game_scene.rb +66 -0
  26. data/lib/patience/sprites/card_deck.png +0 -0
  27. data/lib/patience/sprites/empty_stock.png +0 -0
  28. data/lib/patience/sprites/pile_background.png +0 -0
  29. data/lib/patience/stock.rb +20 -0
  30. data/lib/patience/suit.rb +73 -0
  31. data/lib/patience/tableau.rb +42 -0
  32. data/lib/patience/version.rb +3 -0
  33. data/lib/patience/waste.rb +18 -0
  34. data/test/patience/core_ext/test_class.rb +28 -0
  35. data/test/patience/core_ext/test_object.rb +15 -0
  36. data/test/patience/core_ext/test_string.rb +35 -0
  37. data/test/patience/event_handlers/test_click.rb +142 -0
  38. data/test/patience/event_handlers/test_drag.rb +45 -0
  39. data/test/patience/event_handlers/test_drop.rb +175 -0
  40. data/test/patience/helper.rb +8 -0
  41. data/test/patience/scenes/test_game_scene.rb +14 -0
  42. data/test/patience/test_area.rb +74 -0
  43. data/test/patience/test_card.rb +165 -0
  44. data/test/patience/test_cursor.rb +77 -0
  45. data/test/patience/test_deck.rb +53 -0
  46. data/test/patience/test_foundation.rb +38 -0
  47. data/test/patience/test_game.rb +29 -0
  48. data/test/patience/test_pile.rb +83 -0
  49. data/test/patience/test_processable.rb +159 -0
  50. data/test/patience/test_rank.rb +88 -0
  51. data/test/patience/test_stock.rb +43 -0
  52. data/test/patience/test_suit.rb +87 -0
  53. data/test/patience/test_tableau.rb +57 -0
  54. data/test/patience/test_version.rb +11 -0
  55. data/test/patience/test_waste.rb +35 -0
  56. metadata +135 -0
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ Patience changelog
2
+ ==================
3
+
4
+ ## v0.1.0 (March 06, 2012)
5
+
6
+ * Initial release.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ gem 'ray', '~> 0.2.0'
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,17 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ patience (0.0.0)
5
+ ray (~> 0.2.0)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ ray (0.2.0)
11
+
12
+ PLATFORMS
13
+ ruby
14
+
15
+ DEPENDENCIES
16
+ patience!
17
+ ray (~> 0.2.0)
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2012 Kyrylo Silin
2
+
3
+ This software is provided 'as-is', without any express or implied
4
+ warranty. In no event will the authors be held liable for any damages
5
+ arising from the use of this software.
6
+
7
+ Permission is granted to anyone to use this software for any purpose,
8
+ including commercial applications, and to alter it and redistribute it
9
+ freely, subject to the following restrictions:
10
+
11
+ 1. The origin of this software must not be misrepresented; you must not
12
+ claim that you wrote the original software. If you use this software
13
+ in a product, an acknowledgment in the product documentation would be
14
+ appreciated but is not required.
15
+
16
+ 2. Altered source versions must be plainly marked as such, and must not be
17
+ misrepresented as being the original software.
18
+
19
+ 3. This notice may not be removed or altered from any source distribution.
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ Patience
2
+ ========
3
+
4
+ * [https://github.com/kyrylo/patience/](https://github.com/kyrylo/patience/ "Home page")
5
+
6
+ Description
7
+ -----------
8
+
9
+ ![Patience, version 0.1.0](http://img-fotki.yandex.ru/get/5908/98991937.6/0_7259e_d9e01c58_orig "Patience, version 0.1.0")
10
+
11
+ _Patience_ is a card game (also known as Solitaire), which is written in Ruby.
12
+ The game is based on Ray, a Ruby library for games. Currenlty, _Patience_ runs
13
+ _only_ on GNU/Linux, although, I'm planning to support it on Windows.
14
+
15
+ How to play
16
+ -----------
17
+
18
+ The play area of _Patience_ (Solitaire) consits of four zones: Waste, Tableau,
19
+ Stock and Foundation. The goal of the game is to allocate all the cards from
20
+ Tableau to Foundation. You can drag cards from pile to pile with your mouse.
21
+ Remember, you can't drop cards anywhere: there are some rules, restricting that
22
+ mess. Brief rules:
23
+
24
+ ![Patience, rules](http://img-fotki.yandex.ru/get/6101/98991937.7/0_726e0_789437d8_orig "Patience, rules")
25
+
26
+ ### Stock
27
+
28
+ * In the very beginning of each game comprises of 24 cards
29
+ * A click on Stock reveals its card and moves it to Waste
30
+ * You can't drag cards from Stock
31
+ * You can't drop cards onto Stock
32
+ * If Stock is empty, you can refresh it with cards from Waste (if there are any)
33
+ by clicking the former again.
34
+
35
+ ### Waste
36
+
37
+ * In the very beginning of each game has no cards
38
+ * You can drag cards from Waste and drop them onto Tableau
39
+ * You can't drop cards onto Waste
40
+
41
+ ### Tableau
42
+
43
+ * In the very beginning of the game comprises of 28 cards
44
+ * Consists of 7 piles. Each pile has `number of pile` cards
45
+ * Accepts cards from Waste and other piles from Tableau
46
+ * You can drop _only_ a King onto the freed pile
47
+ * You can move whole (valid) piles onto a proper cards
48
+ * You can drop a card onto a card in the pile, which's lower by one rank and has
49
+ suit of different color.
50
+
51
+ Example:
52
+
53
+ ![Correct drop](http://img-fotki.yandex.ru/get/6200/98991937.7/0_726e1_bc6edf54_orig "Correct drop")
54
+ ![Wrong drop](http://img-fotki.yandex.ru/get/6200/98991937.7/0_726e3_640d9de0_orig "Wrong drop")
55
+
56
+ ### Foundation
57
+
58
+ * In the very beginning of each game has no cards
59
+ * Consists of 4 piles
60
+ * If the pile of Foundation is empty, it can accept only an Ace.
61
+ * If the pile of Foundation isn't empty, it will accept only cards of the same
62
+ suit, that are higher by one rank.
63
+
64
+ ### Winning conditions
65
+
66
+ * All the cards are in Foundation, other zones has no cards.
67
+
68
+ Requirements
69
+ ------------
70
+
71
+ ### Runs on
72
+
73
+ * GNU/Linux
74
+
75
+ ### Dependencies
76
+
77
+ * [Ruby](http://ruby-lang.org/ "Ruby")
78
+ (Tested _only_ on 1.9.3 version)
79
+
80
+ * [Ray](https://github.com/Mon-Ouie/ray/ "Ray") (0.2.0)
81
+
82
+ Installation
83
+ ------------
84
+
85
+ ### Regular GNU/Linux way
86
+
87
+ gem install patience
88
+
89
+ To launch the game:
90
+
91
+ % patience
92
+
93
+ ### Irregular way
94
+
95
+ git clone git://github.com/kyrylo/patience.git patience
96
+
97
+ To launch the game:
98
+
99
+ % cd patience
100
+ % ruby bin/patience
101
+
102
+ Credits
103
+ -------
104
+
105
+ * Thanks to [Bil Bas (Spooner)](https://github.com/Spooner "Bil Bas") for giving advices and answering my questions
106
+ * Thanks to [Mon Ouïe](https://github.com/Mon-Ouie "Mon Ouïe") for Ray
107
+
108
+ License
109
+ -------
110
+
111
+ The project uses Zlib License. See LICENSE file for more information.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'rake/testtask'
2
+
3
+ desc 'Run tests'
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'lib'
6
+ t.libs << 'test'
7
+ t.pattern = 'test/**/test_*.rb'
8
+ t.verbose = true
9
+ end
10
+
11
+ task :default => :test
data/bin/patience ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/patience'
4
+
5
+ Patience::Game.new.run
data/lib/patience.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'ray'
2
+ require 'forwardable'
3
+
4
+ def path_of(resource)
5
+ File.join(File.dirname(__FILE__), resource)
6
+ end
7
+
8
+ Dir.chdir(File.dirname(__FILE__)) do
9
+ Dir['patience/event_handlers/*.rb'].each { |eh| require_relative eh }
10
+ Dir['patience/core_ext/*.rb'].each { |ext| require_relative ext }
11
+ Dir['patience/scenes/*.rb'].each { |scene| require_relative scene }
12
+ Dir['patience/*.rb'].each { |file| require_relative file }
13
+ end
@@ -0,0 +1,62 @@
1
+ module Patience
2
+ ###
3
+ # Patience::Area provides area objects, that consist of piles. The goal of
4
+ # this class is to assemble piles into a logical bundle with a few common
5
+ # methods, so they could be controlled via only one interface. Basically,
6
+ # this class is useless on its own. The purpose of Area is to be inherited
7
+ # by other classes, which are more verbose in their intentions. By default,
8
+ # Area object instantiates without any cards and only with one pile. Every
9
+ # new pile would be created empty, even though you feed some cards to the
10
+ # new object. All the cards you provide to the new object, wouldn't be
11
+ # placed anywhere. You have to allocate them manually.
12
+ # field = Area.new
13
+ # field.piles #=> [#<Patience::Pile>]
14
+ # filed.piles[0].pos #=> (0, 0)
15
+ # field.pos #=> (0, 0)
16
+ #
17
+ class Area
18
+ # Returns an array of piles in the area.
19
+ attr_reader :piles
20
+
21
+ def initialize(cards=[], piles_num=1)
22
+ @piles = []
23
+ @cards = Pile.new(cards)
24
+ piles_num.times { @piles << Pile.new }
25
+ end
26
+
27
+ # Shows position of the area. The position of the very
28
+ # first pile in the area, counts as its actual position.
29
+ def pos
30
+ piles.first.pos
31
+ end
32
+
33
+ # Sets the position of every pile in the area to the same value.
34
+ def pos=(pos)
35
+ piles.each { |pile| pile.pos = *pos }
36
+ end
37
+
38
+ # Collects all cards in every pile and returns the array of these
39
+ # cards. If there are no cards in the area, returns an empty array.
40
+ def cards
41
+ piles.inject([]) { |cards, pile| cards << pile.cards }.flatten
42
+ end
43
+
44
+ # Draws each pile of the area in the window.
45
+ def draw_on(win)
46
+ piles.each { |pile| pile.draw_on(win) }
47
+ end
48
+
49
+ # Returns pile in the area, which has been
50
+ # clicked. If there is no such, returns nil.
51
+ def hit?(mouse_pos)
52
+ piles.find { |pile| pile.hit?(mouse_pos) }
53
+ end
54
+
55
+ # Adds card from outer_pile to the very first
56
+ # pile of the area, flipping it en route.
57
+ def add_from(outer_pile, card)
58
+ card.flip! and piles.first << outer_pile.remove(card)
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,107 @@
1
+ module Patience
2
+ ###
3
+ # Patience::Card is a card creator class. Cards have ranks and suits. Every
4
+ # card has its own sprite. Sprite is a one big image, which contains every
5
+ # play card in the game. A sprite for a card is chosen by shift on the sprite.
6
+ # The shift is determined by integer values on X and Y axis repsectively.
7
+ # card = Card.new(13, 3)
8
+ # card.to_s #=> "Ace of Spades"
9
+ # card.face_down
10
+ # card.face_up? #=> false
11
+ #
12
+ class Card
13
+ extend Forwardable
14
+
15
+ attr_reader :rank, :suit, :sprite
16
+
17
+ # Creates new card object. Both arguments should be Fixnums
18
+ # in the valid ranges. Also, every card has its sprite, which
19
+ # is nothing but an instance of the Ray::Sprite class.
20
+ def initialize(rank, suit)
21
+ @rank = case rank
22
+ when 1 then Rank::Ace.new
23
+ when 2 then Rank::Two.new
24
+ when 3 then Rank::Three.new
25
+ when 4 then Rank::Four.new
26
+ when 5 then Rank::Five.new
27
+ when 6 then Rank::Six.new
28
+ when 7 then Rank::Seven.new
29
+ when 8 then Rank::Eight.new
30
+ when 9 then Rank::Nine.new
31
+ when 10 then Rank::Ten.new
32
+ when 11 then Rank::Jack.new
33
+ when 12 then Rank::Queen.new
34
+ when 13 then Rank::King.new
35
+ else
36
+ raise DefunctRank, "Nonexistent rank: #{rank}"
37
+ end
38
+ @suit = case suit
39
+ when 1 then Suit::Heart.new
40
+ when 2 then Suit::Diamond.new
41
+ when 3 then Suit::Spade.new
42
+ when 4 then Suit::Club.new
43
+ else
44
+ raise DefunctSuit, "Nonexistent suit: #{suit}"
45
+ end
46
+ @sprite = Ray::Sprite.new path_of('patience/sprites/card_deck.png')
47
+ # A sheet with 14 columns and 5 rows. First row and column
48
+ # corresponds to the card back (their coordinats are equal to [0, 0]).
49
+ @sprite.sheet_size = [14, 5]
50
+ @sprite.sheet_pos = [rank, suit]
51
+ end
52
+
53
+ # Prints human readable rank and suit of the card.
54
+ def to_s
55
+ "#{rank} of #{suit}"
56
+ end
57
+
58
+ # Turns the card to its face.
59
+ def face_up
60
+ sprite.sheet_pos = [rank.to_i, suit.to_i]
61
+ end
62
+
63
+ # The opposite of the Card#face_down? method. Returns true
64
+ # if the card is turned to its face. Otherwise, returns false.
65
+ def face_up?
66
+ self.not.face_down?
67
+ end
68
+
69
+ # Turns the card to its back (sets position of the sprite to zero values).
70
+ def face_down
71
+ sprite.sheet_pos = [0, 0]
72
+ end
73
+
74
+ # Returns true if the card is turned to its back (it means, that the
75
+ # sprite of the card is set position of zero). Otherwise, returns false.
76
+ def face_down?
77
+ sprite.sheet_pos == [0, 0]
78
+ end
79
+
80
+ # Either turns the card to its face if it's faced
81
+ # down or turns it to its back if it's faced up.
82
+ def flip!
83
+ (face_up if face_down?) or (face_down if face_up?)
84
+ end
85
+
86
+ # Draws the sprite of card in the window.
87
+ def draw_on(win)
88
+ win.draw(sprite)
89
+ end
90
+
91
+ # Compares two cards with each other,
92
+ # considering their rank and suit equality.
93
+ def eql?(other_card)
94
+ (@rank == other_card.rank) and (@suit == other_card.suit)
95
+ end
96
+
97
+ def overlaps?(other_card)
98
+ sprite.collide?(other_card.sprite) and self.not.eql?(other_card)
99
+ end
100
+
101
+ def_delegators :@sprite, :pos, :pos=, :x, :y, :to_rect
102
+ def_delegator :"@sprite.to_rect", :contain?, :hit?
103
+
104
+ class DefunctRank < StandardError; end
105
+ class DefunctSuit < StandardError; end
106
+ end
107
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'core_ext'
2
+
3
+ class Class
4
+ rake_extension("descendants") do
5
+
6
+ # Returns the array of all descendants of a class. Although there is
7
+ # faster way to do this, I want to stick with this solutions, since
8
+ # it's much simpler to understand and performance isn't crucial for now.
9
+ #
10
+ # The example of faster implementation lives in:
11
+ # rails/activesupport/lib/active_support/descendants_tracker.rb
12
+ def descendants
13
+ ObjectSpace.each_object(::Class).select {|klass| klass < self }
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1 @@
1
+ require 'rake'
@@ -0,0 +1,37 @@
1
+ require_relative 'core_ext'
2
+
3
+ class Object
4
+ rake_extension("not") do
5
+ def not
6
+ Not.new(self)
7
+ end
8
+ end
9
+
10
+ ###
11
+ # The original idea belongs to Jay Fields:
12
+ # http://blog.jayfields.com/2007/08/ruby-adding-not-method-for-readability.html
13
+ #
14
+ # The Object::Not instances are proxies, that send all calls back to the
15
+ # original instance. The Object::Not class privatizes almost all of its
16
+ # methods so that most method calls will be handled by method_missing.
17
+ class Not
18
+ private *instance_methods.select { |m| m !~ /(^__|^\W|^binding$)/ }
19
+
20
+ rake_extension("initialize") do
21
+ def initialize(subject)
22
+ @subject = subject
23
+ end
24
+ end
25
+
26
+ rake_extension("method_missing") do
27
+
28
+ # Forwards on any method call to the subject and negates the result.
29
+ def method_missing(sym, *args, &block)
30
+ !@subject.send(sym, *args, &block)
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,33 @@
1
+ require_relative 'core_ext'
2
+
3
+ class String
4
+ rake_extension("demodulize") do
5
+
6
+ # Removes the module part from the
7
+ # constant expression in the string.
8
+ def demodulize
9
+ self.to_s.gsub(/^.*::/, '')
10
+ end
11
+
12
+ end
13
+
14
+ rake_extension("constantize") do
15
+
16
+ # Constantize tries to find a declared constant with the name specified
17
+ # in the string. It raises a NameError when the name is not in CamelCase
18
+ # or is not initialized.
19
+ # Example:
20
+ # "Module".constantize #=> Module
21
+ # "Class".constantize #=> Class
22
+ # "yadda".constantize #=> NameError: yadda is not a valid constant name!
23
+ #
24
+ def constantize
25
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
26
+ raise NameError, "#{self.inspect} is not a valid constant name!"
27
+ end
28
+
29
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
30
+ end
31
+
32
+ end
33
+ end