trackler 2.0.0.1 → 2.0.0.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 (226) hide show
  1. checksums.yaml +4 -4
  2. data/bin/bump-content +3 -1
  3. data/bin/verify-metadata +2 -2
  4. data/common/exercises/alphametics/canonical-data.json +19 -9
  5. data/common/exercises/food-chain/canonical-data.json +7 -1
  6. data/common/exercises/grains/canonical-data.json +66 -0
  7. data/common/exercises/raindrops/description.md +4 -5
  8. data/common/exercises/triangle/canonical-data.json +81 -54
  9. data/lib/trackler/version.rb +1 -1
  10. data/tracks/c/config.json +26 -19
  11. data/tracks/c/exercises/allergies/makefile +15 -0
  12. data/tracks/c/exercises/allergies/src/allergies.h +21 -0
  13. data/tracks/c/exercises/allergies/src/example.c +32 -0
  14. data/tracks/c/exercises/allergies/src/example.h +26 -0
  15. data/tracks/c/exercises/allergies/test/test_allergies.c +203 -0
  16. data/tracks/c/exercises/allergies/test/vendor/unity.c +1300 -0
  17. data/tracks/c/exercises/allergies/test/vendor/unity.h +274 -0
  18. data/tracks/c/exercises/allergies/test/vendor/unity_internals.h +701 -0
  19. data/tracks/c/exercises/atbash-cipher/makefile +16 -0
  20. data/tracks/c/exercises/atbash-cipher/src/example.c +71 -0
  21. data/tracks/c/exercises/atbash-cipher/src/example.h +7 -0
  22. data/tracks/c/exercises/atbash-cipher/test/test_atbash_cipher.c +113 -0
  23. data/tracks/c/exercises/atbash-cipher/test/vendor/unity.c +1300 -0
  24. data/tracks/c/exercises/atbash-cipher/test/vendor/unity.h +274 -0
  25. data/tracks/c/exercises/atbash-cipher/test/vendor/unity_internals.h +701 -0
  26. data/tracks/c/exercises/phone-number/makefile +16 -0
  27. data/tracks/c/exercises/phone-number/src/example.c +66 -0
  28. data/tracks/c/exercises/phone-number/src/example.h +8 -0
  29. data/tracks/c/exercises/phone-number/test/test_phone_number.c +108 -0
  30. data/tracks/c/exercises/phone-number/test/vendor/unity.c +1300 -0
  31. data/tracks/c/exercises/phone-number/test/vendor/unity.h +274 -0
  32. data/tracks/c/exercises/phone-number/test/vendor/unity_internals.h +701 -0
  33. data/tracks/csharp/config.json +8 -0
  34. data/tracks/csharp/exercises/bowling/BowlingTest.cs +188 -33
  35. data/tracks/csharp/exercises/bowling/Example.cs +38 -9
  36. data/tracks/csharp/exercises/rectangles/Example.cs +91 -0
  37. data/tracks/csharp/exercises/rectangles/RectanglesTest.cs +133 -0
  38. data/tracks/elisp/docs/INSTALLATION.org +1 -1
  39. data/tracks/elixir/config.json +8 -66
  40. data/tracks/elixir/docs/LEARNING.md +1 -1
  41. data/tracks/elixir/exercises/diamond/diamond_test.exs +12 -12
  42. data/tracks/elixir/exercises/grep/example.exs +92 -0
  43. data/tracks/elixir/exercises/grep/grep.exs +6 -0
  44. data/tracks/elixir/exercises/grep/grep_test.exs +259 -0
  45. data/tracks/elixir/exercises/markdown/markdown.exs +59 -2
  46. data/tracks/elixir/exercises/nucleotide-count/nucleotide_count_test.exs +4 -4
  47. data/tracks/elixir/exercises/phone-number/phone_number_test.exs +5 -0
  48. data/tracks/fsharp/exercises/bowling/BowlingTest.fs +158 -43
  49. data/tracks/fsharp/exercises/bowling/Example.fs +53 -24
  50. data/tracks/go/config.json +5 -0
  51. data/tracks/go/exercises/diamond/diamond_test.go +227 -0
  52. data/tracks/go/exercises/diamond/example.go +47 -0
  53. data/tracks/go/exercises/food-chain/example.go +1 -1
  54. data/tracks/go/exercises/food-chain/food_chain_test.go +30 -6
  55. data/tracks/go/exercises/nucleotide-count/example.go +21 -8
  56. data/tracks/go/exercises/nucleotide-count/nucleotide_count_test.go +45 -37
  57. data/tracks/haskell/exercises/anagram/test/Tests.hs +1 -11
  58. data/tracks/java/bin/journey-test.sh +165 -128
  59. data/tracks/java/docs/ABOUT.md +5 -8
  60. data/tracks/java/exercises/hello-world/build.gradle +0 -6
  61. data/tracks/java/exercises/hello-world/src/test/java/HelloWorldTest.java +0 -1
  62. data/tracks/java/exercises/meetup/build.gradle +0 -1
  63. data/tracks/java/exercises/meetup/src/example/java/Meetup.java +9 -7
  64. data/tracks/java/exercises/meetup/src/test/java/MeetupTest.java +185 -188
  65. data/tracks/lua/config.json +0 -73
  66. data/tracks/lua/exercises/bowling/bowling_spec.lua +92 -48
  67. data/tracks/lua/exercises/bowling/example.lua +4 -1
  68. data/tracks/objective-c/config.json +9 -30
  69. data/tracks/objective-c/exercises/pangram/PangramExample.h +7 -0
  70. data/tracks/objective-c/exercises/pangram/PangramExample.m +21 -0
  71. data/tracks/objective-c/exercises/pangram/PangramTest.m +51 -0
  72. data/tracks/objective-c/xcodeProject/ObjectiveC.xcodeproj/project.pbxproj +18 -0
  73. data/tracks/ocaml/README.md +46 -1
  74. data/tracks/ocaml/SETUP.md +21 -2
  75. data/tracks/ocaml/config.json +48 -78
  76. data/tracks/perl5/README.md +15 -8
  77. data/tracks/perl5/testall.pl +5 -5
  78. data/tracks/php/config.json +26 -34
  79. data/tracks/php/docs/ABOUT.md +15 -0
  80. data/tracks/php/exercises/allergies/allergies_test.php +121 -0
  81. data/tracks/php/exercises/allergies/example.php +64 -0
  82. data/tracks/php/exercises/etl/etl_test.php +52 -0
  83. data/tracks/php/exercises/etl/example.php +12 -0
  84. data/tracks/php/exercises/nucleotide-count/example.php +25 -0
  85. data/tracks/php/exercises/nucleotide-count/nucleotide-count_test.php +36 -0
  86. data/tracks/php/exercises/pig-latin/example.php +25 -29
  87. data/tracks/php/exercises/pig-latin/pig-latin_test.php +26 -19
  88. data/tracks/php/exercises/space-age/example.php +65 -0
  89. data/tracks/php/exercises/space-age/space-age_test.php +70 -0
  90. data/tracks/php/exercises/triangle/example.php +43 -0
  91. data/tracks/php/exercises/triangle/triangle_test.php +140 -0
  92. data/tracks/pony/exercises/anagram/example.pony +6 -6
  93. data/tracks/pony/exercises/anagram/test.pony +3 -4
  94. data/tracks/pony/exercises/bob/test.pony +23 -23
  95. data/tracks/pony/exercises/difference-of-squares/test.pony +11 -11
  96. data/tracks/pony/exercises/hamming/example.pony +3 -3
  97. data/tracks/pony/exercises/hamming/test.pony +16 -16
  98. data/tracks/pony/exercises/hello-world/test.pony +5 -4
  99. data/tracks/pony/exercises/leap/test.pony +8 -8
  100. data/tracks/python/config.json +18 -0
  101. data/tracks/python/exercises/diamond/diamond_test.py +33 -0
  102. data/tracks/python/exercises/diamond/example.py +15 -0
  103. data/tracks/python/exercises/linked-list/example.py +47 -0
  104. data/tracks/python/exercises/linked-list/linked_list.py +13 -0
  105. data/tracks/python/exercises/linked-list/linked_list_test.py +49 -0
  106. data/tracks/python/exercises/list-ops/example.py +55 -0
  107. data/tracks/python/exercises/list-ops/list_ops.py +38 -0
  108. data/tracks/python/exercises/list-ops/list_ops_test.py +136 -0
  109. data/tracks/ruby/bin/generate +22 -2
  110. data/tracks/ruby/config.json +12 -83
  111. data/tracks/ruby/exercises/allergies/allergies_test.rb +0 -1
  112. data/tracks/ruby/exercises/atbash-cipher/atbash_cipher_test.rb +0 -1
  113. data/tracks/ruby/exercises/beer-song/beer_song_test.rb +1 -2
  114. data/tracks/ruby/exercises/binary/example.rb +0 -1
  115. data/tracks/ruby/exercises/binary-search-tree/binary_search_tree_test.rb +1 -1
  116. data/tracks/ruby/exercises/bowling/.version +1 -0
  117. data/tracks/ruby/exercises/bowling/bowling_test.rb +109 -133
  118. data/tracks/ruby/exercises/bowling/example.rb +7 -7
  119. data/tracks/ruby/exercises/bowling/example.tt +27 -0
  120. data/tracks/ruby/exercises/circular-buffer/circular_buffer_test.rb +0 -2
  121. data/tracks/ruby/exercises/clock/example.rb +0 -2
  122. data/tracks/ruby/exercises/connect/connect_test.rb +0 -1
  123. data/tracks/ruby/exercises/custom-set/custom_set_test.rb +0 -1
  124. data/tracks/ruby/exercises/diamond/diamond_test.rb +0 -1
  125. data/tracks/ruby/exercises/etl/etl_test.rb +1 -1
  126. data/tracks/ruby/exercises/house/house_test.rb +0 -1
  127. data/tracks/ruby/exercises/isogram/isogram_test.rb +2 -2
  128. data/tracks/ruby/exercises/largest-series-product/example.tt +0 -3
  129. data/tracks/ruby/exercises/largest-series-product/largest_series_product_test.rb +0 -4
  130. data/tracks/ruby/exercises/linked-list/linked_list_test.rb +1 -1
  131. data/tracks/ruby/exercises/meetup/meetup_test.rb +0 -1
  132. data/tracks/ruby/exercises/nth-prime/example.tt +0 -2
  133. data/tracks/ruby/exercises/nth-prime/nth_prime_test.rb +0 -2
  134. data/tracks/ruby/exercises/ocr-numbers/ocr_numbers_test.rb +1 -2
  135. data/tracks/ruby/exercises/protein-translation/protein_translation_test.rb +0 -1
  136. data/tracks/ruby/exercises/proverb/proverb_test.rb +1 -3
  137. data/tracks/ruby/exercises/queen-attack/example.rb +3 -1
  138. data/tracks/ruby/exercises/queen-attack/queen_attack_test.rb +34 -8
  139. data/tracks/ruby/exercises/robot-simulator/robot_simulator_test.rb +1 -1
  140. data/tracks/ruby/exercises/strain/strain_test.rb +2 -2
  141. data/tracks/ruby/exercises/tournament/.version +1 -0
  142. data/tracks/ruby/exercises/tournament/example.rb +54 -0
  143. data/tracks/ruby/exercises/tournament/example.tt +23 -0
  144. data/tracks/ruby/exercises/tournament/tournament_test.rb +92 -0
  145. data/tracks/ruby/exercises/transpose/.version +1 -0
  146. data/tracks/ruby/exercises/transpose/example.rb +14 -0
  147. data/tracks/ruby/exercises/transpose/example.tt +22 -0
  148. data/tracks/ruby/exercises/transpose/transpose_test.rb +303 -0
  149. data/tracks/ruby/lib/bowling_cases.rb +46 -0
  150. data/tracks/ruby/lib/isogram_cases.rb +1 -1
  151. data/tracks/ruby/lib/tournament_cases.rb +45 -0
  152. data/tracks/ruby/lib/transpose_cases.rb +45 -0
  153. data/tracks/rust/config.json +2 -0
  154. data/tracks/rust/exercises/bowling/.gitignore +7 -0
  155. data/tracks/rust/exercises/bowling/Cargo.toml +3 -0
  156. data/tracks/rust/exercises/bowling/example.rs +134 -0
  157. data/tracks/rust/exercises/bowling/tests/bowling.rs +373 -0
  158. data/tracks/rust/exercises/grains/.gitignore +7 -0
  159. data/tracks/rust/exercises/grains/Cargo.toml +3 -0
  160. data/tracks/rust/exercises/grains/example.rs +11 -0
  161. data/tracks/rust/exercises/grains/src/lib.rs +7 -0
  162. data/tracks/rust/exercises/grains/tests/grains.rs +63 -0
  163. data/tracks/rust/problems.md +2 -0
  164. data/tracks/scala/README.md +38 -0
  165. data/tracks/scala/config.json +20 -74
  166. data/tracks/scala/exercises/accumulate/src/test/scala/accumulate_test.scala +4 -0
  167. data/tracks/scala/exercises/allergies/src/test/scala/allergies_test.scala +11 -0
  168. data/tracks/scala/exercises/alphametics/build.sbt +6 -0
  169. data/tracks/scala/exercises/alphametics/example.scala +125 -0
  170. data/tracks/scala/exercises/alphametics/src/main/scala/.keep +0 -0
  171. data/tracks/scala/exercises/alphametics/src/test/scala/AlphameticsTest.scala +62 -0
  172. data/tracks/scala/exercises/atbash-cipher/src/test/scala/atbash_test.scala +8 -0
  173. data/tracks/scala/exercises/bank-account/src/test/scala/BankAccountTest.scala +4 -0
  174. data/tracks/scala/exercises/binary/src/test/scala/binary_test.scala +13 -0
  175. data/tracks/scala/exercises/binary-search-tree/src/test/scala/BstTest.scala +11 -0
  176. data/tracks/scala/exercises/bowling/Example.scala +116 -0
  177. data/tracks/scala/exercises/bowling/build.sbt +3 -0
  178. data/tracks/scala/exercises/bowling/src/main/scala/Bowling.scala +11 -0
  179. data/tracks/scala/exercises/bowling/src/test/scala/BowlingSuite.scala +237 -0
  180. data/tracks/scala/exercises/clock/src/test/scala/ClockTest.scala +50 -0
  181. data/tracks/scala/exercises/connect/README.md +17 -0
  182. data/tracks/scala/exercises/connect/src/test/scala/ConnectTest.scala +7 -0
  183. data/tracks/scala/exercises/crypto-square/src/test/scala/{CrytpoSquareTest.scala → CryptoSquareTest.scala} +9 -0
  184. data/tracks/scala/exercises/custom-set/src/test/scala/CustomSetTest.scala +36 -0
  185. data/tracks/scala/exercises/difference-of-squares/src/test/scala/squares_test.scala +8 -0
  186. data/tracks/scala/exercises/forth/src/test/scala/ForthTest.scala +22 -0
  187. data/tracks/scala/exercises/hexadecimal/src/test/scala/HexadecimalTest.scala +8 -0
  188. data/tracks/scala/exercises/kindergarten-garden/src/test/scala/GardenTest.scala +5 -0
  189. data/tracks/scala/exercises/largest-series-product/src/test/scala/SeriesTest.scala +2 -0
  190. data/tracks/scala/exercises/linked-list/src/test/scala/DequeTest.scala +4 -0
  191. data/tracks/scala/exercises/luhn/src/test/scala/LuhnTest.scala +4 -0
  192. data/tracks/scala/exercises/matrix/src/test/scala/MatrixTest.scala +2 -0
  193. data/tracks/scala/exercises/minesweeper/src/test/scala/MinesweeperTest.scala +6 -0
  194. data/tracks/scala/exercises/nth-prime/src/test/scala/PrimeTest.scala +5 -0
  195. data/tracks/scala/exercises/ocr-numbers/src/test/scala/OcrTest.scala +15 -0
  196. data/tracks/scala/exercises/octal/src/test/scala/OctalTest.scala +7 -0
  197. data/tracks/scala/exercises/parallel-letter-frequency/src/test/scala/FrequencyTest.scala +8 -0
  198. data/tracks/scala/exercises/pascals-triangle/src/test/scala/PascalsTriangleTest.scala +5 -0
  199. data/tracks/scala/exercises/pig-latin/src/test/scala/PigLatinTest.scala +4 -0
  200. data/tracks/scala/exercises/pythagorean-triplet/src/test/scala/PythagoreanTripletTest.scala +3 -0
  201. data/tracks/scala/exercises/queen-attack/src/test/scala/QueensTest.scala +5 -0
  202. data/tracks/scala/exercises/robot-simulator/src/test/scala/RobotTest.scala +16 -8
  203. data/tracks/scala/exercises/say/src/test/scala/SayTest.scala +34 -17
  204. data/tracks/scala/exercises/scrabble-score/src/test/scala/scrabble_score_test.scala +6 -0
  205. data/tracks/scala/exercises/secret-handshake/src/test/scala/SecretHandshakeTest.scala +18 -9
  206. data/tracks/scala/exercises/series/src/test/scala/SeriesTest.scala +4 -2
  207. data/tracks/scala/exercises/sieve/src/test/scala/SieveTest.scala +8 -4
  208. data/tracks/scala/exercises/simple-cipher/src/test/scala/CipherTest.scala +17 -9
  209. data/tracks/scala/exercises/sublist/src/test/scala/sublist_test.scala +17 -0
  210. data/tracks/scala/exercises/trinary/src/test/scala/TrinaryTest.scala +14 -7
  211. data/tracks/scala/exercises/wordy/src/test/scala/WordProblemTest.scala +28 -14
  212. data/tracks/scala/project/Build.scala +3 -3
  213. data/tracks/scala/testgen/src/main/scala/BowlingTestGenerator.scala +57 -0
  214. data/tracks/sml/config.json +41 -6
  215. data/tracks/sml/exercises/flatten-array/example.sml +6 -0
  216. data/tracks/sml/exercises/flatten-array/test_flatten_array.sml +10 -0
  217. data/tracks/sml/exercises/nth-prime/example.sml +46 -0
  218. data/tracks/sml/exercises/nth-prime/test_nth_prime.sml +14 -0
  219. data/tracks/sml/exercises/raindrops/example.sml +9 -0
  220. data/tracks/sml/exercises/raindrops/raindrops.sml +2 -0
  221. data/tracks/sml/exercises/raindrops/test_raindrops.sml +95 -0
  222. data/tracks/swift/config.json +269 -328
  223. data/tracks/swift/exercises/pangram/PangramExample.swift +17 -0
  224. data/tracks/swift/exercises/pangram/PangramTest.swift +43 -0
  225. data/tracks/swift/xcodeProject/xSwift.xcodeproj/project.pbxproj +16 -0
  226. metadata +95 -3
@@ -4,56 +4,171 @@ open NUnit.Framework
4
4
 
5
5
  open Bowling
6
6
 
7
- let rollMany pins count game =
8
- List.replicate count pins
9
- |> List.fold (fun acc item -> roll item acc) game
10
-
11
- let rollSpare game =
12
- game
13
- |> roll 5
14
- |> roll 5
15
-
16
- let rollStrike game = game |> roll 10
7
+ let rollMany rolls game = List.fold (fun game pins -> roll pins game) game rolls
17
8
 
18
9
  [<Test>]
19
- let ``Gutter game`` () =
20
- let result = newGame |> rollMany 0 20
21
- Assert.That(score result, Is.EqualTo(0))
22
-
10
+ let ``Should be able to score a game with all zeros`` () =
11
+ let rolls = [0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]
12
+ let game = rollMany rolls newGame
13
+ Assert.That(score game, Is.EqualTo(Some 0))
14
+
23
15
  [<Test>]
24
16
  [<Ignore("Remove to run test")>]
25
- let ``All ones game`` () =
26
- let result = newGame |> rollMany 1 20
27
- Assert.That(score result, Is.EqualTo(20))
28
-
17
+ let ``Should be able to score a game with no strikes or spares`` () =
18
+ let rolls = [3; 6; 3; 6; 3; 6; 3; 6; 3; 6; 3; 6; 3; 6; 3; 6; 3; 6; 3; 6]
19
+ let game = rollMany rolls newGame
20
+ Assert.That(score game, Is.EqualTo(Some 90))
21
+
29
22
  [<Test>]
30
23
  [<Ignore("Remove to run test")>]
31
- let ``One spare game`` () =
32
- let result =
33
- newGame
34
- |> rollSpare
35
- |> roll 3
36
- |> rollMany 0 17
37
-
38
- Assert.That(score result, Is.EqualTo(16))
39
-
24
+ let ``A spare followed by zeros is worth ten points`` () =
25
+ let rolls = [6; 4; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]
26
+ let game = rollMany rolls newGame
27
+ Assert.That(score game, Is.EqualTo(Some 10))
28
+
40
29
  [<Test>]
41
30
  [<Ignore("Remove to run test")>]
42
- let ``One strike game`` () =
43
- let result =
44
- newGame
45
- |> rollStrike
46
- |> roll 3
47
- |> roll 4
48
- |> rollMany 0 16
49
-
50
- Assert.That(score result, Is.EqualTo(24))
51
-
31
+ let ``Points scored in the roll after a spare are counted twice`` () =
32
+ let rolls = [6; 4; 3; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]
33
+ let game = rollMany rolls newGame
34
+ Assert.That(score game, Is.EqualTo(Some 16))
35
+
52
36
  [<Test>]
53
37
  [<Ignore("Remove to run test")>]
54
- let ``Perfect game`` () =
55
- let result = newGame |> rollMany 10 12
56
-
57
- Assert.That(score result, Is.EqualTo(300))
58
-
59
-
38
+ let ``Consecutive spares each get a one roll bonus`` () =
39
+ let rolls = [5; 5; 3; 7; 4; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]
40
+ let game = rollMany rolls newGame
41
+ Assert.That(score game, Is.EqualTo(Some 31))
42
+
43
+ [<Test>]
44
+ [<Ignore("Remove to run test")>]
45
+ let ``A spare in the last frame gets a one roll bonus that is counted once`` () =
46
+ let rolls = [0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 7; 3; 7]
47
+ let game = rollMany rolls newGame
48
+ Assert.That(score game, Is.EqualTo(Some 17))
49
+
50
+ [<Test>]
51
+ [<Ignore("Remove to run test")>]
52
+ let ``A strike earns ten points in frame with a single roll`` () =
53
+ let rolls = [10; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]
54
+ let game = rollMany rolls newGame
55
+ Assert.That(score game, Is.EqualTo(Some 10))
56
+
57
+ [<Test>]
58
+ [<Ignore("Remove to run test")>]
59
+ let ``Points scored in the two rolls after a strike are counted twice as a bonus`` () =
60
+ let rolls = [10; 5; 3; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]
61
+ let game = rollMany rolls newGame
62
+ Assert.That(score game, Is.EqualTo(Some 26))
63
+
64
+ [<Test>]
65
+ [<Ignore("Remove to run test")>]
66
+ let ``Consecutive strikes each get the two roll bonus`` () =
67
+ let rolls = [10; 10; 10; 5; 3; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]
68
+ let game = rollMany rolls newGame
69
+ Assert.That(score game, Is.EqualTo(Some 81))
70
+
71
+ [<Test>]
72
+ [<Ignore("Remove to run test")>]
73
+ let ``A strike in the last frame gets a two roll bonus that is counted once`` () =
74
+ let rolls = [0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 10; 7; 1]
75
+ let game = rollMany rolls newGame
76
+ Assert.That(score game, Is.EqualTo(Some 18))
77
+
78
+ [<Test>]
79
+ [<Ignore("Remove to run test")>]
80
+ let ``Rolling a spare with the two roll bonus does not get a bonus roll`` () =
81
+ let rolls = [0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 10; 7; 3]
82
+ let game = rollMany rolls newGame
83
+ Assert.That(score game, Is.EqualTo(Some 20))
84
+
85
+ [<Test>]
86
+ [<Ignore("Remove to run test")>]
87
+ let ``Strikes with the two roll bonus do not get bonus rolls`` () =
88
+ let rolls = [0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 10; 10; 10]
89
+ let game = rollMany rolls newGame
90
+ Assert.That(score game, Is.EqualTo(Some 30))
91
+
92
+ [<Test>]
93
+ [<Ignore("Remove to run test")>]
94
+ let ``A strike with the one roll bonus after a spare in the last frame does not get a bonus`` () =
95
+ let rolls = [0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 7; 3; 10]
96
+ let game = rollMany rolls newGame
97
+ Assert.That(score game, Is.EqualTo(Some 20))
98
+
99
+ [<Test>]
100
+ [<Ignore("Remove to run test")>]
101
+ let ``All strikes is a perfect game`` () =
102
+ let rolls = [10; 10; 10; 10; 10; 10; 10; 10; 10; 10; 10; 10]
103
+ let game = rollMany rolls newGame
104
+ Assert.That(score game, Is.EqualTo(Some 300))
105
+
106
+ [<Test>]
107
+ [<Ignore("Remove to run test")>]
108
+ let ``Rolls can not score negative points`` () =
109
+ let rolls = [-1; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]
110
+ let game = rollMany rolls newGame
111
+ Assert.That(score game, Is.EqualTo(None))
112
+
113
+ [<Test>]
114
+ [<Ignore("Remove to run test")>]
115
+ let ``A roll can not score more than 10 points`` () =
116
+ let rolls = [11; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]
117
+ let game = rollMany rolls newGame
118
+ Assert.That(score game, Is.EqualTo(None))
119
+
120
+ [<Test>]
121
+ [<Ignore("Remove to run test")>]
122
+ let ``Two rolls in a frame can not score more than 10 points`` () =
123
+ let rolls = [5; 6; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]
124
+ let game = rollMany rolls newGame
125
+ Assert.That(score game, Is.EqualTo(None))
126
+
127
+ [<Test>]
128
+ [<Ignore("Remove to run test")>]
129
+ let ``Two bonus rolls after a strike in the last frame can not score more than 10 points`` () =
130
+ let rolls = [0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 10; 5; 6]
131
+ let game = rollMany rolls newGame
132
+ Assert.That(score game, Is.EqualTo(None))
133
+
134
+ [<Test>]
135
+ [<Ignore("Remove to run test")>]
136
+ let ``An unstarted game can not be scored`` () =
137
+ let rolls = []
138
+ let game = rollMany rolls newGame
139
+ Assert.That(score game, Is.EqualTo(None))
140
+
141
+ [<Test>]
142
+ [<Ignore("Remove to run test")>]
143
+ let ``An incomplete game can not be scored`` () =
144
+ let rolls = [0; 0]
145
+ let game = rollMany rolls newGame
146
+ Assert.That(score game, Is.EqualTo(None))
147
+
148
+ [<Test>]
149
+ [<Ignore("Remove to run test")>]
150
+ let ``A game with more than ten frames can not be scored`` () =
151
+ let rolls = [0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]
152
+ let game = rollMany rolls newGame
153
+ Assert.That(score game, Is.EqualTo(None))
154
+
155
+ [<Test>]
156
+ [<Ignore("Remove to run test")>]
157
+ let ``Bonus rolls for a strike in the last frame must be rolled before score can be calculated`` () =
158
+ let rolls = [0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 10]
159
+ let game = rollMany rolls newGame
160
+ Assert.That(score game, Is.EqualTo(None))
161
+
162
+ [<Test>]
163
+ [<Ignore("Remove to run test")>]
164
+ let ``Both bonus rolls for a strike in the last frame must be rolled before score can be calculated`` () =
165
+ let rolls = [0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 10; 10]
166
+ let game = rollMany rolls newGame
167
+ Assert.That(score game, Is.EqualTo(None))
168
+
169
+ [<Test>]
170
+ [<Ignore("Remove to run test")>]
171
+ let ``Bonus roll for a spare in the last frame must be rolled before score can be calculated`` () =
172
+ let rolls = [0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 7; 3]
173
+ let game = rollMany rolls newGame
174
+ Assert.That(score game, Is.EqualTo(None))
@@ -1,29 +1,58 @@
1
1
  module Bowling
