Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions code_breaker/sumi/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source "https://rubygems.org"

gem "rspec"
gem "cucumber"
30 changes: 30 additions & 0 deletions code_breaker/sumi/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions code_breaker/sumi/codebreaker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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

'+' * hit_count + '-' * match_count
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hit_count の処理が2回実行されることになりますね。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

メモイズすれば良さげです

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

メモイズって初めて聞きました。
そういう実装があるんですねー。勉強になります

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

メモイズするとgessメソッド中で初期化処理書かないといけない気がするので、
あまり処理速度気にする必要がなければあえてメモイズしないのもアリじゃないでしょうか?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

現状 readable ですしね。
僕だと、↓ みたいな感じで書いちゃいますけど、readable じゃなくなるんですよね。。。

'+' * (h = hit_count) + '-' * (total_count - h)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、文字列の上書きができればそんな感じのやり方でもうちょっとうまく書ける気がしてきました

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こんな感じに書けばメソッド呼び出し1回ずつでいけそうです

('+' * total_count).each_char.to_a.tap {|marks|
    marks.fill('-', hit_count..-1)
}.join

うーん、でもやっぱり読みにくいですね。。。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

('+' * total_count).each_char.to_a

は、↓ で良さそうです。

total_count.times.map { '+' }

この方法も面白いですね。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、ちょっとすっきりしますね

total_count.times.map { '+' }.tap {|marks|
    marks.fill('-', hit_count..-1)
}.join

文字列を一旦配列にしてうんぬんが無駄な気がしますね
文字列の一部を直接指定した文字で埋められたら良さそうですが。。。

('+' * 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この実装面白いですね。なるほどー、って感じです。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

頭ひねってこのやり方しか出てこなかったです
どう実装するのが自然でしょうかね。。。

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
11 changes: 11 additions & 0 deletions code_breaker/sumi/features/codebreaker_starts_game.feature
Original file line number Diff line number Diff line change
@@ -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: "

64 changes: 64 additions & 0 deletions code_breaker/sumi/features/codebreaker_submits_guess.feature
Original file line number Diff line number Diff line change
@@ -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 "<code>"
When I guess "<guess>"
Then the mark should be "<mark>"

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 "++++"

38 changes: 38 additions & 0 deletions code_breaker/sumi/features/step_definitions/codebreaker_steps.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions code_breaker/sumi/features/support/env.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
$LOAD_PATH << File.expand_path('../..', File.dirname(__FILE__))
require 'codebreaker'
79 changes: 79 additions & 0 deletions code_breaker/sumi/spec/codebreaker_spec.rb
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions code_breaker/sumi/spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -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