patience 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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