2
2
 
3
+ let map2 f opt1 opt2 =
4
+ match opt1, opt2 with
5
+ | Some x, Some y -> Some (f x y)
6
+ | _ -> None
7
+
3
8
  let numberOfFrames = 10
4
9
  let maximumFrameScore = 10
5
-
6
- let roll pins game = game @ [pins]
7
-
8
- let score game =
9
-
10
- let roll index = List.item index game
11
-
12
- let isStrike frameIndex = roll frameIndex = maximumFrameScore
13
- let isSpare frameIndex = roll frameIndex + roll (frameIndex + 1) = maximumFrameScore
14
-
15
- let strikeBonus frameIndex = roll (frameIndex + 1) + roll (frameIndex + 2)
16
- let spareBonus frameIndex = roll (frameIndex + 2)
17
-
18
- let sumOfBallsInFrame frameIndex = roll frameIndex + roll (frameIndex + 1)
10
+ let minimumFrameScore = 0
11
+
12
+ let newGame = Some []
13
+
14
+ let validatePins pins =
15
+ if pins < minimumFrameScore || pins > maximumFrameScore then
16
+ None
17
+ else
18
+ Some pins
19
+
20
+ let isStrike pins = pins = maximumFrameScore
21
+ let isSpare pins1 pins2 = pins1 + pins2 = maximumFrameScore
22
+
23
+ let roll pins rolls = map2 (fun rolls pins -> rolls @ [pins]) rolls (validatePins pins)
24
+
25
+ let rec scoreRolls totalScore frame rolls =
26
+ let isLastFrame = frame = numberOfFrames
27
+ let gameFinished = frame = numberOfFrames + 1
28
+
29
+ let scoreStrike remainder =
30
+ match remainder with
31
+ | x::y::zs when isLastFrame ->
32
+ if x + y > 10 && x <> 10 then None
33
+ else scoreRolls (totalScore + 10 + x + y) (frame + 1) zs
34
+ | x::y::zs ->
35
+ scoreRolls (totalScore + 10 + x + y) (frame + 1) (x::y::zs)
36
+ | _ ->
37
+ None
38
+
39
+ let scoreSpare x y remainder =
40
+ match remainder with
41
+ | z::zs->
42
+ scoreRolls (totalScore + x + y + z) (frame + 1) (if isLastFrame then zs else z::zs)
43
+ | _ ->
44
+ None
45
+
46
+ let scoreNormal x y remainder =
47
+ match validatePins (x + y) with
48
+ | Some z -> scoreRolls (totalScore + z) (frame + 1) remainder
49
+ | None -> None
50
+
51
+ match rolls with
52
+ | [] -> if gameFinished then Some totalScore else None
53
+ | x::xs when isStrike x -> scoreStrike xs
54
+ | x::y::ys when isSpare x y -> scoreSpare x y ys
55
+ | x::y::zs -> scoreNormal x y zs
56
+ | _ -> None
19
57
 
