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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.bundle/config +2 -0
  3. data/.github/workflows/release-gem.yml +62 -0
  4. data/.github/workflows/test.yml +23 -0
  5. data/.github/workflows/utils.rb +56 -0
  6. data/.gitignore +11 -0
  7. data/Gemfile +5 -0
  8. data/Gemfile.lock +284 -0
  9. data/LICENSE +21 -0
  10. data/Podfile +31 -0
  11. data/Podfile.lock +59 -0
  12. data/README.md +21 -0
  13. data/Rakefile +100 -0
  14. data/RubySolitaire/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  15. data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/Contents.json +98 -0
  16. data/RubySolitaire/Assets.xcassets/Contents.json +6 -0
  17. data/RubySolitaire/BridgingHeader.h +10 -0
  18. data/RubySolitaire/GameView.swift +47 -0
  19. data/RubySolitaire/Preview Content/Preview Assets.xcassets/Contents.json +6 -0
  20. data/RubySolitaire/RubySolitaireApp.swift +10 -0
  21. data/RubySolitaireTests/RubySolitaireTests.swift +28 -0
  22. data/RubySolitaireUITests/RubySolitaireUITests.swift +35 -0
  23. data/RubySolitaireUITests/RubySolitaireUITestsLaunchTests.swift +25 -0
  24. data/VERSION +1 -0
  25. data/data/button.mp3 +0 -0
  26. data/data/card.png +0 -0
  27. data/data/deal1.mp3 +0 -0
  28. data/data/deal2.mp3 +0 -0
  29. data/data/deal3.mp3 +0 -0
  30. data/data/flip.mp3 +0 -0
  31. data/data/noop.mp3 +0 -0
  32. data/lib/rubysketch/solitaire/background.rb +34 -0
  33. data/lib/rubysketch/solitaire/card.rb +256 -0
  34. data/lib/rubysketch/solitaire/common/animation.rb +116 -0
  35. data/lib/rubysketch/solitaire/common/button.rb +67 -0
  36. data/lib/rubysketch/solitaire/common/dialog.rb +103 -0
  37. data/lib/rubysketch/solitaire/common/history.rb +94 -0
  38. data/lib/rubysketch/solitaire/common/particle.rb +71 -0
  39. data/lib/rubysketch/solitaire/common/scene.rb +128 -0
  40. data/lib/rubysketch/solitaire/common/score.rb +37 -0
  41. data/lib/rubysketch/solitaire/common/settings.rb +31 -0
  42. data/lib/rubysketch/solitaire/common/shake.rb +48 -0
  43. data/lib/rubysketch/solitaire/common/sound.rb +6 -0
  44. data/lib/rubysketch/solitaire/common/timer.rb +35 -0
  45. data/lib/rubysketch/solitaire/common/transitions.rb +149 -0
  46. data/lib/rubysketch/solitaire/common/utils.rb +89 -0
  47. data/lib/rubysketch/solitaire/extension.rb +25 -0
  48. data/lib/rubysketch/solitaire/klondike.rb +676 -0
  49. data/lib/rubysketch/solitaire/places.rb +177 -0
  50. data/lib/rubysketch/solitaire/start.rb +19 -0
  51. data/lib/rubysketch/solitaire.rb +80 -0
  52. data/main.rb +1 -0
  53. data/project.yml +91 -0
  54. data/solitaire.gemspec +28 -0
  55. metadata +137 -0
@@ -0,0 +1,11 @@
1
+ {
2
+ "colors" : [
3
+ {
4
+ "idiom" : "universal"
5
+ }
6
+ ],
7
+ "info" : {
8
+ "author" : "xcode",
9
+ "version" : 1
10
+ }
11
+ }
@@ -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,6 @@
1
+ {
2
+ "info" : {
3
+ "author" : "xcode",
4
+ "version" : 1
5
+ }
6
+ }
@@ -0,0 +1,10 @@
1
+ #ifndef BridgingHeader_h
2
+ #define BridgingHeader_h
3
+
4
+
5
+ #import <CRuby.h>
6
+ #import <RubySketch.h>
7
+ #import "../src/ios/view_controller.h"
8
+
9
+
10
+ #endif /* BridgingHeader_h */
@@ -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,6 @@
1
+ {
2
+ "info" : {
3
+ "author" : "xcode",
4
+ "version" : 1
5
+ }
6
+ }
@@ -0,0 +1,10 @@
1
+ import SwiftUI
2
+
3
+ @main
4
+ struct RubySolitaireApp: App {
5
+ var body: some Scene {
6
+ WindowGroup {
7
+ GameView()
8
+ }
9
+ }
10
+ }
@@ -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