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.
- data/CHANGELOG.md +6 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +17 -0
- data/LICENSE +19 -0
- data/README.md +111 -0
- data/Rakefile +11 -0
- data/bin/patience +5 -0
- data/lib/patience.rb +13 -0
- data/lib/patience/area.rb +62 -0
- data/lib/patience/card.rb +107 -0
- data/lib/patience/core_ext/class.rb +17 -0
- data/lib/patience/core_ext/core_ext.rb +1 -0
- data/lib/patience/core_ext/object.rb +37 -0
- data/lib/patience/core_ext/string.rb +33 -0
- data/lib/patience/cursor.rb +66 -0
- data/lib/patience/deck.rb +21 -0
- data/lib/patience/event_handlers/click.rb +99 -0
- data/lib/patience/event_handlers/drag.rb +36 -0
- data/lib/patience/event_handlers/drop.rb +147 -0
- data/lib/patience/foundation.rb +30 -0
- data/lib/patience/game.rb +10 -0
- data/lib/patience/pile.rb +78 -0
- data/lib/patience/processable.rb +87 -0
- data/lib/patience/rank.rb +56 -0
- data/lib/patience/scenes/game_scene.rb +66 -0
- data/lib/patience/sprites/card_deck.png +0 -0
- data/lib/patience/sprites/empty_stock.png +0 -0
- data/lib/patience/sprites/pile_background.png +0 -0
- data/lib/patience/stock.rb +20 -0
- data/lib/patience/suit.rb +73 -0
- data/lib/patience/tableau.rb +42 -0
- data/lib/patience/version.rb +3 -0
- data/lib/patience/waste.rb +18 -0
- data/test/patience/core_ext/test_class.rb +28 -0
- data/test/patience/core_ext/test_object.rb +15 -0
- data/test/patience/core_ext/test_string.rb +35 -0
- data/test/patience/event_handlers/test_click.rb +142 -0
- data/test/patience/event_handlers/test_drag.rb +45 -0
- data/test/patience/event_handlers/test_drop.rb +175 -0
- data/test/patience/helper.rb +8 -0
- data/test/patience/scenes/test_game_scene.rb +14 -0
- data/test/patience/test_area.rb +74 -0
- data/test/patience/test_card.rb +165 -0
- data/test/patience/test_cursor.rb +77 -0
- data/test/patience/test_deck.rb +53 -0
- data/test/patience/test_foundation.rb +38 -0
- data/test/patience/test_game.rb +29 -0
- data/test/patience/test_pile.rb +83 -0
- data/test/patience/test_processable.rb +159 -0
- data/test/patience/test_rank.rb +88 -0
- data/test/patience/test_stock.rb +43 -0
- data/test/patience/test_suit.rb +87 -0
- data/test/patience/test_tableau.rb +57 -0
- data/test/patience/test_version.rb +11 -0
- data/test/patience/test_waste.rb +35 -0
- metadata +135 -0
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
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
|
+

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

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

|
54
|
+

|
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
data/bin/patience
ADDED
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
|