# Problem Statement

Write a program to output correct score for a tennis game. Here is the summary of tennis scores:

1. A game is won by the first player to if the player has won at least four points in total and at least two points more than the opponent.
2. The running score of each game is describe as 'Love', 'Fifteen', 'Thirty' and 'Forty' for points from zero to three.
3. If at least three points is scored by both players and the scores are equal, the score is 'Deuce'.
4. If at least three points is scored by both players and a player has one more point than his opponent, the score of the game is 'Advantage' for the player with more points.

Sets and matches are out of scope. We only need to report the score for the current game.

Medium

# Step 1

Create `tennis_game_spec.rb` with the following code:

``````class TennisGame

def player_one_score

end

def player_two_score

end
end

describe TennisGame do
it 'initial scores for both players must be Love' do
game = TennisGame.new

player_one_score = game.player_one_score
player_two_score = game.player_two_score

expect(player_one_score).to eq('Love')
expect(player_two_score).to eq('Love')
end
end
``````

This test fails for the right reason with the error message 'Expected Love got nil'.

## Step 2

Make the test pass by returning hard-coded value as follows:

``````class TennisGame
def player_one_score
'Love'
end

def player_two_score
'Love'
end
end
``````

## Step 3

Add the second spec as follows:

``````it 'returns 15 for player one when player one wins the point' do
game = TennisGame.new
game.wins_point(1)

player_one_score = game.player_one_score
player_two_score = game.player_two_score

expect(player_one_score).to eq('Fifteen')
expect(player_two_score).to eq('Love')
end
``````

This test fails. The line:

``````game.wins_point(1)
``````

does not make any sense.

## Step 4

Let's rewrite our spec as follows:

``````it 'returns 15 for player one when player one wins the point' do
player_one = Player.new('McEnroe')
player_two = Player.new('Borg')

game = TennisGame.new(player_one, player_two)
player_one.wins_point

player_one_score = game.player_one_score
player_two_score = game.player_two_score

expect(player_one_score).to eq('Fifteen')
expect(player_two_score).to eq('Love')
end
``````

This breaks the first test also. Let's comment out the second test and focus on getting the first spec to pass.

## Step 5

Comment out the second spec and change the first spec as follows:

``````it 'initial scores for both players must be Love' do
player_one = Player.new('McEnroe')
player_two = Player.new('Borg')

game = TennisGame.new(player_one, player_two)

player_one_score = game.player_one_score
player_two_score = game.player_two_score

expect(player_one_score).to eq('Love')
expect(player_two_score).to eq('Love')
end

xit 'returns 15 for player one when player one wins the point' do
# same code as before
end
``````

The minimal implementation to make this test pass is as follows:

``````class Player
attr_reader :points

def initialize(name)
@name = name
@points = 0
end

def wins_point
@points += 1
end
end

class TennisGame
def initialize(player_one, player_two)
@player_one = player_one
@player_two = player_two
end

def wins_point(player)
@player_one.wins_point
end

def player_one_score
if @player_one.points == 0
'Love'
end
end

def player_two_score
if @player_two.points == 0
'Love'
end
end
end
``````

## Step 6

Uncomment the second spec and run it. It now fails for the right reason with the error message 'expected Fifteen, got nil'. Change the implementation for TennisGame as follows:

``````class TennisGame
def initialize(player_one, player_two)
@player_one = player_one
@player_two = player_two
end

def wins_point(player)
@player_one.wins_point
end

def player_one_score
@player_one.score
end

def player_two_score
@player_two.score
end
end
``````

The test passes.

## Step 7

Let's tackle the duplication of player score code in TennisGame class.

``````class Player
attr_reader :points

def initialize(name)
@name = name
@points = 0
end

def wins_point
@points += 1
end

def score
if @points == 0
'Love'
elsif @points == 1
'Fifteen'
end
end
end

class TennisGame
def initialize(player_one, player_two)
@player_one = player_one
@player_two = player_two
end

def wins_point(player)
@player_one.wins_point
end

def player_one_score
@player_one.score
end

def player_two_score
@player_two.score
end
end
``````

The TennisGame class delegates the score computation to the player class. The tests still pass.

## Step 8

The name has been removed from the player class because it is not relevant in scoring a game. All tests pass after the refactoring. Add the third test as follows:

``````it 'returns 30 for player one when player one wins two points' do
player_one = Player.new
player_two = Player.new

game = TennisGame.new(player_one, player_two)
player_one.wins_point
player_one.wins_point

player_one_score = game.player_one_score
player_two_score = game.player_two_score

expect(player_one_score).to eq('Thirty')
expect(player_two_score).to eq('Love')
end
``````

It fails for the right reason with the error message 'expected: Thirty, got nil'.

## Step 9

To make the test pass, change the score method as follows:

``````def score
if @points == 0
'Love'
elsif @points == 1
'Fifteen'
elsif @points == 2
'Thirty'
end
end
``````

## Step 10

Add the fourth test as follows:

``````it 'returns 40 for player one when player one wins three points' do
player_one = Player.new
player_two = Player.new

game = TennisGame.new(player_one, player_two)
player_one.wins_point
player_one.wins_point
player_one.wins_point

player_one_score = game.player_one_score
player_two_score = game.player_two_score

expect(player_one_score).to eq('Forty')
expect(player_two_score).to eq('Love')
end
``````

It fails for the right reason with the error message 'expected Forty, got nil'.

## Step 11

You can make the test pass by changing the score method as follows:

``````def score
if @points == 0
'Love'
elsif @points == 1
'Fifteen'
elsif @points == 2
'Thirty'
elsif @points == 3
'Forty'
end
end
``````

## Step 12

Let's add a test to check when both players have won one point, the scores are 15-all.

``````it 'returns 15 for both players when both have won one point' do
player_one = Player.new
player_two = Player.new

game = TennisGame.new(player_one, player_two)
player_one.wins_point
player_two.wins_point

player_one_score = game.player_one_score
player_two_score = game.player_two_score

expect(player_one_score).to eq('Fifteen')
expect(player_two_score).to eq('Fifteen')
end
``````

This test passes.

## Step 13

Let's consider the duece case:

``````it 'returns duece both players have won three points' do
player_one = Player.new
player_two = Player.new

game = TennisGame.new(player_one, player_two)
player_one.wins_point
player_one.wins_point
player_one.wins_point

player_two.wins_point
player_two.wins_point
player_two.wins_point

expect(game.score).to eq('Duece')
end
``````

This fails with no score method for TennisGame error.

## Step 14

Add the score method to the TennisGame class as follows:

``````def score
if @player_one.points == 3 and @player_two.points == 3
'Duece'
end
end
``````

The test passes.

## Step 15

Let's tackle the 'Advantage' case now. This is the fourth rule in the problem statement:

If at least three points is scored by both players and a player has one more point than his opponent, the score of the game is 'Advantage' for the player with more points.

Add the new test for this case with the code as follows:

``````it 'returns Advantage for the player with one more point after 3 points by both' do

player_one = Player.new
player_two = Player.new

game = TennisGame.new(player_one, player_two)
player_one.wins_point
player_one.wins_point
player_one.wins_point

player_two.wins_point
player_two.wins_point
player_two.wins_point

player_one.wins_point

expect(game.score).to eq("Advantage #{player_one.name}")
end
``````

This fails.

## Step 16

Change the test as follows:

``````it 'returns Advantage for the player with one more point after 3 points by both' do

player_one = Player.new('McEnroe')
player_two = Player.new('Borg')

game = TennisGame.new(player_one, player_two)
player_one.wins_point
player_one.wins_point
player_one.wins_point

player_two.wins_point
player_two.wins_point
player_two.wins_point

player_one.wins_point

expect(game.score).to eq("Advantage #{player_one.name}")
end
``````

## Step 17

Change the player class as follows:

``````class Player
attr_reader :points, :name

def initialize(name='')
@name = name
@points = 0
end

def wins_point
@points += 1
end

def score
if @points == 0
'Love'
elsif @points == 1
'Fifteen'
elsif @points == 2
'Thirty'
elsif @points == 3
'Forty'
end
end
end
``````

The test now fails for the right reason.

## Step 18

Change the score method of the TennisGame as follows:

``````def score
if @player_one.points == 3 and @player_two.points == 3
'Duece'
elsif (@player_one.points == @player_two.points + 1) and (@player_two.points >=3)
"Advantage #{@player_one.name}"
end
end
``````

All tests now pass.

## Step 19

Let's tackle the winning game with the following rule:

A game is won by the first player to if the player has won at least four points in total and at least two points more than the opponent.

Add the new test for the winning game as follows:

``````it "Game 'winner name' when a player wins 4 points with no points by the opponent" do

player_one = Player.new('McEnroe')
player_two = Player.new('Borg')

game = TennisGame.new(player_one, player_two)
player_one.wins_point
player_one.wins_point
player_one.wins_point
player_one.wins_point

expect(game.score).to eq("Game #{player_one.name}")
end
``````

This fails for the right reason with the error message 'expected: Game McEnroe got nil'.

## Step 20

Change the score method of the TennisGame class as follows:

``````def score
if @player_one.points == 3 and @player_two.points == 3
'Duece'
elsif (@player_one.points == @player_two.points + 1) and (@player_two.points >=3)
"Advantage #{@player_one.name}"
elsif (@player_one.points >= 4) and (@player_one.points >= (@player_two.points + 2))
"Game #{@player_one.name}"
end
end
``````

All tests now pass.

# Discussion

In this kata we discovered a missing collaborator when we found that the allocation of responsibilities were not in the right class. We found the missing abstraction Player when we eliminated the duplication is scoring logic. We also found that the scores must provide player's name in order to say who won the game. The attributes and behavior associated with a player is encapsulated in it's own class.

TennisGame class can be renamed to TennisUmpire class to better represent the abstraction. Because it knows which player won the point and has the knowledge to decide who wins the game based on the points scored by the players.

Some of the tests has two assertions. In a real tennis game, the score is determined based on who is serving. Since we have not taken that into account, we ended up with two assertions. We will keep the two assertions in the tests.

# Exercise

Write tests for the missing cases and make them pass.

# Ace the Technical Interview

• Easily find the gaps in your knowledge
• Get customized lessons based on where you are
• Take consistent action everyday
• Builtin accountability to keep you on track
• You will solve bigger problems over time
• Get the job of your dreams

#### Take the 30 Day Coding Skills Challenge

Gain confidence to attend the interview

No spam ever. Unsubscribe anytime.