diff --git a/code_breaker/sumi/Gemfile b/code_breaker/sumi/Gemfile new file mode 100644 index 0000000..a150120 --- /dev/null +++ b/code_breaker/sumi/Gemfile @@ -0,0 +1,4 @@ +source "https://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/codebreaker.rb b/code_breaker/sumi/codebreaker.rb new file mode 100644 index 0000000..2caef28 --- /dev/null +++ b/code_breaker/sumi/codebreaker.rb @@ -0,0 +1,40 @@ +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) + @output = output + end + + 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 + + ('+' * total_count).fill('-', hit_count..-1) + 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 + end +end 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..dc10ef0 --- /dev/null +++ b/code_breaker/sumi/features/codebreaker_submits_guess.feature @@ -0,0 +1,64 @@ +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 | - | + + 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 "++++" + 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