rubysketch-solitaire 0.1.0 → 0.1.2

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 (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
@@ -13,6 +13,25 @@ class Klondike < Scene
13
13
  super + [*cards, *places].map(&:sprite) + interfaces
14
14
  end
15
15
 
16
+ def difficulty()
17
+ @difficulty ||= :normal
18
+ end
19
+
20
+ def pause()
21
+ super
22
+ @prevTime = nil
23
+ stopTimer :save
24
+ end
25
+
26
+ def resume()
27
+ super
28
+ return if !started? || completed?
29
+ @prevTime = now
30
+ startInterval :save, 1, now: true do
31
+ save
32
+ end
33
+ end
34
+
16
35
  def draw()
17
36
  sprite *places.map(&:sprite)
18
37
  sprite *cards.sort {|a, b| a.z <=> b.z}.map(&:sprite)
@@ -25,6 +44,10 @@ class Klondike < Scene
25
44
  updateLayout w, h
26
45
  end
27
46
 
47
+ def focusChanged(focus)
48
+ focus ? resume : pause if ios?
49
+ end
50
+
28
51
  def canDrop?(card)
29
52
  case card.place
30
53
  when *columns then card.opened?
@@ -70,7 +93,7 @@ class Klondike < Scene
70
93
  def save()
71
94
  settings['state'] = {
72
95
  version: 1,
73
- drawCount: nexts.drawCount,
96
+ difficulty: difficulty,
74
97
  history: history.to_h {|o| o.id if o.respond_to? :id},
75
98
  score: score.to_h,
76
99
  elapsedTime: elapsedTime,
@@ -87,7 +110,7 @@ class Klondike < Scene
87
110
  findAll = -> id { all.find {|obj| obj .id == id} or raise "No object '#{id}'"}
88
111
  findCard = -> id {cards.find {|card| card.id == id} or raise "No card '#{id}'"}
89
112
 
90
- nexts.drawCount = hash['drawCount']
113
+ @difficulty = hash['difficulty'].intern
91
114
 
92
115
  self.history = History.load hash['history'] do |id|
93
116
  (id.respond_to?('=~') && id =~ /^id:/) ? findAll[id] : nil
@@ -110,7 +133,7 @@ class Klondike < Scene
110
133
  raise "Failed to restore state" unless
111
134
  places.reduce([]) {|a, place| a + place.cards}.size == 52
112
135
 
113
- resume
136
+ start!
114
137
  end
115
138
 
116
139
  def inspect()
@@ -151,22 +174,81 @@ class Klondike < Scene
151
174
  }
152
175
  end
153
176
 
177
+ def addScore(name)
178
+ old = score.value
179
+ score.add name if history.enabled?
180
+ history.push [:score, score.value, old] if score.value != old
181
+ end
182
+
154
183
  def bestTime()
155
- settings['bestTime'] || 24 * 60 * 60 - 1
184
+ settings[bestRecordKey :time] || 24 * 60 * 60 - 1
156
185
  end
157
186
 
158
187
  def bestScore()
159
- settings['bestScore'] || 0
188
+ settings[bestRecordKey :score] || 0
189
+ end
190
+
191
+ def dailyBestTime()
192
+ key = bestRecordKey :time, true
193
+ settings[key] = nil if settings["#{key}Date"] != today
194
+ settings[key] || 24 * 60 * 60 - 1
195
+ end
196
+
197
+ def dailyBestScore()
198
+ key = bestRecordKey :score, true
199
+ settings[key] = nil if settings["#{key}Date"] != today
200
+ settings[key] || 0
160
201
  end
161
202
 
162
203
  def updateBests()
163
- newTime = elapsedTime < bestTime
164
- newScore = score.value > bestScore
204
+ newTime = elapsedTime < bestTime
205
+ newScore = score.value > bestScore
206
+ newDailyTime = elapsedTime < dailyBestTime
207
+ newDailyScore = score.value > dailyBestScore
208
+
209
+ settings[bestRecordKey :time] = elapsedTime if newTime
210
+ settings[bestRecordKey :score] = score.value if newScore
211
+
212
+ if newDailyTime
213
+ settings[bestRecordKey(:time, true)] = elapsedTime
214
+ settings[bestRecordKey(:time, true, 'Date')] = today
215
+ end
216
+
217
+ if newDailyScore
218
+ settings[bestRecordKey(:score, true)] = score.value
219
+ settings[bestRecordKey(:score, true, 'Date')] = today
220
+ end
221
+
222
+ return newTime, newScore, newDailyTime, newDailyScore
223
+ end
224
+
225
+ def clearAllTimeBests()
226
+ %i[time score]
227
+ .map {|type| bestRecordKey type}
228
+ .each {|key| settings[key] = nil}
229
+ end
230
+
231
+ def clearDailyBests()
232
+ %i[time score]
233
+ .map {|type| ['', 'Date'].map {|s| bestRecordKey type, true, s}}
234
+ .flatten
235
+ .each {|key| settings[key] = nil}
236
+ end
237
+
238
+ def bestRecordKey(type, daily = false, suffix = '')
239
+ difficulty.to_s +
240
+ (daily ? 'Daily' : '') +
241
+ 'Best' +
242
+ type.to_s.capitalize +
243
+ suffix
244
+ end
165
245
 
166
- settings['bestTime'] = elapsedTime if newTime
167
- settings['bestScore'] = score.value if newScore
246
+ def timeToText(time)
247
+ Time.at(time).strftime('%M:%S')
248
+ end
168
249
 
169
- return newTime, newScore
250
+ def today()
251
+ Time.now.strftime '%Y%m%d'
170
252
  end
171
253
 
172
254
  def cards()
@@ -180,23 +262,23 @@ class Klondike < Scene
180
262
  end
181
263
 
182
264
  def deck()
183
- @deck ||= CardPlace.new(:deck).tap do |deck|
265
+ @deck ||= CardPlace.new(self, :deck).tap do |deck|
184
266
  deck.sprite.mouseClicked {deckClicked}
185
267
  end
186
268
  end
187
269
 
188
270
  def nexts()
189
- @nexts ||= NextsPlace.new(:nexts).tap do |nexts|
271
+ @nexts ||= NextsPlace.new(self, :nexts).tap do |nexts|
190
272
  nexts.sprite.mouseClicked {nextsClicked}
191
273
  end
192
274
  end
193
275
 
194
276
  def marks()
195
- @marks ||= Card::MARKS.size.times.map {|i| MarkPlace.new "mark_#{i + 1}"}
277
+ @marks ||= Card::MARKS.size.times.map {|i| MarkPlace.new self, "mark_#{i + 1}"}
196
278
  end
197
279
 
198
280
  def columns()
199
- @culumns ||= 7.times.map.with_index {|i| ColumnPlace.new "column_#{i + 1}"}
281
+ @culumns ||= 7.times.map.with_index {|i| ColumnPlace.new self, "column_#{i + 1}"}
200
282
  end
201
283
 
202
284
  def dealSound()
@@ -215,12 +297,12 @@ class Klondike < Scene
215
297
  end
216
298
 
217
299
  def interfaces()
218
- [undoButton, redoButton, menuButton, finishButton, status, debugButton]
300
+ [undoButton, redoButton, pauseButton, finishButton, status, debugButton]
219
301
  end
220
302
 
221
303
  def undoButton()
222
304
  @undoButton ||= Button.new(
223
- '◀', rgb: [120, 140, 160], fontSize: 28, round: [20, 4, 4, 20]
305
+ '◀', fontSize: 28, round: [20, 4, 4, 20]
224
306
  ).tap do |b|
225
307
  b.update {b.enable history.canUndo?}
226
308
  b.clicked {history.undo {|action| undo action}}
@@ -229,18 +311,16 @@ class Klondike < Scene
229
311
 
230
312
  def redoButton()
231
313
  @redoButton ||= Button.new(
232
- '▶', rgb: [160, 140, 120], fontSize: 28, round: [4, 20, 20, 4]
314
+ '▶', fontSize: 28, round: [4, 20, 20, 4]
233
315
  ).tap do |b|
234
316
  b.update {b.enable history.canRedo?}
235
317
  b.clicked {history.redo {|action| self.redo action}}
236
318
  end
237
319
  end
238
320
 
239
- def menuButton()
240
- @menuButton ||= Button.new(
241
- '≡', rgb: [140, 160, 120], fontSize: 36
242
- ).tap do |b|
243
- b.clicked {showMenuDialog}
321
+ def pauseButton()
322
+ @pauseButton ||= Button.new(icon: skin.pauseIcon).tap do |b|
323
+ b.clicked {showPauseDialog}
244
324
  end
245
325
  end
246
326
 
@@ -248,7 +328,7 @@ class Klondike < Scene
248
328
  @status ||= Sprite.new.tap do |sp|
249
329
  sp.draw do
250
330
  push do
251
- fill 0, 20
331
+ fill *skin.translucentBackgroundColor
252
332
  rect 0, 0, sp.w, sp.h, 10
253
333
  fill 255
254
334
 
@@ -260,7 +340,7 @@ class Klondike < Scene
260
340
  }.each do |label, value|
