rubysketch-solitaire 0.1.0

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