-
-
Save StandardGiraffe/b946bc787def30807fe2f68de846d0c3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CondimentJar | |
class ContainerClosedError < StandardError; end | |
class ContainerEmptyError < StandardError; end | |
attr_accessor :contents | |
def initialize(contents = nil) | |
@contents = contents # Jar is empty by default. | |
@state_of_the_lid = :closed # I wasn't raised in a barn. | |
end | |
def empty? | |
contents.nil? | |
end | |
# All jars contain one portion for simplicity. | |
def has_stuff? | |
!empty? | |
end | |
def closed? | |
@state_of_the_lid == :closed | |
end | |
def close! | |
@state_of_the_lid = :closed | |
end | |
def open? | |
@state_of_the_lid == :open | |
end | |
def open! | |
@state_of_the_lid = :open | |
end | |
def relinquish_condiment! | |
if closed? | |
raise ContainerClosedError, "The jar is closed and knife-impermeable." | |
end | |
if empty? | |
raise ContainerEmptyError, "The jar is empty. How disappointing." | |
end | |
# Provide the contents and empty the container | |
return contents.tap { |t| self.contents = nil } | |
end | |
end | |
class Knife | |
class DirtyKnifeError < StandardError; end | |
class EmptyKnifeError < StandardError; end | |
class InvalidKnifeError < StandardError; end | |
class InvalidTargetError < StandardError; end | |
attr_accessor :contents | |
def initialize | |
@contents = nil | |
end | |
def clean? | |
contents.nil? | |
end | |
def clean! | |
contents = nil | |
end | |
def loaded? | |
!clean? | |
end | |
def load_from!(container) | |
if loaded? | |
raise DirtyKnifeError, "This knife is already loaded. Don't mix your condiments!" | |
end | |
@contents = container.relinquish_condiment! | |
end | |
def smear!(bread_surface) | |
unless loaded? | |
raise EmptyKnifeError, "This knife is too unbesmirched by condiment to smear anything! Load it first." | |
end | |
unless bread_surface.is_a?(SliceOfBread::Surface) | |
raise InvalidTargetError, "#{bread_surface} isn't the surface of a slice of bread! Put the knife down!" | |
end | |
bread_surface.be_smeared!(self) | |
end | |
end | |
class SliceOfBread | |
class Surface | |
class AlreadySmearedError < StandardError; end | |
attr_accessor :contents | |
def initialize | |
@contents = nil | |
end | |
def plain? | |
contents.nil? | |
end | |
def smeared? | |
!plain? | |
end | |
def be_smeared!(knife) | |
unless knife.is_a?(Knife) | |
raise ::Knife::InvalidKnifeError, "That's not hygienic." | |
end | |
unless plain? | |
raise AlreadySmearedError, "This surface was already smeared!" | |
end | |
unless knife.loaded? | |
raise ::Knife::EmptyKnifeError, "This knife is still too clean to smear with. How did this even happen...?" | |
end | |
self.contents = knife.contents | |
knife.contents = nil | |
end | |
end | |
attr_reader :top | |
attr_reader :bottom | |
def initialize | |
@top = Surface.new | |
@bottom = Surface.new | |
end | |
def plain? | |
top.plain? and bottom.plain? | |
end | |
def smeared? | |
top.smeared? or bottom.smeared? | |
end | |
end | |
class Sandwich | |
class NotEnoughSlicesError < StandardError; end | |
class OutsideSmearedError < StandardError; end | |
class AlreadyBuiltError < StandardError; end | |
class TooPlainError < StandardError; end | |
class ImmatureSandwichError < StandardError; end | |
class AlreadyCutError < StandardError; end | |
# These errors already exist in the Knife and SliceOfBread namespaces respectively; I feel okay about adding them again here because I might want to add specific code to these versions in the future. | |
class InvalidKnifeError < StandardError; end | |
class DirtyKnifeError < StandardError; end | |
attr_reader :slices | |
def initialize(*slices_of_bread) | |
@slices = slices_of_bread || [ ] | |
@built = false | |
@cut = false | |
end | |
def flavours | |
slices.flat_map do |slice| | |
[ slice.bottom.contents, slice.top.contents ] | |
end.uniq.compact | |
end | |
def flavours_human_readable | |
f = flavours.map(&:downcase) | |
if f.count == 2 | |
return f.join(' and ') | |
end | |
f[-1] = 'and ' + f[-1] | |
f.join(', ') | |
end | |
def <<(slice) | |
@slices << slice | |
end | |
def ready_to_eat? | |
@built and @cut | |
end | |
def build! | |
if @built | |
raise AlreadyBuiltError, "It's already a glorious tower of food!" | |
end | |
if slices.count < 2 | |
raise NotEnoughSlicesError, "#{slices.length} slices of bread does not a sandwich make." | |
end | |
unless slices.first.bottom.plain? and slices.last.top.plain? | |
raise OutsideSmearedError, "This sandwich would be icky to hold." | |
end | |
# Check all but the top slice for plainness | |
if slices[..-2].map(&:plain?).any?(true) | |
raise TooPlainError, "This sandwich might actually be a loaf." | |
end | |
@built = true | |
end | |
def cut!(knife) | |
unless @built | |
raise ImmatureSandwichError, "Build the sandwich and then cut it in one glorious stroke." | |
end | |
unless knife.is_a?(Knife) | |
raise InvalidKnifeError, "That's not hygienic." | |
end | |
unless knife.clean? | |
raise DirtyKnifeError, "No! You'll get the edge all yucky with that knife." | |
end | |
if @cut | |
raise AlreadyCutError, "One cut will do." | |
end | |
@cut = true | |
end | |
end | |
class Chef | |
class TakenForGrantedError < StandardError; end | |
class UnspeakablyBlandError < StandardError; end | |
def self.number_of_slices_for(number_of_requested_flavours) | |
1 + (number_of_requested_flavours / 2.0).ceil | |
end | |
def initialize(condiments: [], knife: nil, slices_of_bread: []) | |
@condiments = condiments | |
@knife = knife | |
@slices_of_bread = slices_of_bread | |
@under_appreciated = true | |
end | |
def please # Courtesy is important | |
@under_appreciated = false | |
self | |
end | |
def make_me_a_sandwich!(*requested_flavours) | |
# === Gate Conditions ==================================================== | |
if @under_appreciated # Courtesy is important! | |
raise TakenForGrantedError, "Poof: you're a sandwich." | |
end | |
@under_appreciated = true # You get ONE request per courtesy. | |
# No vacuum- or integer-sandwiches please; they give me a headache. | |
requested_flavours.keep_if { |flavour| flavour.is_a? String } | |
if requested_flavours.count < 1 | |
raise UnspeakablyBlandError, "I think you might just want some dry toast." | |
end | |
condiments = opened_condiment_jars_for(requested_flavours) | |
sandwich_bread = plain_slices_for(requested_flavours.count) | |
knife = readied_knife | |
# Explode the bread into surfaces: | |
sandwich_bread_surfaces = sandwich_bread.map do |slice| | |
[ slice.bottom, slice.top ] | |
end.flatten | |
# Skipping the bottom surface, apply condiments until we're out of condiments! | |
sandwich_bread_surfaces[1..].zip(condiments).each do | surface, condiment | | |
break if !condiment | |
knife.load_from!(condiment) | |
knife.smear!(surface) | |
end | |
# The magic happens | |
sandwich = Sandwich.new(*sandwich_bread) | |
sandwich.build! | |
sandwich.cut!(knife) | |
if sandwich.ready_to_eat? # How could it not be? Still. | |
puts "Finit. One #{sandwich.flavours_human_readable} sandwich; bon appétit!" | |
return sandwich # Needlessly explicit "return"; assume to be flourish. | |
else | |
puts "Hmm. Something went wrong despite my genius." | |
end | |
end | |
private | |
def readied_knife | |
ensure_knife | |
@knife.clean! | |
@knife | |
end | |
def opened_condiment_jars_for(requested_flavours) | |
# Insurance: | |
ensure_condiments_are_actually_condiments | |
ensure_condiments_are_available(requested_flavours) | |
# Array Building: | |
requested_flavours.map do |flavour| | |
condiment = @condiments.find { |jar| jar.contents == flavour} | |
condiment.open! | |
condiment | |
end | |
end | |
def plain_slices_for(number_of_requested_flavours) | |
ensure_bread_is_actually_bread | |
ensure_enough_bread_is_available(number_of_requested_flavours) | |
# #slice! will remove the specified range of elements from the host array and return them as a new array. The "- 1" accounts for Ruby arrays being zero-indexed. | |
@slices_of_bread.slice!( | |
0..(Chef.number_of_slices_for(number_of_requested_flavours) - 1) | |
) | |
end | |
# Throw out any empty bottles and/or cans of paint. | |
def ensure_condiments_are_actually_condiments | |
@condiments.keep_if do |condiment| | |
condiment.is_a?(CondimentJar) and condiment.has_stuff? | |
end | |
end | |
# Gather any missing condiments... | |
def ensure_condiments_are_available(requested_flavours) | |
missing_condiments = [ ] | |
# Let's avoid multiple trips to the store by taking stock before we go. | |
requested_flavours.each do |flavour| | |
unless @condiments.find { |jar| jar.contents == flavour} | |
missing_condiments << flavour | |
end | |
end | |
if missing_condiments.any? | |
puts "Looks like we could use a few things from the store..." | |
missing_condiments.each do |condiment| | |
@condiments << CondimentJar.new(condiment) | |
puts "Bought me some #{condiment}" | |
end | |
puts "There we go. That's better." | |
end | |
end | |
# Safety first. | |
def ensure_knife | |
return if @knife.is_a?(Knife) | |
puts "I'll probably want a knife for this..." | |
@knife = Knife.new | |
puts "... Full tang mokume gane butter knife. Perfect." | |
end | |
# Keep our existing bread collection sane and unsullied. | |
def ensure_bread_is_actually_bread | |
@slices_of_bread.keep_if do |slice| | |
slice.is_a?(SliceOfBread) and slice.plain? | |
end | |
end | |
# Make sure we won't run out in the middle of the operation. | |
def ensure_enough_bread_is_available(number_of_requested_flavours) | |
sufficient_slices = Chef.number_of_slices_for(number_of_requested_flavours) | |
return if @slices_of_bread.count >= sufficient_slices | |
puts "This isn't enough bread to make your sandwich. Let me just grab some more." | |
while @slices_of_bread.count < sufficient_slices | |
@slices_of_bread << SliceOfBread.new | |
puts "Yoink!" | |
end | |
puts "... There we go. Pile o' bread!" | |
end | |
end | |
daddy = Chef.new | |
lunch = daddy.please.make_me_a_sandwich!("Relish", "Marmite", "Nutella™", "Sriracha") | |
pp lunch |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment