From 1b626d63896784f42630c7d6c43737d096b6645f Mon Sep 17 00:00:00 2001 From: Sangeeth Sudheer Date: Wed, 27 Dec 2023 00:24:23 +0530 Subject: [PATCH] initial commit --- .gitignore | 135 +++++++++ .path_progress | 1 + .rtx.toml | 2 + GREED_RULES.txt | 66 +++++ README.rdoc | 191 ++++++++++++ Rakefile | 12 + about_array_assignment.rb | 51 ++++ about_arrays.rb | 84 ++++++ about_asserts.rb | 40 +++ about_blocks.rb | 96 ++++++ about_class_methods.rb | 169 +++++++++++ about_classes.rb | 190 ++++++++++++ about_constants.rb | 87 ++++++ about_control_statements.rb | 142 +++++++++ about_dice_project.rb | 63 ++++ about_exceptions.rb | 68 +++++ about_extra_credit.rb | 8 + about_hashes.rb | 126 ++++++++ about_inheritance.rb | 85 ++++++ about_iteration.rb | 122 ++++++++ about_java_interop.rb | 137 +++++++++ about_keyword_arguments.rb | 43 +++ about_message_passing.rb | 185 ++++++++++++ about_methods.rb | 153 ++++++++++ about_modules.rb | 63 ++++ about_nil.rb | 38 +++ about_objects.rb | 50 ++++ about_open_classes.rb | 45 +++ about_pattern_matching.rb | 215 ++++++++++++++ about_proxy_object_project.rb | 156 ++++++++++ about_regular_expressions.rb | 161 ++++++++++ about_sandwich_code.rb | 106 +++++++ about_scope.rb | 79 +++++ about_scoring_project.rb | 77 +++++ about_strings.rb | 197 +++++++++++++ about_symbols.rb | 100 +++++++ about_to_str.rb | 54 ++++ about_triangle_project.rb | 24 ++ about_triangle_project_2.rb | 16 + about_true_and_false.rb | 33 +++ example_file.txt | 4 + koans.watchr | 3 + neo.rb | 541 ++++++++++++++++++++++++++++++++++ path_to_enlightenment.rb | 44 +++ triangle.rb | 22 ++ 45 files changed, 4284 insertions(+) create mode 100644 .gitignore create mode 100644 .path_progress create mode 100644 .rtx.toml create mode 100644 GREED_RULES.txt create mode 100644 README.rdoc create mode 100644 Rakefile create mode 100644 about_array_assignment.rb create mode 100644 about_arrays.rb create mode 100644 about_asserts.rb create mode 100644 about_blocks.rb create mode 100644 about_class_methods.rb create mode 100644 about_classes.rb create mode 100644 about_constants.rb create mode 100644 about_control_statements.rb create mode 100644 about_dice_project.rb create mode 100644 about_exceptions.rb create mode 100644 about_extra_credit.rb create mode 100644 about_hashes.rb create mode 100644 about_inheritance.rb create mode 100644 about_iteration.rb create mode 100644 about_java_interop.rb create mode 100644 about_keyword_arguments.rb create mode 100644 about_message_passing.rb create mode 100644 about_methods.rb create mode 100644 about_modules.rb create mode 100644 about_nil.rb create mode 100644 about_objects.rb create mode 100644 about_open_classes.rb create mode 100644 about_pattern_matching.rb create mode 100644 about_proxy_object_project.rb create mode 100644 about_regular_expressions.rb create mode 100644 about_sandwich_code.rb create mode 100644 about_scope.rb create mode 100644 about_scoring_project.rb create mode 100644 about_strings.rb create mode 100644 about_symbols.rb create mode 100644 about_to_str.rb create mode 100644 about_triangle_project.rb create mode 100644 about_triangle_project_2.rb create mode 100644 about_true_and_false.rb create mode 100644 example_file.txt create mode 100644 koans.watchr create mode 100644 neo.rb create mode 100644 path_to_enlightenment.rb create mode 100644 triangle.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d447f4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,135 @@ +# Created by https://www.toptal.com/developers/gitignore/api/ruby,macos,linux,windows +# Edit at https://www.toptal.com/developers/gitignore?templates=ruby,macos,linux,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Ruby ### +*.gem +*.rbc +/.config +/coverage/ +/InstalledFiles +/pkg/ +/spec/reports/ +/spec/examples.txt +/test/tmp/ +/test/version_tmp/ +/tmp/ + +# Used by dotenv library to load environment variables. +# .env + +# Ignore Byebug command history file. +.byebug_history + +## Specific to RubyMotion: +.dat* +.repl_history +build/ +*.bridgesupport +build-iPhoneOS/ +build-iPhoneSimulator/ + +## Specific to RubyMotion (use of CocoaPods): +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# vendor/Pods/ + +## Documentation cache and generated files: +/.yardoc/ +/_yardoc/ +/doc/ +/rdoc/ + +## Environment normalization: +/.bundle/ +/vendor/bundle +/lib/bundler/man/ + +# for a library or gem, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# Gemfile.lock +# .ruby-version +# .ruby-gemset + +# unless supporting rvm < 1.11.0 or doing something fancy, ignore this: +.rvmrc + +# Used by RuboCop. Remote config files pulled in from inherit_from directive. +# .rubocop-https?--* + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/ruby,macos,linux,windows + diff --git a/.path_progress b/.path_progress new file mode 100644 index 0000000..8041235 --- /dev/null +++ b/.path_progress @@ -0,0 +1 @@ +0,5,5,9,9,14,15,15,15,15,15,15,16,16,16,16,17,18,19,20,21,22,23,25,26,28,29,29,30,31,31,32,33,34,35,36,37,38,39,40,40,40,41,42,43,44,45,46,47,48,49,49,50,51,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,53,53,54,54,55,55,55,56,57,58,60,62,64,65,65,65,66,66,67,67,68,70,71,72,73,73,74,75,76,78 \ No newline at end of file diff --git a/.rtx.toml b/.rtx.toml new file mode 100644 index 0000000..8b15774 --- /dev/null +++ b/.rtx.toml @@ -0,0 +1,2 @@ +[tools] +ruby = "3.1.4" diff --git a/GREED_RULES.txt b/GREED_RULES.txt new file mode 100644 index 0000000..58b5a9c --- /dev/null +++ b/GREED_RULES.txt @@ -0,0 +1,66 @@ += Playing Greed + +Greed is a dice game played among 2 or more players, using 5 +six-sided dice. + +== Playing Greed + +Each player takes a turn consisting of one or more rolls of the dice. +On the first roll of the game, a player rolls all five dice which are +scored according to the following: + + Three 1's => 1000 points + Three 6's => 600 points + Three 5's => 500 points + Three 4's => 400 points + Three 3's => 300 points + Three 2's => 200 points + One 1 => 100 points + One 5 => 50 points + +A single die can only be counted once in each roll. For example, +a "5" can only count as part of a triplet (contributing to the 500 +points) or as a single 50 points, but not both in the same roll. + +Example Scoring + + Throw Score + --------- ------------------ + 5 1 3 4 1 50 + 2 * 100 = 250 + 1 1 1 3 1 1000 + 100 = 1100 + 2 4 4 5 4 400 + 50 = 450 + +The dice not contributing to the score are called the non-scoring +dice. "3" and "4" are non-scoring dice in the first example. "3" is +a non-scoring die in the second, and "2" is a non-score die in the +final example. + +After a player rolls and the score is calculated, the scoring dice are +removed and the player has the option of rolling again using only the +non-scoring dice. If all of the thrown dice are scoring, then the +player may roll all 5 dice in the next roll. + +The player may continue to roll as long as each roll scores points. If +a roll has zero points, then the player loses not only their turn, but +also accumulated score for that turn. If a player decides to stop +rolling before rolling a zero-point roll, then the accumulated points +for the turn is added to his total score. + +== Getting "In The Game" + +Before a player is allowed to accumulate points, they must get at +least 300 points in a single turn. Once they have achieved 300 points +in a single turn, the points earned in that turn and each following +turn will be counted toward their total score. + +== End Game + +Once a player reaches 3000 (or more) points, the game enters the final +round where each of the other players gets one more turn. The winner +is the player with the highest score after the final round. + +== References + +Greed is described on Wikipedia at +http://en.wikipedia.org/wiki/Greed_(dice_game), however the rules are +a bit different from the rules given here. diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 0000000..aa54efd --- /dev/null +++ b/README.rdoc @@ -0,0 +1,191 @@ += Ruby Koans + +The Ruby Koans walk you along the path to enlightenment in order to learn Ruby. +The goal is to learn the Ruby language, syntax, structure, and some common +functions and libraries. We also teach you culture by basing the koans on tests. +Testing is not just something we pay lip service to, but something we +live. Testing is essential in your quest to learn and do great things in Ruby. + +== The Structure + +The koans are broken out into areas by file, hashes are covered in +about_hashes.rb+, +modules are introduced in +about_modules.rb+, etc. They are presented in +order in the +path_to_enlightenment.rb+ file. + +Each koan builds up your knowledge of Ruby and builds upon itself. It will stop at +the first place you need to correct. + +Some koans simply need to have the correct answer substituted for an incorrect one. +Some, however, require you to supply your own answer. If you see the method +__+ (a +double underscore) listed, it is a hint to you to supply your own code in order to +make it work correctly. + +== Installing Ruby + +If you do not have Ruby setup, please visit http://ruby-lang.org/en/downloads/ for +operating specific instructions. In order to run the koans you need +ruby+ and ++rake+ installed. To check your installations simply type: + +*nix platforms from any terminal window: + + [~] $ ruby --version + [~] $ rake --version + +Windows from the command prompt (+cmd.exe+) + + c:\ruby --version + c:\rake --version + +If you don't have +rake+ installed, just run gem install rake + +Any response for Ruby with a version number greater than 1.8 is fine (should be +around 1.8.6 or more). Any version of +rake+ will do. + +== Generating the Koans + +A fresh checkout will not include the koans, you will need to generate +them. + + [ruby_koans] $ rake gen # generates the koans directory + +If you need to regenerate the koans, thus wiping your current `koans`, + + [ruby_koans] $ rake regen # regenerates the koans directory, wiping the original + +== The Path To Enlightenment + +You can run the tests through +rake+ or by calling the file itself (+rake+ is the +recommended way to run them as we might build more functionality into this task). + +*nix platforms, from the +ruby_koans+ directory + + [ruby_koans] $ rake # runs the default target :walk_the_path + [ruby_koans] $ ruby path_to_enlightenment.rb # simply call the file directly + +Windows is the same thing + + c:\ruby_koans\rake # runs the default target :walk_the_path + c:\ruby_koans\ruby path_to_enlightenment.rb # simply call the file directly + +=== Red, Green, Refactor + +In test-driven development the mantra has always been red, green, refactor. +Write a failing test and run it (red), make the test pass (green), +then look at the code and consider if you can make it any better (refactor). + +While walking the path to Ruby enlightenment you will need to run the koan and +see it fail (red), make the test pass (green), then take a moment +and reflect upon the test to see what it is teaching you and improve the code to +better communicate its intent (refactor). + +The very first time you run the koans you will see the following output: + + [ ruby_koans ] $ rake + (in /Users/person/dev/ruby_koans) + /usr/bin/ruby1.8 path_to_enlightenment.rb + + AboutAsserts#test_assert_truth has damaged your karma. + + The Master says: + You have not yet reached enlightenment. + + The answers you seek... + is not true. + + Please meditate on the following code: + ./about_asserts.rb:10:in `test_assert_truth' + path_to_enlightenment.rb:38:in `each_with_index' + path_to_enlightenment.rb:38 + + mountains are merely mountains + your path thus far [X_________________________________________________] 0/280 (0%) + +You have come to your first stage. Notice it is telling you where to look for +the first solution: + + Please meditate on the following code: + ./about_asserts.rb:10:in `test_assert_truth' + path_to_enlightenment.rb:38:in `each_with_index' + path_to_enlightenment.rb:38 + +Open the +about_asserts.rb+ file and look at the first test: + + # We shall contemplate truth by testing reality, via asserts. + def test_assert_truth + assert false # This should be true + end + +Change the +false+ to +true+ and re-run the test. After you are +done, think about what you are learning. In this case, ignore everything except +the method name (+test_assert_truth+) and the parts inside the method (everything +before the +end+). + +In this case the goal is for you to see that if you pass a value to the +assert+ +method, it will either ensure it is +true+ and continue on, or fail if +the statement is +false+. + +=== Running the Koans automatically + +This section is optional. + +Normally the path to enlightenment looks like this: + + cd ruby_koans + rake + # edit + rake + # edit + rake + # etc + +If you prefer, you can keep the koans running in the background so that after you +make a change in your editor, the koans will immediately run again. This will +hopefully keep your focus on learning Ruby instead of on the command line. + +Install the Ruby gem (library) called +observr+ and then ask it to +"watch" the koans for changes: + + cd ruby_koans + rake + # decide to run rake automatically from now on as you edit + gem install observr + observr ./koans/koans.watchr + +== Inspiration + +A special thanks to Mike Clark and Ara Howard for inspiring this +project. Mike Clark wrote an excellent blog post about learning Ruby +through unit testing. This sparked an idea that has taken a bit to +solidify, that of bringing new rubyists into the community through +testing. Ara Howard then gave us the idea for the Koans in his ruby +quiz entry on Meta Koans (a must for any rubyist wanting to improve +their skills). Also, "The Little Lisper" taught us all the value of +the short questions/simple answers style of learning. + +Mike Clark's post :: http://www.clarkware.com/cgi/blosxom/2005/03/18 +Meta Koans :: http://rubyquiz.com/quiz67.html +The Little Lisper :: http://www.amazon.com/Little-LISPer-Third-Daniel-Friedman/dp/0023397632 + +== Other Resources + +The Ruby Language :: http://ruby-lang.org +Try Ruby in your browser :: http://tryruby.org + +Dave Thomas' introduction to Ruby Programming Ruby (the Pick Axe) :: http://pragprog.com/titles/ruby/programming-ruby + +Brian Marick's fantastic guide for beginners Everyday Scripting with Ruby :: http://pragprog.com/titles/bmsft/everyday-scripting-with-ruby + += Other stuff + +Author :: Jim Weirich +Author :: Joe O'Brien +Issue Tracker :: https://github.com/edgecase/ruby_koans/issues +Requires :: Ruby 1.8.x or later and Rake (any recent version) + += License + +http://i.creativecommons.org/l/by-nc-sa/3.0/88x31.png + +RubyKoans is released under a Creative Commons, +Attribution-NonCommercial-ShareAlike, Version 3.0 +(http://creativecommons.org/licenses/by-nc-sa/3.0/) License. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..1a2c7f2 --- /dev/null +++ b/Rakefile @@ -0,0 +1,12 @@ +#!/usr/bin/env ruby +# -*- ruby -*- + +require 'rake/clean' +require 'rake/testtask' + +task :default => :test + +task :test do + ruby 'path_to_enlightenment.rb' +end + diff --git a/about_array_assignment.rb b/about_array_assignment.rb new file mode 100644 index 0000000..0008608 --- /dev/null +++ b/about_array_assignment.rb @@ -0,0 +1,51 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutArrayAssignment < Neo::Koan + def test_non_parallel_assignment + names = ["John", "Smith"] + assert_equal ["John", "Smith"], names + end + + def test_parallel_assignments + first_name, last_name = ["John", "Smith"] + assert_equal "John", first_name + assert_equal "Smith", last_name + end + + def test_parallel_assignments_with_extra_values + first_name, last_name = ["John", "Smith", "III"] + assert_equal "John", first_name + assert_equal "Smith", last_name # weird, should've errored? + end + + def test_parallel_assignments_with_splat_operator + first_name, *last_name = ["John", "Smith", "III"] + assert_equal "John", first_name + assert_equal ["Smith", "III"], last_name + end + + def test_parallel_assignments_with_too_few_values + first_name, last_name = ["Cher"] + assert_equal "Cher", first_name + assert_equal nil, last_name + end + + def test_parallel_assignments_with_subarrays + first_name, last_name = [["Willie", "Rae"], "Johnson"] + assert_equal ["Willie", "Rae"], first_name + assert_equal "Johnson", last_name + end + + def test_parallel_assignment_with_one_variable + first_name, = ["John", "Smith"] + assert_equal "John", first_name + end + + def test_swapping_with_parallel_assignment + first_name = "Roy" + last_name = "Rob" + first_name, last_name = last_name, first_name + assert_equal "Rob", first_name + assert_equal "Roy", last_name + end +end diff --git a/about_arrays.rb b/about_arrays.rb new file mode 100644 index 0000000..df3a258 --- /dev/null +++ b/about_arrays.rb @@ -0,0 +1,84 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutArrays < Neo::Koan + def test_creating_arrays + empty_array = Array.new + assert_equal Array, empty_array.class + assert_equal 0, empty_array.size + end + + def test_array_literals + array = Array.new + assert_equal [], array + + array[0] = 1 + assert_equal [1], array + + array[1] = 2 + assert_equal [1, 2], array + + array << 333 + assert_equal [1, 2, 333], array + end + + def test_accessing_array_elements + array = [:peanut, :butter, :and, :jelly] + + assert_equal :peanut, array[0] + assert_equal :peanut, array.first + assert_equal :jelly, array[3] + assert_equal :jelly, array.last + assert_equal :jelly, array[-1] + assert_equal :butter, array[-3] + end + + def test_slicing_arrays + array = [:peanut, :butter, :and, :jelly] + + assert_equal [:peanut], array[0,1] + assert_equal [:peanut, :butter], array[0,2] + assert_equal [:and, :jelly], array[2,2] + assert_equal [:and, :jelly], array[2,20] + assert_equal [], array[4,0] # NOTE: weird, you expect it to be nil but it's not. So upto `length` index, you get array back + assert_equal [], array[4,100] # same weirdness as above + assert_equal nil, array[5,0] + end + + def test_arrays_and_ranges + assert_equal Range, (1..5).class + assert_not_equal [1,2,3,4,5], (1..5) + assert_equal [1, 2, 3, 4, 5], (1..5).to_a # also weird, i thought .. should be exclusive + assert_equal [1, 2, 3, 4], (1...5).to_a + end + + def test_slicing_with_ranges + array = [:peanut, :butter, :and, :jelly] + + assert_equal [:peanut, :butter, :and], array[0..2] + assert_equal [:peanut, :butter], array[0...2] + assert_equal [:and, :jelly], array[2..-1] + end + + def test_pushing_and_popping_arrays + array = [1,2] + array.push(:last) + + assert_equal [1, 2, :last], array + + popped_value = array.pop + assert_equal :last, popped_value + assert_equal [1, 2], array + end + + def test_shifting_arrays + array = [1,2] + array.unshift(:first) + + assert_equal [:first, 1, 2], array + + shifted_value = array.shift + assert_equal :first, shifted_value + assert_equal [1, 2], array + end + +end diff --git a/about_asserts.rb b/about_asserts.rb new file mode 100644 index 0000000..158394f --- /dev/null +++ b/about_asserts.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby +# -*- ruby -*- + +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutAsserts < Neo::Koan + + # We shall contemplate truth by testing reality, via asserts. + def test_assert_truth + assert true # This should be true + end + + # Enlightenment may be more easily achieved with appropriate + # messages. + def test_assert_with_message + assert true, "This should be true -- Please fix this" + end + + # To understand reality, we must compare our expectations against + # reality. + def test_assert_equality + expected_value = 2 + actual_value = 1 + 1 + + assert expected_value == actual_value + end + + # Some ways of asserting equality are better than others. + def test_a_better_way_of_asserting_equality + expected_value = 2 + actual_value = 1 + 1 + + assert_equal expected_value, actual_value + end + + # Sometimes we will ask you to fill in the values + def test_fill_in_values + assert_equal 2, 1 + 1 + end +end diff --git a/about_blocks.rb b/about_blocks.rb new file mode 100644 index 0000000..c02dab9 --- /dev/null +++ b/about_blocks.rb @@ -0,0 +1,96 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutBlocks < Neo::Koan + def method_with_block + result = yield + result + end + + def test_methods_can_take_blocks + yielded_result = method_with_block { 1 + 2 } + assert_equal __, yielded_result + end + + def test_blocks_can_be_defined_with_do_end_too + yielded_result = method_with_block do 1 + 2 end + assert_equal __, yielded_result + end + + # ------------------------------------------------------------------ + + def method_with_block_arguments + yield("Jim") + end + + def test_blocks_can_take_arguments + method_with_block_arguments do |argument| + assert_equal __, argument + end + end + + # ------------------------------------------------------------------ + + def many_yields + yield(:peanut) + yield(:butter) + yield(:and) + yield(:jelly) + end + + def test_methods_can_call_yield_many_times + result = [] + many_yields { |item| result << item } + assert_equal __, result + end + + # ------------------------------------------------------------------ + + def yield_tester + if block_given? + yield + else + :no_block + end + end + + def test_methods_can_see_if_they_have_been_called_with_a_block + assert_equal __, yield_tester { :with_block } + assert_equal __, yield_tester + end + + # ------------------------------------------------------------------ + + def test_block_can_affect_variables_in_the_code_where_they_are_created + value = :initial_value + method_with_block { value = :modified_in_a_block } + assert_equal __, value + end + + def test_blocks_can_be_assigned_to_variables_and_called_explicitly + add_one = lambda { |n| n + 1 } + assert_equal __, add_one.call(10) + + # Alternative calling syntax + assert_equal __, add_one[10] + end + + def test_stand_alone_blocks_can_be_passed_to_methods_expecting_blocks + make_upper = lambda { |n| n.upcase } + result = method_with_block_arguments(&make_upper) + assert_equal __, result + end + + # ------------------------------------------------------------------ + + def method_with_explicit_block(&block) + block.call(10) + end + + def test_methods_can_take_an_explicit_block_argument + assert_equal __, method_with_explicit_block { |n| n * 2 } + + add_one = lambda { |n| n + 1 } + assert_equal __, method_with_explicit_block(&add_one) + end + +end diff --git a/about_class_methods.rb b/about_class_methods.rb new file mode 100644 index 0000000..46aab74 --- /dev/null +++ b/about_class_methods.rb @@ -0,0 +1,169 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutClassMethods < Neo::Koan + class Dog + end + + def test_objects_are_objects + fido = Dog.new + assert_equal __, fido.is_a?(Object) + end + + def test_classes_are_classes + assert_equal __, Dog.is_a?(Class) + end + + def test_classes_are_objects_too + assert_equal __, Dog.is_a?(Object) + end + + def test_objects_have_methods + fido = Dog.new + assert fido.methods.size > _n_ + end + + def test_classes_have_methods + assert Dog.methods.size > _n_ + end + + def test_you_can_define_methods_on_individual_objects + fido = Dog.new + def fido.wag + :fidos_wag + end + assert_equal __, fido.wag + end + + def test_other_objects_are_not_affected_by_these_singleton_methods + fido = Dog.new + rover = Dog.new + def fido.wag + :fidos_wag + end + + assert_raise(___) do + rover.wag + end + end + + # ------------------------------------------------------------------ + + class Dog2 + def wag + :instance_level_wag + end + end + + def Dog2.wag + :class_level_wag + end + + def test_since_classes_are_objects_you_can_define_singleton_methods_on_them_too + assert_equal __, Dog2.wag + end + + def test_class_methods_are_independent_of_instance_methods + fido = Dog2.new + assert_equal __, fido.wag + assert_equal __, Dog2.wag + end + + # ------------------------------------------------------------------ + + class Dog + attr_accessor :name + end + + def Dog.name + @name + end + + def test_classes_and_instances_do_not_share_instance_variables + fido = Dog.new + fido.name = "Fido" + assert_equal __, fido.name + assert_equal __, Dog.name + end + + # ------------------------------------------------------------------ + + class Dog + def Dog.a_class_method + :dogs_class_method + end + end + + def test_you_can_define_class_methods_inside_the_class + assert_equal __, Dog.a_class_method + end + + # ------------------------------------------------------------------ + + LastExpressionInClassStatement = class Dog + 21 + end + + def test_class_statements_return_the_value_of_their_last_expression + assert_equal __, LastExpressionInClassStatement + end + + # ------------------------------------------------------------------ + + SelfInsideOfClassStatement = class Dog + self + end + + def test_self_while_inside_class_is_class_object_not_instance + assert_equal __, Dog == SelfInsideOfClassStatement + end + + # ------------------------------------------------------------------ + + class Dog + def self.class_method2 + :another_way_to_write_class_methods + end + end + + def test_you_can_use_self_instead_of_an_explicit_reference_to_dog + assert_equal __, Dog.class_method2 + end + + # ------------------------------------------------------------------ + + class Dog + class << self + def another_class_method + :still_another_way + end + end + end + + def test_heres_still_another_way_to_write_class_methods + assert_equal __, Dog.another_class_method + end + + # THINK ABOUT IT: + # + # The two major ways to write class methods are: + # class Demo + # def self.method + # end + # + # class << self + # def class_methods + # end + # end + # end + # + # Which do you prefer and why? + # Are there times you might prefer one over the other? + + # ------------------------------------------------------------------ + + def test_heres_an_easy_way_to_call_class_methods_from_instance_methods + fido = Dog.new + assert_equal __, fido.class.another_class_method + end + +end diff --git a/about_classes.rb b/about_classes.rb new file mode 100644 index 0000000..1d146be --- /dev/null +++ b/about_classes.rb @@ -0,0 +1,190 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutClasses < Neo::Koan + class Dog + end + + def test_instances_of_classes_can_be_created_with_new + fido = Dog.new + assert_equal __, fido.class + end + + # ------------------------------------------------------------------ + + class Dog2 + def set_name(a_name) + @name = a_name + end + end + + def test_instance_variables_can_be_set_by_assigning_to_them + fido = Dog2.new + assert_equal __, fido.instance_variables + + fido.set_name("Fido") + assert_equal __, fido.instance_variables + end + + def test_instance_variables_cannot_be_accessed_outside_the_class + fido = Dog2.new + fido.set_name("Fido") + + assert_raise(___) do + fido.name + end + + assert_raise(___) do + eval "fido.@name" + # NOTE: Using eval because the above line is a syntax error. + end + end + + def test_you_can_politely_ask_for_instance_variable_values + fido = Dog2.new + fido.set_name("Fido") + + assert_equal __, fido.instance_variable_get("@name") + end + + def test_you_can_rip_the_value_out_using_instance_eval + fido = Dog2.new + fido.set_name("Fido") + + assert_equal __, fido.instance_eval("@name") # string version + assert_equal __, fido.instance_eval { @name } # block version + end + + # ------------------------------------------------------------------ + + class Dog3 + def set_name(a_name) + @name = a_name + end + def name + @name + end + end + + def test_you_can_create_accessor_methods_to_return_instance_variables + fido = Dog3.new + fido.set_name("Fido") + + assert_equal __, fido.name + end + + # ------------------------------------------------------------------ + + class Dog4 + attr_reader :name + + def set_name(a_name) + @name = a_name + end + end + + + def test_attr_reader_will_automatically_define_an_accessor + fido = Dog4.new + fido.set_name("Fido") + + assert_equal __, fido.name + end + + # ------------------------------------------------------------------ + + class Dog5 + attr_accessor :name + end + + + def test_attr_accessor_will_automatically_define_both_read_and_write_accessors + fido = Dog5.new + + fido.name = "Fido" + assert_equal __, fido.name + end + + # ------------------------------------------------------------------ + + class Dog6 + attr_reader :name + def initialize(initial_name) + @name = initial_name + end + end + + def test_initialize_provides_initial_values_for_instance_variables + fido = Dog6.new("Fido") + assert_equal __, fido.name + end + + def test_args_to_new_must_match_initialize + assert_raise(___) do + Dog6.new + end + # THINK ABOUT IT: + # Why is this so? + end + + def test_different_objects_have_different_instance_variables + fido = Dog6.new("Fido") + rover = Dog6.new("Rover") + + assert_equal __, rover.name != fido.name + end + + # ------------------------------------------------------------------ + + class Dog7 + attr_reader :name + + def initialize(initial_name) + @name = initial_name + end + + def get_self + self + end + + def to_s + @name + end + + def inspect + "" + end + end + + def test_inside_a_method_self_refers_to_the_containing_object + fido = Dog7.new("Fido") + + fidos_self = fido.get_self + assert_equal __, fidos_self + end + + def test_to_s_provides_a_string_version_of_the_object + fido = Dog7.new("Fido") + assert_equal __, fido.to_s + end + + def test_to_s_is_used_in_string_interpolation + fido = Dog7.new("Fido") + assert_equal __, "My dog is #{fido}" + end + + def test_inspect_provides_a_more_complete_string_version + fido = Dog7.new("Fido") + assert_equal __, fido.inspect + end + + def test_all_objects_support_to_s_and_inspect + array = [1,2,3] + + assert_equal __, array.to_s + assert_equal __, array.inspect + + assert_equal __, "STRING".to_s + assert_equal __, "STRING".inspect + end + +end diff --git a/about_constants.rb b/about_constants.rb new file mode 100644 index 0000000..68ae078 --- /dev/null +++ b/about_constants.rb @@ -0,0 +1,87 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +C = "top level" + +class AboutConstants < Neo::Koan + + C = "nested" + + def test_nested_constants_may_also_be_referenced_with_relative_paths + assert_equal __, C + end + + def test_top_level_constants_are_referenced_by_double_colons + assert_equal __, ::C + end + + def test_nested_constants_are_referenced_by_their_complete_path + assert_equal __, AboutConstants::C + assert_equal __, ::AboutConstants::C + end + + # ------------------------------------------------------------------ + + class Animal + LEGS = 4 + def legs_in_animal + LEGS + end + + class NestedAnimal + def legs_in_nested_animal + LEGS + end + end + end + + def test_nested_classes_inherit_constants_from_enclosing_classes + assert_equal __, Animal::NestedAnimal.new.legs_in_nested_animal + end + + # ------------------------------------------------------------------ + + class Reptile < Animal + def legs_in_reptile + LEGS + end + end + + def test_subclasses_inherit_constants_from_parent_classes + assert_equal __, Reptile.new.legs_in_reptile + end + + # ------------------------------------------------------------------ + + class MyAnimals + LEGS = 2 + + class Bird < Animal + def legs_in_bird + LEGS + end + end + end + + def test_who_wins_with_both_nested_and_inherited_constants + assert_equal __, MyAnimals::Bird.new.legs_in_bird + end + + # QUESTION: Which has precedence: The constant in the lexical scope, + # or the constant from the inheritance hierarchy? + + # ------------------------------------------------------------------ + + class MyAnimals::Oyster < Animal + def legs_in_oyster + LEGS + end + end + + def test_who_wins_with_explicit_scoping_on_class_definition + assert_equal __, MyAnimals::Oyster.new.legs_in_oyster + end + + # QUESTION: Now which has precedence: The constant in the lexical + # scope, or the constant from the inheritance hierarchy? Why is it + # different than the previous answer? +end diff --git a/about_control_statements.rb b/about_control_statements.rb new file mode 100644 index 0000000..f323757 --- /dev/null +++ b/about_control_statements.rb @@ -0,0 +1,142 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutControlStatements < Neo::Koan + + def test_if_then_else_statements + if true + result = :true_value + else + result = :false_value + end + assert_equal __, result + end + + def test_if_then_statements + result = :default_value + if true + result = :true_value + end + assert_equal __, result + end + + def test_if_statements_return_values + value = if true + :true_value + else + :false_value + end + assert_equal __, value + + value = if false + :true_value + else + :false_value + end + assert_equal __, value + + # NOTE: Actually, EVERY statement in Ruby will return a value, not + # just if statements. + end + + def test_if_statements_with_no_else_with_false_condition_return_value + value = if false + :true_value + end + assert_equal __, value + end + + def test_condition_operators + assert_equal __, (true ? :true_value : :false_value) + assert_equal __, (false ? :true_value : :false_value) + end + + def test_if_statement_modifiers + result = :default_value + result = :true_value if true + + assert_equal __, result + end + + def test_unless_statement + result = :default_value + unless false # same as saying 'if !false', which evaluates as 'if true' + result = :false_value + end + assert_equal __, result + end + + def test_unless_statement_evaluate_true + result = :default_value + unless true # same as saying 'if !true', which evaluates as 'if false' + result = :true_value + end + assert_equal __, result + end + + def test_unless_statement_modifier + result = :default_value + result = :false_value unless false + + assert_equal __, result + end + + def test_while_statement + i = 1 + result = 1 + while i <= 10 + result = result * i + i += 1 + end + assert_equal __, result + end + + def test_break_statement + i = 1 + result = 1 + while true + break unless i <= 10 + result = result * i + i += 1 + end + assert_equal __, result + end + + def test_break_statement_returns_values + i = 1 + result = while i <= 10 + break i if i % 2 == 0 + i += 1 + end + + assert_equal __, result + end + + def test_next_statement + i = 0 + result = [] + while i < 10 + i += 1 + next if (i % 2) == 0 + result << i + end + assert_equal __, result + end + + def test_for_statement + array = ["fish", "and", "chips"] + result = [] + for item in array + result << item.upcase + end + assert_equal [__, __, __], result + end + + def test_times_statement + sum = 0 + 10.times do + sum += 1 + end + assert_equal __, sum + end + +end diff --git a/about_dice_project.rb b/about_dice_project.rb new file mode 100644 index 0000000..5a1848f --- /dev/null +++ b/about_dice_project.rb @@ -0,0 +1,63 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +# Implement a DiceSet Class here: +# +# class DiceSet +# code ... +# end + +class AboutDiceProject < Neo::Koan + def test_can_create_a_dice_set + dice = DiceSet.new + assert_not_nil dice + end + + def test_rolling_the_dice_returns_a_set_of_integers_between_1_and_6 + dice = DiceSet.new + + dice.roll(5) + assert dice.values.is_a?(Array), "should be an array" + assert_equal 5, dice.values.size + dice.values.each do |value| + assert value >= 1 && value <= 6, "value #{value} must be between 1 and 6" + end + end + + def test_dice_values_do_not_change_unless_explicitly_rolled + dice = DiceSet.new + dice.roll(5) + first_time = dice.values + second_time = dice.values + assert_equal first_time, second_time + end + + def test_dice_values_should_change_between_rolls + dice = DiceSet.new + + dice.roll(5) + first_time = dice.values + + dice.roll(5) + second_time = dice.values + + assert_not_equal first_time, second_time, + "Two rolls should not be equal" + + # THINK ABOUT IT: + # + # If the rolls are random, then it is possible (although not + # likely) that two consecutive rolls are equal. What would be a + # better way to test this? + end + + def test_you_can_roll_different_numbers_of_dice + dice = DiceSet.new + + dice.roll(3) + assert_equal 3, dice.values.size + + dice.roll(1) + assert_equal 1, dice.values.size + end + +end diff --git a/about_exceptions.rb b/about_exceptions.rb new file mode 100644 index 0000000..464f57e --- /dev/null +++ b/about_exceptions.rb @@ -0,0 +1,68 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutExceptions < Neo::Koan + + class MySpecialError < RuntimeError + end + + def test_exceptions_inherit_from_Exception + assert_equal __, MySpecialError.ancestors[1] + assert_equal __, MySpecialError.ancestors[2] + assert_equal __, MySpecialError.ancestors[3] + assert_equal __, MySpecialError.ancestors[4] + end + + def test_rescue_clause + result = nil + begin + fail "Oops" + rescue StandardError => ex + result = :exception_handled + end + + assert_equal __, result + + assert_equal __, ex.is_a?(StandardError), "Should be a Standard Error" + assert_equal __, ex.is_a?(RuntimeError), "Should be a Runtime Error" + + assert RuntimeError.ancestors.include?(StandardError), + "RuntimeError is a subclass of StandardError" + + assert_equal __, ex.message + end + + def test_raising_a_particular_error + result = nil + begin + # 'raise' and 'fail' are synonyms + raise MySpecialError, "My Message" + rescue MySpecialError => ex + result = :exception_handled + end + + assert_equal __, result + assert_equal __, ex.message + end + + def test_ensure_clause + result = nil + begin + fail "Oops" + rescue StandardError + # no code here + ensure + result = :always_run + end + + assert_equal __, result + end + + # Sometimes, we must know about the unknown + def test_asserting_an_error_is_raised + # A do-end is a block, a topic to explore more later + assert_raise(___) do + raise MySpecialError.new("New instances can be raised directly.") + end + end + +end diff --git a/about_extra_credit.rb b/about_extra_credit.rb new file mode 100644 index 0000000..5012edf --- /dev/null +++ b/about_extra_credit.rb @@ -0,0 +1,8 @@ +# EXTRA CREDIT: +# +# Create a program that will play the Greed Game. +# Rules for the game are in GREED_RULES.TXT. +# +# You already have a DiceSet class and score function you can use. +# Write a player class and a Game class to complete the project. This +# is a free form assignment, so approach it however you desire. diff --git a/about_hashes.rb b/about_hashes.rb new file mode 100644 index 0000000..67142d8 --- /dev/null +++ b/about_hashes.rb @@ -0,0 +1,126 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutHashes < Neo::Koan + def test_creating_hashes + empty_hash = Hash.new + assert_equal Hash, empty_hash.class + assert_equal({}, empty_hash) + assert_equal 0, empty_hash.size + end + + def test_hash_literals + hash = { :one => "uno", :two => "dos" } + assert_equal 2, hash.size + end + + def test_accessing_hashes + hash = { :one => "uno", :two => "dos" } + assert_equal "uno", hash[:one] + assert_equal "dos", hash[:two] + assert_equal nil, hash[:doesnt_exist] + end + + def test_accessing_hashes_with_fetch + hash = { :one => "uno" } + assert_equal __, hash.fetch(:one) + assert_raise(___) do + hash.fetch(:doesnt_exist) + end + + # THINK ABOUT IT: + # + # Why might you want to use #fetch instead of #[] when accessing hash keys? + end + + def test_changing_hashes + hash = { :one => "uno", :two => "dos" } + hash[:one] = "eins" + + expected = { :one => __, :two => "dos" } + assert_equal __, hash + + # Bonus Question: Why was "expected" broken out into a variable + # rather than used as a literal? + end + + def test_hash_is_unordered + hash1 = { :one => "uno", :two => "dos" } + hash2 = { :two => "dos", :one => "uno" } + + assert_equal __, hash1 == hash2 + end + + def test_hash_keys + hash = { :one => "uno", :two => "dos" } + assert_equal __, hash.keys.size + assert_equal __, hash.keys.include?(:one) + assert_equal __, hash.keys.include?(:two) + assert_equal __, hash.keys.class + end + + def test_hash_values + hash = { :one => "uno", :two => "dos" } + assert_equal __, hash.values.size + assert_equal __, hash.values.include?("uno") + assert_equal __, hash.values.include?("dos") + assert_equal __, hash.values.class + end + + def test_combining_hashes + hash = { "jim" => 53, "amy" => 20, "dan" => 23 } + new_hash = hash.merge({ "jim" => 54, "jenny" => 26 }) + + assert_equal __, hash != new_hash + + expected = { "jim" => __, "amy" => 20, "dan" => 23, "jenny" => __ } + assert_equal __, expected == new_hash + end + + def test_default_value + hash1 = Hash.new + hash1[:one] = 1 + + assert_equal __, hash1[:one] + assert_equal __, hash1[:two] + + hash2 = Hash.new("dos") + hash2[:one] = 1 + + assert_equal __, hash2[:one] + assert_equal __, hash2[:two] + end + + def test_default_value_is_the_same_object + hash = Hash.new([]) + + hash[:one] << "uno" + hash[:two] << "dos" + + assert_equal __, hash[:one] + assert_equal __, hash[:two] + assert_equal __, hash[:three] + + assert_equal __, hash[:one].object_id == hash[:two].object_id + end + + def test_default_value_with_block + hash = Hash.new {|hash, key| hash[key] = [] } + + hash[:one] << "uno" + hash[:two] << "dos" + + assert_equal __, hash[:one] + assert_equal __, hash[:two] + assert_equal __, hash[:three] + end + + def test_default_value_attribute + hash = Hash.new + + assert_equal __, hash[:some_key] + + hash.default = 'peanut' + + assert_equal __, hash[:some_key] + end +end diff --git a/about_inheritance.rb b/about_inheritance.rb new file mode 100644 index 0000000..61bc890 --- /dev/null +++ b/about_inheritance.rb @@ -0,0 +1,85 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutInheritance < Neo::Koan + class Dog + attr_reader :name + + def initialize(name) + @name = name + end + + def bark + "WOOF" + end + end + + class Chihuahua < Dog + def wag + :happy + end + + def bark + "yip" + end + end + + def test_subclasses_have_the_parent_as_an_ancestor + assert_equal __, Chihuahua.ancestors.include?(Dog) + end + + def test_all_classes_ultimately_inherit_from_object + assert_equal __, Chihuahua.ancestors.include?(Object) + end + + def test_subclasses_inherit_behavior_from_parent_class + chico = Chihuahua.new("Chico") + assert_equal __, chico.name + end + + def test_subclasses_add_new_behavior + chico = Chihuahua.new("Chico") + assert_equal __, chico.wag + + assert_raise(___) do + fido = Dog.new("Fido") + fido.wag + end + end + + def test_subclasses_can_modify_existing_behavior + chico = Chihuahua.new("Chico") + assert_equal __, chico.bark + + fido = Dog.new("Fido") + assert_equal __, fido.bark + end + + # ------------------------------------------------------------------ + + class BullDog < Dog + def bark + super + ", GROWL" + end + end + + def test_subclasses_can_invoke_parent_behavior_via_super + ralph = BullDog.new("Ralph") + assert_equal __, ralph.bark + end + + # ------------------------------------------------------------------ + + class GreatDane < Dog + def growl + super.bark + ", GROWL" + end + end + + def test_super_does_not_work_cross_method + george = GreatDane.new("George") + assert_raise(___) do + george.growl + end + end + +end diff --git a/about_iteration.rb b/about_iteration.rb new file mode 100644 index 0000000..c46a6f8 --- /dev/null +++ b/about_iteration.rb @@ -0,0 +1,122 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutIteration < Neo::Koan + + # -- An Aside ------------------------------------------------------ + # Ruby 1.8 stores names as strings. Ruby 1.9 and later stores names + # as symbols. So we use a version dependent method "as_name" to + # convert to the right format in the koans. We will use "as_name" + # whenever comparing to lists of methods. + + in_ruby_version("1.8") do + def as_name(name) + name.to_s + end + end + + in_ruby_version("1.9", "2", "3") do + def as_name(name) + name.to_sym + end + end + + # Ok, now back to the Koans. + # ------------------------------------------------------------------- + + def test_each_is_a_method_on_arrays + assert_equal __, [].methods.include?(as_name(:each)) + end + + def test_iterating_with_each + array = [1, 2, 3] + sum = 0 + array.each do |item| + sum += item + end + assert_equal __, sum + end + + def test_each_can_use_curly_brace_blocks_too + array = [1, 2, 3] + sum = 0 + array.each { |item| sum += item } + assert_equal __, sum + end + + def test_break_works_with_each_style_iterations + array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + sum = 0 + array.each do |item| + break if item > 3 + sum += item + end + assert_equal __, sum + end + + def test_collect_transforms_elements_of_an_array + array = [1, 2, 3] + new_array = array.collect { |item| item + 10 } + assert_equal __, new_array + + # NOTE: 'map' is another name for the 'collect' operation + another_array = array.map { |item| item + 10 } + assert_equal __, another_array + end + + def test_select_selects_certain_items_from_an_array + array = [1, 2, 3, 4, 5, 6] + + even_numbers = array.select { |item| (item % 2) == 0 } + assert_equal __, even_numbers + + # NOTE: 'find_all' is another name for the 'select' operation + more_even_numbers = array.find_all { |item| (item % 2) == 0 } + assert_equal __, more_even_numbers + end + + def test_find_locates_the_first_element_matching_a_criteria + array = ["Jim", "Bill", "Clarence", "Doug", "Eli"] + + assert_equal __, array.find { |item| item.size > 4 } + end + + def test_inject_will_blow_your_mind + result = [2, 3, 4].inject(0) { |sum, item| sum + item } + assert_equal __, result + + result2 = [2, 3, 4].inject(1) { |product, item| product * item } + assert_equal __, result2 + + # Extra Credit: + # Describe in your own words what inject does. + end + + def test_all_iteration_methods_work_on_any_collection_not_just_arrays + # Ranges act like a collection + result = (1..3).map { |item| item + 10 } + assert_equal __, result + + # Files act like a collection of lines + File.open("example_file.txt") do |file| + upcase_lines = file.map { |line| line.strip.upcase } + assert_equal __, upcase_lines + end + + # NOTE: You can create your own collections that work with each, + # map, select, etc. + end + + # Bonus Question: In the previous koan, we saw the construct: + # + # File.open(filename) do |file| + # # code to read 'file' + # end + # + # Why did we do it that way instead of the following? + # + # file = File.open(filename) + # # code to read 'file' + # + # When you get to the "AboutSandwichCode" koan, recheck your answer. + +end diff --git a/about_java_interop.rb b/about_java_interop.rb new file mode 100644 index 0000000..47fef28 --- /dev/null +++ b/about_java_interop.rb @@ -0,0 +1,137 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +include Java + +# Concepts +# * Pull in a java class +# * calling a method, Camel vs snake +# * Resolving module/class name conflicts +# * Showing what gets returned +# * Ruby Strings VS Java Strings +# * Calling custom java class +# * Calling Ruby from java??? + +class AboutJavaInterop < Neo::Koan + def test_using_a_java_library_class + java_array = java.util.ArrayList.new + assert_equal __, java_array.class + end + + def test_java_class_can_be_referenced_using_both_ruby_and_java_like_syntax + assert_equal __, Java::JavaUtil::ArrayList == java.util.ArrayList + end + + def test_include_class_includes_class_in_module_scope + assert_nil defined?(TreeSet) + include_class "java.util.TreeSet" + assert_equal __, defined?(TreeSet) + end + + # THINK ABOUT IT: + # + # What if we use: + # + # include_class "java.lang.String" + # + # What would be the value of the String constant after this + # include_class is run? Would it be useful to provide a way of + # aliasing java classes to different names? + + JString = java.lang.String + def test_also_java_class_can_be_given_ruby_aliases + java_string = JString.new("A Java String") + assert_equal __, java_string.class + assert_equal __, JString + end + + def test_can_directly_call_java_methods_on_java_objects + java_string = JString.new("A Java String") + assert_equal __, java_string.toLowerCase + end + + def test_jruby_provides_snake_case_versions_of_java_methods + java_string = JString.new("A Java String") + assert_equal __, java_string.to_lower_case + end + + def test_jruby_provides_question_mark_versions_of_boolean_methods + java_string = JString.new("A Java String") + assert_equal __, java_string.endsWith("String") + assert_equal __, java_string.ends_with("String") + assert_equal __, java_string.ends_with?("String") + end + + def test_java_string_are_not_ruby_strings + ruby_string = "A Java String" + java_string = java.lang.String.new(ruby_string) + assert_equal __, java_string.is_a?(java.lang.String) + assert_equal __, java_string.is_a?(String) + end + + def test_java_strings_can_be_compared_to_ruby_strings_maybe + ruby_string = "A Java String" + java_string = java.lang.String.new(ruby_string) + assert_equal __, ruby_string == java_string + assert_equal __, java_string == ruby_string + + # THINK ABOUT IT: + # + # Is there any possible way for this to be more wrong? + # + # SERIOUSLY, THINK ABOUT IT: + # + # Why do you suppose that Ruby and Java strings compare like that? + # + # ADVANCED THINK ABOUT IT: + # + # Is there a way to make Ruby/Java string comparisons commutative? + # How would you do it? + end + + def test_however_most_methods_returning_strings_return_ruby_strings + java_array = java.util.ArrayList.new + assert_equal __, java_array.toString + assert_equal __, java_array.toString.is_a?(String) + assert_equal __, java_array.toString.is_a?(java.lang.String) + end + + def test_some_ruby_objects_can_be_coerced_to_java + assert_equal __, "ruby string".to_java.class + assert_equal __, 1.to_java.class + assert_equal __, 9.32.to_java.class + assert_equal __, false.to_java.class + end + + def test_some_ruby_objects_are_not_coerced_to_what_you_might_expect + assert_equal __, [].to_java.class == Java::JavaUtil::ArrayList + assert_equal __, {}.to_java.class == Java::JavaUtil::HashMap + assert_equal __, Object.new.to_java.class == Java::JavaLang::Object + end + + def test_java_collections_are_enumerable + java_array = java.util.ArrayList.new + java_array << "one" << "two" << "three" + assert_equal __, java_array.map { |item| item.upcase } + end + + # ------------------------------------------------------------------ + + # Open the Java ArrayList class and add a new method. + class Java::JavaUtil::ArrayList + def multiply_all + result = 1 + each do |item| + result *= item + end + result + end + end + + def test_java_class_are_open_from_ruby + java_array = java.util.ArrayList.new + java_array.add_all([1,2,3,4,5]) + + assert_equal __, java_array.multiply_all + end + +end diff --git a/about_keyword_arguments.rb b/about_keyword_arguments.rb new file mode 100644 index 0000000..5a5908e --- /dev/null +++ b/about_keyword_arguments.rb @@ -0,0 +1,43 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutKeywordArguments < Neo::Koan + + def method_with_keyword_arguments(one: 1, two: 'two') + [one, two] + end + + def test_keyword_arguments + assert_equal __, method_with_keyword_arguments.class + assert_equal __, method_with_keyword_arguments + assert_equal __, method_with_keyword_arguments(one: 'one') + assert_equal __, method_with_keyword_arguments(two: 2) + end + + def method_with_keyword_arguments_with_mandatory_argument(one, two: 2, three: 3) + [one, two, three] + end + + def test_keyword_arguments_with_wrong_number_of_arguments + exception = assert_raise (___) do + method_with_keyword_arguments_with_mandatory_argument + end + assert_match(/__/, exception.message) + end + + def method_with_mandatory_keyword_arguments(one:, two: 'two') + [one, two] + end + + def test_mandatory_keyword_arguments + assert_equal __, method_with_mandatory_keyword_arguments(one: 'one') + assert_equal __, method_with_mandatory_keyword_arguments(two: 2, one: 1) + end + + def test_mandatory_keyword_arguments_without_mandatory_argument + exception = assert_raise(___) do + method_with_mandatory_keyword_arguments + end + assert_match(/__/, exception.message) + end + +end diff --git a/about_message_passing.rb b/about_message_passing.rb new file mode 100644 index 0000000..c6d80a8 --- /dev/null +++ b/about_message_passing.rb @@ -0,0 +1,185 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutMessagePassing < Neo::Koan + + class MessageCatcher + def caught? + true + end + end + + def test_methods_can_be_called_directly + mc = MessageCatcher.new + + assert mc.caught? + end + + def test_methods_can_be_invoked_by_sending_the_message + mc = MessageCatcher.new + + assert mc.send(:caught?) + end + + def test_methods_can_be_invoked_more_dynamically + mc = MessageCatcher.new + + assert mc.send("caught?") + assert mc.send("caught" + __ ) # What do you need to add to the first string? + assert mc.send("CAUGHT?".____ ) # What would you need to do to the string? + end + + def test_send_with_underscores_will_also_send_messages + mc = MessageCatcher.new + + assert_equal __, mc.__send__(:caught?) + + # THINK ABOUT IT: + # + # Why does Ruby provide both send and __send__ ? + end + + def test_classes_can_be_asked_if_they_know_how_to_respond + mc = MessageCatcher.new + + assert_equal __, mc.respond_to?(:caught?) + assert_equal __, mc.respond_to?(:does_not_exist) + end + + # ------------------------------------------------------------------ + + class MessageCatcher + def add_a_payload(*args) + args + end + end + + def test_sending_a_message_with_arguments + mc = MessageCatcher.new + + assert_equal __, mc.add_a_payload + assert_equal __, mc.send(:add_a_payload) + + assert_equal __, mc.add_a_payload(3, 4, nil, 6) + assert_equal __, mc.send(:add_a_payload, 3, 4, nil, 6) + end + + # NOTE: + # + # Both obj.msg and obj.send(:msg) sends the message named :msg to + # the object. We use "send" when the name of the message can vary + # dynamically (e.g. calculated at run time), but by far the most + # common way of sending a message is just to say: obj.msg. + + # ------------------------------------------------------------------ + + class TypicalObject + end + + def test_sending_undefined_messages_to_a_typical_object_results_in_errors + typical = TypicalObject.new + + exception = assert_raise(___) do + typical.foobar + end + assert_match(/foobar/, exception.message) + end + + def test_calling_method_missing_causes_the_no_method_error + typical = TypicalObject.new + + exception = assert_raise(___) do + typical.method_missing(:foobar) + end + assert_match(/foobar/, exception.message) + + # THINK ABOUT IT: + # + # If the method :method_missing causes the NoMethodError, then + # what would happen if we redefine method_missing? + # + # NOTE: + # + # In Ruby 1.8 the method_missing method is public and can be + # called as shown above. However, in Ruby 1.9 (and later versions) + # the method_missing method is private. We explicitly made it + # public in the testing framework so this example works in both + # versions of Ruby. Just keep in mind you can't call + # method_missing like that after Ruby 1.9 normally. + # + # Thanks. We now return you to your regularly scheduled Ruby + # Koans. + end + + # ------------------------------------------------------------------ + + class AllMessageCatcher + def method_missing(method_name, *args, &block) + "Someone called #{method_name} with <#{args.join(", ")}>" + end + end + + def test_all_messages_are_caught + catcher = AllMessageCatcher.new + + assert_equal __, catcher.foobar + assert_equal __, catcher.foobaz(1) + assert_equal __, catcher.sum(1,2,3,4,5,6) + end + + def test_catching_messages_makes_respond_to_lie + catcher = AllMessageCatcher.new + + assert_nothing_raised do + catcher.any_method + end + assert_equal __, catcher.respond_to?(:any_method) + end + + # ------------------------------------------------------------------ + + class WellBehavedFooCatcher + def method_missing(method_name, *args, &block) + if method_name.to_s[0,3] == "foo" + "Foo to you too" + else + super(method_name, *args, &block) + end + end + end + + def test_foo_method_are_caught + catcher = WellBehavedFooCatcher.new + + assert_equal __, catcher.foo_bar + assert_equal __, catcher.foo_baz + end + + def test_non_foo_messages_are_treated_normally + catcher = WellBehavedFooCatcher.new + + assert_raise(___) do + catcher.normal_undefined_method + end + end + + # ------------------------------------------------------------------ + + # (note: just reopening class from above) + class WellBehavedFooCatcher + def respond_to?(method_name) + if method_name.to_s[0,3] == "foo" + true + else + super(method_name) + end + end + end + + def test_explicitly_implementing_respond_to_lets_objects_tell_the_truth + catcher = WellBehavedFooCatcher.new + + assert_equal __, catcher.respond_to?(:foo_bar) + assert_equal __, catcher.respond_to?(:something_else) + end + +end diff --git a/about_methods.rb b/about_methods.rb new file mode 100644 index 0000000..54d7da3 --- /dev/null +++ b/about_methods.rb @@ -0,0 +1,153 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +def my_global_method(a,b) + a + b +end + +class AboutMethods < Neo::Koan + + def test_calling_global_methods + assert_equal __, my_global_method(2,3) + end + + def test_calling_global_methods_without_parentheses + result = my_global_method 2, 3 + assert_equal __, result + end + + # (NOTE: We are Using eval below because the example code is + # considered to be syntactically invalid). + def test_sometimes_missing_parentheses_are_ambiguous + eval "assert_equal 5, my_global_method 2, 3" # ENABLE CHECK + # + # Ruby doesn't know if you mean: + # + # assert_equal(5, my_global_method(2), 3) + # or + # assert_equal(5, my_global_method(2, 3)) + # + # Rewrite the eval string to continue. + # + end + + # NOTE: wrong number of arguments is not a SYNTAX error, but a + # runtime error. + def test_calling_global_methods_with_wrong_number_of_arguments + exception = assert_raise(___) do + my_global_method + end + assert_match(/__/, exception.message) + + exception = assert_raise(___) do + my_global_method(1,2,3) + end + assert_match(/__/, exception.message) + end + + # ------------------------------------------------------------------ + + def method_with_defaults(a, b=:default_value) + [a, b] + end + + def test_calling_with_default_values + assert_equal [1, __], method_with_defaults(1) + assert_equal [1, __], method_with_defaults(1, 2) + end + + # ------------------------------------------------------------------ + + def method_with_var_args(*args) + args + end + + def test_calling_with_variable_arguments + assert_equal __, method_with_var_args.class + assert_equal __, method_with_var_args + assert_equal __, method_with_var_args(:one) + assert_equal __, method_with_var_args(:one, :two) + end + + # ------------------------------------------------------------------ + + def method_with_explicit_return + :a_non_return_value + return :return_value + :another_non_return_value + end + + def test_method_with_explicit_return + assert_equal __, method_with_explicit_return + end + + # ------------------------------------------------------------------ + + def method_without_explicit_return + :a_non_return_value + :return_value + end + + def test_method_without_explicit_return + assert_equal __, method_without_explicit_return + end + + # ------------------------------------------------------------------ + + def my_method_in_the_same_class(a, b) + a * b + end + + def test_calling_methods_in_same_class + assert_equal __, my_method_in_the_same_class(3,4) + end + + def test_calling_methods_in_same_class_with_explicit_receiver + assert_equal __, self.my_method_in_the_same_class(3,4) + end + + # ------------------------------------------------------------------ + + def my_private_method + "a secret" + end + private :my_private_method + + def test_calling_private_methods_without_receiver + assert_equal __, my_private_method + end + + if before_ruby_version("2.7") # https://github.com/edgecase/ruby_koans/issues/12 + def test_calling_private_methods_with_an_explicit_receiver + exception = assert_raise(___) do + self.my_private_method + end + assert_match /__/, exception.message + end + end + + # ------------------------------------------------------------------ + + class Dog + def name + "Fido" + end + + private + + def tail + "tail" + end + end + + def test_calling_methods_in_other_objects_require_explicit_receiver + rover = Dog.new + assert_equal __, rover.name + end + + def test_calling_private_methods_in_other_objects + rover = Dog.new + assert_raise(___) do + rover.tail + end + end +end diff --git a/about_modules.rb b/about_modules.rb new file mode 100644 index 0000000..38652df --- /dev/null +++ b/about_modules.rb @@ -0,0 +1,63 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutModules < Neo::Koan + module Nameable + def set_name(new_name) + @name = new_name + end + + def here + :in_module + end + end + + def test_cant_instantiate_modules + assert_raise(___) do + Nameable.new + end + end + + # ------------------------------------------------------------------ + + class Dog + include Nameable + + attr_reader :name + + def initialize + @name = "Fido" + end + + def bark + "WOOF" + end + + def here + :in_object + end + end + + def test_normal_methods_are_available_in_the_object + fido = Dog.new + assert_equal __, fido.bark + end + + def test_module_methods_are_also_available_in_the_object + fido = Dog.new + assert_nothing_raised do + fido.set_name("Rover") + end + end + + def test_module_methods_can_affect_instance_variables_in_the_object + fido = Dog.new + assert_equal __, fido.name + fido.set_name("Rover") + assert_equal __, fido.name + end + + def test_classes_can_override_module_methods + fido = Dog.new + assert_equal __, fido.here + end +end diff --git a/about_nil.rb b/about_nil.rb new file mode 100644 index 0000000..8a3412d --- /dev/null +++ b/about_nil.rb @@ -0,0 +1,38 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutNil < Neo::Koan + def test_nil_is_an_object + assert_equal true, nil.is_a?(Object), "Unlike NULL in other languages" + end + + def test_you_dont_get_null_pointer_errors_when_calling_methods_on_nil + # What happens when you call a method that doesn't exist. The + # following begin/rescue/end code block captures the exception and + # makes some assertions about it. + begin + nil.some_method_nil_doesnt_know_about + rescue Exception => ex + # What exception has been caught? + assert_equal NoMethodError, ex.class + + # What message was attached to the exception? + # (HINT: replace __ with part of the error message.) + assert_match(/undefined method/, ex.message) + end + end + + def test_nil_has_a_few_methods_defined_on_it + assert_equal true, nil.nil? + assert_equal "", nil.to_s + assert_equal "nil", nil.inspect + + # THINK ABOUT IT: + # + # Is it better to use + # obj.nil? + # or + # obj == nil + # Why? + end + +end diff --git a/about_objects.rb b/about_objects.rb new file mode 100644 index 0000000..c842804 --- /dev/null +++ b/about_objects.rb @@ -0,0 +1,50 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutObjects < Neo::Koan + def test_everything_is_an_object + assert_equal true, 1.is_a?(Object) + assert_equal true, 1.5.is_a?(Object) + assert_equal true, "string".is_a?(Object) + assert_equal true, nil.is_a?(Object) # like JS? + assert_equal true, Object.is_a?(Object) + end + + def test_objects_can_be_converted_to_strings + assert_equal "123", 123.to_s + assert_equal "", nil.to_s + end + + def test_objects_can_be_inspected + assert_equal '123', 123.inspect + assert_equal "nil", nil.inspect + end + + def test_every_object_has_an_id + obj = Object.new + assert_equal Integer, obj.object_id.class + end + + def test_every_object_has_different_id + obj = Object.new + another_obj = Object.new + assert_equal true, obj.object_id != another_obj.object_id + end + + def test_small_integers_have_fixed_ids + assert_equal 1, 0.object_id # pattern is id = value + (value + 1) + assert_equal 3, 1.object_id + assert_equal 5, 2.object_id + assert_equal 201, 100.object_id + + # THINK ABOUT IT: + # What pattern do the object IDs for small integers follow? + end + + def test_clone_creates_a_different_object + obj = Object.new + copy = obj.clone + + assert_equal true, obj != copy + assert_equal true, obj.object_id != copy.object_id + end +end diff --git a/about_open_classes.rb b/about_open_classes.rb new file mode 100644 index 0000000..749f39f --- /dev/null +++ b/about_open_classes.rb @@ -0,0 +1,45 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutOpenClasses < Neo::Koan + class Dog + def bark + "WOOF" + end + end + + def test_as_defined_dogs_do_bark + fido = Dog.new + assert_equal __, fido.bark + end + + # ------------------------------------------------------------------ + + # Open the existing Dog class and add a new method. + class Dog + def wag + "HAPPY" + end + end + + def test_after_reopening_dogs_can_both_wag_and_bark + fido = Dog.new + assert_equal __, fido.wag + assert_equal __, fido.bark + end + + # ------------------------------------------------------------------ + + class ::Integer + def answer_to_life_universe_and_everything? + self == 42 + end + end + + def test_even_existing_built_in_classes_can_be_reopened + assert_equal __, 1.answer_to_life_universe_and_everything? + assert_equal __, 42.answer_to_life_universe_and_everything? + end + + # NOTE: To understand why we need the :: before Integer, you need to + # become enlightened about scope. +end diff --git a/about_pattern_matching.rb b/about_pattern_matching.rb new file mode 100644 index 0000000..073985c --- /dev/null +++ b/about_pattern_matching.rb @@ -0,0 +1,215 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutPatternMatching < Neo::Koan + + def test_pattern_may_not_match + begin + case [true, false] + in [a, b] if a == b # The condition after pattern is called guard. + :match + end + rescue Exception => ex + # What exception has been caught? + assert_equal __, ex.class + end + end + + def test_we_can_use_else + result = case [true, false] + in [a, b] if a == b + :match + else + :no_match + end + + assert_equal __, result + end + + # ------------------------------------------------------------------ + + def value_pattern(variable) + case variable + in 0 + :match_exact_value + in 1..10 + :match_in_range + in Integer + :match_with_class + else + :no_match + end + end + + def test_value_pattern + assert_equal __, value_pattern(0) + assert_equal __, value_pattern(5) + assert_equal __, value_pattern(100) + assert_equal __, value_pattern('Not a Number!') + end + + # ------------------------------------------------------------------ + # This pattern will bind variable to the value + + def variable_pattern_with_binding(variable) + case 0 + in variable + variable + else + :no_match + end + end + + def test_variable_pattern_with_binding + assert_equal __, variable_pattern_with_binding(1) + end + + # ------------------------------------------------------------------ + + # We can pin the value of the variable with ^ + + def variable_pattern_with_pin(variable) + case 0 + in ^variable + variable + else + :no_match + end + end + + def test_variable_pattern_with_pin + assert_equal __, variable_pattern_with_pin(1) + end + + # ------------------------------------------------------------------ + + # We can drop values from pattern + + def pattern_with_dropping(variable) + case variable + in [_, 2] + :match + else + :no_match + end + end + + def test_pattern_with_dropping + assert_equal __, pattern_with_dropping(['I will not be checked', 2]) + assert_equal __, pattern_with_dropping(['I will not be checked', 'But I will!']) + end + + # ------------------------------------------------------------------ + + # We can use logical *or* in patterns + + def alternative_pattern(variable) + case variable + in 0 | false | nil + :match + else + :no_match + end + end + + def test_alternative_pattern + assert_equal __, alternative_pattern(0) + assert_equal __, alternative_pattern(false) + assert_equal __, alternative_pattern(nil) + assert_equal __, alternative_pattern(4) + end + + # ------------------------------------------------------------------ + + # As pattern binds the variable to the value if pattern matches + # pat: pat => var + + def as_pattern + a = 'First I was afraid' + + case 'I was petrified' + in String => a + a + else + :no_match + end + end + + def test_as_pattern + assert_equal __, as_pattern + end + + # ------------------------------------------------------------------ + + # Array pattern works with all objects that have #deconstruct method that returns Array + # It is useful to cut needed parts from Array-ish objects + + class Deconstructible + def initialize(str) + @data = str + end + + def deconstruct + @data&.split('') + end + end + + def array_pattern(deconstructible) + case deconstructible + in 'a', *res, 'd' + res + else + :no_match + end + end + + def test_array_pattern + assert_equal __, array_pattern(Deconstructible.new('abcd')) + assert_equal __, array_pattern(Deconstructible.new('123')) + end + + # ------------------------------------------------------------------ + + # Hash pattern is quite the same as Array pattern, but it expects #deconsturct_keys(keys) method + # It works with symbol keys for now + + class LetterAccountant + def initialize(str) + @data = str + end + + def deconstruct_keys(keys) + # we will count number of occurrences of each key in our data + keys.map { |key| [key, @data.count(key.to_s)] }.to_h + end + end + + def hash_pattern(deconstructible_as_hash) + case deconstructible_as_hash + in {a: a, b: b} + [a, b] + else + :no_match + end + end + + def test_hash_pattern + assert_equal __, hash_pattern(LetterAccountant.new('aaabbc')) + assert_equal __, hash_pattern(LetterAccountant.new('xyz')) + end + + # we can write it even shorter + def hash_pattern_with_sugar(deconstructible_as_hash) + case deconstructible_as_hash + in a:, b: + [a, b] + else + :no_match + end + end + + def test_hash_pattern_with_sugar + assert_equal __, hash_pattern_with_sugar(LetterAccountant.new('aaabbc')) + assert_equal __, hash_pattern_with_sugar(LetterAccountant.new('xyz')) + end + +end diff --git a/about_proxy_object_project.rb b/about_proxy_object_project.rb new file mode 100644 index 0000000..36d1cf0 --- /dev/null +++ b/about_proxy_object_project.rb @@ -0,0 +1,156 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +# Project: Create a Proxy Class +# +# In this assignment, create a proxy class (one is started for you +# below). You should be able to initialize the proxy object with any +# object. Any messages sent to the proxy object should be forwarded +# to the target object. As each message is sent, the proxy should +# record the name of the method sent. +# +# The proxy class is started for you. You will need to add a method +# missing handler and any other supporting methods. The specification +# of the Proxy class is given in the AboutProxyObjectProject koan. + +class Proxy + def initialize(target_object) + @object = target_object + # ADD MORE CODE HERE + end + + # WRITE CODE HERE +end + +# The proxy object should pass the following Koan: +# +class AboutProxyObjectProject < Neo::Koan + def test_proxy_method_returns_wrapped_object + # NOTE: The Television class is defined below + tv = Proxy.new(Television.new) + + # HINT: Proxy class is defined above, may need tweaking... + + assert tv.instance_of?(Proxy) + end + + def test_tv_methods_still_perform_their_function + tv = Proxy.new(Television.new) + + tv.channel = 10 + tv.power + + assert_equal 10, tv.channel + assert tv.on? + end + + def test_proxy_records_messages_sent_to_tv + tv = Proxy.new(Television.new) + + tv.power + tv.channel = 10 + + assert_equal [:power, :channel=], tv.messages + end + + def test_proxy_handles_invalid_messages + tv = Proxy.new(Television.new) + + assert_raise(NoMethodError) do + tv.no_such_method + end + end + + def test_proxy_reports_methods_have_been_called + tv = Proxy.new(Television.new) + + tv.power + tv.power + + assert tv.called?(:power) + assert ! tv.called?(:channel) + end + + def test_proxy_counts_method_calls + tv = Proxy.new(Television.new) + + tv.power + tv.channel = 48 + tv.power + + assert_equal 2, tv.number_of_times_called(:power) + assert_equal 1, tv.number_of_times_called(:channel=) + assert_equal 0, tv.number_of_times_called(:on?) + end + + def test_proxy_can_record_more_than_just_tv_objects + proxy = Proxy.new("Code Mash 2009") + + proxy.upcase! + result = proxy.split + + assert_equal ["CODE", "MASH", "2009"], result + assert_equal [:upcase!, :split], proxy.messages + end +end + + +# ==================================================================== +# The following code is to support the testing of the Proxy class. No +# changes should be necessary to anything below this comment. + +# Example class using in the proxy testing above. +class Television + attr_accessor :channel + + def power + if @power == :on + @power = :off + else + @power = :on + end + end + + def on? + @power == :on + end +end + +# Tests for the Television class. All of theses tests should pass. +class TelevisionTest < Neo::Koan + def test_it_turns_on + tv = Television.new + + tv.power + assert tv.on? + end + + def test_it_also_turns_off + tv = Television.new + + tv.power + tv.power + + assert ! tv.on? + end + + def test_edge_case_on_off + tv = Television.new + + tv.power + tv.power + tv.power + + assert tv.on? + + tv.power + + assert ! tv.on? + end + + def test_can_set_the_channel + tv = Television.new + + tv.channel = 11 + assert_equal 11, tv.channel + end +end diff --git a/about_regular_expressions.rb b/about_regular_expressions.rb new file mode 100644 index 0000000..b1b47a2 --- /dev/null +++ b/about_regular_expressions.rb @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutRegularExpressions < Neo::Koan + def test_a_pattern_is_a_regular_expression + assert_equal __, /pattern/.class + end + + def test_a_regexp_can_search_a_string_for_matching_content + assert_equal __, "some matching content"[/match/] + end + + def test_a_failed_match_returns_nil + assert_equal __, "some matching content"[/missing/] + end + + # ------------------------------------------------------------------ + + def test_question_mark_means_optional + assert_equal __, "abbcccddddeeeee"[/ab?/] + assert_equal __, "abbcccddddeeeee"[/az?/] + end + + def test_plus_means_one_or_more + assert_equal __, "abbcccddddeeeee"[/bc+/] + end + + def test_asterisk_means_zero_or_more + assert_equal __, "abbcccddddeeeee"[/ab*/] + assert_equal __, "abbcccddddeeeee"[/az*/] + assert_equal __, "abbcccddddeeeee"[/z*/] + + # THINK ABOUT IT: + # + # When would * fail to match? + end + + # THINK ABOUT IT: + # + # We say that the repetition operators above are "greedy." + # + # Why? + + # ------------------------------------------------------------------ + + def test_the_left_most_match_wins + assert_equal __, "abbccc az"[/az*/] + end + + # ------------------------------------------------------------------ + + def test_character_classes_give_options_for_a_character + animals = ["cat", "bat", "rat", "zat"] + assert_equal __, animals.select { |a| a[/[cbr]at/] } + end + + def test_slash_d_is_a_shortcut_for_a_digit_character_class + assert_equal __, "the number is 42"[/[0123456789]+/] + assert_equal __, "the number is 42"[/\d+/] + end + + def test_character_classes_can_include_ranges + assert_equal __, "the number is 42"[/[0-9]+/] + end + + def test_slash_s_is_a_shortcut_for_a_whitespace_character_class + assert_equal __, "space: \t\n"[/\s+/] + end + + def test_slash_w_is_a_shortcut_for_a_word_character_class + # NOTE: This is more like how a programmer might define a word. + assert_equal __, "variable_1 = 42"[/[a-zA-Z0-9_]+/] + assert_equal __, "variable_1 = 42"[/\w+/] + end + + def test_period_is_a_shortcut_for_any_non_newline_character + assert_equal __, "abc\n123"[/a.+/] + end + + def test_a_character_class_can_be_negated + assert_equal __, "the number is 42"[/[^0-9]+/] + end + + def test_shortcut_character_classes_are_negated_with_capitals + assert_equal __, "the number is 42"[/\D+/] + assert_equal __, "space: \t\n"[/\S+/] + # ... a programmer would most likely do + assert_equal __, "variable_1 = 42"[/[^a-zA-Z0-9_]+/] + assert_equal __, "variable_1 = 42"[/\W+/] + end + + # ------------------------------------------------------------------ + + def test_slash_a_anchors_to_the_start_of_the_string + assert_equal __, "start end"[/\Astart/] + assert_equal __, "start end"[/\Aend/] + end + + def test_slash_z_anchors_to_the_end_of_the_string + assert_equal __, "start end"[/end\z/] + assert_equal __, "start end"[/start\z/] + end + + def test_caret_anchors_to_the_start_of_lines + assert_equal __, "num 42\n2 lines"[/^\d+/] + end + + def test_dollar_sign_anchors_to_the_end_of_lines + assert_equal __, "2 lines\nnum 42"[/\d+$/] + end + + def test_slash_b_anchors_to_a_word_boundary + assert_equal __, "bovine vines"[/\bvine./] + end + + # ------------------------------------------------------------------ + + def test_parentheses_group_contents + assert_equal __, "ahahaha"[/(ha)+/] + end + + # ------------------------------------------------------------------ + + def test_parentheses_also_capture_matched_content_by_number + assert_equal __, "Gray, James"[/(\w+), (\w+)/, 1] + assert_equal __, "Gray, James"[/(\w+), (\w+)/, 2] + end + + def test_variables_can_also_be_used_to_access_captures + assert_equal __, "Name: Gray, James"[/(\w+), (\w+)/] + assert_equal __, $1 + assert_equal __, $2 + end + + # ------------------------------------------------------------------ + + def test_a_vertical_pipe_means_or + grays = /(James|Dana|Summer) Gray/ + assert_equal __, "James Gray"[grays] + assert_equal __, "Summer Gray"[grays, 1] + assert_equal __, "Jim Gray"[grays, 1] + end + + # THINK ABOUT IT: + # + # Explain the difference between a character class ([...]) and alternation (|). + + # ------------------------------------------------------------------ + + def test_scan_is_like_find_all + assert_equal __, "one two-three".scan(/\w+/) + end + + def test_sub_is_like_find_and_replace + assert_equal __, "one two-three".sub(/(t\w*)/) { $1[0, 1] } + end + + def test_gsub_is_like_find_and_replace_all + assert_equal __, "one two-three".gsub(/(t\w*)/) { $1[0, 1] } + end +end diff --git a/about_sandwich_code.rb b/about_sandwich_code.rb new file mode 100644 index 0000000..ca9c554 --- /dev/null +++ b/about_sandwich_code.rb @@ -0,0 +1,106 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutSandwichCode < Neo::Koan + + def count_lines(file_name) + file = open(file_name) + count = 0 + while file.gets + count += 1 + end + count + ensure + file.close if file + end + + def test_counting_lines + assert_equal __, count_lines("example_file.txt") + end + + # ------------------------------------------------------------------ + + def find_line(file_name) + file = open(file_name) + while line = file.gets + return line if line.match(/e/) + end + ensure + file.close if file + end + + def test_finding_lines + assert_equal __, find_line("example_file.txt") + end + + # ------------------------------------------------------------------ + # THINK ABOUT IT: + # + # The count_lines and find_line are similar, and yet different. + # They both follow the pattern of "sandwich code". + # + # Sandwich code is code that comes in three parts: (1) the top slice + # of bread, (2) the meat, and (3) the bottom slice of bread. The + # bread part of the sandwich almost always goes together, but + # the meat part changes all the time. + # + # Because the changing part of the sandwich code is in the middle, + # abstracting the top and bottom bread slices to a library can be + # difficult in many languages. + # + # (Aside for C++ programmers: The idiom of capturing allocated + # pointers in a smart pointer constructor is an attempt to deal with + # the problem of sandwich code for resource allocation.) + # + # Consider the following code: + # + + def file_sandwich(file_name) + file = open(file_name) + yield(file) + ensure + file.close if file + end + + # Now we write: + + def count_lines2(file_name) + file_sandwich(file_name) do |file| + count = 0 + while file.gets + count += 1 + end + count + end + end + + def test_counting_lines2 + assert_equal __, count_lines2("example_file.txt") + end + + # ------------------------------------------------------------------ + + def find_line2(file_name) + # Rewrite find_line using the file_sandwich library function. + end + + def test_finding_lines2 + assert_equal __, find_line2("example_file.txt") + end + + # ------------------------------------------------------------------ + + def count_lines3(file_name) + open(file_name) do |file| + count = 0 + while file.gets + count += 1 + end + count + end + end + + def test_open_handles_the_file_sandwich_when_given_a_block + assert_equal __, count_lines3("example_file.txt") + end + +end diff --git a/about_scope.rb b/about_scope.rb new file mode 100644 index 0000000..451e98b --- /dev/null +++ b/about_scope.rb @@ -0,0 +1,79 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutScope < Neo::Koan + module Jims + class Dog + def identify + :jims_dog + end + end + end + + module Joes + class Dog + def identify + :joes_dog + end + end + end + + def test_dog_is_not_available_in_the_current_scope + assert_raise(___) do + Dog.new + end + end + + def test_you_can_reference_nested_classes_using_the_scope_operator + fido = Jims::Dog.new + rover = Joes::Dog.new + assert_equal __, fido.identify + assert_equal __, rover.identify + + assert_equal __, fido.class != rover.class + assert_equal __, Jims::Dog != Joes::Dog + end + + # ------------------------------------------------------------------ + + class String + end + + def test_bare_bones_class_names_assume_the_current_scope + assert_equal __, AboutScope::String == String + end + + def test_nested_string_is_not_the_same_as_the_system_string + assert_equal __, String == "HI".class + end + + def test_use_the_prefix_scope_operator_to_force_the_global_scope + assert_equal __, ::String == "HI".class + end + + # ------------------------------------------------------------------ + + PI = 3.1416 + + def test_constants_are_defined_with_an_initial_uppercase_letter + assert_equal __, PI + end + + # ------------------------------------------------------------------ + + MyString = ::String + + def test_class_names_are_just_constants + assert_equal __, MyString == ::String + assert_equal __, MyString == "HI".class + end + + def test_constants_can_be_looked_up_explicitly + assert_equal __, PI == AboutScope.const_get("PI") + assert_equal __, MyString == AboutScope.const_get("MyString") + end + + def test_you_can_get_a_list_of_constants_for_any_class_or_module + assert_equal __, Jims.constants + assert Object.constants.size > _n_ + end +end diff --git a/about_scoring_project.rb b/about_scoring_project.rb new file mode 100644 index 0000000..bdc5dd1 --- /dev/null +++ b/about_scoring_project.rb @@ -0,0 +1,77 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +# Greed is a dice game where you roll up to five dice to accumulate +# points. The following "score" function will be used to calculate the +# score of a single roll of the dice. +# +# A greed roll is scored as follows: +# +# * A set of three ones is 1000 points +# +# * A set of three numbers (other than ones) is worth 100 times the +# number. (e.g. three fives is 500 points). +# +# * A one (that is not part of a set of three) is worth 100 points. +# +# * A five (that is not part of a set of three) is worth 50 points. +# +# * Everything else is worth 0 points. +# +# +# Examples: +# +# score([1,1,1,5,1]) => 1150 points +# score([2,3,4,6,2]) => 0 points +# score([3,4,5,3,3]) => 350 points +# score([1,5,1,2,4]) => 250 points +# +# More scoring examples are given in the tests below: +# +# Your goal is to write the score method. + +def score(dice) + # You need to write this method +end + +class AboutScoringProject < Neo::Koan + def test_score_of_an_empty_list_is_zero + assert_equal 0, score([]) + end + + def test_score_of_a_single_roll_of_5_is_50 + assert_equal 50, score([5]) + end + + def test_score_of_a_single_roll_of_1_is_100 + assert_equal 100, score([1]) + end + + def test_score_of_multiple_1s_and_5s_is_the_sum_of_individual_scores + assert_equal 300, score([1,5,5,1]) + end + + def test_score_of_single_2s_3s_4s_and_6s_are_zero + assert_equal 0, score([2,3,4,6]) + end + + def test_score_of_a_triple_1_is_1000 + assert_equal 1000, score([1,1,1]) + end + + def test_score_of_other_triples_is_100x + assert_equal 200, score([2,2,2]) + assert_equal 300, score([3,3,3]) + assert_equal 400, score([4,4,4]) + assert_equal 500, score([5,5,5]) + assert_equal 600, score([6,6,6]) + end + + def test_score_of_mixed_is_sum + assert_equal 250, score([2,5,2,2,3]) + assert_equal 550, score([5,5,5,5]) + assert_equal 1100, score([1,1,1,1]) + assert_equal 1200, score([1,1,1,1,1]) + assert_equal 1150, score([1,1,1,5,1]) + end + +end diff --git a/about_strings.rb b/about_strings.rb new file mode 100644 index 0000000..548096b --- /dev/null +++ b/about_strings.rb @@ -0,0 +1,197 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutStrings < Neo::Koan + def test_double_quoted_strings_are_strings + string = "Hello, World" + assert_equal true, string.is_a?(String) + end + + def test_single_quoted_strings_are_also_strings + string = 'Goodbye, World' + assert_equal true, string.is_a?(String) + end + + def test_use_single_quotes_to_create_string_with_double_quotes + string = 'He said, "Go Away."' + assert_equal 'He said, "Go Away."', string + end + + def test_use_double_quotes_to_create_strings_with_single_quotes + string = "Don't" + assert_equal "Don't", string + end + + def test_use_backslash_for_those_hard_cases + a = "He said, \"Don't\"" + b = 'He said, "Don\'t"' + assert_equal true, a == b + end + + def test_use_flexible_quoting_to_handle_really_hard_cases + a = %(flexible quotes can handle both ' and " characters) + b = %!flexible quotes can handle both ' and " characters! + c = %{flexible quotes can handle both ' and " characters} + assert_equal true, a == b + assert_equal true, a == c + end + + def test_flexible_quotes_can_handle_multiple_lines + long_string = %{ +It was the best of times, +It was the worst of times. +} + assert_equal 54, long_string.length + assert_equal 3, long_string.lines.count + assert_equal "\n", long_string[0,1] + end + + def test_here_documents_can_also_handle_multiple_lines + long_string = < 0, :black => 30, :red => 31, + :green => 32, :yellow => 33, :blue => 34, + :magenta => 35, :cyan => 36, + } + + module_function + + COLORS.each do |color, value| + module_eval "def #{color}(string); colorize(string, #{value}); end" + module_function color + end + + def colorize(string, color_value) + if use_colors? + color(color_value) + string + color(COLORS[:clear]) + else + string + end + end + + def color(color_value) + "\e[#{color_value}m" + end + + def use_colors? + return false if ENV['NO_COLOR'] + if ENV['ANSI_COLOR'].nil? + if using_windows? + using_win32console + else + return true + end + else + ENV['ANSI_COLOR'] =~ /^(t|y)/i + end + end + + def using_windows? + File::ALT_SEPARATOR + end + + def using_win32console + defined? Win32::Console + end + end + + module Assertions + FailedAssertionError = Class.new(StandardError) + + def flunk(msg) + raise FailedAssertionError, msg + end + + def assert(condition, msg=nil) + msg ||= "Failed assertion." + flunk(msg) unless condition + true + end + + def assert_equal(expected, actual, msg=nil) + msg ||= "Expected #{expected.inspect} to equal #{actual.inspect}" + assert(expected == actual, msg) + end + + def assert_not_equal(expected, actual, msg=nil) + msg ||= "Expected #{expected.inspect} to not equal #{actual.inspect}" + assert(expected != actual, msg) + end + + def assert_nil(actual, msg=nil) + msg ||= "Expected #{actual.inspect} to be nil" + assert(nil == actual, msg) + end + + def assert_not_nil(actual, msg=nil) + msg ||= "Expected #{actual.inspect} to not be nil" + assert(nil != actual, msg) + end + + def assert_match(pattern, actual, msg=nil) + msg ||= "Expected #{actual.inspect} to match #{pattern.inspect}" + assert pattern =~ actual, msg + end + + def assert_raise(exception) + begin + yield + rescue Exception => ex + expected = ex.is_a?(exception) + assert(expected, "Exception #{exception.inspect} expected, but #{ex.inspect} was raised") + return ex + end + flunk "Exception #{exception.inspect} expected, but nothing raised" + end + + def assert_nothing_raised + begin + yield + rescue Exception => ex + flunk "Expected nothing to be raised, but exception #{exception.inspect} was raised" + end + end + end + + class Sensei + attr_reader :failure, :failed_test, :pass_count + + FailedAssertionError = Assertions::FailedAssertionError + + def initialize + @pass_count = 0 + @failure = nil + @failed_test = nil + @observations = [] + end + + PROGRESS_FILE_NAME = '.path_progress' + + def add_progress(prog) + @_contents = nil + exists = File.exist?(PROGRESS_FILE_NAME) + File.open(PROGRESS_FILE_NAME,'a+') do |f| + f.print "#{',' if exists}#{prog}" + end + end + + def progress + if @_contents.nil? + if File.exist?(PROGRESS_FILE_NAME) + File.open(PROGRESS_FILE_NAME,'r') do |f| + @_contents = f.read.to_s.gsub(/\s/,'').split(',') + end + else + @_contents = [] + end + end + @_contents + end + + def observe(step) + if step.passed? + @pass_count += 1 + if @pass_count > progress.last.to_i + @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.") + end + else + @failed_test = step + @failure = step.failure + add_progress(@pass_count) + @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.") + throw :neo_exit + end + end + + def failed? + ! @failure.nil? + end + + def assert_failed? + failure.is_a?(FailedAssertionError) + end + + def instruct + if failed? + @observations.each{|c| puts c } + encourage + guide_through_error + a_zenlike_statement + show_progress + else + end_screen + end + end + + def show_progress + bar_width = 50 + total_tests = Neo::Koan.total_tests + scale = bar_width.to_f/total_tests + print Color.green("your path thus far [") + happy_steps = (pass_count*scale).to_i + happy_steps = 1 if happy_steps == 0 && pass_count > 0 + print Color.green('.'*happy_steps) + if failed? + print Color.red('X') + print Color.cyan('_'*(bar_width-1-happy_steps)) + end + print Color.green(']') + print " #{pass_count}/#{total_tests} (#{pass_count*100/total_tests}%)" + puts + end + + def end_screen + if Neo.simple_output + boring_end_screen + else + artistic_end_screen + end + end + + def boring_end_screen + puts "Mountains are again merely mountains" + end + + def artistic_end_screen + "JRuby 1.9.x Koans" + ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})" + ruby_version = ruby_version.side_padding(54) + completed = <<-ENDTEXT + ,, , ,, + : ::::, :::, + , ,,: :::::::::::::,, :::: : , + , ,,, ,:::::::::::::::::::, ,: ,: ,, + :, ::, , , :, ,::::::::::::::::::, ::: ,:::: + : : ::, ,:::::::: ::, ,:::: + , ,::::: :,:::::::,::::, + ,: , ,:,,: ::::::::::::: + ::,: ,,:::, ,::::::::::::, + ,:::, :,,::: ::::::::::::, + ,::: :::::::, Mountains are again merely mountains ,:::::::::::: + :::,,,:::::: :::::::::::: + ,:::::::::::, ::::::::::::, + :::::::::::, ,:::::::::::: +::::::::::::: ,:::::::::::: +:::::::::::: Ruby Koans :::::::::::: +::::::::::::#{ ruby_version },:::::::::::: +:::::::::::, , ::::::::::: +,:::::::::::::, brought to you by ,,:::::::::::: +:::::::::::::: ,:::::::::::: + ::::::::::::::, ,::::::::::::: + ::::::::::::, Neo Software Artisans , :::::::::::: + :,::::::::: :::: ::::::::::::: + ,::::::::::: ,: ,,:::::::::::::, + :::::::::::: ,::::::::::::::, + :::::::::::::::::, :::::::::::::::: + :::::::::::::::::::, :::::::::::::::: + ::::::::::::::::::::::, ,::::,:, , ::::,::: + :::::::::::::::::::::::, ::,: ::,::, ,,: :::: + ,:::::::::::::::::::: ::,, , ,, ,:::: + ,:::::::::::::::: ::,, , ,:::, + ,:::: , ,, + ,,, +ENDTEXT + puts completed + end + + def encourage + puts + puts "The Master says:" + puts Color.cyan(" You have not yet reached enlightenment.") + if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1) + puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.") + elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1 + puts Color.cyan(" Do not lose hope.") + elsif progress.last.to_i > 0 + puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.") + end + end + + def guide_through_error + puts + puts "The answers you seek..." + puts Color.red(indent(failure.message).join) + puts + puts "Please meditate on the following code:" + puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace))) + puts + end + + def embolden_first_line_only(text) + first_line = true + text.collect { |t| + if first_line + first_line = false + Color.red(t) + else + Color.cyan(t) + end + } + end + + def indent(text) + text = text.split(/\n/) if text.is_a?(String) + text.collect{|t| " #{t}"} + end + + def find_interesting_lines(backtrace) + backtrace.reject { |line| + line =~ /neo\.rb/ + } + end + + # Hat's tip to Ara T. Howard for the zen statements from his + # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html) + def a_zenlike_statement + if !failed? + zen_statement = "Mountains are again merely mountains" + else + zen_statement = case (@pass_count % 10) + when 0 + "mountains are merely mountains" + when 1, 2 + "learn the rules so you know how to break them properly" + when 3, 4 + "remember that silence is sometimes the best answer" + when 5, 6 + "sleep is the best meditation" + when 7, 8 + "when you lose, don't lose the lesson" + else + "things are not what they appear to be: nor are they otherwise" + end + end + puts Color.green(zen_statement) + end + end + + class Koan + include Assertions + + attr_reader :name, :failure, :koan_count, :step_count, :koan_file + + def initialize(name, koan_file=nil, koan_count=0, step_count=0) + @name = name + @failure = nil + @koan_count = koan_count + @step_count = step_count + @koan_file = koan_file + end + + def passed? + @failure.nil? + end + + def failed(failure) + @failure = failure + end + + def setup + end + + def teardown + end + + def meditate + setup + begin + send(name) + rescue StandardError, Neo::Sensei::FailedAssertionError => ex + failed(ex) + ensure + begin + teardown + rescue StandardError, Neo::Sensei::FailedAssertionError => ex + failed(ex) if passed? + end + end + self + end + + # Class methods for the Neo test suite. + class << self + def inherited(subclass) + subclasses << subclass + end + + def method_added(name) + testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s + end + + def end_of_enlightenment + @tests_disabled = true + end + + def command_line(args) + args.each do |arg| + case arg + when /^-n\/(.*)\/$/ + @test_pattern = Regexp.new($1) + when /^-n(.*)$/ + @test_pattern = Regexp.new(Regexp.quote($1)) + else + if File.exist?(arg) + load(arg) + else + fail "Unknown command line argument '#{arg}'" + end + end + end + end + + # Lazy initialize list of subclasses + def subclasses + @subclasses ||= [] + end + + # Lazy initialize list of test methods. + def testmethods + @test_methods ||= [] + end + + def tests_disabled? + @tests_disabled ||= false + end + + def test_pattern + @test_pattern ||= /^test_/ + end + + def total_tests + self.subclasses.inject(0){|total, k| total + k.testmethods.size } + end + end + end + + class ThePath + def walk + sensei = Neo::Sensei.new + each_step do |step| + sensei.observe(step.meditate) + end + sensei.instruct + end + + def each_step + catch(:neo_exit) { + step_count = 0 + Neo::Koan.subclasses.each_with_index do |koan,koan_index| + koan.testmethods.each do |method_name| + step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1) + yield step + end + end + } + end + end +end + +END { + Neo::Koan.command_line(ARGV) + Neo::ThePath.new.walk +} diff --git a/path_to_enlightenment.rb b/path_to_enlightenment.rb new file mode 100644 index 0000000..726579c --- /dev/null +++ b/path_to_enlightenment.rb @@ -0,0 +1,44 @@ +# The path to Ruby Enlightenment starts with the following: + +$LOAD_PATH << File.dirname(__FILE__) + +require 'about_asserts' +require 'about_true_and_false' +require 'about_strings' +require 'about_symbols' +require 'about_arrays' +require 'about_array_assignment' +require 'about_objects' +require 'about_nil' +require 'about_hashes' +require 'about_methods' +in_ruby_version("2", "3") do + require 'about_keyword_arguments' +end +require 'about_constants' +require 'about_regular_expressions' +require 'about_control_statements' +require 'about_triangle_project' +require 'about_exceptions' +require 'about_triangle_project_2' +require 'about_iteration' +require 'about_blocks' +require 'about_sandwich_code' +require 'about_scoring_project' +require 'about_classes' +require 'about_open_classes' +require 'about_dice_project' +require 'about_inheritance' +require 'about_modules' +require 'about_scope' +require 'about_class_methods' +require 'about_message_passing' +require 'about_proxy_object_project' +require 'about_to_str' +in_ruby_version("jruby") do + require 'about_java_interop' +end +in_ruby_version("2.7") do + require 'about_pattern_matching' +end +require 'about_extra_credit' diff --git a/triangle.rb b/triangle.rb new file mode 100644 index 0000000..8df052a --- /dev/null +++ b/triangle.rb @@ -0,0 +1,22 @@ +# Triangle Project Code. + +# Triangle analyzes the lengths of the sides of a triangle +# (represented by a, b and c) and returns the type of triangle. +# +# It returns: +# :equilateral if all sides are equal +# :isosceles if exactly 2 sides are equal +# :scalene if no sides are equal +# +# The tests for this method can be found in +# about_triangle_project.rb +# and +# about_triangle_project_2.rb +# +def triangle(a, b, c) + # WRITE THIS CODE +end + +# Error class used in part 2. No need to change this code. +class TriangleError < StandardError +end