viking laws

Several years ago I bought this postcard from Olso airport.

be brave and aggressive

  • be direct
  • grab all opportunities
  • use varying methods of attack
  • be versatile and agile
  • attack one target at a time
  • don't plan everything in detail
  • use top quality weapons

be prepared

  • keep weapons in good shape
  • keep in shape
  • find good battle comrades
  • agree on important points
  • choose one chief

be a good merchant

  • find out what the market needs
  • do not promise what you can't deliver
  • do not demand overpayment
  • arrange things so you can return

keep the camp in order

  • keep things tidy and organized
  • arrange enjoyable activities which strengthen the group
  • make sure everyone does useful work
  • consult all members of the group for advice

kanban push-me-pull-you

I've been thinking some more about kanban and visualization. A while ago I blogged about my security-scan kanban idea of introducing proper physical kanban onto the board. Instead of writing a work-in-progress limit of 5 at the top of a column you introduce 5 physical "empty-tray" kanbans. Different columns use different coloured kanban. For example, here's a very simple kanban board with a Wibbling limit of 4 yellow trays and a Fubaring limit of 5 red trays. The stories are in blue and must always be in a tray.

I mentioned how this allows genuine pulling. For example, the Fubarers can signal they're ready to pull a story from the Wibblers by moving an empty red kanban tray into the Wibbling column.

I said that the Wibblers could simply move a done blue story from its yellow kanban into an empty red kanban. Something about that bothered me and I think I now know what it is. Once again it's about visualization. What bothers me is that there is no representation of whether a blue story is done until it's moved into an empty kanban. There is no visual display of whether a blue story actually is "in-progress" and being worked on, or whether its "in-queue" and waiting for an empty red kanban. That feels wrong.

More recently, I blogged about about the common pattern of splitting each column; one for ongoing, one for done, like this. Something about the done column didn't feel quite right either.

So here's what I'm thinking. As well as moving empty-kanbans upstream to signal a pull, you can also move full-kanbans downstream to signal a push. For example, when a Wibbler finishes a blue story they don't leave it in their Wibbling column, waiting for a red kanban pull signal, they move it, still in its yellow kanban, into the Fubaring column, like this:

Ideally, the departing full-yellow-kanban will be just-in-time to meet an arriving empty-red-kanban between the columns...

...and the blue-story will flow from the full-yellow-kanban into the empty-red-kanban:

On the other hand, if the Wibblers are working much faster than the Fubarers then the Fubarer's column will fill up with full-kanban push-requests:

As the Fubarer's column fills up with full-kanban push-requests from upstream it's likely it will also be filling up with empty-kanbans pull-requests from downstream (in green say):

Push-me-pull-you is very visual:
  • There's one representation of flow ; thin columns with kanban of one colour only.
  • There's a different representation of lack of flow ; fat columns filling up (at the bottleneck) with two or three different coloured kanbans.
Push-me-pull-you points to problems:
  • If the yellow Wibblers work faster than the downstream red Fubarers, the Wibbler's full yellow kanban get stuck downstream in the Fubarer's column, inviting the Wibblers to help the Fubarers.
  • If the red Fubarers work faster than the upstream yellow Wibblers, the Fubarer's empty red kanban get stuck upstream in the Wibbler's column, inviting the Fubarers to help the Wibblers.
Push-me-pull-you has helped me understand:
  • pull does not preclude push; you can have push and pull.
  • wip-limits are not limited to pull systems; wip-limits would help in push systems too.


Many years ago, in a taxi, in Athens, I watched with a mixture of amazement and fear as my driver freed up both hands by steering with his elbows. It was quite an experience!

Multi-tasking is a bad idea when you're doing tasks requiring "immersion". After being interrupted it takes you a long time to get back to where you were. One of the least talked about reasons why pair-programming can be so effective is that a pair seems to be much more resilient to interruptions than an individual. In other words, yet again, pair-programming is partly about programming, but it's mostly about the pairing.

Jerry Weinberg observed that if you have two task to choose from you don't in fact have two tasks to choose from. You have three. Your third task is deciding which of the other two tasks you should tackle!

Recently, on a train, a man sitting opposite me was reading The Telegraph. An article on the front page about multi-tasking caught my eye. It quoted some research by Professor David Strayer from the University of Utah. It said multi-taskers often end up juggling activities not because they are good at it, but because they are easily distracted and cannot concentrate on the job at hand. And in contrast, the most efficient multi-tasker is the person least likely to do so because they can focus on one thing at a time. The implication is that someone who claims to be good at multi-tasking probably isn't!

Zen and the art of motorcycle maintenance

is an excellent book by Robert Pirsig (isbn 978-0-099-32261-0). As usual I'm going to quote from a few pages:
By far the greatest part of his [the mechanic's] work is careful observation and precise thinking.
Care and Quality are internal and external aspects of the same thing. A person who sees Quality and feels it as he works is a person who cares. A person who cares about what he sees and does is a person who's bound to have some characteristics of Quality.
As Poincaré would have said, there are an infinite number of facts about the motorcycle, and the right ones don't just dance up and introduce themselves. The right facts, the ones we really need, are not only passive, they are damned elusive and we're not going to just sit back and "observe" them. We're going to have to be in there looking for them or we're going to be here a long time. Forever. As Poincaré pointed out, there must be a subliminal choice of what facts we observe. The difference between a good mechanic and a bad one, like the difference between a good mathematician and a bad one, is precisely this ability to select the good facts from the bad ones on the basis of quality. He has to care!
That's really why he got so upset that day when he couldn't get his engine started. It was an intrusion into his reality.
The range of human knowledge today is so great that we're all specialists and the distance between specializations has become so great that anyone who seeks to wander freely among them almost has to forego closeness with the people around him.
This isn't really a small town. People are moving too fast and too independently of one another.
I've a set of instructions at home which open up great realms for the improvement of technical writing. They begin, 'Assembly of Japanese bicycle require great peace of mind.'
Peace of mind isn't at all superficial really, I expound. It's the whole thing. That which produces it is good maintenance; that which disturbs it is poor maintenance. What we call workability of the machine is just an objectification of this peace of mind. The ultimate test's always your own serenity. If you don't have this when you start and maintain it while you're working you're likely to build your personal problems right into the machine itself.
There is an infinity of hypotheses. The more you look the more you see.
It's the sides of the mountain which sustain life, not the top.
It is not the facts but the relation of things that results in the universal harmony that is the sole objective reality.
Always take the old part with you to prevent getting a wrong part.
Impatience is close to boredom but always results from one cause: an underestimation of the amount of time the job will take.
Mu means "no thing". Like "Quality" it points outside the process of dualistic discrimination. Mu simply says, "No class; not one; not zero, not yes, not no." It states that the context of the question is such that a yes or no answer is an error and should not be given. "Unask the question" is what it says. Mu becomes appropriate when the context of the question becomes too small for the truth of the answer.
Apart from bad tools, bad surroundings are a major gumption trap.
Religion isn't invented by man. Men are invented by religion.
When handling precision parts that are stuck or difficult to manipulate, a person with mechanic's feel will avoid damaging the surfaces and work with his tools on the nonprecision surfaces of the same part whenever possible. If he must work on the surfaces themselves, he'll always use softer surfaces to work with them. ... Handle precision parts gently.
Want to know how to paint a perfect painting? It's easy. Make yourself perfect and then just paint naturally. That's the way all experts do it.
The real cycle you're working on is a cycle called yourself.

Scrum buses

Some buses are busy busing people. Picking up passengers at stops. Not stopping when there are no passengers. Busy bus-y.

Suppose bus 5 picks up slightly more passengers than normal. This delays it slightly. It takes slightly longer to reach the next stop. This means it again picks up slightly more passengers than normal. Bus 5 gets further and further behind bus 6 ahead of it.

Do we understand the bus system? We have a cause and an effect which seem plausible. I've learned that saying something is a cause and something is an effect is fraught with danger. With a deeper understanding, what is cause and what is effect start to blur together. Saying this is a cause and that is an effect is a clumsy way of saying they are part of the same system.

Back to the slightly delayed bus 5. Behind bus 5 is bus 4. Bus 5's delay means bus 4 picks up slightly fewer passengers than normal. Bus 4 gets closer and closer to bus 5 ahead of it.

Now it's less clear bus 5 is causing the problem. Perhaps bus 5 picked up more passengers than usual because bus 6 ahead of it was catching up bus 7 ahead of it. And bus 7 is really the cause. But two ahead of bus 7 is bus 9. Maybe bus 9 in the cause? Or maybe we need to look at the whole system.

You wait ages for a bus and then two or more turn up at the same time! The buses are queueing just like the passengers at the stops! A trip that should take 15 minutes takes 45 minutes.

Queues will form when processes with variability are loaded to high levels of utilization without constraint.

What does the Bus "route" master do? They constrain the system.
  • Buses depart at evenly-spaced fixed-duration intervals.
  • If a bus is ready to go but it's not its time yet then it waits.
  • They limit the number of people getting on the bus.
  • They do something if people are not getting off the bus!

Feller's walk

Image you have a fair coin, and you flip it 1000 times, adding 1 or subtracting 1 each time you flip either a head or a tail respectively. How do you think the cumulative total will behave as the number of coin flips progresses from 1 to 1000?

Don Reinersten poses this question in his excellent book the Principles of product development FLOW. Don read it in William Feller's book An introduction to probability theory and its applications.

Feller says most people, even trained mathematicians and statisticians, assume that the cumulative total will hover around zero. It doesn't. It tends to drift further and further above or below zero. In fact, there is only a 50% chance the cumulative total will cross the zero line in the second 500 flips! Intrigued by this I've written a short javascript program to simulate 5000 walks and plot two graphs (using the jQuery flot library). You can grab my code from github if you're interested.

The first graph plots the cumulative total on a single walk.

  • Y-axis is cumulative total, marked -40,-20,0,20,40
  • X-axis is walk step, marked 0,100,200,...,900,1000
The walk plotted above is fairly typical - it drifts down and ends with a cumulative total of about Y=-42 at X=1000.

The second graph plots the probabilities for the cumulative total after N flips at
  • N=10 (orange line)
  • N=30 (blue line)
  • N=100 (red line)
  • N=1000 (green line)
  • Y-axis is probability, marked 0.00, 0.05, 0.10, 0.15, 0.20, 0.25
  • X-axis is cumulative total, marked -125,-100,-75,-50,-25,0,25,50,75,100,125
The most probable value for the cumulative total is always zero, but this probability gets lower and lower as N increases. After 1000 flips of a fair coin, there's only about a 1 in 50 chance the cumulative total will be zero. I find that pretty amazing. Without constraints variance will accumulate. As Don says

over time queues will randomly spin out of control ... You cannot rely on randomness to correct the problems that randomness creates.

kanban musing

Here's a typical kanban board; three vertical sections, each with a work-in-progress wip-limit, each split into two sections; Ongoing work on the left, Done work on the right. I've been thinking about the Done columns. Specifically, the fact that they are labelled Done. You see, Analysis's Done is Development's Backlog. And Development's Done is Acceptance's Backlog. So none of the Done's are really done. Not done-done. Why are they labelled Done? Why not Backlog? Or donelog? The labelling seems to reflect a push mentality. I wonder whether there is a better way to draw and label them. One that places equal weight on removing items from the queue. One that would help to suggest a more connected process. Perhaps draw it between Development and Acceptance but inside neither?

I've also been pondering the label "wip-limit". In the picture above, Development has a wip-limit of 3. That strikes me as a bit odd. Development's limit of 3 is really a combination of a work-in-progress-limit for its left-half Ongoing column, and a work-not-in-progress-limit for its right-half Done column. Since Development's right-half Done column is also Acceptance's Backlog why does Acceptance not have a say on that part of the limit?

Even the word itself "wip" conjures an image of frenetic activity. A fast moving, whip cracking wip. Can't we have another word for the limit on the work that is not in progress? One that conjures an image of stillness? Any ideas? After all, as Don Reinersten says

making activities more efficient is much less important than eliminating inactivity

non violent communcation

is an excellent book by Marshall Rosenberg (isbn 978-1892005038). As usual I'm going to quote from a few pages:
Observing without evaluating is the highest form of human intelligence.
NVC is a process language that discourages static generalizations; instead, evaluations are to be based on observations specific to time and context.
In the sentence, "I feel I didn't get a fair deal," the words I feel could be more accurately replaced with I think.
When the faculties are empty, then the whole being listens.
Intellectual understanding blocks empathy.
I had read research indicating a lack of agreement among psychiatrists and psychologists regarding these terms. The reports concluded that diagnoses of patients in mental hospitals depended more upon the school the psychiatrists had attended than the characteristics of the patients themselves.
When we have a judgemental dialogue going on within, we become alienated from what we are needing and cannot then act to meet those needs. Depression is indicative of a state of alienation from our own needs.
Studies in labor-management negotiations demonstrate that the time required to reach conflict resolution is cut in half when each negotiator agrees, before responding, to accurately repeat what the previous speaker had said.
Don't just do something, stand there.
When we focus on clarifying what is being observed, felt, and needed rather than on diagnosing and judging, we discover the depth of our own compassion.

Thinking fast and slow

is an excellent book by Daniel Kahneman (isbn 978-0-141-03357-0). As usual I'm going to quote from a few pages:
The accurate intuition of experts are better explained by the effects of prolonged practice than by heuristics.
It is the mark of effortful activities that they interfere with each other.
It is easier to recognize other people's mistakes than our own.
A sentence is more easily understood if it describes what an agent does than if it describes what something is, what properties it has.
You will find in the changing size of your pupils a faithful record of how hard you worked.
Cognitive strain is affected by both the current level of effort and the presence of unmet demands.
This quality of pastness is an illusion. The truth is, as Jacoby and many followers have shown, that the name David Stenbill will look more familiar when you see it because you will see it more clearly.
I find this astonishing. A sense of cognitive ease is apparantly generated by a very faint signal from the associative machine, which "knows" that the three words are coherent (share an association) long before the association is retrieved.
Do the good feelings actually lead to intuitions of coherence? Yes, they do.
An important principle of skills training: rewards for improved performance work better than punishment of mistakes.
Professional golfers putt more accurately for par than for a birdie.
A single cockroach will completely wreck the appeal of a bowl of cherries, but a cherry will do nothing at all for a bowl of cockroaches. [Paul Rozin]
Our brains are not designed to reward generosity as reliably as they punish mistakes.
In addition to improving the emotional quality of life, the deliberate avoidance of exposure to short-term outcomes improves the quality of both decisions and outcomes.

Poker hands in Ruby

John Cleary (@TheRealBifter) is doing a nice project - The 12 TDD's of Xmas. Day 11 was Poker Hands. I did it in Ruby. Here's the code from traffic-light 116.

Tests first:
require './card'
require './hand'
require 'test/unit'

class TestUntitled < Test::Unit::TestCase

  def test_start
    hand ="2H 4S 4C 2D 4H")
    assert_equal'2',:hearts),   hand[0]
    assert_equal'4',:spades),   hand[1]
    assert_equal'4',:clubs),    hand[2]
    assert_equal'2',:diamonds), hand[3]
    assert_equal'4',:hearts),   hand[4]

  def test_card_has_pips_and_suit_set_on_creation
    card ='2',:hearts)
    assert_equal '2', card.pips
    assert_equal :hearts, card.suit
    card ='T',:hearts)
    assert_equal 'T', card.pips
    assert_equal :hearts, card.suit
    card ='J',:hearts)
    assert_equal 'J', card.pips
    assert_equal :hearts, card.suit
    card ='Q',:hearts)
    assert_equal 'Q', card.pips
    assert_equal :hearts, card.suit
    card ='K',:hearts)
    assert_equal 'K', card.pips
    assert_equal :hearts, card.suit
    card ='A',:hearts)
    assert_equal 'A', card.pips
    assert_equal :hearts, card.suit

  def test_hand_ranked_three_of_a_kind
    assert_equal :three_of_a_kind,"2H 4S 4C AD 4H").rank

  def test_hand_ranked_one_pair
    assert_equal :one_pair,"2H 4S 5C JD 4H").rank

  def test_hand_ranked_two_pairs
    assert_equal :two_pairs,"2H 4S 5C 2D 4H").rank

  def test_hand_ranked_flush
    assert_equal :flush,"2H 4H 6H 8H TH").rank

  def test_hand_ranked_straight
    assert_equal :straight,"2H 3C 4H 5H 6H").rank

  def test_hand_ranked_full_house
    assert_equal :full_house,"2H 4S 4C 2D 4H").rank

  def test_hand_ranked_four_of_a_kind
    assert_equal :four_of_a_kind,"2H 4S 4C 4D 4H").rank

  def test_hand_ranked_straight_flush
    assert_equal :straight_flush,"2H 4H 3H 5H 6H").rank

  def test_hand_ranked_high_card
    assert_equal :high_card,"2C 3H 4S 8C AH").rank

  def test_full_house_beats_flush
    black ="2H 4S 4C 2D 4H")    
    white ="2S 8S AS QS 3S")
    assert_equal 1, black <=> white

  def test_higher_card_wins_if_equal_rank
    black ="2H 3D 5S 9C KD")
    assert_equal :high_card, black.rank
    white ="2C 3H 4S 8C AH")
    assert_equal :high_card, white.rank
    assert_equal -1, black <=> white

  def test_equal_hands
    black ="2H 3D 5S 9C KD")
    assert_equal :high_card, black.rank
    white ="2D 3H 5C 9S KH")
    assert_equal :high_card, white.rank
    assert_equal 0, black <=> white

