rubysketch-solitaire 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.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/.github/workflows/release-gem.yml +62 -0
- data/.github/workflows/test.yml +23 -0
- data/.github/workflows/utils.rb +56 -0
- data/.gitignore +11 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +284 -0
- data/LICENSE +21 -0
- data/Podfile +31 -0
- data/Podfile.lock +59 -0
- data/README.md +21 -0
- data/Rakefile +100 -0
- data/RubySolitaire/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/Contents.json +98 -0
- data/RubySolitaire/Assets.xcassets/Contents.json +6 -0
- data/RubySolitaire/BridgingHeader.h +10 -0
- data/RubySolitaire/GameView.swift +47 -0
- data/RubySolitaire/Preview Content/Preview Assets.xcassets/Contents.json +6 -0
- data/RubySolitaire/RubySolitaireApp.swift +10 -0
- data/RubySolitaireTests/RubySolitaireTests.swift +28 -0
- data/RubySolitaireUITests/RubySolitaireUITests.swift +35 -0
- data/RubySolitaireUITests/RubySolitaireUITestsLaunchTests.swift +25 -0
- data/VERSION +1 -0
- data/data/button.mp3 +0 -0
- data/data/card.png +0 -0
- data/data/deal1.mp3 +0 -0
- data/data/deal2.mp3 +0 -0
- data/data/deal3.mp3 +0 -0
- data/data/flip.mp3 +0 -0
- data/data/noop.mp3 +0 -0
- data/lib/rubysketch/solitaire/background.rb +34 -0
- data/lib/rubysketch/solitaire/card.rb +256 -0
- data/lib/rubysketch/solitaire/common/animation.rb +116 -0
- data/lib/rubysketch/solitaire/common/button.rb +67 -0
- data/lib/rubysketch/solitaire/common/dialog.rb +103 -0
- data/lib/rubysketch/solitaire/common/history.rb +94 -0
- data/lib/rubysketch/solitaire/common/particle.rb +71 -0
- data/lib/rubysketch/solitaire/common/scene.rb +128 -0
- data/lib/rubysketch/solitaire/common/score.rb +37 -0
- data/lib/rubysketch/solitaire/common/settings.rb +31 -0
- data/lib/rubysketch/solitaire/common/shake.rb +48 -0
- data/lib/rubysketch/solitaire/common/sound.rb +6 -0
- data/lib/rubysketch/solitaire/common/timer.rb +35 -0
- data/lib/rubysketch/solitaire/common/transitions.rb +149 -0
- data/lib/rubysketch/solitaire/common/utils.rb +89 -0
- data/lib/rubysketch/solitaire/extension.rb +25 -0
- data/lib/rubysketch/solitaire/klondike.rb +676 -0
- data/lib/rubysketch/solitaire/places.rb +177 -0
- data/lib/rubysketch/solitaire/start.rb +19 -0
- data/lib/rubysketch/solitaire.rb +80 -0
- data/main.rb +1 -0
- data/project.yml +91 -0
- data/solitaire.gemspec +28 -0
- metadata +137 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
{
|
2
|
+
"images" : [
|
3
|
+
{
|
4
|
+
"idiom" : "iphone",
|
5
|
+
"scale" : "2x",
|
6
|
+
"size" : "20x20"
|
7
|
+
},
|
8
|
+
{
|
9
|
+
"idiom" : "iphone",
|
10
|
+
"scale" : "3x",
|
11
|
+
"size" : "20x20"
|
12
|
+
},
|
13
|
+
{
|
14
|
+
"idiom" : "iphone",
|
15
|
+
"scale" : "2x",
|
16
|
+
"size" : "29x29"
|
17
|
+
},
|
18
|
+
{
|
19
|
+
"idiom" : "iphone",
|
20
|
+
"scale" : "3x",
|
21
|
+
"size" : "29x29"
|
22
|
+
},
|
23
|
+
{
|
24
|
+
"idiom" : "iphone",
|
25
|
+
"scale" : "2x",
|
26
|
+
"size" : "40x40"
|
27
|
+
},
|
28
|
+
{
|
29
|
+
"idiom" : "iphone",
|
30
|
+
"scale" : "3x",
|
31
|
+
"size" : "40x40"
|
32
|
+
},
|
33
|
+
{
|
34
|
+
"idiom" : "iphone",
|
35
|
+
"scale" : "2x",
|
36
|
+
"size" : "60x60"
|
37
|
+
},
|
38
|
+
{
|
39
|
+
"idiom" : "iphone",
|
40
|
+
"scale" : "3x",
|
41
|
+
"size" : "60x60"
|
42
|
+
},
|
43
|
+
{
|
44
|
+
"idiom" : "ipad",
|
45
|
+
"scale" : "1x",
|
46
|
+
"size" : "20x20"
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"idiom" : "ipad",
|
50
|
+
"scale" : "2x",
|
51
|
+
"size" : "20x20"
|
52
|
+
},
|
53
|
+
{
|
54
|
+
"idiom" : "ipad",
|
55
|
+
"scale" : "1x",
|
56
|
+
"size" : "29x29"
|
57
|
+
},
|
58
|
+
{
|
59
|
+
"idiom" : "ipad",
|
60
|
+
"scale" : "2x",
|
61
|
+
"size" : "29x29"
|
62
|
+
},
|
63
|
+
{
|
64
|
+
"idiom" : "ipad",
|
65
|
+
"scale" : "1x",
|
66
|
+
"size" : "40x40"
|
67
|
+
},
|
68
|
+
{
|
69
|
+
"idiom" : "ipad",
|
70
|
+
"scale" : "2x",
|
71
|
+
"size" : "40x40"
|
72
|
+
},
|
73
|
+
{
|
74
|
+
"idiom" : "ipad",
|
75
|
+
"scale" : "1x",
|
76
|
+
"size" : "76x76"
|
77
|
+
},
|
78
|
+
{
|
79
|
+
"idiom" : "ipad",
|
80
|
+
"scale" : "2x",
|
81
|
+
"size" : "76x76"
|
82
|
+
},
|
83
|
+
{
|
84
|
+
"idiom" : "ipad",
|
85
|
+
"scale" : "2x",
|
86
|
+
"size" : "83.5x83.5"
|
87
|
+
},
|
88
|
+
{
|
89
|
+
"idiom" : "ios-marketing",
|
90
|
+
"scale" : "1x",
|
91
|
+
"size" : "1024x1024"
|
92
|
+
}
|
93
|
+
],
|
94
|
+
"info" : {
|
95
|
+
"author" : "xcode",
|
96
|
+
"version" : 1
|
97
|
+
}
|
98
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import SwiftUI
|
2
|
+
|
3
|
+
func getDocumentDir() -> URL {
|
4
|
+
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
5
|
+
}
|
6
|
+
|
7
|
+
class GameViewController : ReflexViewController {
|
8
|
+
override func viewDidLoad() {
|
9
|
+
super.viewDidLoad()
|
10
|
+
|
11
|
+
let mainBundleDir = Bundle.main.bundlePath
|
12
|
+
|
13
|
+
CRuby.evaluate("""
|
14
|
+
Encoding.default_internal = Encoding::UTF_8
|
15
|
+
Encoding.default_external = Encoding::UTF_8
|
16
|
+
Warning[:experimental] = false
|
17
|
+
|
18
|
+
%w[
|
19
|
+
lib
|
20
|
+
].each do |lib|
|
21
|
+
$LOAD_PATH.unshift File.join '\(mainBundleDir)', lib
|
22
|
+
end
|
23
|
+
|
24
|
+
Dir.chdir '\(getDocumentDir().path)'
|
25
|
+
""")
|
26
|
+
|
27
|
+
RubySketch.setup()
|
28
|
+
RubySketch.setActiveReflexViewController(self)
|
29
|
+
|
30
|
+
RubySketch.start("\(mainBundleDir)/main.rb");
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
struct GameView: View {
|
35
|
+
var body: some View {
|
36
|
+
GameViewControllerWrapper()
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
struct GameViewControllerWrapper : UIViewControllerRepresentable {
|
41
|
+
func makeUIViewController(context: Context) -> some UIViewController {
|
42
|
+
return GameViewController()
|
43
|
+
}
|
44
|
+
|
45
|
+
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
46
|
+
}
|
47
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import XCTest
|
2
|
+
@testable import RubySolitaire
|
3
|
+
|
4
|
+
class RubySolitaireTests: XCTestCase {
|
5
|
+
|
6
|
+
override func setUpWithError() throws {
|
7
|
+
// Put setup code here. This method is called before the invocation of each test method in the class.
|
8
|
+
}
|
9
|
+
|
10
|
+
override func tearDownWithError() throws {
|
11
|
+
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
12
|
+
}
|
13
|
+
|
14
|
+
func testExample() throws {
|
15
|
+
// This is an example of a functional test case.
|
16
|
+
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
17
|
+
// Any test you write for XCTest can be annotated as throws and async.
|
18
|
+
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
|
19
|
+
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
|
20
|
+
}
|
21
|
+
|
22
|
+
func testPerformanceExample() throws {
|
23
|
+
// This is an example of a performance test case.
|
24
|
+
self.measure {
|
25
|
+
// Put the code you want to measure the time of here.
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import XCTest
|
2
|
+
|
3
|
+
class RubySolitaireUITests: XCTestCase {
|
4
|
+
|
5
|
+
override func setUpWithError() throws {
|
6
|
+
// Put setup code here. This method is called before the invocation of each test method in the class.
|
7
|
+
|
8
|
+
// In UI tests it is usually best to stop immediately when a failure occurs.
|
9
|
+
continueAfterFailure = false
|
10
|
+
|
11
|
+
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
|
12
|
+
}
|
13
|
+
|
14
|
+
override func tearDownWithError() throws {
|
15
|
+
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
16
|
+
}
|
17
|
+
|
18
|
+
func testExample() throws {
|
19
|
+
// UI tests must launch the application that they test.
|
20
|
+
let app = XCUIApplication()
|
21
|
+
app.launch()
|
22
|
+
|
23
|
+
// Use recording to get started writing UI tests.
|
24
|
+
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
25
|
+
}
|
26
|
+
|
27
|
+
func testLaunchPerformance() throws {
|
28
|
+
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
|
29
|
+
// This measures how long it takes to launch your application.
|
30
|
+
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
31
|
+
XCUIApplication().launch()
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import XCTest
|
2
|
+
|
3
|
+
class RubySolitaireUITestsLaunchTests: XCTestCase {
|
4
|
+
|
5
|
+
override class var runsForEachTargetApplicationUIConfiguration: Bool {
|
6
|
+
true
|
7
|
+
}
|
8
|
+
|
9
|
+
override func setUpWithError() throws {
|
10
|
+
continueAfterFailure = false
|
11
|
+
}
|
12
|
+
|
13
|
+
func testLaunch() throws {
|
14
|
+
let app = XCUIApplication()
|
15
|
+
app.launch()
|
16
|
+
|
17
|
+
// Insert steps here to perform after app launch but before taking a screenshot,
|
18
|
+
// such as logging into a test account or navigating somewhere in the app
|
19
|
+
|
20
|
+
let attachment = XCTAttachment(screenshot: app.screenshot())
|
21
|
+
attachment.name = "Launch Screen"
|
22
|
+
attachment.lifetime = .keepAlways
|
23
|
+
add(attachment)
|
24
|
+
}
|
25
|
+
}
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/data/button.mp3
ADDED
Binary file
|
data/data/card.png
ADDED
Binary file
|
data/data/deal1.mp3
ADDED
Binary file
|
data/data/deal2.mp3
ADDED
Binary file
|
data/data/deal3.mp3
ADDED
Binary file
|
data/data/flip.mp3
ADDED
Binary file
|
data/data/noop.mp3
ADDED
Binary file
|
@@ -0,0 +1,34 @@
|
|
1
|
+
using RubySketch
|
2
|
+
|
3
|
+
|
4
|
+
class Background < Scene
|
5
|
+
|
6
|
+
def initialize()
|
7
|
+
super
|
8
|
+
@start = now
|
9
|
+
end
|
10
|
+
|
11
|
+
def draw()
|
12
|
+
pushStyle do
|
13
|
+
checker.set :time, now - @start
|
14
|
+
shader checker
|
15
|
+
rect 0, 0, width, height
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def checker()
|
22
|
+
@checker ||= createShader nil, <<~END
|
23
|
+
varying vec4 vertTexCoord;
|
24
|
+
uniform float time;
|
25
|
+
void main() {
|
26
|
+
float t = mod(time, 32.0) * 16.0;
|
27
|
+
float x = mod(vertTexCoord.x + t, 32.0) < 16.0 ? 1.0 : 0.0;
|
28
|
+
float y = mod(vertTexCoord.y + t, 32.0) < 16.0 ? 1.0 : 0.0;
|
29
|
+
gl_FragColor = x != y ? vec4(0.6, 0.9, 0.7, 1) : vec4(0.7, 0.9, 0.6, 1);
|
30
|
+
}
|
31
|
+
END
|
32
|
+
end
|
33
|
+
|
34
|
+
end# Background
|
@@ -0,0 +1,256 @@
|
|
1
|
+
using RubySketch
|
2
|
+
|
3
|
+
|
4
|
+
class Card
|
5
|
+
|
6
|
+
include HasSprite
|
7
|
+
include Enumerable
|
8
|
+
include Comparable
|
9
|
+
|
10
|
+
MARKS = %i[heart diamond clover spade]
|
11
|
+
|
12
|
+
def initialize(game, mark, number)
|
13
|
+
@game, @mark, @number = game, mark, number
|
14
|
+
@state, @open = :close, 0
|
15
|
+
@place = @next = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :mark, :number, :place
|
19
|
+
|
20
|
+
attr_accessor :next
|
21
|
+
|
22
|
+
def each(&block)
|
23
|
+
return to_enum :each unless block
|
24
|
+
card = self
|
25
|
+
while card
|
26
|
+
next_ = card.next
|
27
|
+
block.call card
|
28
|
+
card = next_
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def hover(rise = 100, base: self.z)
|
34
|
+
self.z = base + rise
|
35
|
+
end
|
36
|
+
|
37
|
+
def name()
|
38
|
+
@name ||= "#{mark}_#{number}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def id()
|
42
|
+
@id ||= "id:#{name}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def place=(place)
|
46
|
+
@place = place
|
47
|
+
self.next&.place = place
|
48
|
+
end
|
49
|
+
|
50
|
+
def pos=(pos)
|
51
|
+
old = self.pos.dup
|
52
|
+
super
|
53
|
+
self.next&.pos += self.pos - old
|
54
|
+
self.pos
|
55
|
+
end
|
56
|
+
|
57
|
+
def x=(x)
|
58
|
+
self.pos = createVector x, self.y, self.z
|
59
|
+
self.x
|
60
|
+
end
|
61
|
+
|
62
|
+
def y=(y)
|
63
|
+
self.pos = createVector self.x, y, self.z
|
64
|
+
self.y
|
65
|
+
end
|
66
|
+
|
67
|
+
def z=(z)
|
68
|
+
self.pos = createVector self.x, self.y, z
|
69
|
+
self.z
|
70
|
+
end
|
71
|
+
|
72
|
+
def open(sec = 0)
|
73
|
+
@state = :open
|
74
|
+
animate(sec) {|t| @open = 180 * t}
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def opened?()
|
79
|
+
@state == :open
|
80
|
+
end
|
81
|
+
|
82
|
+
def close(sec = 0)
|
83
|
+
@state = :close
|
84
|
+
animate(sec) {|t| @open = 180 * (1.0 - t)}
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
def closed?()
|
89
|
+
@state == :close
|
90
|
+
end
|
91
|
+
|
92
|
+
def color()
|
93
|
+
self.class.markColor mark
|
94
|
+
end
|
95
|
+
|
96
|
+
def count()
|
97
|
+
reduce(0) {|n| n + 1}
|
98
|
+
end
|
99
|
+
|
100
|
+
def last?()
|
101
|
+
place.last == self
|
102
|
+
end
|
103
|
+
|
104
|
+
def canDrop?()
|
105
|
+
@game.canDrop? self
|
106
|
+
end
|
107
|
+
|
108
|
+
def <=>(o)
|
109
|
+
number != o.number ? number <=> o.number :
|
110
|
+
mark <=> o.mark
|
111
|
+
end
|
112
|
+
|
113
|
+
def sprite()
|
114
|
+
@sprite ||= Sprite.new(0, 0, *spriteSize, image: closedImage).tap do |sp|
|
115
|
+
sp.pivot = [rand, rand]
|
116
|
+
sp.angle = rand -5.0..5.0
|
117
|
+
sp.update do
|
118
|
+
sp.image = @open > 90 ? openedImage : closedImage
|
119
|
+
end
|
120
|
+
sp.draw do |&draw|
|
121
|
+
push do
|
122
|
+
px, py = *sp.pivot
|
123
|
+
translate px, py
|
124
|
+
rotate -sp.angle
|
125
|
+
translate 2, 5
|
126
|
+
rotate sp.angle
|
127
|
+
translate -px, -py
|
128
|
+
fill 0, 50
|
129
|
+
rect 0, 0, w, h, 4
|
130
|
+
end
|
131
|
+
translate sp.w / 2, sp.h / 2
|
132
|
+
rotate @open
|
133
|
+
translate -sp.w / 2, -sp.h / 2
|
134
|
+
image sp.image, 0, 0, w, h
|
135
|
+
end
|
136
|
+
sp.mousePressed do
|
137
|
+
mousePressed sp.mouseX, sp.mouseY
|
138
|
+
end
|
139
|
+
sp.mouseReleased do
|
140
|
+
mouseReleased sp.mouseX, sp.mouseY, sp.clickCount
|
141
|
+
end
|
142
|
+
sp.mouseDragged do
|
143
|
+
x, y = sp.mouseX, sp.mouseY
|
144
|
+
mouseDragged x, y, x - sp.pmouseX, y - sp.pmouseY
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def inspect()
|
150
|
+
"#<Card #{name}>"
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def mousePressed(x, y)
|
156
|
+
@prevPlace = place
|
157
|
+
@startPos = createVector x, y, self.z
|
158
|
+
hover
|
159
|
+
end
|
160
|
+
|
161
|
+
def mouseReleased(x, y, clickCount)
|
162
|
+
self.z = @startPos.z if @startPos
|
163
|
+
pos = sprite.to_screen createVector x, y
|
164
|
+
@game.cardDropped pos.x, pos.y, self, @prevPlace if
|
165
|
+
@prevPlace && clickCount == 0
|
166
|
+
end
|
167
|
+
|
168
|
+
def mouseDragged(x, y, dx, dy)
|
169
|
+
self.pos += createVector x - @startPos.x, y - @startPos.y if @startPos
|
170
|
+
end
|
171
|
+
|
172
|
+
def openedImage()
|
173
|
+
@openedImage ||= createGraphics(*self.class.cardSize).tap do |g|
|
174
|
+
c, w, h, m = self.class, g.width, g.height, 16# margin
|
175
|
+
image = c.cardImage
|
176
|
+
nx, ny, nw, nh = c.numberRect number
|
177
|
+
mx, my, mw, mh = c.markRect mark
|
178
|
+
mnh = m + nh
|
179
|
+
mxx, myy = (w - mw) / 2, mnh + ((h - mnh) - mh) / 2
|
180
|
+
g.beginDraw
|
181
|
+
g.angleMode DEGREES
|
182
|
+
g.translate w / 2, h / 2
|
183
|
+
g.rotate 180
|
184
|
+
g.translate -w / 2, -h / 2
|
185
|
+
g.copy image, 896, 0, w, h, 0, 0, w, h
|
186
|
+
g.tint *c.markColor(mark)
|
187
|
+
g.copy image, nx, ny, nw, nh, m, m, nw, nh
|
188
|
+
g.copy image, mx, my, mw, mh, mxx, myy, mw, mh
|
189
|
+
g.endDraw
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def closedImage()
|
194
|
+
self.class.closedImages[3]
|
195
|
+
end
|
196
|
+
|
197
|
+
def spriteSize()
|
198
|
+
self.class.spriteSize
|
199
|
+
end
|
200
|
+
|
201
|
+
def self.closedImages()
|
202
|
+
@closedImages ||= (0..3).map {|n| n * 256}.map do |x|
|
203
|
+
createGraphics(*cardSize).tap do |g|
|
204
|
+
w, h = g.width, g.height
|
205
|
+
g.beginDraw
|
206
|
+
g.copy cardImage, x, 256, w, h, 0, 0, w, h
|
207
|
+
g.endDraw
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def self.cardImage()
|
213
|
+
@cardImage ||= loadImage dataPath 'card.png'
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.cardSize()
|
217
|
+
[164, 252]
|
218
|
+
end
|
219
|
+
|
220
|
+
def self.spriteSize()
|
221
|
+
@spriteSize ||= cardSize.then do |cw, ch|
|
222
|
+
ncolumns = 7
|
223
|
+
size = [width, height].min
|
224
|
+
cardWidth = (size - margin * (ncolumns + 1)) / ncolumns
|
225
|
+
[cardWidth, cardWidth * (ch.to_f / cw.to_f)]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def self.margin()
|
230
|
+
@marin ||= [width, height].min * 0.02
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.markRect(mark)
|
234
|
+
w = h = 128
|
235
|
+
[MARKS.index(mark) * w, 0, w, h]
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.numberRect(number)
|
239
|
+
w = h = 64
|
240
|
+
[(number - 1) * w, 128, w, h]
|
241
|
+
end
|
242
|
+
|
243
|
+
def self.markColor(mark)
|
244
|
+
MARKS[0, 2].include?(mark) ? [255, 111, 61] : [62, 79, 60]
|
245
|
+
end
|
246
|
+
|
247
|
+
def __find_or_last_and_prev(card = nil)
|
248
|
+
prev, it = nil, self
|
249
|
+
while it.next
|
250
|
+
break if it == card
|
251
|
+
prev, it = it, it.next
|
252
|
+
end
|
253
|
+
return it, prev
|
254
|
+
end
|
255
|
+
|
256
|
+
end# Card
|