Skip to content

Instantly share code, notes, and snippets.

@jimweirich
Last active January 10, 2019 05:12
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jimweirich/7432160 to your computer and use it in GitHub Desktop.
Save jimweirich/7432160 to your computer and use it in GitHub Desktop.
Bottles of Beer, for Sandi Metz
module Beer
# Beer::Number represents a number of bottles on the wall. It uses
# MODULUS 100 arithmetic and has special knowledge of verses,
# plurals and other song related phrases.
class Number
def initialize(n)
@value = n
end
def pred
Number.new(@value-1)
end
def to_s
@value.to_s
end
def verse
result = ""
result << on_the_wall.capitalize << ", "
result << of_beer << ".\n"
result << action << ", "
result << pred.on_the_wall << ".\n"
result
end
protected
def on_the_wall
"#{of_beer} on the wall"
end
private
def of_beer
"#{self} #{bottles} of beer"
end
def bottles
"bottles"
end
def action
"Take #{pronoun} down and pass it around"
end
def pronoun
"one"
end
class SpecialNumber < Number
def self.new
super(:ignore)
end
end
class One < SpecialNumber
def initialize(_)
super(1)
end
def bottles
"bottle"
end
def pronoun
"it"
end
end
class Zero < SpecialNumber
def initialize(_)
super(0)
end
def pred
Number.new(99)
end
def to_s
"no more"
end
def action
"Go to the store and buy some more"
end
end
CACHE = {
0 => Zero.new,
1 => One.new,
}
def self.new(number)
CACHE[number] ||= super
end
end
# Beer::Song is the 99 bottles of beer song.
class Song
def verse(n)
Number.new(n).verse
end
def verses(start=99, finish=0)
start.downto(finish).map { |i| verse(i) + "\n" }.join
end
end
end
if $0 == __FILE__
puts Beer::Song.new.verses
end
require 'rspec/given'
require 'beer_song'
module Beer
V99 =
"99 bottles of beer on the wall, 99 bottles of beer.\n" +
"Take one down and pass it around, 98 bottles of beer on the wall.\n"
V98 =
"98 bottles of beer on the wall, 98 bottles of beer.\n" +
"Take one down and pass it around, 97 bottles of beer on the wall.\n"
V2 =
"2 bottles of beer on the wall, 2 bottles of beer.\n" +
"Take one down and pass it around, 1 bottle of beer on the wall.\n"
V1 =
"1 bottle of beer on the wall, 1 bottle of beer.\n" +
"Take it down and pass it around, no more bottles of beer on the wall.\n"
V0 =
"No more bottles of beer on the wall, no more bottles of beer.\n" +
"Go to the store and buy some more, 99 bottles of beer on the wall.\n"
describe Number do
Given(:bn) { Number.new(n) }
Invariant { bn.pred == Number.new((n+99) % 100) }
context "with 99" do
Given(:n) { 99 }
Then { bn.to_s == '99' }
Then { bn.verse == V99 }
end
context "with 98" do
Given(:n) { 98 }
Then { bn.to_s == '98' }
Then { bn.verse == V98 }
end
context "with 2" do
Given(:n) { 2 }
Then { bn.to_s == '2' }
Then { bn.verse == V2 }
end
context "with 1" do
Given(:n) { 1 }
Then { bn.to_s == '1' }
Then { bn.verse == V1 }
end
context "with 0" do
Given(:n) { 0 }
Then { bn.to_s == 'no more' }
Then { bn.verse == V0 }
end
end
describe Song do
Given(:bottle) { Song.new }
def count(string, pattern)
string.scan(Regexp.new(Regexp.quote(pattern))).size
end
context "one verse" do
When(:result) { bottle.verse(2) }
Then { result == V2 }
end
context "entire song" do
When(:result) { bottle.verses(99) }
Then { count(result, V99 + "\n") == 1 }
Then { count(result, V98 + "\n") == 1 }
Then { count(result, V2 + "\n") == 1 }
Then { count(result, V1 + "\n") == 1 }
Then { count(result, V0 + "\n") == 1 }
end
end
end
require 'minitest/autorun'
require 'minitest/pride'
require './beer_song'
class BeerSongTest < Minitest::Test
def beer_song
@beer_song = Beer::Song.new
end
def teardown
@beer_song = nil
end
def test_a_typical_verse
expected =
"8 bottles of beer on the wall, 8 bottles of beer.\n" +
"Take one down and pass it around, 7 bottles of beer on the wall.\n"
assert_equal expected, beer_song.verse(8)
end
def test_another_typical_verse
expected =
"3 bottles of beer on the wall, 3 bottles of beer.\n" +
"Take one down and pass it around, 2 bottles of beer on the wall.\n"
assert_equal expected, beer_song.verse(3)
end
def test_verse_1
expected =
"1 bottle of beer on the wall, 1 bottle of beer.\n" +
"Take it down and pass it around, no more bottles of beer on the wall.\n"
assert_equal expected, beer_song.verse(1)
end
def test_verse_2
expected =
"2 bottles of beer on the wall, 2 bottles of beer.\n" +
"Take one down and pass it around, 1 bottle of beer on the wall.\n"
assert_equal expected, beer_song.verse(2)
end
def test_verse_0
expected =
"No more bottles of beer on the wall, no more bottles of beer.\n" +
"Go to the store and buy some more, 99 bottles of beer on the wall.\n"
assert_equal expected, beer_song.verse(0)
end
def test_several_verses
expected =
"8 bottles of beer on the wall, 8 bottles of beer.\n" +
"Take one down and pass it around, 7 bottles of beer on the wall.\n\n" +
"7 bottles of beer on the wall, 7 bottles of beer.\n" +
"Take one down and pass it around, 6 bottles of beer on the wall.\n\n" +
"6 bottles of beer on the wall, 6 bottles of beer.\n" +
"Take one down and pass it around, 5 bottles of beer on the wall.\n\n"
assert_equal expected, beer_song.verses(8, 6)
end
def test_all_the_rest_of_the_verses
expected =
"3 bottles of beer on the wall, 3 bottles of beer.\n" +
"Take one down and pass it around, 2 bottles of beer on the wall.\n" +
"\n" +
"2 bottles of beer on the wall, 2 bottles of beer.\n" +
"Take one down and pass it around, 1 bottle of beer on the wall.\n" +
"\n" +
"1 bottle of beer on the wall, 1 bottle of beer.\n" +
"Take it down and pass it around, no more bottles of beer on the wall.\n" +
"\n" +
"No more bottles of beer on the wall, no more bottles of beer.\n" +
"Go to the store and buy some more, 99 bottles of beer on the wall.\n" +
"\n"
assert_equal expected, beer_song.verses(3)
end
end
#!/usr/bin/env ruby
require 'rake/clean'
require 'rake/testtask'
task :default => [:spec, :test]
task :spec do
sh "rspec ."
end
task :test do
ruby "beer_song_test.rb"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment