From 7bc57d79a98e8d0bbe53a6f1ba6626df6b3dccb0 Mon Sep 17 00:00:00 2001 From: kakusuke Date: Thu, 7 Nov 2013 20:36:24 +0900 Subject: [PATCH 1/8] copy feature files --- code_breaker/sumi/Gemfile | 4 + code_breaker/sumi/Gemfile.lock | 30 +++++++ .../features/codebreaker_starts_game.feature | 11 +++ .../codebreaker_submits_guess.feature | 56 +++++++++++++ .../step_definitions/codebreaker_steps.rb | 38 +++++++++ code_breaker/sumi/features/support/env.rb | 2 + code_breaker/sumi/spec/codebreaker_spec.rb | 79 +++++++++++++++++++ code_breaker/sumi/spec/spec_helper.rb | 13 +++ 8 files changed, 233 insertions(+) create mode 100644 code_breaker/sumi/Gemfile create mode 100644 code_breaker/sumi/Gemfile.lock create mode 100644 code_breaker/sumi/features/codebreaker_starts_game.feature create mode 100644 code_breaker/sumi/features/codebreaker_submits_guess.feature create mode 100644 code_breaker/sumi/features/step_definitions/codebreaker_steps.rb create mode 100644 code_breaker/sumi/features/support/env.rb create mode 100644 code_breaker/sumi/spec/codebreaker_spec.rb create mode 100644 code_breaker/sumi/spec/spec_helper.rb diff --git a/code_breaker/sumi/Gemfile b/code_breaker/sumi/Gemfile new file mode 100644 index 0000000..b21033b --- /dev/null +++ b/code_breaker/sumi/Gemfile @@ -0,0 +1,4 @@ +source "http://rubygems.org" + +gem "rspec" +gem "cucumber" diff --git a/code_breaker/sumi/Gemfile.lock b/code_breaker/sumi/Gemfile.lock new file mode 100644 index 0000000..a7e0f8c --- /dev/null +++ b/code_breaker/sumi/Gemfile.lock @@ -0,0 +1,30 @@ +GEM + remote: http://rubygems.org/ + specs: + builder (3.2.2) + cucumber (1.3.9) + builder (>= 2.1.2) + diff-lcs (>= 1.1.3) + gherkin (~> 2.12) + multi_json (>= 1.7.5, < 2.0) + multi_test (>= 0.0.2) + diff-lcs (1.2.4) + gherkin (2.12.2) + multi_json (~> 1.3) + multi_json (1.8.2) + multi_test (0.0.2) + rspec (2.14.1) + rspec-core (~> 2.14.0) + rspec-expectations (~> 2.14.0) + rspec-mocks (~> 2.14.0) + rspec-core (2.14.7) + rspec-expectations (2.14.4) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.14.4) + +PLATFORMS + ruby + +DEPENDENCIES + cucumber + rspec diff --git a/code_breaker/sumi/features/codebreaker_starts_game.feature b/code_breaker/sumi/features/codebreaker_starts_game.feature new file mode 100644 index 0000000..5c926b6 --- /dev/null +++ b/code_breaker/sumi/features/codebreaker_starts_game.feature @@ -0,0 +1,11 @@ +Feature: codebreaker starts game + As a codebreaker + I want to start a game + So that I can break the code + + Scenario: start game + Given I am not yet playing + When I start a new game + Then I should see "Welcome to Codebreaker!" + And I should see "Enter guess: " + \ No newline at end of file diff --git a/code_breaker/sumi/features/codebreaker_submits_guess.feature b/code_breaker/sumi/features/codebreaker_submits_guess.feature new file mode 100644 index 0000000..4adbccf --- /dev/null +++ b/code_breaker/sumi/features/codebreaker_submits_guess.feature @@ -0,0 +1,56 @@ +Feature: codebreaker submits guess + The codebreaker submits a guess of four numbers. The game marks the guess with + and - signs. + + For each number in the guess that matches the number and position of a number in the secret code, the mark includes one + sign. For each number in the guess that matches the number but not the position of a number in the secret code, the mark includes one - sign. + + Each position in the secret code can only be matched once. For example, a guess of 1134 against a secret code of 1234 would get three plus signs: one for each of the exact matches in the first, third and fourth positions. The number match in the second position would be ignored. + + Scenario Outline: submit a guess + Given the secret code is "" + When I guess "" + Then the mark should be "" + + Scenarios: no matches + | code | guess | mark | + | 1234 | 5555 | | + + Scenarios: 1 number correct + | code | guess | mark | + | 1234 | 1555 | + | + | 1234 | 2555 | - | + + Scenarios: 2 numbers correct + | code | guess | mark | + | 1234 | 5254 | ++ | + | 1234 | 5154 | +- | + | 1234 | 2545 | -- | + + Scenarios: 3 numbers correct + | code | guess | mark | + | 1234 | 5234 | +++ | + | 1234 | 5134 | ++- | + | 1234 | 5124 | +-- | + | 1234 | 5123 | --- | + + Scenarios: all numbers correct + | code | guess | mark | + | 1234 | 1234 | ++++ | + | 1234 | 1243 | ++-- | + | 1234 | 1423 | +--- | + | 1234 | 4321 | ---- | + + Scenarios: matches with duplicates + | code | guess | mark | + | 1234 | 1155 | + | + | 1234 | 5115 | - | + | 1134 | 1155 | ++ | + | 1134 | 5115 | +- | + | 1134 | 5511 | -- | + | 1134 | 1115 | ++ | + | 1134 | 5111 | +- | + | 1155 | 1234 | + | + | 1111 | 1112 | +++ | + | 1113 | 1121 | ++- | + | 3111 | 1311 | ++-- | + | 3114 | 1251 | -- | + | 1511 | 2134 | - | diff --git a/code_breaker/sumi/features/step_definitions/codebreaker_steps.rb b/code_breaker/sumi/features/step_definitions/codebreaker_steps.rb new file mode 100644 index 0000000..dd97dd7 --- /dev/null +++ b/code_breaker/sumi/features/step_definitions/codebreaker_steps.rb @@ -0,0 +1,38 @@ +Given /^I am not yet playing$/ do +end + +When /^I start a new game$/ do + game = Codebreaker::Game.new(output) + game.start('1234') +end + +Then /^I should see "([^\"]*)"$/ do |message| + output.messages.should include(message) +end + +Given /^the secret code is "([^\"]*)"$/ do |secret| + @game = Codebreaker::Game.new(output) + @game.start(secret) +end + +When /^I guess "([^\"]*)"$/ do |guess| + @guess = guess +end + +Then /^the mark should be "([^\"]*)"$/ do |mark| + @game.guess(@guess).should == mark +end + +class Output + def messages + @messages ||= [] + end + + def puts(message) + messages << message + end +end + +def output + @output ||= Output.new +end diff --git a/code_breaker/sumi/features/support/env.rb b/code_breaker/sumi/features/support/env.rb new file mode 100644 index 0000000..dbdae17 --- /dev/null +++ b/code_breaker/sumi/features/support/env.rb @@ -0,0 +1,2 @@ +$LOAD_PATH << File.expand_path('../..', File.dirname(__FILE__)) +require 'codebreaker' diff --git a/code_breaker/sumi/spec/codebreaker_spec.rb b/code_breaker/sumi/spec/codebreaker_spec.rb new file mode 100644 index 0000000..1e86b89 --- /dev/null +++ b/code_breaker/sumi/spec/codebreaker_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' +require 'codebreaker' + +module Codebreaker + describe Game do + let(:output) { double('output').as_null_object } + let(:game) { Game.new(output) } + + describe "#start" do + it "sends a welcome message" do + output.should_receive(:puts).with('Welcome to Codebreaker!') + game.start('1234') + end + + it "prompts for the first guess" do + output.should_receive(:puts).with('Enter guess: ') + game.start('1234') + end + end + + describe "#guess" do + context "with no matches" do + it "sends a mark with ''" do + game.start('1234') + game.guess('5555').should == '' + end + end + + context "with 1 number match" do + it "sends a mark with '-'" do + game.start('1234') + game.guess('2555').should == '-' + end + end + + context "with 1 exact match" do + it "sends a mark with '+'" do + game.start('1234') + game.guess('1555').should == '+' + end + end + + context "with 2 number matches" do + it "sends a mark with '--'" do + game.start('1234') + game.guess('2355').should == '--' + end + end + + context "with 1 number match and 1 exact match (in that order)" do + it "sends a mark with '+-'" do + game.start('1234') + game.guess('2535').should == '+-' + end + end + + context "with 1 exact match duplicated in guess" do + it "sends a mark with '+'" do + game.start('1234') + game.guess('1155').should == '+' + end + end + + context "with 1 exact match duplicated in secret" do + it "sends a mark with '+'" do + game.start('1155') + game.guess('1234').should == '+' + end + end + + context "with 1 number match duplicated in secret" do + it "sends a mark with '-'" do + game.start('1511') + game.guess('2134').should == '-' + end + end + end + end +end diff --git a/code_breaker/sumi/spec/spec_helper.rb b/code_breaker/sumi/spec/spec_helper.rb new file mode 100644 index 0000000..c6f2930 --- /dev/null +++ b/code_breaker/sumi/spec/spec_helper.rb @@ -0,0 +1,13 @@ +# by default, rspec only loads lib/ into the path, not the root project directory +$LOAD_PATH << File.expand_path('..', File.dirname(__FILE__)) + +RSpec.configure do |config| + # Use color in STDOUT + config.color_enabled = true + + # Use color not only in STDOUT but also in pagers and files + # config.tty = true + + # Use the specified formatter + config.formatter = :documentation # :progress, :html, :textmate +end \ No newline at end of file From b8618a03327575182a0c52c52dbcc8513b67d876 Mon Sep 17 00:00:00 2001 From: kakusuke Date: Thu, 7 Nov 2013 20:37:34 +0900 Subject: [PATCH 2/8] add src file --- code_breaker/sumi/codebreaker.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 code_breaker/sumi/codebreaker.rb diff --git a/code_breaker/sumi/codebreaker.rb b/code_breaker/sumi/codebreaker.rb new file mode 100644 index 0000000..e69de29 From 1b5bf1adf0ae791e122b538054dd63b3e09d8e3d Mon Sep 17 00:00:00 2001 From: kakusuke Date: Thu, 7 Nov 2013 20:46:35 +0900 Subject: [PATCH 3/8] pass start game senario --- code_breaker/sumi/codebreaker.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/code_breaker/sumi/codebreaker.rb b/code_breaker/sumi/codebreaker.rb index e69de29..1b3bc92 100644 --- a/code_breaker/sumi/codebreaker.rb +++ b/code_breaker/sumi/codebreaker.rb @@ -0,0 +1,11 @@ +module Codebreaker + class Game + def initialize(output) + output.puts "Welcome to Codebreaker!" + output.puts "Enter guess: " + end + + def start(number) + end + end +end From 63e0ff80562f3f87da6493a9107c4aa34d016414 Mon Sep 17 00:00:00 2001 From: kakusuke Date: Thu, 7 Nov 2013 20:50:00 +0900 Subject: [PATCH 4/8] all test passed! --- code_breaker/sumi/codebreaker.rb | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/code_breaker/sumi/codebreaker.rb b/code_breaker/sumi/codebreaker.rb index 1b3bc92..232a8a9 100644 --- a/code_breaker/sumi/codebreaker.rb +++ b/code_breaker/sumi/codebreaker.rb @@ -1,11 +1,36 @@ module Codebreaker class Game def initialize(output) - output.puts "Welcome to Codebreaker!" - output.puts "Enter guess: " + @output = output end - def start(number) + def start(secret) + @secret = secret.each_char.to_a + @output.puts "Welcome to Codebreaker!" + @output.puts "Enter guess: " + end + + def guess(numbers) + @numbers = numbers.each_char.to_a + + '+' * hit_count + '-' * match_count + end + + private + def total_count + (0..9).map(&:to_s).reduce(0) do |count, n| + count + [@secret.count(n), @numbers.count(n)].min + end + end + + def hit_count + @secret.each_index.reduce(0) do |count, idx| + count + (@secret[idx] == @numbers[idx] ? 1 : 0) + end + end + + def match_count + total_count - hit_count end end end From d62bfa2d6b32fde4b9e0efa8a27983340013a91d Mon Sep 17 00:00:00 2001 From: kakusuke Date: Fri, 8 Nov 2013 22:12:26 +0900 Subject: [PATCH 5/8] http -> https --- code_breaker/sumi/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code_breaker/sumi/Gemfile b/code_breaker/sumi/Gemfile index b21033b..a150120 100644 --- a/code_breaker/sumi/Gemfile +++ b/code_breaker/sumi/Gemfile @@ -1,4 +1,4 @@ -source "http://rubygems.org" +source "https://rubygems.org" gem "rspec" gem "cucumber" From d365eea9faba0faee6983f527ebb3dfebc497786 Mon Sep 17 00:00:00 2001 From: kakusuke Date: Fri, 8 Nov 2013 22:22:43 +0900 Subject: [PATCH 6/8] memoize hit_count --- code_breaker/sumi/codebreaker.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code_breaker/sumi/codebreaker.rb b/code_breaker/sumi/codebreaker.rb index 232a8a9..3579a9d 100644 --- a/code_breaker/sumi/codebreaker.rb +++ b/code_breaker/sumi/codebreaker.rb @@ -12,6 +12,7 @@ def start(secret) def guess(numbers) @numbers = numbers.each_char.to_a + @hit_count = nil '+' * hit_count + '-' * match_count end @@ -24,7 +25,7 @@ def total_count end def hit_count - @secret.each_index.reduce(0) do |count, idx| + @hit_count ||= @secret.each_index.reduce(0) do |count, idx| count + (@secret[idx] == @numbers[idx] ? 1 : 0) end end From 47c3bebc000281d4573f6cd2a5fee1c78217f1fb Mon Sep 17 00:00:00 2001 From: kakusuke Date: Fri, 8 Nov 2013 22:42:42 +0900 Subject: [PATCH 7/8] revert memoize --- code_breaker/sumi/codebreaker.rb | 3 +-- .../sumi/features/codebreaker_submits_guess.feature | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/code_breaker/sumi/codebreaker.rb b/code_breaker/sumi/codebreaker.rb index 3579a9d..232a8a9 100644 --- a/code_breaker/sumi/codebreaker.rb +++ b/code_breaker/sumi/codebreaker.rb @@ -12,7 +12,6 @@ def start(secret) def guess(numbers) @numbers = numbers.each_char.to_a - @hit_count = nil '+' * hit_count + '-' * match_count end @@ -25,7 +24,7 @@ def total_count end def hit_count - @hit_count ||= @secret.each_index.reduce(0) do |count, idx| + @secret.each_index.reduce(0) do |count, idx| count + (@secret[idx] == @numbers[idx] ? 1 : 0) end end diff --git a/code_breaker/sumi/features/codebreaker_submits_guess.feature b/code_breaker/sumi/features/codebreaker_submits_guess.feature index 4adbccf..dc10ef0 100644 --- a/code_breaker/sumi/features/codebreaker_submits_guess.feature +++ b/code_breaker/sumi/features/codebreaker_submits_guess.feature @@ -54,3 +54,11 @@ Feature: codebreaker submits guess | 3111 | 1311 | ++-- | | 3114 | 1251 | -- | | 1511 | 2134 | - | + + Scenario: try and error + Given the secret code is "1234" + When I guess "4321" + Then the mark should be "----" + When I guess "1234" + Then the mark should be "++++" + From a85fc9cdf0f82ca13c6e3b39c6dba9e7281017c2 Mon Sep 17 00:00:00 2001 From: kakusuke Date: Sat, 9 Nov 2013 12:51:49 +0900 Subject: [PATCH 8/8] tweak guess method --- code_breaker/sumi/codebreaker.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/code_breaker/sumi/codebreaker.rb b/code_breaker/sumi/codebreaker.rb index 232a8a9..2caef28 100644 --- a/code_breaker/sumi/codebreaker.rb +++ b/code_breaker/sumi/codebreaker.rb @@ -1,3 +1,11 @@ +class String + def fill(char, range) + self.each_char.to_a.tap {|target| + target.fill(char, range) + }.join + end +end + module Codebreaker class Game def initialize(output) @@ -13,7 +21,7 @@ def start(secret) def guess(numbers) @numbers = numbers.each_char.to_a - '+' * hit_count + '-' * match_count + ('+' * total_count).fill('-', hit_count..-1) end private @@ -28,9 +36,5 @@ def hit_count count + (@secret[idx] == @numbers[idx] ? 1 : 0) end end - - def match_count - total_count - hit_count - end end end