20
- let folder (score, frameIndex) _ =
21
- if isStrike frameIndex then (score + 10 + strikeBonus frameIndex, frameIndex + 1)
22
- elif isSpare frameIndex then (score + 10 + spareBonus frameIndex, frameIndex + 2)
23
- else (score + sumOfBallsInFrame frameIndex, frameIndex + 2)
24
-
25
- [1..numberOfFrames]
26
- |> List.fold folder (0, 0)
27
- |> fst
28
-
29
- let newGame = []
58
+ let score = Option.bind (scoreRolls 0 1)
@@ -211,6 +211,11 @@
211
211
  "slug": "robot-name",
212
212
  "topics": []
213
213
  },
214
+ {
215
+ "difficulty": 1,
216
+ "slug": "diamond",
217
+ "topics": []
218
+ },
214
219
  {
215
220
  "difficulty": 1,
216
221
  "slug": "react",
@@ -0,0 +1,227 @@
1
+ package diamond
2
+
3
+ import (
4
+ "math/rand"
5
+ "reflect"
6
+ "strings"
7
+ "testing"
8
+ "testing/quick"
9
+ "time"
10
+ )
11
+
12
+ var config = &quick.Config{Rand: rand.New(rand.NewSource(time.Now().UnixNano()))}
13
+
14
+ type correctChar byte
15
+
16
+ func (c correctChar) Generate(rand *rand.Rand, _ int) reflect.Value {
17
+ return reflect.ValueOf(correctChar('A' + rand.Intn('Z'-'A'+1)))
18
+ }
19
+
20
+ func checkCorrect(requirement func(byte, []string) bool, keepSeparator bool, t *testing.T) {
21
+ assertion := func(char correctChar) bool {
22
+ d, err := Gen(byte(char))
23
+ if err != nil {
24
+ return false
25
+ }
26
+ separator := strings.Split
27
+ if keepSeparator {
28
+ separator = strings.SplitAfter
29
+ }
30
+ rows := separator(d, "\n")
31
+ if len(rows) < 2 {
32
+ return false
33
+ }
34
+ return requirement(byte(char), rows[:len(rows)-1])
35
+ }
36
+ if err := quick.Check(assertion, config); err != nil {
37
+ t.Error(err)
38
+ }
39
+ }
40
+
41
+ func TestFirstRowContainsOneA(t *testing.T) {
42
+ requirement := func(char byte, rows []string) bool {
43
+ return len(rows) > 0 && strings.Count(rows[0], "A") == 1
44
+ }
45
+ checkCorrect(requirement, false, t)
46
+ }
47
+
48
+ func TestLastRowContainsOneA(t *testing.T) {
49
+ requirement := func(char byte, rows []string) bool {
50
+ return len(rows) > 0 && strings.Count(rows[len(rows)-1], "A") == 1
51
+ }
52
+ checkCorrect(requirement, false, t)
53
+ }
54
+
55
+ func TestAllRowsIdenticalLettersExceptFirstAndLast(t *testing.T) {
56
+ requirement := func(char byte, rows []string) bool {
57
+ for i, row := range rows {
58
+ r := strings.TrimSpace(row)
59
+ if r[0] != r[len(r)-1] {
60
+ return false
61
+ }
62
+ if len(r) < 2 && i != 0 && i != len(rows)-1 {
63
+ return false
64
+ }
65
+ }
66
+ return true
67
+ }
68
+ checkCorrect(requirement, false, t)
69
+ }
70
+
71
+ func TestAllRowsHaveSameTrailingSpaces(t *testing.T) {
72
+ requirement := func(char byte, rows []string) bool {
73
+ for _, row := range rows {
74
+ if len(row) == 0 {
75
+ return false
76
+ }
77
+ for i, j := 0, len(row)-1; i < j && row[i] == ' '; i, j = i+1, j-1 {
78
+ if row[j] != ' ' {
79
+ return false
80
+ }
81
+ }
82
+ }
83
+ return true
84
+ }
85
+ checkCorrect(requirement, false, t)
86
+ }
87
+
88
+ func TestDiamondIsHorizontallySymmetric(t *testing.T) {
89
+ requirement := func(char byte, rows []string) bool {
90
+ for _, row := range rows {
91
+ l := len(row)
92
+ for i := l/2 - 1; i >= 0; i-- {
93
+ if row[i] != row[l-1-i] {
94
+ return false
95
+ }
96
+ }
97
+ }
98
+ return true
99
+ }
100
+ checkCorrect(requirement, false, t)
101
+ }
102
+
103
+ func TestDiamondIsVerticallySymmetric(t *testing.T) {
104
+ requirement := func(char byte, rows []string) bool {
105
+ for i, j := 0, len(rows)-1; i < j; i, j = i+1, j-1 {
106
+ if rows[i] != rows[j] {
107
+ return false
108
+ }
109
+ }
110
+ return true
111
+ }
112
+ checkCorrect(requirement, true, t)
113
+ }
114
+
115
+ func TestDiamondIsSquare(t *testing.T) {
116
+ requirement := func(char byte, rows []string) bool {
117
+ if int(char-'A')*2+1 != len(rows) {
118
+ return false
119
+ }
120
+ for _, row := range rows {
121
+ if len(row) != len(rows) {
122
+ return false
123
+ }
124
+ }
125
+ return true
126
+ }
127
+ checkCorrect(requirement, false, t)
128
+ }
129
+
130
+ func TestDiamondHasItsShape(t *testing.T) {
131
+ requirement := func(char byte, rows []string) bool {
132
+ var n int
133
+ for i, row := range rows {
134
+ s := len(strings.TrimSpace(row))
135
+ if i > len(rows)/2 && n <= s {
136
+ return false
137
+ } else if i <= len(rows)/2 && n >= s {
138
+ return false
139
+ }
140
+ n = s
141
+ }
142
+ return true
143
+ }
144
+ checkCorrect(requirement, false, t)
145
+ }
146
+
147
+ func TestTopHalfHasAscendingLetters(t *testing.T) {
148
+ requirement := func(char byte, rows []string) bool {
149
+ var start byte = 'A' - 1
150
+ for i := 0; i <= len(rows)/2; i++ {
151
+ s := strings.TrimLeft(rows[i], " ")
152
+ if s == "" || s[0] <= start {
153
+ return false
154
+ }
155
+ start = s[0]
156
+ }
157
+ return true
158
+ }
159
+ checkCorrect(requirement, false, t)
160
+ }
161
+
162
+ func TestBottomHalfHasDescendingLetters(t *testing.T) {
163
+ requirement := func(char byte, rows []string) bool {
164
+ var start byte = 'A' - 1
165
+ for i := len(rows) - 1; i > len(rows)/2; i-- {
166
+ s := strings.TrimLeft(rows[i], " ")
167
+ if s == "" || s[0] <= start {
168
+ return false
169
+ }
170
+ start = s[0]
171
+ }
172
+ return true
173
+ }
174
+ checkCorrect(requirement, false, t)
175
+ }
176
+
177
+ func TestDiamondFourCornersAreTriangle(t *testing.T) {
178
+ requirement := func(char byte, rows []string) bool {
179
+ notSpace := func(r rune) bool { return r <= 'Z' && r >= 'A' }
180
+ var n int
181
+ for i, row := range rows {
182
+ s := strings.IndexFunc(row, notSpace)
183
+ e := len(row) - strings.LastIndexFunc(row, notSpace) - 1
184
+ if s != e {
185
+ return false
186
+ } else if i == 0 {
187
+ n = s
188
+ } else {
189
+ if i > len(rows)/2 && n >= s {
190
+ return false
191
+ } else if i <= len(rows)/2 && n <= s {
192
+ return false
193
+ }
194
+ n = s
195
+ }
196
+ }
197
+ return true
198
+ }
199
+ checkCorrect(requirement, false, t)
200
+ }
201
+
202
+ type wrongChar byte
203
+
204
+ func (c wrongChar) Generate(rand *rand.Rand, _ int) reflect.Value {
205
+ b := rand.Intn(256)
206
+ for ; b >= 'A' && b <= 'Z'; b = rand.Intn(256) {
207
+ }
208
+ return reflect.ValueOf(wrongChar(b))
209
+ }
210
+
211
+ func TestCharOutOfRangeShouldGiveError(t *testing.T) {
212
+ assertion := func(char wrongChar) bool {
213
+ _, err := Gen(byte(char))
214
+ return err != nil
215
+ }
216
+ if err := quick.Check(assertion, config); err != nil {
217
+ t.Error(err)
218
+ }
219
+ }
220
+
221
+ const targetTestVersion = 1
222
+
223
+ func TestTestVersion(t *testing.T) {
224
+ if testVersion != targetTestVersion {
225
+ t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
226
+ }
227
+ }
@@ -0,0 +1,47 @@
1
+ package diamond
2
+
3
+ import (
4
+ "errors"
5
+ "strings"
6
+ )
7
+
8
+ const testVersion = 1
9
+
10
+ const startIndex = 'A'
11
+
12
+ // Gen builds a diamond
13
+ func Gen(char byte) (string, error) {
14
+ if char > 'Z' || char < 'A' {
15
+ return "", errors.New(string(char) + " isn't supported, only between (A, Z)")
16
+ }
17
+ return gen(char), nil
18
+ }
19
+
20
+ func gen(char byte) string {
21
+ var output []string
22
+ currentIndex := int(char - startIndex)
23
+ for i := 0; i <= currentIndex; i++ {
24
+ output = append(output, getLine(currentIndex, i))
25
+ }
26
+ for i := currentIndex - 1; i > -1; i-- {
27
+ output = append(output, getLine(currentIndex, i))
28
+ }
29
+ return strings.Join(output, "\n") + "\n"
30
+ }
31
+
32
+ func getLine(currentStart, current int) string {
33
+ diff := currentStart - current
34
+ return spaces(diff) + alphabets(current) + spaces(diff)
35
+ }
36
+
37
+ func alphabets(current int) string {
38
+ if current == 0 {
39
+ return "A"
40
+ }
41
+ c := current + startIndex
42
+ return string(c) + spaces(current*2-1) + string(c)
43
+ }
44
+
45
+ func spaces(n int) string {
46
+ return strings.Repeat(" ", n)
47
+ }
@@ -1,6 +1,6 @@
1
1
  package foodchain
2
2
 
3
- const testVersion = 2
3
+ const testVersion = 3
4
4
 
5
5
  var verse = []struct{ eaten, comment string }{
6
6
  {"", ""},
@@ -1,11 +1,12 @@
1
1
  package foodchain
2
2
 
3
3
  import (
4
+ "fmt"
4
5
  "strings"
5
6
  "testing"
6
7
  )
7
8
 
8
- const targetTestVersion = 2
9
+ const targetTestVersion = 3
9
10
 
10
11
  func TestTestVersion(t *testing.T) {
11
12
  if testVersion != targetTestVersion {
@@ -67,24 +68,47 @@ I don't know why she swallowed the fly. Perhaps she'll die.`,
67
68
  She's dead, of course!`,
68
69
  }
69
70
 
71
+ // diff compares two multi-line strings and returns a helpful comment
72
+ func diff(got, want string) string {
73
+ g := strings.Split(got, "\n")
74
+ w := strings.Split(want, "\n")
75
+ for i := 0; ; i++ {
76
+ switch {
77
+ case i < len(g) && i < len(w):
78
+ if g[i] == w[i] {
79
+ continue
80
+ }
81
+ return fmt.Sprintf("-- first difference in line %d:\n"+
82
+ "-- got : %q\n-- want: %q\n", i+1, g[i], w[i])
83
+ case i < len(g):
84
+ return fmt.Sprintf("-- got %d extra lines after line %d:\n"+
85
+ "-- first extra line: %q\n", len(g)-len(w), i, g[i])
86
+ case i < len(w):
87
+ return fmt.Sprintf("-- got %d correct lines, want %d more lines:\n"+
88
+ "-- want next: %q\n", i, len(w)-i, w[i])
89
+ default:
90
+ return "no differences found"
91
+ }
92
+ }
93
+ }
94
+
70
95
  func TestVerse(t *testing.T) {
71
96
  for v := 1; v <= 8; v++ {
72
97
  if ret := Verse(v); ret != ref[v] {
73
- t.Fatalf("Verse(%d) =\n%s\n want:\n%s", v, ret, ref[v])
98
+ t.Fatalf("Verse(%d) =\n%s\n want:\n%s\n%s", v, ret, ref[v], diff(ret, ref[v]))
74
99
  }
75
100
  }
76
101
  }
77
102
 
78
103
  func TestVerses(t *testing.T) {
79
- if ret, want := Verses(1, 2), ref[1]+"\n\n"+ref[2]; ret != want {
80
- t.Fatalf("Verses(1, 2) =\n%s\n want:\n%s", ret, want)
104
+ if ret, want := Verses(1, 3), strings.Join(ref[1:4], "\n\n"); ret != want {
105
+ t.Fatalf("Verses(1, 3) =\n%s\n want:\n%s\n%s", ret, want, diff(ret, want))
81
106
  }
82
-
83
107
  }
84
108
 
85
109
  func TestSong(t *testing.T) {
86
110
  if ret, want := Song(), strings.Join(ref[1:], "\n\n"); ret != want {
87
- t.Fatalf("Song() =\n%s\n want:\n%s", ret, want)
111
+ t.Fatalf("Song() =\n%s\n want:\n%s\n%s", ret, want, diff(ret, want))
88
112
  }
89
113
  }
90
114