rubysketch-solitaire 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +3 -0
  3. data/.gitignore +2 -0
  4. data/.hooks/pre-commit +69 -0
  5. data/ChangeLog.md +21 -0
  6. data/Gemfile.lock +10 -10
  7. data/Podfile.lock +33 -33
  8. data/Rakefile +107 -10
  9. data/RubySolitaire/Ad.swift +88 -0
  10. data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/AppIcon.png +0 -0
  11. data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/Contents.json +3 -87
  12. data/RubySolitaire/Base.lproj/Localizable.strings +6 -0
  13. data/RubySolitaire/Extensions.swift +5 -0
  14. data/RubySolitaire/GameView.swift +75 -13
  15. data/RubySolitaire/Helper.swift +70 -0
  16. data/RubySolitaire/MenuScreen.swift +140 -0
  17. data/RubySolitaire/RubySolitaireApp.swift +188 -1
  18. data/RubySolitaire/SafariView.swift +16 -0
  19. data/RubySolitaire/Strings.swift +48 -0
  20. data/RubySolitaire/ja.lproj/InfoPlist.strings +4 -0
  21. data/RubySolitaire/ja.lproj/Localizable.strings +6 -0
  22. data/VERSION +1 -1
  23. data/data/card.png +0 -0
  24. data/data/classicPSPWave.glsl +94 -0
  25. data/data/colorfulUnderwaterBubbles2.glsl +113 -0
  26. data/data/cosmic2.glsl +121 -0
  27. data/data/reflectiveHexes.glsl +219 -0
  28. data/fastlane/Fastfile +76 -0
  29. data/fastlane/metadata/copyright.txt +1 -0
  30. data/fastlane/metadata/en-US/apple_tv_privacy_policy.txt +1 -0
  31. data/fastlane/metadata/en-US/description.txt +5 -0
  32. data/fastlane/metadata/en-US/keywords.txt +1 -0
  33. data/fastlane/metadata/en-US/marketing_url.txt +1 -0
  34. data/fastlane/metadata/en-US/name.txt +1 -0
  35. data/fastlane/metadata/en-US/privacy_url.txt +1 -0
  36. data/fastlane/metadata/en-US/promotional_text.txt +1 -0
  37. data/fastlane/metadata/en-US/release_notes.txt +1 -0
  38. data/fastlane/metadata/en-US/subtitle.txt +1 -0
  39. data/fastlane/metadata/en-US/support_url.txt +1 -0
  40. data/fastlane/metadata/ja/apple_tv_privacy_policy.txt +1 -0
  41. data/fastlane/metadata/ja/description.txt +5 -0
  42. data/fastlane/metadata/ja/keywords.txt +1 -0
  43. data/fastlane/metadata/ja/marketing_url.txt +1 -0
  44. data/fastlane/metadata/ja/name.txt +1 -0
  45. data/fastlane/metadata/ja/privacy_url.txt +1 -0
  46. data/fastlane/metadata/ja/promotional_text.txt +1 -0
  47. data/fastlane/metadata/ja/release_notes.txt +1 -0
  48. data/fastlane/metadata/ja/subtitle.txt +1 -0
  49. data/fastlane/metadata/ja/support_url.txt +1 -0
  50. data/fastlane/metadata/primary_category.txt +1 -0
  51. data/fastlane/metadata/primary_first_sub_category.txt +1 -0
  52. data/fastlane/metadata/primary_second_sub_category.txt +1 -0
  53. data/fastlane/metadata/review_information/demo_password.txt +1 -0
  54. data/fastlane/metadata/review_information/demo_user.txt +1 -0
  55. data/fastlane/metadata/review_information/email_address.txt +1 -0
  56. data/fastlane/metadata/review_information/first_name.txt +0 -0
  57. data/fastlane/metadata/review_information/last_name.txt +0 -0
  58. data/fastlane/metadata/review_information/notes.txt +1 -0
  59. data/fastlane/metadata/review_information/phone_number.txt +0 -0
  60. data/fastlane/metadata/secondary_category.txt +1 -0
  61. data/fastlane/metadata/secondary_first_sub_category.txt +1 -0
  62. data/fastlane/metadata/secondary_second_sub_category.txt +1 -0
  63. data/lib/rubysketch/solitaire/background.rb +115 -12
  64. data/lib/rubysketch/solitaire/card.rb +10 -87
  65. data/lib/rubysketch/solitaire/common/animation.rb +34 -34
  66. data/lib/rubysketch/solitaire/common/button.rb +49 -19
  67. data/lib/rubysketch/solitaire/common/dialog.rb +78 -21
  68. data/lib/rubysketch/solitaire/common/scene.rb +15 -4
  69. data/lib/rubysketch/solitaire/common/settings.rb +10 -2
  70. data/lib/rubysketch/solitaire/common/timer.rb +2 -2
  71. data/lib/rubysketch/solitaire/common/transitions.rb +12 -6
  72. data/lib/rubysketch/solitaire/common/utils.rb +15 -0
  73. data/lib/rubysketch/solitaire/klondike.rb +378 -81
  74. data/lib/rubysketch/solitaire/places.rb +12 -102
  75. data/lib/rubysketch/solitaire/skin.rb +151 -0
  76. data/lib/rubysketch/solitaire.rb +33 -9
  77. data/main.rb +1 -0
  78. data/project.yml +85 -4
  79. metadata +54 -2