261
341
  textSize 12
262
342
  textAlign LEFT, TOP
263
- text label, x + mx, my, w - mx, sp.h - my * 2
343
+ text str(label), x + mx, my, w - mx, sp.h - my * 2
264
344
  textSize 20
265
345
  textAlign LEFT, BOTTOM
266
346
  text value, x + mx, my, w - mx, sp.h - my * 2
@@ -282,71 +362,174 @@ class Klondike < Scene
282
362
 
283
363
  def debugButton()
284
364
  @debugButton ||= Button.new(:DEBUG, width: 3).tap do |b|
285
- b.hide
286
- b.clicked {}
365
+ b.hide unless debug?
366
+ b.clicked {showDebugDialog}
287
367
  end
288
368
  end
289
369
 
290
370
  def showReadyDialog()
291
371
  add Dialog.new(alpha: 50).tap {|d|
292
- d.addButton 'EASY', width: 5 do
293
- start 1
372
+ d.addButton str('EASY'), width: 5 do
373
+ start :easy
294
374
  d.close
295
375
  end
296
- d.addButton 'HARD', width: 5 do
297
- start 3
376
+ d.addButton str('NORMAL'), width: 5 do
377
+ start :normal
378
+ d.close
379
+ end
380
+ d.addButton str('HARD'), width: 5 do
381
+ start :hard
298
382
  d.close
299
383
  end
300
384
  }
