rubysketch-solitaire 0.1.0 → 0.9.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +3 -0
  3. data/.gitignore +2 -0
  4. data/.hooks/post-commit +37 -0
  5. data/.hooks/pre-commit +69 -0
  6. data/ChangeLog.md +27 -0
  7. data/Gemfile.lock +10 -10
  8. data/Podfile.lock +33 -33
  9. data/README.md +8 -0
  10. data/Rakefile +107 -10
  11. data/RubySolitaire/Ad.swift +88 -0
  12. data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/AppIcon.png +0 -0
  13. data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/Contents.json +3 -87
  14. data/RubySolitaire/Base.lproj/Localizable.strings +6 -0
  15. data/RubySolitaire/Extensions.swift +5 -0
  16. data/RubySolitaire/GameView.swift +75 -13
  17. data/RubySolitaire/Helper.swift +70 -0
  18. data/RubySolitaire/MenuScreen.swift +141 -0
  19. data/RubySolitaire/RubySolitaireApp.swift +188 -1
  20. data/RubySolitaire/SafariView.swift +16 -0
  21. data/RubySolitaire/Strings.swift +48 -0
  22. data/RubySolitaire/ja.lproj/InfoPlist.strings +4 -0
  23. data/RubySolitaire/ja.lproj/Localizable.strings +6 -0
  24. data/VERSION +1 -1
  25. data/data/card.png +0 -0
  26. data/data/classicPSPWave.glsl +94 -0
  27. data/data/colorfulUnderwaterBubbles2.glsl +113 -0
  28. data/data/cosmic2.glsl +121 -0
  29. data/data/reflectiveHexes.glsl +219 -0
  30. data/fastlane/Fastfile +76 -0
  31. data/fastlane/metadata/copyright.txt +1 -0
  32. data/fastlane/metadata/en-US/apple_tv_privacy_policy.txt +1 -0
  33. data/fastlane/metadata/en-US/description.txt +5 -0
  34. data/fastlane/metadata/en-US/keywords.txt +1 -0
  35. data/fastlane/metadata/en-US/marketing_url.txt +1 -0
  36. data/fastlane/metadata/en-US/name.txt +1 -0
  37. data/fastlane/metadata/en-US/privacy_url.txt +1 -0
  38. data/fastlane/metadata/en-US/promotional_text.txt +1 -0
  39. data/fastlane/metadata/en-US/release_notes.txt +1 -0
  40. data/fastlane/metadata/en-US/subtitle.txt +1 -0
  41. data/fastlane/metadata/en-US/support_url.txt +1 -0
  42. data/fastlane/metadata/ja/apple_tv_privacy_policy.txt +1 -0
  43. data/fastlane/metadata/ja/description.txt +5 -0
  44. data/fastlane/metadata/ja/keywords.txt +1 -0
  45. data/fastlane/metadata/ja/marketing_url.txt +1 -0
  46. data/fastlane/metadata/ja/name.txt +1 -0
  47. data/fastlane/metadata/ja/privacy_url.txt +1 -0
  48. data/fastlane/metadata/ja/promotional_text.txt +1 -0
  49. data/fastlane/metadata/ja/release_notes.txt +1 -0
  50. data/fastlane/metadata/ja/subtitle.txt +1 -0
  51. data/fastlane/metadata/ja/support_url.txt +1 -0
  52. data/fastlane/metadata/primary_category.txt +1 -0
  53. data/fastlane/metadata/primary_first_sub_category.txt +1 -0
  54. data/fastlane/metadata/primary_second_sub_category.txt +1 -0
  55. data/fastlane/metadata/review_information/demo_password.txt +1 -0
  56. data/fastlane/metadata/review_information/demo_user.txt +1 -0
  57. data/fastlane/metadata/review_information/email_address.txt +1 -0
  58. data/fastlane/metadata/review_information/first_name.txt +0 -0
  59. data/fastlane/metadata/review_information/last_name.txt +0 -0
  60. data/fastlane/metadata/review_information/notes.txt +1 -0
  61. data/fastlane/metadata/review_information/phone_number.txt +0 -0
  62. data/fastlane/metadata/secondary_category.txt +1 -0
  63. data/fastlane/metadata/secondary_first_sub_category.txt +1 -0
  64. data/fastlane/metadata/secondary_second_sub_category.txt +1 -0
  65. data/lib/rubysketch/solitaire/background.rb +115 -12
  66. data/lib/rubysketch/solitaire/card.rb +10 -87
  67. data/lib/rubysketch/solitaire/common/animation.rb +34 -34
  68. data/lib/rubysketch/solitaire/common/button.rb +49 -19
  69. data/lib/rubysketch/solitaire/common/dialog.rb +78 -21
  70. data/lib/rubysketch/solitaire/common/scene.rb +15 -4
  71. data/lib/rubysketch/solitaire/common/settings.rb +15 -3
  72. data/lib/rubysketch/solitaire/common/timer.rb +2 -2
  73. data/lib/rubysketch/solitaire/common/transitions.rb +12 -6
  74. data/lib/rubysketch/solitaire/common/utils.rb +15 -0
  75. data/lib/rubysketch/solitaire/klondike.rb +382 -81
  76. data/lib/rubysketch/solitaire/places.rb +12 -102
  77. data/lib/rubysketch/solitaire/skin.rb +151 -0
  78. data/lib/rubysketch/solitaire.rb +33 -9
  79. data/main.rb +1 -0
  80. data/project.yml +85 -4
  81. metadata +55 -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
165
211
 
166
- settings['bestTime'] = elapsedTime if newTime
167
- settings['bestScore'] = score.value if newScore
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
168
221
 
169
- return newTime, newScore
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
245
+
246
+ def timeToText(time)
247
+ Time.at(time).strftime('%M:%S')
248
+ end
249
+
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,178 @@ 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
307
396
  d.close
308
- resume
309
397
  end
310
- d.addButton 'NEW GAME', width: 5 do
398
+ d.addButton str('New Game'), width: 6 do
399
+ d.close
400
+ showNewGameDialog
401
+ end
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("Dump settings"), width: 6 do
514
+ puts settings.to_json
515
+ d.close
516
+ end
517
+ d.addButton str('Close'), width: 6 do
518
+ d.close
519
+ end
520
+ }
521
+ end
522
+
336
523
  def updateLayout(w, h)
337
524
  card = cards.first
338
525
  cw, ch = card.then {|c| [c.w, c.h]}
339
- mx, my = Card.margin, cw * 0.2 # margin x, y
526
+ mx, my = skin.margin, cw * 0.2 # margin x, y
340
527
  y = my
341
528
 
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
529
+ undoButton.pos = [mx, y]
530
+ redoButton.pos = [undoButton.x + undoButton.w + 2, y]
531
+ pauseButton.pos = [width - (pauseButton.w + mx), y]
532
+ status.pos = [redoButton.right + mx, y]
533
+ status.right = pauseButton.left - mx
534
+ status.height = pauseButton.h
348
535
 
349
- y = undoButton.y + undoButton.h + my
536
+ y = undoButton.y + undoButton.h + my * 3
350
537
 
351
538
  deck.pos = [w - (deck.w + mx), y]
352
539
  nexts.pos = [deck.x - (nexts.w + mx), deck.y]
@@ -379,8 +566,9 @@ class Klondike < Scene
379
566
  end
380
567
  end
381
568
 
382
- def start(drawCount = 1)
383
- nexts.drawCount = drawCount
569
+ def start(difficulty = :normal)
570
+ @difficulty = difficulty
571
+ start!
384
572
 
385
573
  history.disable
386
574
  lasts = columns.map(&:last).compact
@@ -390,7 +578,6 @@ class Klondike < Scene
390
578
  if lasts.all? {|card| card.opened?}
391
579
  drawNexts
392
580
  history.enable
393
- resume
394
581
  end
395
582
  end
396
583
  end
@@ -411,7 +598,7 @@ class Klondike < Scene
411
598
 
412
599
  def firstDistribution()
413
600
  n = columns.size
414
- (0...n).map { |row| (row...n).map { |col| [col, row] } }.flatten(1)
601
+ (0...n).map {|row| (row...n).map {|col| [col, row]}}.flatten(1)
415
602
  end
416
603
 
417
604
  def openCard(card, gain: 0.5)
@@ -471,6 +658,19 @@ class Klondike < Scene
471
658
  completed if completed?
472
659
  end
473
660
 
661
+ def start!()
662
+ @started = true
663
+ [*cards, *places].each do |o|
664
+ o.started if o.respond_to? :started
665
+ end
666
+ places.each {|p| p.updateCards 0}
667
+ resume
668
+ end
669
+
670
+ def started?()
671
+ @started ||= false
672
+ end
673
+
474
674
  def canFinish?()
475
675
  deck.empty? && nexts.empty? &&
476
676
  columns.any? {|col| !col.empty?} &&
@@ -525,7 +725,7 @@ class Klondike < Scene
525
725
 
526
726
  def showFinishButton()
527
727
  finishButton.tap do |b|
528
- m = Card.margin
728
+ m = skin.margin
529
729
  b.x = marks.last.then {|mark| mark.x + mark.w} + m * 2
530
730
  b.y = -deck.h
531
731
  b.w = width - b.x - m
@@ -588,6 +788,7 @@ class Klondike < Scene
588
788
  end
589
789
 
590
790
  def backToPlace(card, vel)
791
+ return if vel.mag == 0
591
792
  vec = vel.dup.normalize * sqrt(vel.mag) / 10 * sqrt(card.count)
592
793
  return if vec.mag < 3
593
794
  shakeScreen vector: vec
@@ -623,28 +824,6 @@ class Klondike < Scene
623
824
  end
624
825
  end
625
826
 
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
827
  def undo(action)
649
828
  history.disable do
650
829
  case action
@@ -670,7 +849,129 @@ class Klondike < Scene
670
849
  end
671
850
 
672
851
  def startNewGame()
673
- transition self.class.new, [Fade, Curtain, Pixelate].sample
852
+ $newGameCount ||= 0
853
+ $newGameCount += 1
854
+ showAd = $newGameCount % 3 == 0
855
+ transition self.class.new, [Fade, Curtain, Pixelate].sample, showAd: showAd
856
+ end
857
+
858
+ STRINGS = {
859
+ OK: {},
860
+ Cancel: {ja: 'キャンセル'},
861
+ Close: {ja: '閉じる'},
862
+
863
+ Time: {ja: 'タイム'},
864
+ Score: {ja: 'スコア'},
865
+ Move: {ja: '移動回数'},
866
+
867
+ Difficulty: {ja: '難易度'},
868
+ EASY: {ja: '簡単'},
869
+ NORMAL: {ja: '普通'},
870
+ HARD: {ja: '難しい'},
871
+
872
+ 'New Game': {ja: '新規ゲーム'},
873
+ 'Resume': {ja: 'ゲーム再開'},
874
+
875
+ 'Best Time': {ja: 'ベストタイム'},
876
+ 'Best Score': {ja: 'ベストスコア'},
877
+ "Today's Best Time": {ja: '本日のベストタイム'},
878
+ "Today's Best Score": {ja: '本日のベストスコア'},
879
+
880
+ "Start New Game?": {ja: '新しいゲームをはじめますか?'},
881
+ "Start Next Game": {ja: '次のゲームを開始'},
882
+
883
+ "Change Card Design": {ja: 'カードデザインを変更'},
884
+ "Change Background": {ja: 'ゲーム背景を変更'},
885
+
886
+ '(New Record!)': {ja: '(新記録!)'},
887
+ "(Today's Best!)": {ja: '(本日のベスト!)'},
888
+ }
889
+
890
+ def str(s, lang: $language)
891
+ STRINGS.dig(s.intern, lang&.intern) || s.to_s
674
892
  end
675
893
 
676
894
  end# Klondike
895
+
896
+
897
+ class Klondike::NextsPlace < CardPlace
898
+
899
+ def drawCount()
900
+ @game.difficulty == :hard ? 3 : 1
901
+ end
902
+
903
+ def started()
904
+ w = skin.cardSpriteSize[0] + overlap * (drawCount - 1)
905
+ self.x -= w - self.w
906
+ self.w = w
907
+ end
908
+
909
+ def add(*cards, **kwargs)
910
+ super
911
+ updateCards excludes: cards
912
+ end
913
+
914
+ def pop(*args)
915
+ super
916
+ updateCards
917
+ end
918
+
919
+ def posFor(card, index = nil)
920
+ index ||= indexFor card
921
+ super.tap do |pos|
922
+ rindex = cards.size - index
923
+ pos.x += overlap * (drawCount - rindex).clamp(0, drawCount - 1)
924
+ end
925
+ end
926
+
927
+ def overlap()
928
+ skin.cardSpriteSize[0] * 0.4
929
+ end
930
+
931
+ end# Klondike::NextsPlace
932
+
933
+
934
+ class Klondike::MarkPlace < CardPlace
935
+
936
+ def mark()
937
+ last&.mark
938
+ end
939
+
940
+ def accept?(x, y, card)
941
+ return false if !card || card.closed? || !card.canDrop?
942
+ hit?(x, y) &&
943
+ card.last? &&
944
+ card.opened? &&
945
+ (!mark || mark == card.mark) &&
946
+ card.number == last&.number.then {|n| n ? n + 1 : 1}
947
+ end
948
+
949
+ end# Klondike::MarkPlace
950
+
951
+
952
+ class Klondike::ColumnPlace < CardPlace
953
+
954
+ def initialize(*args, **kwargs, &block)
955
+ super(*args, linkCards: true, **kwargs, &block)
956
+ end
957
+
958
+ def accept?(x, y, card)
959
+ return false if !card || card.closed? || !card.canDrop?
960
+ if empty?
961
+ hit?(x, y) &&
962
+ card.number == 13
963
+ else
964
+ any? {|card| card.hit?(x, y)} &&
965
+ card.number == last.number - 1 &&
966
+ (@game.difficulty == :easy || card.color != last.color)
967
+ end
968
+ end
969
+
970
+ def posFor(card, index = nil)
971
+ index ||= indexFor card
972
+ super.tap do |pos|
973
+ pos.y += self.h * 0.3 * index
974
+ end
975
+ end
976
+
977
+ end# Klondike::ColumnPlace