@@ -1,15 +1,20 @@
1
1
  import SwiftUI
2
2
 
3
- func getDocumentDir() -> URL {
4
- return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
3
+
4
+ protocol GameViewControllerDelegate {
5
+ func run(command: String)
5
6
  }
6
7
 
7
- class GameViewController : ReflexViewController {
8
+
9
+ class GameViewController: ReflexViewController {
10
+
11
+ var delegate: GameViewControllerDelegate?
12
+
13
+ private var started = false
14
+
8
15
  override func viewDidLoad() {
9
16
  super.viewDidLoad()
10
17
 
11
- let mainBundleDir = Bundle.main.bundlePath
12
-
13
18
  CRuby.evaluate("""
14
19
  Encoding.default_internal = Encoding::UTF_8
15
20
  Encoding.default_external = Encoding::UTF_8
@@ -18,7 +23,7 @@ class GameViewController : ReflexViewController {
18
23
  %w[
19
24
  lib
20
25
  ].each do |lib|
21
- $LOAD_PATH.unshift File.join '\(mainBundleDir)', lib
26
+ $LOAD_PATH.unshift File.join '\(Bundle.main.bundlePath)', lib
22
27
  end
23
28
 
24
29
  Dir.chdir '\(getDocumentDir().path)'
@@ -26,22 +31,79 @@ class GameViewController : ReflexViewController {
26
31
 
27
32
  RubySketch.setup()
28
33
  RubySketch.setActiveReflexViewController(self)
34
+ }
29
35
 
30
- RubySketch.start("\(mainBundleDir)/main.rb");
36
+ private func getDocumentDir() -> URL {
37
+ return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
38
+ }
39
+
40
+ override func viewWillDisappear(_ animated: Bool) {
41
+ RubySketch.resetActiveReflexViewController()
42
+ super.viewWillDisappear(animated)
43
+ }
44
+
45
+ override func viewDidLayoutSubviews() {
46
+ super.viewDidLayoutSubviews()
47
+
48
+ if started {return}
49
+ started = true
50
+
51
+ let frame = self.reflexView.frame
52
+ CRuby.evaluate("""
53
+ ENV['WIDTH'] = \(frame.width).to_s
54
+ ENV['HEIGHT'] = \(frame.height).to_s
55
+ """)
56
+
57
+ RubySketch.start("\(Bundle.main.bundlePath)/main.rb");
31
58
  }
32
- }
33
59
 
34
- struct GameView: View {
35
- var body: some View {
36
- GameViewControllerWrapper()
60
+ override func update() {
61
+ if let delegate = delegate, let command = CRuby.evaluate("$command")?.toString() {
62
+ delegate.run(command: command)
63
+ } else {
64
+ super.update()
65
+ }
37
66
  }
38
67
  }
39
68
 
40
- struct GameViewControllerWrapper : UIViewControllerRepresentable {
69
+
70
+ struct GameView: UIViewControllerRepresentable {
71
+
72
+ @Binding var command: String
73
+
74
+ func makeCoordinator() -> Coordinator {
75
+ Coordinator(command: $command)
76
+ }
77
+
41
78
  func makeUIViewController(context: Context) -> some UIViewController {
42
- return GameViewController()
79
+ let vc = GameViewController()
80
+ vc.delegate = context.coordinator
81
+ return vc
43
82
  }
44
83
 
45
84
  func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
85
+ let c = context.coordinator
86
+ if c.prevCommand != nil && command.isEmpty {
87
+ CRuby.evaluate("$command = nil")
88
+ c.prevCommand = nil
89
+ }
90
+ }
91
+
92
+ class Coordinator: NSObject, GameViewControllerDelegate {
93
+
94
+ @Binding var command: String
95
+
96
+ var prevCommand: String?
97
+
98
+ init(command: Binding<String>) {
99
+ _command = command
100
+ }
101
+
102
+ func run(command: String) {
103
+ if command != prevCommand {
104
+ self.command = command
105
+ prevCommand = command
106
+ }
107
+ }
46
108
  }
47
109
  }
@@ -0,0 +1,70 @@
1
+ class Helper {
2
+
3
+ static var isDebug: Bool {
4
+ #if DEBUG
5
+ true
6
+ #else
7
+ false
8
+ #endif
9
+ }
10
+
11
+ static var isRelease: Bool {
12
+ !isDebug
13
+ }
14
+
15
+ static var isUnitTest: Bool {
16
+ NSClassFromString("XCTest") != nil
17
+ }
18
+
19
+ static var isTestFlight: Bool {
20
+ Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
21
+ }
22
+
23
+ static var isPhone: Bool {
24
+ UIDevice.current.userInterfaceIdiom == .phone
25
+ }
26
+
27
+ static var isPad: Bool {
28
+ UIDevice.current.userInterfaceIdiom == .pad
29
+ }
30
+
31
+ static var isMac: Bool {
32
+ ProcessInfo.processInfo.isiOSAppOnMac
33
+ }
34
+
35
+ static var language: String? {
36
+ guard let lang = Locale.preferredLanguages.first else {
37
+ return nil
38
+ }
39
+ guard let first = lang.split(separator: "-").first else {
40
+ return lang
41
+ }
42
+ return String(first)
43
+ }
44
+
45
+ static var appVersion: String {
46
+ getInfoString("CFBundleShortVersionString")!
47
+ }
48
+
49
+ static func getInfoString(_ key: String, plistName: String? = nil) -> String? {
50
+ guard let dic = getPlist(plistName) else {
51
+ return nil
52
+ }
53
+ return dic[key] as? String
54
+ }
55
+
56
+ static func getPlist(_ name: String? = nil) -> [String: Any]? {
57
+ guard let name = name else {
58
+ return Bundle.main.infoDictionary
59
+ }
60
+ guard let path = Bundle.main.path(forResource: name, ofType: "plist") else {
61
+ return nil
62
+ }
63
+ return NSDictionary(contentsOfFile: path) as? [String: Any]
64
+ }
65
+
66
+ static func openURL(_ url: URL) {
67
+ UIApplication.shared.open(url)
68
+ }
69
+
70
+ }// Helper
@@ -0,0 +1,140 @@
1
+ import SwiftUI
2
+
3
+
4
+ struct MenuScreen: View {
5
+
6
+ @Binding var isVisible: Bool
7
+
8
+ var body: some View {
9
+ NavigationView {
10
+ List {
11
+ PrivacyPolicy()
12
+ Licenses()
13
+ }
14
+ .navigationTitle("\(Strings.appName.s) v\(Helper.appVersion)")
15
+ .navigationBarItems(leading: BackButton())
16
+ }
17
+ }
18
+
19
+ private func BackButton() -> some View {
20
+ Button {
21
+ isVisible = false
22
+ } label: {
23
+ Text(Strings.close.s)
24
+ }
25
+ }
26
+
27
+ private func PrivacyPolicy() -> some View {
28
+ Button {
29
+ Helper.openURL(URL(string: "https://xord.org/rubysolitaire/privacy_policy.html")!)
30
+ } label: {
31
+ Text(Strings.menuPrivacyPolicy.s)
32
+ }
33
+ }
34
+
35
+ private func Licenses() -> some View {
36
+ NavigationLink {
37
+ LicensesScreen()
38
+ } label: {
39
+ Text(Strings.menuLicenses.s)
40
+ }
41
+ }
42
+ }
43
+
44
+
45
+ struct LicensesScreen: View {
46
+ var body: some View {
47
+ ScrollView {
48
+ Text("""
49
+ # GLSL Shaders
50
+
51
+ ## Cosmic 2
52
+
53
+ // Started as Star Nest by Pablo Román Andrioli
54
+ // Modifications by Beibei Wang and Huw Bowles.
55
+ // This content is under the MIT License.
56
+
57
+ https://www.shadertoy.com/view/XllGzN
58
+
59
+ ## Classic PSP Wave
60
+
61
+ MIT License
62
+
63
+ Copyright (c) 2023 Parking Lot Studio
64
+
65
+ Permission is hereby granted, free of charge, to any person obtaining a copy
66
+ of this software and associated documentation files (the "Software"), to deal
67
+ in the Software without restriction, including without limitation the rights
68
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
69
+ copies of the Software, and to permit persons to whom the Software is
70
+ furnished to do so, subject to the following conditions:
71
+
72
+ The above copyright notice and this permission notice shall be included in all
73
+ copies or substantial portions of the Software.
74
+
75
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
76
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
77
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
78
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
79
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
80
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
81
+ SOFTWARE.
82
+
83
+ https://www.shadertoy.com/view/ddV3DK
84
+
85
+ ## Reflective hexes
86
+
87
+ // CC0: Reflective hexes
88
+ https://creativecommons.org/publicdomain/zero/1.0/
89
+
90
+ https://www.shadertoy.com/view/ds2XRt
91
+
92
+ ## Colorful underwater bubbles II
93
+
94
+ // CCO: Colorful underwater bubbles II
95
+ https://creativecommons.org/publicdomain/zero/1.0/
96
+
97
+ https://www.shadertoy.com/view/mlBSWc
98
+
99
+
100
+ # Assets
101
+
102
+ ## Bg-patterns
103
+ https://bg-patterns.com/
104
+
105
+ ## 効果音ラボ
106
+ https://soundeffect-lab.info/
107
+
108
+
109
+ # Licenses
110
+
111
+ ## Ruby
112
+
113
+ Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
114
+
115
+ Redistribution and use in source and binary forms, with or without
116
+ modification, are permitted provided that the following conditions
117
+ are met:
118
+ 1. Redistributions of source code must retain the above copyright
119
+ notice, this list of conditions and the following disclaimer.
120
+ 2. Redistributions in binary form must reproduce the above copyright
121
+ notice, this list of conditions and the following disclaimer in the
122
+ documentation and/or other materials provided with the distribution.
123
+
124
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
125
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
126
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
127
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
128
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
129
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
130
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
131
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
132
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
133
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
134
+ SUCH DAMAGE.
135
+ """)
136
+ .padding(10)
137
+ }
138
+ .navigationTitle(Strings.menuLicenses.s)
139
+ }
140
+ }
@@ -1,10 +1,197 @@
1
1
  import SwiftUI
2
+ import FirebaseCore
3
+ import GoogleMobileAds
4
+ import AppTrackingTransparency
5
+
6
+
7
+ class AppContext: NSObject, ObservableObject {
8
+ @Published var ready = false
9
+ }
10
+
11
+
12
+ class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {
13
+
14
+ var window: UIWindow?
15
+
16
+ func scene(
17
+ _ scene: UIScene,
18
+ willConnectTo session: UISceneSession,
19
+ options connectionOptions: UIScene.ConnectionOptions
20
+ ) {
21
+ guard let windowScene = scene as? UIWindowScene else {
22
+ return
23
+ }
24
+
25
+ if #available(iOS 15.0, *) {
26
+ window = windowScene.keyWindow
27
+ } else {
28
+ window = windowScene.windows.first
29
+ }
30
+ }
31
+
32
+ func sceneDidBecomeActive(_ scene: UIScene) {
33
+ showATTDialog()
34
+ }
35
+
36
+ func showATTDialog() {
37
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
38
+ guard ATTrackingManager.trackingAuthorizationStatus == .notDetermined else {
39
+ return
40
+ }
41
+ ATTrackingManager.requestTrackingAuthorization {_ in}
42
+ }
43
+ }
44
+ }
45
+
46
+
47
+ class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
48
+
49
+ @Published var appContext = AppContext()
50
+
51
+ func application(
52
+ _ application: UIApplication,
53
+ configurationForConnecting connectingSceneSession: UISceneSession,
54
+ options: UIScene.ConnectionOptions
55
+ ) -> UISceneConfiguration {
56
+ let configuration = UISceneConfiguration(
57
+ name: nil, sessionRole: connectingSceneSession.role)
58
+ if (connectingSceneSession.role == .windowApplication) {
59
+ configuration.delegateClass = SceneDelegate.self
60
+ }
61
+ return configuration
62
+ }
63
+
64
+ func application(
65
+ _ application: UIApplication,
66
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
67
+ ) -> Bool {
68
+ if let lang = Helper.language {
69
+ CRuby.evaluate("$language = '\(lang)'")
70
+ }
71
+
72
+ if Helper.isDebug {
73
+ CRuby.evaluate("$debug = true")
74
+ }
75
+
76
+ if Helper.isRelease {
77
+ FirebaseApp.configure()
78
+ }
79
+
80
+ GADMobileAds.sharedInstance().start(completionHandler: { [weak self] _ in
81
+ self!.appContext.ready = true
82
+ })
83
+
84
+ return true
85
+ }
86
+ }
87
+
2
88
 
3
89
  @main
4
90
  struct RubySolitaireApp: App {
91
+
92
+ @UIApplicationDelegateAdaptor var appDelegate: AppDelegate
93
+
5
94
  var body: some Scene {
6
95
  WindowGroup {
7
- GameView()
96
+ GameScreen()
8
97
  }
9
98
  }
10
99
  }
100
+
101
+
102
+ struct GameScreen: View {
103
+
104
+ @EnvironmentObject private var sceneDelegate: SceneDelegate
105
+
106
+ @StateObject private var interstitialAd = InterstitialAd(
107
+ adUnitID: Helper.getInfoString("GADGameScreenInterstitial")!)
108
+
109
+ @State private var command: String = ""
110
+
111
+ @State private var url: URL? = nil
112
+
113
+ @State private var isMenuVisible = false
114
+ @State private var isInterstitialAdVisible = false
115
+
116
+ var body: some View {
117
+ ZStack {
118
+ Color.black
119
+ .ignoresSafeArea(.all)
120
+ GeometryReader { gr in
121
+ VStack(spacing: 2) {
122
+ GameView(command: $command)
123
+ AdBannerView(
124
+ width: gr.size.width,
125
+ adUnitID: Helper.getInfoString("GADGameScreenBottomBanner")!,
126
+ rootViewController: sceneDelegate.window!.rootViewController!
127
+ )
128
+ .frame(width: gr.size.width, height: 50)
129
+ }
130
+ }
131
+ }
132
+ .statusBarHidden()
133
+ .onChange(of: command) { _ in
134
+ if command.isEmpty {
135
+ return
136
+ }
137
+ let split = command.split(separator: ":").map {$0.removingPercentEncoding}
138
+ let commandAndArgs = split.compactMap {$0}
139
+ if commandAndArgs.count != split.count || commandAndArgs.count == 0 {
140
+ return clearCommand()
141
+ }
142
+ runCommand(command: commandAndArgs.first!, args: Array<String>(commandAndArgs.dropFirst(1)))
143
+ }
144
+ .onChange(of: url) { _ in
145
+ if url == nil {
146
+ clearCommand()
147
+ }
148
+ }
149
+ .onChange(of: isMenuVisible) { visible in
150
+ if !visible {
151
+ clearCommand()
152
+ }
153
+ }
154
+ .onChange(of: isInterstitialAdVisible) { visible in
155
+ if visible, interstitialAd.ready {
156
+ interstitialAd.show(
157
+ rootViewController: sceneDelegate.window!.rootViewController!
158
+ ) { error in
159
+ isInterstitialAdVisible = false
160
+ clearCommand()
161
+ }
162
+ } else {
163
+ isInterstitialAdVisible = false
164
+ clearCommand()
165
+ }
166
+ }
167
+ .sheet(isPresented: $isMenuVisible) {
168
+ MenuScreen(isVisible: $isMenuVisible)
169
+ }
170
+ .fullScreenCover(item: $url) { _ in
171
+ if let url = url {
172
+ SafariView(url: url)
173
+ .ignoresSafeArea()
174
+ }
175
+ }
176
+ }
177
+
178
+ func runCommand(command: String, args: [String]) {
179
+ switch command {
180
+ case "openURL":
181
+ guard let str = args.first, let url = URL(string: str) else {
182
+ return clearCommand()
183
+ }
184
+ self.url = url
185
+ case "showMenu":
186
+ isMenuVisible = true;
187
+ case "showInterstitialAd":
188
+ isInterstitialAdVisible = true
189
+ default:
190
+ clearCommand()
191
+ }
192
+ }
193
+
194
+ func clearCommand() {
195
+ command = ""
196
+ }
197
+ }
@@ -0,0 +1,16 @@
1
+ import SwiftUI
2
+ import SafariServices
3
+
4
+
5
+ struct SafariView: UIViewControllerRepresentable {
6
+ let url: URL
7
+
8
+ func makeUIViewController(context: Context) -> some UIViewController {
9
+ let vc = SFSafariViewController(url: url)
10
+ vc.dismissButtonStyle = .close
11
+ return vc
12
+ }
13
+
14
+ func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
15
+ }
16
+ }
@@ -0,0 +1,48 @@
1
+ enum Strings : String, CaseIterable {
2
+ case appName
3
+
4
+ case close
5
+
6
+ case menuPrivacyPolicy
7
+ case menuLicenses
8
+
9
+ var s: String {
10
+ return getString(rawValue) ?? getBaseString(rawValue)
11
+ }
12
+
13
+ func format(_ args: CVarArg...) -> String {
14
+ return String.init(format: s, arguments: args)
15
+ }
16
+
17
+ private func getString(_ key: String) -> String? {
18
+ let string = NSLocalizedString(key, value: Strings.NOT_FOUND, comment: "")
19
+ if string == Strings.NOT_FOUND {
20
+ return nil
21
+ }
22
+
23
+ return string
24
+ }
25
+
26
+ private func getBaseString(_ key: String) -> String {
27
+ guard let bundle = Strings.BASE_BUNDLE else {
28
+ fatalError("Base bundle not found")
29
+ }
30
+
31
+ let string = NSLocalizedString(key, bundle: bundle, value: Strings.NOT_FOUND, comment: "")
32
+ if string == Strings.NOT_FOUND {
33
+ fatalError("'\(key)' not found")
34
+ }
35
+
36
+ return string
37
+ }
38
+
39
+ static private let NOT_FOUND = "__NOT_FOUND__"
40
+
41
+ static private let BASE_BUNDLE: Bundle? = {
42
+ guard let path = Bundle.main.path(forResource: "Base", ofType: "lproj") else {
43
+ return nil
44
+ }
45
+
46
+ return Bundle(path: path)
47
+ }()
48
+ }
@@ -0,0 +1,4 @@
1
+ CFBundleDisplayName = "ソリティア";
2
+
3
+ NSCameraUsageDescription = "Rubyスクリプトからカメラの画像を取得できるようになります。";
4
+ NSUserTrackingUsageDescription = "許可すると、あなたの好みに合った広告が表示されやすくなります。";
@@ -0,0 +1,6 @@
1
+ appName = "宇宙ソリティア";
2
+
3
+ close = "閉じる";
4
+
5
+ menuPrivacyPolicy = "プライバシーポリシー";
6
+ menuLicenses = "ライセンス";
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.2
data/data/card.png CHANGED
Binary file