diff --git a/tictactoe/features/step_definitions/tic-tac-toe-steps.rb b/tictactoe/features/step_definitions/tic-tac-toe-steps.rb new file mode 100644 index 0000000..a9bd88e --- /dev/null +++ b/tictactoe/features/step_definitions/tic-tac-toe-steps.rb @@ -0,0 +1,124 @@ +require 'rspec/mocks/standalone' +require 'rspec/expectations' +Given /^I start a new Tic\-Tac\-Toe game$/ do + @game = TicTacToe.new +end + +When /^I enter my name (\w+)$/ do |name| + @game.player = name +end + +Then /^the computer welcomes me to the game with "(.*?)"$/ do |arg1| + @game.welcome_player.should eq arg1 +end + +Then /^randomly chooses who goes first$/ do + [@game.player, "Computer"].should include @game.current_player +end + +Then /^who is X and who is O$/ do + TicTacToe::SYMBOLS.should include @game.player_symbol, @game.computer_symbol +end + +Given /^I have a started Tic\-Tac\-Toe game$/ do + @game = TicTacToe.new(:player) + @game.player = "Renee" +end + +Given /^it is my turn$/ do + @game.current_player.should eq "Renee" +end + +Given /^the computer knows my name is Renee$/ do + @game.player.should eq "Renee" +end + +Then /^the computer prints "(.*?)"$/ do |arg1| + @game.should_receive(:puts).with(arg1) + @game.indicate_player_turn +end + +Then /^waits for my input of "(.*?)"$/ do |arg1| + @game.should_receive(:gets).and_return(arg1) + @game.get_player_move +end + +Given /^it is the computer's turn$/ do + @game = TicTacToe.new(:computer, :O) + @game.current_player.should eq "Computer" +end + +Then /^the computer randomly chooses an open position for its move$/ do + open_spots = @game.open_spots + @com_move = @game.computer_move + open_spots.should include(@com_move) +end + +Given /^the computer is playing X$/ do + @game.computer_symbol.should eq :X +end + +Then /^the board should have an X on it$/ do + @game.current_state.should include 'X' +end + +Given /^I am playing X$/ do + @game = TicTacToe.new(:computer, :X) + @game.player_symbol.should eq :X +end + +When /^I enter a position "(.*?)" on the board$/ do |arg1| + @old_pos = @game.board[arg1.to_sym] + @game.should_receive(:get_player_move).and_return(arg1) + @game.player_move.should eq arg1.to_sym +end + +When /^"(.*?)" is not taken$/ do |arg1| + @old_pos.should eq " " +end + +Then /^it is now the computer's turn$/ do + @game.current_player.should eq "Computer" +end + +When /^there are three X's in a row$/ do + @game = TicTacToe.new(:computer, :X) + @game.board[:C1] = @game.board[:B2] = @game.board[:A3] = :X +end + +Then /^I am declared the winner$/ do + @game.determine_winner + @game.player_won?.should be_true +end + +Then /^the game ends$/ do + @game.over?.should be_true +end + +Given /^there are not three symbols in a row$/ do + @game.board = { + :A1 => :X, :A2 => :O, :A3 => :X, + :B1 => :X, :B2 => :O, :B3 => :X, + :C1 => :O, :C2 => :X, :C3 => :O + } + @game.determine_winner +end + +When /^there are no open spaces left on the board$/ do + @game.spots_open?.should be_false +end + +Then /^the game is declared a draw$/ do + @game.draw?.should be_true +end + +When /^"(.*?)" is taken$/ do |arg1| + @game.board[arg1.to_sym] = :O + @taken_spot = arg1.to_sym +end + +Then /^computer should ask me for another position "(.*?)"$/ do |arg1| + @game.board[arg1.to_sym] = ' ' + @game.should_receive(:get_player_move).twice.and_return(@taken_spot, arg1) + @game.player_move.should eq arg1.to_sym +end diff --git a/tictactoe/features/step_definitions/tic-tac-toe.rb b/tictactoe/features/step_definitions/tic-tac-toe.rb new file mode 100644 index 0000000..f8e35b4 --- /dev/null +++ b/tictactoe/features/step_definitions/tic-tac-toe.rb @@ -0,0 +1,146 @@ +class TicTacToe + SYMBOLS = [:X, :O] + PLAYERS = [:player, :computer] + RED = "\e[31m" + GREEN = "\e[32m" + OFF = "\e[0m" + attr_reader :player, :last_taken + attr_accessor :player_symbol, :computer_symbol, :players, :whose_turn, :board + + def initialize starts=PLAYERS.sample, s=SYMBOLS.sample + @name = {:player => 'Player', :computer => 'Computer'} + @whose_turn = PLAYERS.index starts + @whose_symb = SYMBOLS.index s + @symbol = { + #PLAYERS[@whose_turn] => s, + :player => s, + :computer => SYMBOLS[(SYMBOLS.index s)^1] + } + @board = { + :A1 => ' ', :A2 => ' ', :A3 => ' ', + :B1 => ' ', :B2 => ' ', :B3 => ' ', + :C1 => ' ', :C2 => ' ', :C3 => ' ' + } + @draw = @game_over = false + end + + def player= name + @player = @name[:player] = name + end + + def open_spots + board.select{|k, v| v == ' '}.keys + end + + [:player, :computer].each do |s| + + define_method("#{s}_symbol") do + instance_variable_get("@symbol")[s] + end + + define_method("#{s}_won?") do + ws = instance_variable_get("@winning_symbol") + ss = instance_variable_get("@symbol")[s] + ws == ss + end + + end + + def welcome_player + "Welcome #{@player}" + end + + def current_player + @name[PLAYERS[@whose_turn]] + end + + def indicate_player_turn + puts "#{@player}'s Move:" + end + + def get_player_move + move = gets.chomp.upcase + end + + def player_move + pos = get_player_move.to_sym + until self.open_spots.include? pos + puts "#{pos} is taken, yo. Try again." + pos = get_player_move.to_sym + end + @board[pos] = @symbol[:player] + @last_taken = pos + pos + end + + def computer_move + puts "My move, yo:" + pos = open_spots.sample + @board[pos] = @symbol[:computer] + @last_taken = pos + pos + end + + def current_state + b = @board.map{|k,v| k == @last_taken ? colorize(v, OFF) : colorize(v)} + + #b = @board.values.map{|v| v.to_s} + cs = '' + [0,3,6].each do |row| + b[row...row+3].each{|cell| cs << cell} + cs << "\n" + end + cs + end + + def over? + @game_over + end + + def spots_open? + open_spots.length > 0 + end + + def determine_winner + + winning_line = [ + [:A1, :A2, :A3], + [:B1, :B2, :B3], + [:C1, :C2, :C3], + + [:A1, :B1, :C1], + [:A2, :B2, :C2], + [:A3, :B3, :C3], + + [:A1, :B2, :C3], + [:A3, :B2, :C1], + ] + [player_symbol, computer_symbol].each do |sym| + owns = @board.select{|k,v| v == sym}.keys + winning_line.each do |line| + if (line & owns).length == 3 + @winning_symbol = sym + @game_over = true + end + end + end + + @game_over = true unless spots_open? + toggle_current_player + end + + def draw? + !(computer_won? || player_won?) + end + + private + + def toggle_current_player + self.whose_turn = self.whose_turn^1 + end + + def colorize cell, color = cell == :X ? RED : GREEN + "#{color}#{cell.to_s}#{OFF}" + end + +end diff --git a/tictactoe/features/tic-tac-toe.feature b/tictactoe/features/tic-tac-toe.feature new file mode 100644 index 0000000..6f3134d --- /dev/null +++ b/tictactoe/features/tic-tac-toe.feature @@ -0,0 +1,57 @@ +Feature: Tic-Tac-Toe Game + As a game player I like tic-tac-toe + In order to up my skills + I would like to play agaist the computer + +Scenario: Begin Game + Given I start a new Tic-Tac-Toe game + When I enter my name Renee + Then the computer welcomes me to the game with "Welcome Renee" + And randomly chooses who goes first + And who is X and who is O + +Scenario: My Turn + Given I have a started Tic-Tac-Toe game + And it is my turn + And the computer knows my name is Renee + Then the computer prints "Renee's Move:" + And waits for my input of "B2" + +Scenario: Computer's Turn + Given I have a started Tic-Tac-Toe game + And it is the computer's turn + And the computer is playing X + Then the computer randomly chooses an open position for its move + And the board should have an X on it + +Scenario: Making Moves + Given I have a started Tic-Tac-Toe game + And it is my turn + And I am playing X + When I enter a position "A1" on the board + And "A1" is not taken + Then the board should have an X on it + And it is now the computer's turn + +Scenario: Making Bad Moves + Given I have a started Tic-Tac-Toe game + And it is my turn + And I am playing X + When I enter a position "A1" on the board + And "A1" is taken + Then computer should ask me for another position "B2" + And it is now the computer's turn + +Scenario: Winning the Game + Given I have a started Tic-Tac-Toe game + And I am playing X + When there are three X's in a row + Then I am declared the winner + And the game ends + +Scenario: Game is a draw + Given I have a started Tic-Tac-Toe game + And there are not three symbols in a row + When there are no open spaces left on the board + Then the game is declared a draw + And the game ends diff --git a/tictactoe/play_game.rb b/tictactoe/play_game.rb new file mode 100644 index 0000000..7b99f10 --- /dev/null +++ b/tictactoe/play_game.rb @@ -0,0 +1,22 @@ +require './features/step_definitions/tic-tac-toe.rb' + +@game = TicTacToe.new +puts "What is your name?" +@game.player = gets.chomp +puts @game.welcome_player + +until @game.over? + case @game.current_player + when "Computer" + @game.computer_move + when @game.player + @game.indicate_player_turn + @game.player_move + end + puts @game.current_state + @game.determine_winner +end + +puts "You Won!" if @game.player_won? +puts "I Won!" if @game.computer_won? +puts "DRAW!" if @game.draw? diff --git a/week1/homework/questions.txt b/week1/homework/questions.txt index 2257bb9..ad7dfa0 100644 --- a/week1/homework/questions.txt +++ b/week1/homework/questions.txt @@ -1,15 +1,37 @@ -Please read: -Chapter 3 Classes, Objects, and Variables +Please read: +Chapter 3 Classes, Objects, and Variables p.86-90 Strings (Strings section in Chapter 6 Standard Types) 1. What is an object? +A representation of a "thing" in a program. 2. What is a variable? +In ruby, a named pointer to an object. In some other languages, more like a +container for a value. 3. What is the difference between an object and a class? +A class is a representation of a "type of thing" - e.g. the class of all +toasters. An object is a representation of an individual "thing" - e.g. the +toaster in my kitchen. This is not the same as a Platonic Ideal Form vs a Real +Object - ruby objects are not second rate to classes; each is a correct +"instance" of its class. 4. What is a String? +A sequence of characters. In ruby, an object of the class String. And in ruby, +it is mutable. This still freaks me out and I would like to understand if this +is considered to be a deliberate feature or a side-effect of something. -5. What are three messages that I can send to a string object? Hint: think methods +5. What are three messages that I can send to a string object? Hint: think methods +upcase +kind_of? +sub 6. What are two ways of defining a String literal? Bonus: What is the difference between them? +'single quoted, which has #{no interpration}, except +\\\\ renders as \\, and \\\' renders as \'' + +interpretation='lots of interprations' +"double quoted, which has #{interpretation}" + +%q{wrapped in some arbitrary brace pair, allowing one to embed plenty of +nasties like ', ", and \, without escapes} diff --git a/week1/homework/strings_and_rspec_spec.rb b/week1/homework/strings_and_rspec_spec.rb index ea79e4c..0ad4fe9 100644 --- a/week1/homework/strings_and_rspec_spec.rb +++ b/week1/homework/strings_and_rspec_spec.rb @@ -12,15 +12,18 @@ before(:all) do @my_string = "Renée is a fun teacher. Ruby is a really cool programming language" end - it "should be able to count the charaters" + it "should be able to count the characters" do + @my_string.length.should == 66 + end + it "should be able to count the characters rspec style" do + @my_string.should have(66).characters + end it "should be able to split on the . charater" do - pending - result = #do something with @my_string here + result = @my_string.split '.' result.should have(2).items end it "should be able to give the encoding of the string" do - pending 'helpful hint: should eq (Encoding.find("UTF-8"))' + @my_string.encoding.name.should == 'UTF-8' end end end - diff --git a/week1/ruby_spec.rb b/week1/ruby_spec.rb index 5dbd8a3..85f512b 100644 --- a/week1/ruby_spec.rb +++ b/week1/ruby_spec.rb @@ -1,7 +1,7 @@ -describe "Playing With Ruby! " do - context 'when adding numbers' do - it "should add numbers" do - (2+2).should eq 4 - end - end +describe "Playing With Ruby! " do + context 'when adding numbers' do + it "should add numbers" do + (2+2).should eq 4 + end + end end \ No newline at end of file diff --git a/week2/homework/questions.txt b/week2/homework/questions.txt index 939e42d..0594909 100644 --- a/week2/homework/questions.txt +++ b/week2/homework/questions.txt @@ -3,11 +3,60 @@ Containers, Blocks, and Iterators Sharing Functionality: Inheritance, Modules, and Mixins 1. What is the difference between a Hash and an Array? +An array is an ordered list of items (objects in ruby), indexed by sequential +integers starting at 0. + +A hash is a set of key=value pairs, indexed by the key. Traditionally it is not +ordered, but the pickaxe book says ruby retains the order in which items are +stored. Other languages would view this as too costly. Is ruby slower by this? 2. When would you use an Array over a Hash and vice versa? +I would use an array when + order is the best way access or enter the values, or + the data structure has a known number of items +A hash is nice when + a name is the best way to access or enter the values 3. What is a module? Enumerable is a built in Ruby module, what is it? +A module providing a discrete namespace, so I can "require" multiple source +files (or otherwise work on things too big to hold in my head), and not worry +about namespace collisions, by fully qualifying the module's methods and +constants: +Module1.method(Module3::CONSTANT) +Module2.method(Module4::CONSTANT) + +A module also provides a way to 'include' code in multiple classes, allowing us +to reuse methods and variables without the horror of multiple inheritance. + +Enumerable provides nice generic methods appropriate to arrays and hashes - +map, include? and others. Would inject come from Enumerable? How do I tell? ri +inject gives "Implementation from Enumerable". OK, How do I go the other way? +(ie, what does Enumerable provide?) Yay, it works how I would hope: ri +Enumerable gives in introductory paragraph and a list of methods. 4. Can you inherit more than one thing in Ruby? How could you get around this problem? +You can't inherit from more than one class. My instinct is that this is a +feature, not a problem, but pickaxe assures us that it is a problem, and for it +we need mixins. +In a class, we can include a module and get it's methods. Nice enough. +We can also get its instance variables, which seems to me a way of destroying +all of that good namespace separation. I do not understand the bit in pickaxe +about using a "module-level hash, indexed by the current object ID". +So mixins alarm me at first glance, especially the instance variables. 5. What is the difference between a Module and a Class? +A class is, well, a class... from ri Class: +"first-class objects---each is an instance of class Class". +So they can be sub-classed, and all objects belong to some a class. You can +define methods for them, which are inherited by subclasses and by instances of +the class. + +Modules, according to pickaxe, are not classes, but just provide namespace +(and mixins 'for free'). But, from ri Class: += Class < Module +So Classes are a class of Module? Or do I misread that? + +Anyway, you can define methods and constants for a Module too, but they are +then pulled in to classes and the methods and constants used there. And +instance variable too, but I don't understand how to do that safely. + diff --git a/week2/homework/simon_says.rb b/week2/homework/simon_says.rb new file mode 100644 index 0000000..9370aa6 --- /dev/null +++ b/week2/homework/simon_says.rb @@ -0,0 +1,36 @@ +module SimonSays + def echo(string) + string + end + + def shout(string) + string.upcase + end + + # FIXME ruby, not perl + def repeat(string, nrepeats = 2) + output = ((string + " ")*nrepeats).sub(/ $/, '') + end + + def start_of_word(string, nchars) + string.slice(0..nchars-1) + end + + def first_word(string) + ## This one works, for the tests given... + #string.sub(/[^a-zA-Z'].*/, '') + # + # And this one works on my added test. But it's uglier + #string.sub(/^\W*(\w+)[^a-zA-Z'].*/, '\1') + + ##Is there a more ruby way to do this? + # + #This isn't bad, and seems more ruby-ish. I like it. + #but it breaks on my additional test. + #string.split(/\s+/).first + # + #This works on my additional test. + #But it might be uglier than my perl-ish version. + (string.split(/\s+/).select { |word| word !~ /^\s*$/ }).first + end +end diff --git a/week2/homework/simon_says_spec.rb b/week2/homework/simon_says_spec.rb index 7f329e5..6ae66ce 100644 --- a/week2/homework/simon_says_spec.rb +++ b/week2/homework/simon_says_spec.rb @@ -37,6 +37,11 @@ start_of_word("Bob", 2).should == "Bo" end + it "should do something sane if word is shorter than we thought" do + pending + start_of_word("Bob", 4).should == what? + end + it "should tell us the first word of 'Hello World' is 'Hello'" do first_word("Hello World").should == "Hello" end @@ -44,4 +49,8 @@ it "should tell us the first word of 'oh dear' is 'oh'" do first_word("oh dear").should == "oh" end + + it "should maybe tell us the first word of ' oh dear' is 'oh'" do + first_word(" oh dear").should == "oh" + end end diff --git a/week3/homework/calculator.rb b/week3/homework/calculator.rb new file mode 100644 index 0000000..836a2a3 --- /dev/null +++ b/week3/homework/calculator.rb @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby + +class Calculator + def initialize + end + + # must explicitly 0 the inject for parameter [] to not return nil + def sum(addends) + addends.inject(0, :+) + end + + # This looks gross and repetitive to me. (It passes, though.) What I want + # is to flatten all incoming arguments to one array. How do I do that? + # +# def multiply(multiplicands, multiplier = 1) +# (Array(multiplicands) + Array(multiplier)).inject(1, :*) +# end + + # So, we splat out the parameters to a list, then flatten to an array. + def multiply(*multiplicands) + multiplicands.flatten.inject(1, :*) + end + + def pow(base, index) +# # just mirroring the code in the spec: +# p = 1 +# # This returns index, +# index.times { p *= base } +# # so we must explicitly return p +# p +# # more concisely, we'll use the builtin. + base**index + + end + + def fac(input) + # I'd like a some error checking for what we were passed + # But, not in the spec. + case input + # Can we combine the first 2 whens? +# when 0 +# 1 +# when 1 +# 1 + # Our Benevolent Dictator Google says Yes, with commas, not pipes + when 0, 1 + 1 + else + input * fac(input - 1) + end + end + +end diff --git a/week3/homework/calculator_spec.rb b/week3/homework/calculator_spec.rb index 5a418ed..ded3b42 100644 --- a/week3/homework/calculator_spec.rb +++ b/week3/homework/calculator_spec.rb @@ -31,6 +31,14 @@ @calculator.multiply(2,2).should eq 4 end + it "multiplies an array of length 1 by 1" do + @calculator.multiply([500]).should eq 500 + end + + it "multiplies floats " do + @calculator.multiply([1.1,2]).should eq 2.2 + end + it "multiplies an array of numbers" do @calculator.multiply([2,2]).should eq 4 end @@ -62,6 +70,8 @@ it "computes the factorial of 10" do @calculator.fac(10).should eq 3628800 end + # How do you do error checking? I would like my fac() to complain + # about floats on input, without killing the program. end diff --git a/week3/homework/questions.txt b/week3/homework/questions.txt index dfb158d..a0b214c 100644 --- a/week3/homework/questions.txt +++ b/week3/homework/questions.txt @@ -5,11 +5,58 @@ Please Read: - Chapter 22 The Ruby Language: basic types (symbols), variables and constants 1. What is a symbol? +An immutable string of characters. It seems like we've seen them mostly for +hash keys (which is a reasonable use), but I am inclined to want to use them +somehow to replace ruby's missing constants. Is that possible, or advisable? 2. What is the difference between a symbol and a string? +> "foo".methods.sort - :foo.methods.sort +Lots of bang methods. Why can't I sub a symbol? I can upcase it. Hm. + +> :foo.methods.sort - "foo".methods.sort +=> [:id2name, :to_proc] + +Well, mostly that I can't change a symbol, I think. But it is missing a few +methods, too. 3. What is a block and how do I call a block? +In ruby, some code wrapped in braces or do/end. Used like an anonymous method. +Not used immediately, but when called later. (This part I find subtle and +difficult to understand.) Can only appear after another method, so: +> ["foo", "bar", "baz"].each {|metasyntactic_variable_name| puts metasyntactic_variable_name } + +but +> "foo" { |alone| puts alone } +is meaningless and an error. 4. How do I pass a block to a method? What is the method signature? +Just put the block right after the method, as discussed above, with any +items passed to the block placed between pipes at the beginning of the block. +I am not sure if this is a different question than "how do I call a block?". + +What is the method signature of what? I don't think I understand what a method +signature is, in any case. Googling around makes me think it is analogous to +a function declaration - a statement of what parameters a method takes. Is it +simply the def line? 5. Where would you use regular expressions? +All over the place, because I come from shell and perl. In ruby, I can +(should?) use them less, because: + 1. Ruby has a lot more string methods (which are maybe calling regexes under + the hood, but + > string.strip + is a lot more readable and concise than + > string.gsub(/^\s*|\s*$/, "") + 2. Things that I "shouldn't" have been doing with regexes (like parsing html, + or otherwise using them for syntax) are probably easier done with all of + ruby's endless builtins and gems. + 3. Well, that's about it, but that's plenty. + +But then again, + > "foot".sub("fo") {|subbed| "Wo" if subbed.end_with? "o" } + OMG, OMG, OMG! Passing substitutions to blocks! I could fall in love with + regexes all over again! + +Anyway, the formal answer is: wherever I need to make string matches or +substitutions which are not syntax- or context-sensitive, and there is no +builtin for the task. diff --git a/week4/homework/Makefile b/week4/homework/Makefile new file mode 100644 index 0000000..19af7f9 --- /dev/null +++ b/week4/homework/Makefile @@ -0,0 +1,7 @@ +PROG = worker +TESTEE = ${PROG}.rb +TESTER = ${PROG}_spec.rb +SPEC = rspec -c --format nested + +test : ${TESTEE} ${TESTER} + ${SPEC} ${TESTER} diff --git a/week4/homework/Rakefile b/week4/homework/Rakefile new file mode 100644 index 0000000..81dcf3f --- /dev/null +++ b/week4/homework/Rakefile @@ -0,0 +1,21 @@ +task :default => [:test] + +# OK, you don't get a free shell. +#task :test do +# rspec -c --format nested "worker_spec.rb" +#end + +## But shelling out just cheerfully returns success (and loses the output) +#task :test do +# `rspec -c --format nested "worker_spec.rb"` +#end + +#http://blog.revathskumar.com/2011/12/run-rspec-as-rake-task.html +# +#Didn't work until moving spec to ./spec/, per +#https://www.relishapp.com/rspec/rspec-core/docs/command-line/rake-task +#which meant I had to change header in rspec. +#Seriously? I have got to be missing something. +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) +task :test => :spec diff --git a/week4/homework/questions.txt b/week4/homework/questions.txt index 187b3d3..bef64fa 100644 --- a/week4/homework/questions.txt +++ b/week4/homework/questions.txt @@ -3,12 +3,75 @@ Chapter 10 Basic Input and Output The Rake Gem: http://rake.rubyforge.org/ 1. How does Ruby read files? +With File IO objects. Practically, you can mostly do +> while line = gets +but there is also explicit +> to_be_read = File.new("readme", "r") +or, more idiomatically +> File.open("readme", "r") { |file| } 2. How would you output "Hello World!" to a file called my_output.txt? +Very casually: +> File.open("my_output.txt", "a"){|file| file.puts "Hello World!"} +Very very casually, and maybe not so portably (though `sokay across *nix and +Windows): +> `echo "Hello World!" >>my_output.txt` 3. What is the Directory class and what is it used for? +I have no idea. I can't find such a class. + +The Dir class, though, is a class of directories, and gives us nice *nixy stuff +like .pwd, .home, and .entries (which I would like to see aliased as 'ls'). Is +this what you were looking for? It seems fitting with IO. 4. What is an IO object? +An object representing something outside of your program that you can use as +a sink or source for data. Files, stdin, stdout, stderr, sockets, FIFOs, and +I'm not sure what else would all be represented as IO objects of some sort. +Here is a bit where I am still thinking shell is nicer than ruby, because - +well, see my shell out in question 2. 5. What is rake and what is it used for? What is a rake task? +A replacement for Makefiles, supposedly with cleaner syntax. I'll get back to +you on this one. A task is the rake equivalent of a target: something which +we can request be accomplished, which rake will use logic programming to +determine what prerequisites must be fulfilled in order to accomplish. Is +'default' always the default task, or is it like make where the first target +is the default target? I've been using make to run tests, so I'll commit +my Makefile and add a rakefile and compare the two. + +OK, the rakefile looks a bit ugly. + +In ../rake-v-make/ are comparable rake and make files, based on the rake intro +in pickaxe, plus tasks to compare, and to generate dummy files. + +$ time make fake default compare +... +real 0m0.013s +user 0m0.005s +sys 0m0.008s + +$ time rake fake default compare +... +real 0m0.109s +user 0m0.094s +sys 0m0.013s +This is all too fast to worry about, but the difference in speeds is shocking. +I wonder what big builds end up looking like. + +rake is 37 SLOC vs make's 18 +I'm sure a more experienced rubyist could shorten that up at least a few +lines. Also, the 5 desc lines aren't necessary - but neither are the 2 lines +providing 'make help' + +make uses shell utilities, so you get stuff like wc(1) for free, instead of +re-implementing in ruby. + +Contrariwise, make uses shell utilities, so you can't be sure it will run the +same (or at all) on all platforms. I wanted to use seq(1) in the Makefile, but +that isn't in *BSD base (and when it is ported, it's called gseq). Rake uses +1.upto, and if you're running rake you have .upto. + +rake is a hell of a lot more readable than make, even for a seasoned maker / +noob rubyist like me. diff --git a/week4/homework/spec/worker_spec.rb b/week4/homework/spec/worker_spec.rb new file mode 100644 index 0000000..956432f --- /dev/null +++ b/week4/homework/spec/worker_spec.rb @@ -0,0 +1,36 @@ +require "#{File.dirname(__FILE__)}/../worker" + +describe Worker do + + it "executes a block and returns a string" do + result = Worker.work do + "hello" + end + result.should == "hello" + end + + it "executes a block and returns a number" do + result = Worker.work do + 3 + 4 + end + result.should == 7 + end + + it "executes a block in the context of the calling method" do + n = 1 + result = Worker.work do + n + 4 + end + result.should == 5 + end + + + it "executes a block 3 times and returns the result" do + n = 5 + result = Worker.work(3) do + n += 1 + end + result.should == 8 + end + +end diff --git a/week4/homework/worker.rb b/week4/homework/worker.rb new file mode 100644 index 0000000..6367983 --- /dev/null +++ b/week4/homework/worker.rb @@ -0,0 +1,52 @@ +#!/usr/bin/ruby2.0 +# +class Worker + + def initialize + end + +# # works on 1st 3 tests. Gives 6 on 4th test +# def self.work +# yield +# end +# # some failures: +# # +# # returns n. times always returns the receiver. ?! +# # (EDIT - I will understand this better after the third failure) +# def self.work n=1 +# n.times { yield } +# end +# +# #returns nil for any block. I don't understand this one at all. +# def self.work reps = 1 +# i = 0 +# while i <= reps +# i += 1 +# yield +# end +# end +# +# # always returns 1. OK, so the block is a side effect, upto always returns +# # truthiness. Why doesn't it return reps? Oh, it returns the receiver! +# # (Thanks, irb and ri. No thanks to you, ruby-doc.org.) +# def self.work reps=1 +# 1.upto(reps){ yield } +# end + + # The block acts as a side effect, so we need to capture what happens in the + # block. Not sure I like this approach, but it is reasonably concise and + # works. + # Heh - I was using $ to keep result in scope. Matthew Spah pointed out that + # this is a global variable. Let's make it @. + def self.work reps=1 + 1.upto(reps){ @result = yield } + @result + end + +# # Renée's - she says to watch for the pattern that can be resolved down + # to inject +# def self.work t=1 +# t.times.inject(nil) {yield} +# end +end + diff --git a/week4/rake-v-make/Makefile b/week4/rake-v-make/Makefile new file mode 100644 index 0000000..bff8e48 --- /dev/null +++ b/week4/rake-v-make/Makefile @@ -0,0 +1,28 @@ +WIN = *.bak +NIX = *~ +RM = rm -fv + +default : win nix #Remove Unix and Windows backup files + +win : # Remove files with a .bak extension + @${RM} ${WIN} + +nix : # Remove files whose names end with a tilde + @${RM} ${NIX} # Remove files whose names end with a tilde + +# OK, here rake wins. I was about to use seq to generate some numbers, +# but it's not portable. There are workarounds, but I am going to be +# lazy and hardcode my numbers in. +fake : # create dummy files for testing + @for i in 1 2 3; do touch $$i.bak $$i~; echo $$i.bak; echo $$i~; done + +help : # show available tasks + @sed -n 's,^\([^ .]\+\):[^#]*#\s*\(.*\),\1: \2,p' Makefile + +compare : # show non-comment non-blank lines of code + @for i in Makefile Rakefile; do \ + echo "$$i:"; \ + grep -v -e '^\s*#' -e '^\s*$$' $$i | wc ; \ + done + +.PHONY : default win nix fake help compare diff --git a/week4/rake-v-make/Rakefile b/week4/rake-v-make/Rakefile new file mode 100644 index 0000000..6b592e2 --- /dev/null +++ b/week4/rake-v-make/Rakefile @@ -0,0 +1,48 @@ +task :default => :delete_backups + +def delete(pattern) + files = Dir[pattern] + rm(files, verbose: true) unless files.empty? +end + +desc "Remove files whose names end with a tilde" +task :delete_unix_backups do + delete "*~" +end + +desc "Remove files with a .bak extension" +task :delete_windows_backups do + delete "*.bak" +end + +desc "Remove Unix and Windows backup files" +task :delete_backups => [ :delete_unix_backups, :delete_windows_backups ] do + puts "All backups deleted" +end + +desc "make dummy files for testing" +task :fake do + # cool or disgusting? is map really what I want here? It's sort of + # a 2D array I'm doing here. Anyway, it works. + 1.upto(3){|n| ['.bak','~'].each{|x| touch n.to_s + x}} +end + +desc "show non-comment non-blank lines of code" +task :compare do + ["Makefile", "Rakefile"].each do |f| + puts f + ":" + File.open(f, 'r') do |fh| + count = Hash.new(0) + while line=fh.gets do #FIXME refactor + next if (line =~ /(^\s*#)|(^\s*$)/) + count[:l] += 1 + count[:w] += line.scan(/\S+/).length + count[:c] += line.length + end + puts count.values.join("\t") + end + end +end + + + diff --git a/week7/homework/features/step_definitions/pirate.rb b/week7/homework/features/step_definitions/pirate.rb new file mode 100644 index 0000000..341690d --- /dev/null +++ b/week7/homework/features/step_definitions/pirate.rb @@ -0,0 +1,20 @@ +class PirateTranslator + def intitialize + end + + def say phrase + end + + def translate + + #FIXME ugly, dumb trick from + # http://rubyquicktips.com/post/4438542511/heredoc-and-indent + @piratish = <<-END.gsub(/^ {6}/, '').chomp + Ahoy Matey + Shiber Me Timbers You Scurvey Dogs!! + END + + #FIXME also ugly and dumb. + @piratish = 'Ahoy Matey' << "\n Shiber Me Timbers You Scurvey Dogs!!" + end +end diff --git a/week7/homework/features/step_definitions/tic-tac-toe.rb b/week7/homework/features/step_definitions/tic-tac-toe.rb new file mode 100644 index 0000000..6404efb --- /dev/null +++ b/week7/homework/features/step_definitions/tic-tac-toe.rb @@ -0,0 +1,85 @@ +class TicTacToe + SYMBOLS = [:X, :O] + PLAYERS = [:player, :computer] + attr_reader :player + attr_accessor :player_symbol, :computer_symbol, :players, :whose_turn + + def initialize starts=PLAYERS[dien], s=SYMBOLS[dien] + @name = {:player => 'Player', :computer => 'Computer'} + @whose_turn = PLAYERS.index starts + @whose_symb = SYMBOLS.index s + @symbol = { + #PLAYERS[@whose_turn] => s, + :player => s, + :computer => SYMBOLS[(SYMBOLS.index s)^1] + } + @board = { + :A1 => ' ', :A2 => ' ', :A3 => ' ', + :B1 => ' ', :B2 => ' ', :B3 => ' ', + :C1 => ' ', :C2 => ' ', :C3 => ' ' + } + end + + def player= name + @player = @name[:player] = name + end + + def board + @board + end + + def open_spots + board.select{|spot, v| spot if v.is_a? String}.map{|k,v| k} + end + + #FIXME refactor these 2 + def player_symbol + @symbol[:player] + end + + def computer_symbol + @symbol[:computer] + end + + def welcome_player + "Welcome #{@player}" + end + + def current_player + @name[PLAYERS[@whose_turn]] + end + + def indicate_palyer_turn + puts "#{@player}'s Move:" + end + + def get_player_move + move = gets.chomp + end + + def player_move + pos = get_player_move.to_sym + @board[pos] = @symbol[:player] + pos + end + + def computer_move + pos = open_spots[dien open_spots.length - 1] + @board[pos] = @symbol[:computer] + pos + end + + def current_state + @board.map{|pos, state| state.to_s} + end + private + + def toggle_current_player + self.whose_turn = self.whose_turn^1 + end + + def dien n=1 + rand(0..n) + end + +end diff --git a/week7/homework/questions.txt b/week7/homework/questions.txt index d55387d..3d36ce8 100644 --- a/week7/homework/questions.txt +++ b/week7/homework/questions.txt @@ -3,7 +3,76 @@ Please Read Chapters 23 and 24 DuckTyping and MetaProgramming Questions: 1. What is method_missing and how can it be used? + +method_missing is a method of class Object which is called if no other method +can be found by the name called. It raises an exception. It can be usefully +used by overriding it in your class, and: + Catching methods that match a regex, and generating them on the fly (passing + the mismatches on up the line to Object.method_missing). This is how the + Rails find_by_.* is implemented. + + (as per [1]:) Using it in a wrapper class to decide which methods can proceed + 'even though we said no'. (I like Perrota's example, but am not sure I can + think of my own usage scenario yet.) + 2. What is and Eigenclass and what is it used for? Where Do Singleton methods live? + +An Eigenclass (syns: ghost class, singleton class, metaclass [2]) is a class +which Ruby automagically generates to hold any singleton methods of an object. +It is the class of the object, and a subclass of the object's original +superclass. In other words, Ruby inserts an Eigenclass in the class hierarchy +between an object and it's superclass whenever a singleton method is generated. +#FIXME what are they used for? + +Singleton methods live within the Eigenclass - they are defined for a single +object, rather than for an entire class, and Ruby hoists them up into the +Eigenclass. (Though classes are a sort of object, so it seems to me that class +methods should be viewed as the special case here.) + 3. When would you use DuckTypeing? How would you use it to improve your code? + +I would use it implicitly everywhere in Ruby or Python. Consciously using it +would improve my code by: + Removing superfluous code for checking types where it doesn't matter + + Making my code extensible or modifiable by not wiring down types + 4. What is the difference between a class method and an instance method? What is the difference between instance_eval and class_eval? + +A class method is available on the class itself. (Singh says a class method is +a singleton method of the class, so they live in the Eigenclass of the +class.[2]) An instance method is available to instances of the class. So: + class Myclass + def self.my_class_method + end + + def my_instance_method + end + end + + Myclass.my_class_method + m = Myclass.new + m.my_instance_method + +#FIXME I don't really understand where I should be using class methods. + +instance_eval sets self to the receiver and executes a block within that +context. It lets you peek at the receiver's instance variables, but also lets +you run instance methods as barewords, like in the Pickaxe book turtle program. +So defining a method with instance_eval will define a class methods. + +class_eval does the same, but at a module or class level. So defining a method +with class_eval will define an instance method. + 5. What is the difference between a singleton class and a singleton method? + +This question is question 2 turned inside out? + +A singleton method is a method defined for one object. A singleton class (aka +Eigenclass, anonymous class, ghost class, metaclass) is the container class +generated by ruby to be a superclass of the object to which the singleton +method is attached, thus allowing Ruby to keep it's tidy method look-up +techniques in order. + +[1] http://rubylearning.com/blog/2010/10/07/do-you-know-rubys-chainsaw-method/ +[2] http://madebydna.com/all/code/2011/06/24/eigenclasses-demystified.html