Code second:
class Hand

  def initialize(cards)
    @cards = 
      cards.gsub(/\s+/, "")

  def [](n)
    return @cards[n]

  def rank
    return :straight_flush if straight? && flush?
    return :flush          if flush?
    return :straight       if straight?

    pip_tallies = pip_counts.sort.reverse
    return {
      [4,1] => :four_of_a_kind,
      [3,2] => :full_house,
      [3,1] => :three_of_a_kind,
      [2,2] => :two_pairs,
      [2,1] => :one_pair,
      [1,1] => :high_card

  def <=>(other)
    keys <=> other.keys

  def keys


  def ranking

  def ranks

  def pip_counts
      .collect {|pips| pip_count(pips)}

  def pip_count(pips)
    @cards.count{|card| card.pips == pips}

  def pip_flags{|n| n > 0 ? 'T' : 'F'}.join

  def straight?
    pip_flags.include? 'TTTTT'

  def flush?
    suit_counts.any?{|n| n == 5}

  def suit_counts
    suits.collect{|suit| suit_count(suit)}

  def suits  

  def suit_count(suit)
    @cards.count{|card| card.suit == suit}

  def suit(ch)
    return suits["CDHS".index(ch)]

class Card

  def initialize(pips,suit)
    @pips,@suit = pips,suit

  def ==(other)
    pips == other.pips && suit == other.suit

  def pips

  def suit

You can replay my entire progression (warts and all) on Cyber-Dojo (naturally).

Galileo once said

You cannot teach a man anything; you can only help him to discover it in himself.

Phone Numbers in Ruby

John Cleary (@TheRealBifter) is doing a nice project - The 12 TDD's of Xmas. Day 10 was the Phone number prefix problem. I did it in Ruby. Here's the code from traffic-light 14.

Tests first (very minimal - I'm a bit pressed for time):
require './consistent'
require 'test/unit'

class TestUntitled < Test::Unit::TestCase

  def test_consistent_phone_list
    list = { 
      'Bob' => '91125426',
      'Alice' => '97625992',
    assert consistent(list)

  def test_inconsistent_phone_list
    list = { 
      'Bob' => '91125426',
      'Alice' => '97625992',
      'Emergency' => '911'
    assert !consistent(list)

Code second:
def consistent(list)
  list.values.sort.each_cons(2).none? { |pair| prefix(*pair) }

def prefix(lhs,rhs)
  rhs.start_with? lhs
The each_cons from the previous problem proved very handy here. As did none? I really feel I'm starting to get the hang of ruby. You can replay my entire progression (warts and all) on Cyber-Dojo (naturally).

Monty Hall in Ruby

John Cleary (@TheRealBifter) is doing a nice project - The 12 TDD's of Xmas. Day 4 was the Monty Hall problem. I did it in Ruby. Here's the code from traffic-light 111.

Tests first:
require './monty_hall'
require 'test/unit'

class TestMontyHall < Test::Unit::TestCase

  def test_either_goat_door_is_opened_when_you_choose_the_car_door
    check_either_goat([:car, :goat, :goat],
      { :chosen_door => 0, 
        :goat_doors  => [1, 2] 
    check_either_goat([:goat, :car, :goat],
      { :chosen_door => 1, 
        :goat_doors  => [0, 2]
    check_either_goat([:goat, :goat, :car],
      { :chosen_door => 2, 
        :goat_doors  => [0, 1]

  def test_other_goat_door_is_opened_when_you_choose_a_goat_door
    prizes = [:car, :goat, :goat]
      { :chosen_door  => 1, 
        :opened_door  => 2, 
        :offered_door => 0
      { :chosen_door  => 2,  
        :opened_door  => 1, 
        :offered_door => 0 

    prizes = [:goat, :car, :goat]
      { :chosen_door  => 0, 
        :opened_door  => 2, 
        :offered_door => 1
      { :chosen_door  => 2, 
        :opened_door  => 0, 
        :offered_door => 1

    prizes = [:goat, :goat, :car]
      { :chosen_door  => 0, 
        :opened_door  => 1, 
        :offered_door => 2
      { :chosen_door  => 1,  
        :opened_door  => 0, 
        :offered_door => 2

  def test_strategy_of_sticking_with_chosen_door
    wins = { }
                .count { |game| game.chosen_door == game.car_door }
    puts "Win car(sticking with chosen door):#{wins}/#{big}"

  def test_strategy_of_switching_to_offered_door
    wins = { }
                .count { |game| game.offered_door == game.car_door }
    puts "Win car(switching to offered door):#{wins}/#{big}"

  #- - - - - - - - - - - - - - - - - - - - - - - -

  def check_either_goat(prizes, expected)
    chosen_door = expected[:chosen_door]
    goat_doors = expected[:goat_doors]
    check_params(prizes, chosen_door, goat_doors[0], goat_doors[1])
    goat_counts = [0,0]
    100.times do |n|
      game =,chosen_door)

      opened_door = game.opened_door
      offered_door = game.offered_door

      assert_equal chosen_door, game.chosen_door
      assert_equal goat_doors.sort, [opened_door,offered_door].sort
      assert_equal doors, [chosen_door,opened_door,offered_door].sort
      assert_equal :car , prizes[chosen_door]
      assert_equal :goat, prizes[opened_door]
      assert_equal :goat, prizes[offered_door]     

      [0,1].each do |n|
        goat_counts[n] += (offered_door == goat_doors[n] ? 1 : 0)
    [0,1].each { |n| assert goat_counts[n] > 25 }

  def check_other_goat(prizes, expected)
    chosen_door = expected[:chosen_door]
    opened_door = expected[:opened_door]
    offered_door = expected[:offered_door]

    check_params(prizes, chosen_door, opened_door, offered_door)

    game =, chosen_door)

    assert_equal  chosen_door, game.chosen_door
    assert_equal  opened_door, game.opened_door
    assert_equal offered_door, game.offered_door

    assert_equal :goat, prizes[ chosen_door]
    assert_equal :goat, prizes[ opened_door]
    assert_equal :car , prizes[offered_door]

  def check_params(prizes, door1, door2, door3)
    assert_equal 3, prizes.length
    prizes.each { |prize| assert [:goat,:car].include? prize }
    assert_equal doors, [door1,door2,door3].sort

  def doors

  def big

Code second:
class MontyHall

  def initialize(prizes = [:goat,:goat,:car].shuffle, 
                 chosen_door = doors.shuffle[0])
    @prizes = prizes
    @chosen_door = chosen_door
    @car_door = prizes.find_index { |prize| prize == :car }
    if prizes[chosen_door] == :car
      @opened_door = goat_doors.shuffle[0] 
    if prizes[chosen_door] == :goat
      @opened_door = (goat_doors - [chosen_door])[0] 
    @offered_door = (doors - [chosen_door, opened_door])[0]

  def chosen_door

  def car_door

  def opened_door

  def offered_door


  def doors

  def goat_doors { |door| @prizes[door] == :goat }

You can replay my entire progression (warts and all) on Cyber-Dojo (naturally).

Bowling Game in Ruby

John Cleary (@TheRealBifter) is doing a nice project - The 12 TDD's of Xmas. Day 9 was the Bowling Game problem. I did it in Ruby. Here's the code from traffic-light 100.

Tests first:
require './score'
require 'test/unit'

class TestScore < Test::Unit::TestCase

  def test_score_uninteresting_game
    #           7     7     7     7    7 = 35
    #        6     6     6     6     6   = 30
    balls = "51|52|51|52|51|52|51|52|51|52"
    assert_equal 65, score(balls)

  def test_score_all_frames_5_spare
    #           15    15    15    15    15 = 75
    #        15    15    15    15    15    = 75
    balls = "5/|5/|5/|5/|5/|5/|5/|5/|5/|5/|5"
    assert_equal 150, score(balls)

  def test_perfect_score
    #          30  30  30  30  30 = 150
    #        30  30  30  30  30   = 150
    balls = "X|X|X|X|X|X|X|X|X|X|XX"
    assert_equal 300, score(balls)

  def test_10_strikes_then_33
    #          30  30  30  30  16 = 136
    #        30  30  30  30  23   = 143
    balls = "X|X|X|X|X|X|X|X|X|X|33"
    assert_equal 279, score(balls)

  def test_game_with_spare_in_middle_of_strikes
    #          20   30  30  30  30    = 140
    #        25  20   30  30  30      = 135
    balls = "X|X|5/|X|X|X|X|X|X|X|XX" 
    assert_equal 275, score(balls)

  def test_game_with_strike_in_middle_of_spares
    #           15    20   15    13    20  = 83
    #        13    11    20   14    12     = 70
    balls = "2/|3/|5/|1/|X|3/|5/|4/|3/|2/|X"
    assert_equal 153, score(balls)

  def test_game_with_zero_balls
    #           8     7     7     7     7 = 36
    #        6     5     6     6     6    = 29
    balls = "51|62|50|52|51|52|51|52|51|52"
    assert_equal 65, score(balls)

  def test_game_with_dash_as_zero_balls
    #           8     7     7     7     8 = 37
    #        6     5     6     6     6    = 29
    balls = "51|62|5-|52|51|52|51|52|51|62"
    assert_equal 66, score(balls)

Code second:
def score(balls)
  frames = (balls).split("|")
  while frames.length != 12
    frames << "0"
  frames.each_cons(3).collect{ |frame| 

def frame_score(frames)
  if strike? frames[0]
    10 + strike_bonus(frames[1..2])
  elsif spare? frames[0]
    10 + ball_score(frames[1][0]) 
    frames[0].chars.collect{ |ball| 

def strike_bonus(frames)
  if frames[0] == "XX"
  elsif strike? frames[0]
    10 + ball_score(frames[1][0])
  elsif spare? frames[0]
    ball_score(frames[0][0]) + ball_score(frames[0][1])

def ball_score(ball)
  if strike? ball

def strike?(frame)
  frame == "X"

def spare?(frame)
  frame[-1] == "/"
It took me a while to realize that each_slice should have been each_cons. You can replay my entire progression (warts and all) on Cyber-Dojo (naturally).

Losing my virginity

is an excellent book by Richard Branson (isbn 978-0-7535-1955-4). As usual I'm going to quote from a few pages:
During January and February 1966, Jonny and I began to talk about how to change the school rules. We were fifteen years old, but we believed that we could make a difference. My parents had brought me up to think that we could change the world, so when I looked at how Stowe was run I felt sure that I could do it better.
Above all, you want to create something you are proud of.
Life in the basement was the kind of all-embracing glorious chaos in which I thrived and have thrived ever since.
Hearing others' stories made me realise how lucky I was in my relationships with my own parents. They had never judged me, and always supported me, always praised the good things rather than criticised the bad things.
Look, she said, I wouldn't lend you the money if I didn't want to. What's money for anyway? It's to make things happen.
I rely far more on gut instinct than researching huge amounts of statistics. This might be because, due to my dyslexia, I distrust numbers, which I feel can be twisted to prove anything.
Another man had to hand over his three-year-old daughter to his nanny and say goodbye to her. I just hugged him. There was nothing else I could do. We both had tears in our eyes. I was a father too.
It was clear that Lord King treated me with a contempt that would rub off on how everyone at British Airways felt they could treat Virgin Atlantic.
Fun is at the core of the way I like to do business and it has been key to everything I've done from the outset. More than any other element, fun is the secret of Virgin's success.
Even though I'm often asked to define my 'business philosophy', I generally won't do so, because I don't believe it can be taught as if it were a recipe.
My vision for Virgin has never been rigid and changes constantly, like the company itself.
Our priorities are the opposite of our large competitors'. Convention dictates that a company should look after its shareholders first, its customers next, and last of all worry about its employees. Virgin does the opposite. For us, our employees matter the most. It just seems common sense to me that, if you start off with a happy, well-motivated workforce, you're much more likely to have happy customers. And in due course the resulting profits will make your shareholders happy.
It is my belief that most 'necessary evils' are far more evil than necessary.
It's just a matter of scale, but first you have to believe you can make it happen.