rubysketch-solitaire 0.1.0 → 0.9.0

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