301
385
  end
302
386
 
303
- def showMenuDialog()
304
- pause
305
- add Dialog.new.tap {|d|
306
- d.addButton 'RESUME', width: 5 do
387
+ def showPauseDialog()
388
+ add Dialog.new {|d|
389
+ d.addLabel "#{str 'Difficulty'}: #{str difficulty.upcase}"
390
+ d.addLabel "#{str 'Best Time'}: #{timeToText bestTime}"
391
+ d.addLabel "#{str 'Best Score'}: #{bestScore}"
392
+ d.addLabel "#{str "Today's Best Time"}: #{timeToText dailyBestTime}"
393
+ d.addLabel "#{str "Today's Best Score"}: #{dailyBestScore}"
394
+ d.addSpace 20
395
+ d.addButton str('Resume'), width: 6 do
396
+ d.close
397
+ end
398
+ d.addButton str('New Game'), width: 6 do
307
399
  d.close
308
- resume
400
+ showNewGameDialog
309
401
  end
310
- d.addButton 'NEW GAME', width: 5 do
402
+ d.addSpace 10
403
+ d.group do
404
+ if ios?
405
+ d.addButton icon: skin.menuIcon do
406
+ sendCommand :showMenu
407
+ end
408
+ end
409
+ d.addButton icon: skin.settingsIcon do
410
+ showSettingsDialog
411
+ end
412
+ end
413
+ }
414
+ end
415
+
416
+ def showNewGameDialog()
417
+ add Dialog.new(alpha: 180).tap {|d|
418
+ d.addLabel str("Start New Game?")
419
+ d.addSpace 20
420
+ d.addButton str('OK'), width: 4 do
311
421
  startNewGame
422
+ d.close
423
+ end
424
+ d.addButton str('Cancel'), width: 4 do
425
+ d.close
312
426
  end
313
- d.addSpace 50
314
- d.addLabel "Best Time: #{timeToText bestTime}"
315
- d.addLabel "Best Score: #{bestScore}"
316
427
  }
317
428
  end
318
429
 
319
- def showCompletedDialog(newBestTime = false, newBestScore = false)
320
- pause
430
+ def showSettingsDialog()
431
+ closedImage = -> {
432
+ i = skin.closedImage
433
+ resizeImage i, i.width / 2, i.height / 2
434
+ }
435
+ add Dialog.new(alpha: 255).tap {|d|
436
+ bg = d.add Background.new backgroundScene.type
437
+ cardImage = d.addElement Sprite.new image: closedImage.call
438
+ d.addButton str('Change Card Design'), width: 7 do
439
+ skin skin.index + 1
440
+ settings['skinIndex'] = skin.index
441
+ cardImage.image = closedImage.call
442
+ end
443
+ d.addSpace 10
444
+ author = -> {bg.author&.then {|author| "by #{author}"} || '-'}
445
+ bgName = bgAuthor = nil
446
+ d.group :vertical, space: 0 do
447
+ bgName = d.addLabel(bg.name, alpha: 0) {sendCommand :openURL, bg.url}
448
+ bgAuthor = d.addLabel author.call, fontSize: 16, alpha: 0
449
+ end
450
+ d.addButton str('Change Background'), width: 7 do
451
+ bg.set bg.nextType
452
+ bgName .label = bg.name
453
+ bgAuthor.label = author.call
454
+ backgroundScene.set bg.type
455
+ end
456
+ d.addSpace 20
457
+ d.addButton str('Close'), width: 6 do
458
+ d.close
459
+ end
460
+ }
461
+ end
462
+
463
+ def showCompletedDialog(
464
+ bestTime = false, bestScore = false,
465
+ dailyBestTime = false, dailyBestScore = false)
466
+
467
+ suffix = -> allTime, daily do
468
+ return str '(New Record!)' if allTime
469
+ return str "(Today's Best!)" if daily
470
+ ''
471
+ end
472
+
321
473
  add Dialog.new.tap {|d|
322
- d.addLabel 'Congratulations!', fontSize: 44
474
+ d.addLabel str('Congratulations!'), fontSize: 44
323
475
  d.addLabel(
324
- "Time: #{timeToText elapsedTime} #{newBestTime ? '(NEW!)' : ''}",
476
+ "#{str 'Time'}: #{timeToText elapsedTime} #{suffix.call bestTime, dailyBestTime}",
325
477
  fontSize: 28)
326
478
  d.addLabel(
327
- "Score: #{score.value} #{newBestScore ? '(NEW!)' : ''}",
479
+ "#{str 'Score'}: #{score.value} #{suffix.call bestScore, dailyBestScore}",
328
480
  fontSize: 28)
329
481
  d.addSpace 50
330
- d.addButton 'NEW GAME', width: 5 do
482
+ d.addButton str('Start Next Game'), width: 5 do
331
483
  startNewGame
332
484
  end
333
485
  }
334
486
  end
335
487
 
488
+ def showDebugDialog()
489
+ add Dialog.new.tap {|d|
490
+ d.addButton str('Clear all settings'), width: 6 do
491
+ settings.clear
492
+ d.close
493
+ end
494
+ d.addButton str('Clear all time bests'), width: 6 do
495
+ clearAllTimeBests
496
+ d.close
497
+ end
498
+ d.addButton str("Clear today's bests"), width: 6 do
499
+ clearDailyBests
500
+ d.close
501
+ end
502
+ d.addButton str("One step for completion"), width: 6 do
503
+ cards.sort.group_by(&:mark).each.with_index do |(mark, cards), index|
504
+ place = marks[index]
505
+ place.clear
506
+ cards.reverse.each.with_index do |card, i|
507
+ card.z = i
508
+ place.add card.open
509
+ end
510
+ end
511
+ d.close
512
+ end
513
+ d.addButton str('Close'), width: 6 do
514
+ d.close
515
+ end
516
+ }
517
+ end
518
+
336
519
  def updateLayout(w, h)
337
520
  card = cards.first
338
521
  cw, ch = card.then {|c| [c.w, c.h]}
339
- mx, my = Card.margin, cw * 0.2 # margin x, y
522
+ mx, my = skin.margin, cw * 0.2 # margin x, y
340
523
  y = my
341
524
 
342
- undoButton.pos = [mx, y]
343
- redoButton.pos = [undoButton.x + undoButton.w + 2, y]
344
- menuButton.pos = [width - (menuButton.w + mx), y]
345
- status.pos = [redoButton.right + mx, y]
346
- status.right = menuButton.left - mx
347
- status.height = menuButton.h
525
+ undoButton.pos = [mx, y]
526
+ redoButton.pos = [undoButton.x + undoButton.w + 2, y]
527
+ pauseButton.pos = [width - (pauseButton.w + mx), y]
528
+ status.pos = [redoButton.right + mx, y]
529
+ status.right = pauseButton.left - mx
530
+ status.height = pauseButton.h
348
531
 
349
- y = undoButton.y + undoButton.h + my
532
+ y = undoButton.y + undoButton.h + my * 3
350
533
 
351
534
  deck.pos = [w - (deck.w + mx), y]
352
535
  nexts.pos = [deck.x - (nexts.w + mx), deck.y]
@@ -379,8 +562,9 @@ class Klondike < Scene
379
562
  end
380
563
  end
381
564
 
382
- def start(drawCount = 1)
383
- nexts.drawCount = drawCount
565
+ def start(difficulty = :normal)
566
+ @difficulty = difficulty
567
+ start!
384
568
 
385
569
  history.disable
386
570
  lasts = columns.map(&:last).compact
@@ -390,7 +574,6 @@ class Klondike < Scene
390
574
  if lasts.all? {|card| card.opened?}
391
575
  drawNexts
392
576
  history.enable
393
- resume
394
577
  end
395
578
  end
396
579
  end
@@ -411,7 +594,7 @@ class Klondike < Scene
411
594
 
412
595
  def firstDistribution()
413
596
  n = columns.size
414
- (0...n).map { |row| (row...n).map { |col| [col, row] } }.flatten(1)
597
+ (0...n).map {|row| (row...n).map {|col| [col, row]}}.flatten(1)
415
598
  end
416
599
 
417
600
  def openCard(card, gain: 0.5)
@@ -471,6 +654,19 @@ class Klondike < Scene
471
654
  completed if completed?
472
655
  end
473
656
 
657
+ def start!()
658
+ @started = true
659
+ [*cards, *places].each do |o|
660
+ o.started if o.respond_to? :started
661
+ end
662
+ places.each {|p| p.updateCards 0}
663
+ resume
664
+ end
665
+
666
+ def started?()
667
+ @started ||= false
668
+ end
669
+
474
670
  def canFinish?()
475
671
  deck.empty? && nexts.empty? &&
476
672
  columns.any? {|col| !col.empty?} &&
@@ -525,7 +721,7 @@ class Klondike < Scene
525
721
 
526
722
  def showFinishButton()
527
723
  finishButton.tap do |b|
528
- m = Card.margin
724
+ m = skin.margin
529
725
  b.x = marks.last.then {|mark| mark.x + mark.w} + m * 2
530
726
  b.y = -deck.h
531
727
  b.w = width - b.x - m
@@ -588,6 +784,7 @@ class Klondike < Scene
588
784
  end
589
785
 
590
786
  def backToPlace(card, vel)
787
+ return if vel.mag == 0
591
788
  vec = vel.dup.normalize * sqrt(vel.mag) / 10 * sqrt(card.count)
592
789
  return if vec.mag < 3
593
790
  shakeScreen vector: vec
@@ -623,28 +820,6 @@ class Klondike < Scene
623
820
  end
624
821
  end
625
822
 
626
- def timeToText(time)
627
- Time.at(time).strftime('%M:%S')
628
- end
629
-
630
- def pause()
631
- @prevTime = nil
632
- stopTimer :save
633
- end
634
-
635
- def resume()
636
- @prevTime = now
637
- startInterval :save, 1, now: true do
638
- save
639
- end
640
- end
641
-
642
- def addScore(name)
643
- old = score.value
644
- score.add name if history.enabled?
645
- history.push [:score, score.value, old] if score.value != old
646
- end
647
-
648
823
  def undo(action)
649
824
  history.disable do
650
825
  case action
@@ -670,7 +845,129 @@ class Klondike < Scene
670
845
  end
671
846
 
672
847
  def startNewGame()
673
- transition self.class.new, [Fade, Curtain, Pixelate].sample
848
+ $newGameCount ||= 0
849
+ $newGameCount += 1
850
+ showAd = $newGameCount % 3 == 0
851
+ transition self.class.new, [Fade, Curtain, Pixelate].sample, showAd: showAd
852
+ end
853
+
854
+ STRINGS = {
855
+ OK: {},
856
+ Cancel: {ja: 'キャンセル'},
857
+ Close: {ja: '閉じる'},
858
+
859
+ Time: {ja: 'タイム'},
860
+ Score: {ja: 'スコア'},
861
+ Move: {ja: '移動回数'},
862
+
863
+ Difficulty: {ja: '難易度'},
864
+ EASY: {ja: '簡単'},
865
+ NORMAL: {ja: '普通'},
866
+ HARD: {ja: '難しい'},
867
+
868
+ 'New Game': {ja: '新規ゲーム'},
869
+ 'Resume': {ja: 'ゲーム再開'},
870
+
871
+ 'Best Time': {ja: 'ベストタイム'},
872
+ 'Best Score': {ja: 'ベストスコア'},
873
+ "Today's Best Time": {ja: '本日のベストタイム'},
874
+ "Today's Best Score": {ja: '本日のベストスコア'},
875
+
876
+ "Start New Game?": {ja: '新しいゲームをはじめますか?'},
877
+ "Start Next Game": {ja: '次のゲームを開始'},
878
+
879
+ "Change Card Design": {ja: 'カードデザインを変更'},
880
+ "Change Background": {ja: 'ゲーム背景を変更'},
881
+
882
+ '(New Record!)': {ja: '(新記録!)'},
883
+ "(Today's Best!)": {ja: '(本日のベスト!)'},
884
+ }
885
+
886
+ def str(s, lang: $language)
887
+ STRINGS.dig(s.intern, lang&.intern) || s.to_s
674
888
  end
675
889
 
676
890
  end# Klondike
891
+
892
+
893
+ class Klondike::NextsPlace < CardPlace
894
+
895
+ def drawCount()
896
+ @game.difficulty == :hard ? 3 : 1
897
+ end
898
+
899
+ def started()
900
+ w = skin.cardSpriteSize[0] + overlap * (drawCount - 1)
901
+ self.x -= w - self.w
902
+ self.w = w
903
+ end
904
+
905
+ def add(*cards, **kwargs)
906
+ super
907
+ updateCards excludes: cards
908
+ end
909
+
910
+ def pop(*args)
911
+ super
912
+ updateCards
913
+ end
914
+
915
+ def posFor(card, index = nil)
916
+ index ||= indexFor card
917
+ super.tap do |pos|
918
+ rindex = cards.size - index
919
+ pos.x += overlap * (drawCount - rindex).clamp(0, drawCount - 1)
920
+ end
921
+ end
922
+
923
+ def overlap()
924
+ skin.cardSpriteSize[0] * 0.4
925
+ end
926
+
927
+ end# Klondike::NextsPlace
928
+
929
+
930
+ class Klondike::MarkPlace < CardPlace
931
+
932
+ def mark()
933
+ last&.mark
934
+ end
935
+
936
+ def accept?(x, y, card)
937
+ return false if !card || card.closed? || !card.canDrop?
938
+ hit?(x, y) &&
939
+ card.last? &&
940
+ card.opened? &&
941
+ (!mark || mark == card.mark) &&
942
+ card.number == last&.number.then {|n| n ? n + 1 : 1}
943
+ end
944
+
945
+ end# Klondike::MarkPlace
946
+
947
+
948
+ class Klondike::ColumnPlace < CardPlace
949
+
950
+ def initialize(*args, **kwargs, &block)
951
+ super(*args, linkCards: true, **kwargs, &block)
952
+ end
953
+
954
+ def accept?(x, y, card)
955
+ return false if !card || card.closed? || !card.canDrop?
956
+ if empty?
957
+ hit?(x, y) &&
958
+ card.number == 13
959
+ else
960
+ any? {|card| card.hit?(x, y)} &&
961
+ card.number == last.number - 1 &&
962
+ (@game.difficulty == :easy || card.color != last.color)
963
+ end
964
+ end
965
+
966
+ def posFor(card, index = nil)
967
+ index ||= indexFor card
968
+ super.tap do |pos|
969
+ pos.y += self.h * 0.3 * index
970
+ end
971
+ end
972
+
973
+ end# Klondike::ColumnPlace