feat(kata-tennis): added tennis refactoring kata

parent 2e17243e
......@@ -29,4 +29,5 @@ include:
- local: "/leap-years/.gitlab-ci.yml"
- local: "/employee-report/.gitlab-ci.yml"
- local: "/java-concurrence/.gitlab-ci.yml"
- local: "/tennis/refactoring/.gitlab-ci.yml"
- local: "/bowling-game/.gitlab-ci.yml"
......@@ -59,3 +59,4 @@ Ce dépôt Git a pour but de partager les différents ateliers pouvant être ré
| [Retour sur les 3 jours de conférences de DDD Europe](https://youtu.be/sDUuDpnLWXs) | Discussion | |
| [Concurrence en Java](/java-concurrence) | Code&coffee | Enervé |
| [Bowling Game](/bowling-game) | Kata | Moyen |
| [Tennis Refactoring kata](/tennis/refactoring) | Kata | Moyenne |
# Tennis coding and refactoring katas
This katas is inspired and uses starting code
from [this great github repository](https://github.com/emilybache/Tennis-Refactoring-Kata) from Emily Bache
## Tennis Coding Kata
Tennis has a rather quirky scoring system, and to newcomers it can be a little difficult to keep track of. The tennis
society has contracted you to build a scoreboard to display the current score during tennis games.
Your task is to write a “TennisGame” class containing the logic which outputs the correct score as a string for display
on the scoreboard. When a player scores a point, it triggers a method to be called on your class letting you know who
scored the point. Later, you will get a call “score()” from the scoreboard asking what it should display. This method
should return a string with the current score.
You can read more about Tennis scores [here](http://en.wikipedia.org/wiki/Tennis#Scoring) which is summarized below:
1. A game is won by the first player to have scored at least four points in total and at least two points more than the
opponent.
2. The running score of each game is described in a manner peculiar to tennis: scores from zero to three points are
described as "Love", "Fifteen", "Thirty", and "Forty" respectively.
3. If at least three points have been scored by each player, and the scores are equal, the score is "Deuce".
4. If at least three points have been scored by each side and a player has one more point than his opponent, the score
of the game is "Advantage" for the player in the lead.
You need only report the score for the current game. Sets and Matches are out of scope.
## Tennis Refactoring Kata
Imagine you work for a consultancy company, and one of your colleagues has been doing some work for the Tennis Society.
The contract is for 10 hours billable work, and your colleague has spent 8.5 hours working on it. Unfortunately he has
now fallen ill. He says he has completed the work, and the tests all pass. Your boss has asked you to take over from
him. She wants you to spend an hour or so on the code so she can bill the client for the full 10 hours. She instructs
you to tidy up the code a little and perhaps make some notes so you can give your colleague some feedback on his chosen
design. You should also prepare to talk to your boss about the value of this refactoring work, over and above the extra
billable hours.
There are three versions of this refactoring kata, each with their own design smells and challenges. I suggest you start
with the first one, with the class "TennisGame1". The test suite provided is fairly comprehensive, and fast to run. You
should not need to change the tests, only run them often as you refactor.
If you like this Kata, you may be interested in my
book, ["The Coding Dojo Handbook"](https://leanpub.com/codingdojohandbook)
## Questions to discuss afterwards
* How did it feel to work with such fast, comprehensive tests?
* Did you make mistakes while refactoring that were caught by the tests?
* If you used a tool to record your test runs, review it. Could you have taken smaller steps? Made fewer refactoring
mistakes?
* Did you ever make any refactoring mistakes and then back out your changes? How did it feel to throw away code?
* What would you say to your colleague if they had written this code?
* What would you say to your boss about the value of this refactoring work? Was there more reason to do it over and
above the extra billable hour or so?
.idea
*.iml
target/
.classpath
.project
.settings/
package-tennis-refactoring:
variables:
PROJECT_FOLDER: "tennis/refactoring"
extends: .java
only:
refs:
- master
- merge_requests
changes:
- ".gitlab-common-ci.yml"
- "tennis/refactoring/**/*"
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<version>1.0.0</version>
<groupId>fr.ippon.kata</groupId>
<artifactId>java-parent</artifactId>
<relativePath>../../java-parent</relativePath>
</parent>
<version>1.0.0-SNAPSHOT</version>
<artifactId>kata-refactoring-tennis</artifactId>
<developers>
<developer>
<email>msauboua@ippon.fr</email>
<name>Matthieu SAUBOUA-BENELUZ</name>
</developer>
<developer>
<email>arey@ippon.fr</email>
<name>Anthony REY</name>
</developer>
</developers>
<properties>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package fr.ippon.kata;
public interface TennisGame {
void wonPoint(String playerName);
String score();
}
package fr.ippon.kata;
import java.util.Map;
public class TennisGame1 implements TennisGame {
private static final String LOVE = "Love";
private static final String FIFTEEN = "Fifteen";
private static final String THIRTY = "Thirty";
private static final String FORTY = "Forty";
private static final String ALL = "All";
private static final String DEUCE = "Deuce";
private static final String SCORE_SEPARATOR = "-";
private static final Map<Integer, String> SCORES = Map.of(0, LOVE, 1, FIFTEEN, 2, THIRTY, 3, FORTY);
private static final String WIN_FOR = "Win for ";
private static final String ADVANTAGE = "Advantage ";
private int player1Score = 0;
private int player2Score = 0;
private final String player1Name;
private final String player2Name;
public TennisGame1(String player1Name, String player2Name) {
this.player1Name = player1Name;
this.player2Name = player2Name;
}
public void wonPoint(String playerName) {
if (player1Name.equals(playerName))
player1Score += 1;
else
player2Score += 1;
}
public String score() {
if (player1Score == player2Score) {
return equalityScore();
}
if (player1Score >= 4 || player2Score >= 4) {
return endGameScore();
}
return middleGameScore();
}
private String middleGameScore() {
return SCORES.get(player1Score) +
SCORE_SEPARATOR +
SCORES.get(player2Score);
}
private String endGameScore() {
int player1AdvantageValue = player1Score - player2Score;
if (Math.abs(player1AdvantageValue) == 1) {
return ADVANTAGE + advantagedPlayer();
}
return WIN_FOR + advantagedPlayer();
}
private String advantagedPlayer() {
return player1Score > player2Score ? player1Name : player2Name;
}
private String equalityScore() {
if (player1Score >= 3) {
return DEUCE;
}
return SCORES.get(player1Score) +
SCORE_SEPARATOR +
ALL;
}
}
package fr.ippon.kata;
public class TennisGame2 implements TennisGame {
public int P1point = 0;
public int P2point = 0;
public String P1res = "";
public String P2res = "";
private String player1Name;
private String player2Name;
public TennisGame2(String player1Name, String player2Name) {
this.player1Name = player1Name;
this.player2Name = player2Name;
}
public String score() {
String score = "";
if (P1point == P2point && P1point < 4) {
if (P1point == 0)
score = "Love";
if (P1point == 1)
score = "Fifteen";
if (P1point == 2)
score = "Thirty";
score += "-All";
}
if (P1point == P2point && P1point >= 3)
score = "Deuce";
if (P1point > 0 && P2point == 0) {
if (P1point == 1)
P1res = "Fifteen";
if (P1point == 2)
P1res = "Thirty";
if (P1point == 3)
P1res = "Forty";
P2res = "Love";
score = P1res + "-" + P2res;
}
if (P2point > 0 && P1point == 0) {
if (P2point == 1)
P2res = "Fifteen";
if (P2point == 2)
P2res = "Thirty";
if (P2point == 3)
P2res = "Forty";
P1res = "Love";
score = P1res + "-" + P2res;
}
if (P1point > P2point && P1point < 4) {
if (P1point == 2)
P1res = "Thirty";
if (P1point == 3)
P1res = "Forty";
if (P2point == 1)
P2res = "Fifteen";
if (P2point == 2)
P2res = "Thirty";
score = P1res + "-" + P2res;
}
if (P2point > P1point && P2point < 4) {
if (P2point == 2)
P2res = "Thirty";
if (P2point == 3)
P2res = "Forty";
if (P1point == 1)
P1res = "Fifteen";
if (P1point == 2)
P1res = "Thirty";
score = P1res + "-" + P2res;
}
if (P1point > P2point && P2point >= 3) {
score = "Advantage player1";
}
if (P2point > P1point && P1point >= 3) {
score = "Advantage player2";
}
if (P1point >= 4 && P2point >= 0 && (P1point - P2point) >= 2) {
score = "Win for player1";
}
if (P2point >= 4 && P1point >= 0 && (P2point - P1point) >= 2) {
score = "Win for player2";
}
return score;
}
public void SetP1Score(int number) {
for (int i = 0; i < number; i++) {
P1Score();
}
}
public void SetP2Score(int number) {
for (int i = 0; i < number; i++) {
P2Score();
}
}
public void P1Score() {
P1point++;
}
public void P2Score() {
P2point++;
}
public void wonPoint(String player) {
if (player == "player1")
P1Score();
else
P2Score();
}
}
package fr.ippon.kata;
public class TennisGame3 implements TennisGame {
private int p2;
private int p1;
private String p1N;
private String p2N;
public TennisGame3(String p1N, String p2N) {
this.p1N = p1N;
this.p2N = p2N;
}
public String score() {
String s;
if (p1 < 4 && p2 < 4 && !(p1 + p2 == 6)) {
String[] p = new String[]{"Love", "Fifteen", "Thirty", "Forty"};
s = p[p1];
return (p1 == p2) ? s + "-All" : s + "-" + p[p2];
} else {
if (p1 == p2)
return "Deuce";
s = p1 > p2 ? p1N : p2N;
return ((p1 - p2) * (p1 - p2) == 1) ? "Advantage " + s : "Win for " + s;
}
}
public void wonPoint(String playerName) {
if (playerName == "player1")
this.p1 += 1;
else
this.p2 += 1;
}
}
package fr.ippon.kata;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class TennisGameUnitTest {
@ParameterizedTest
@CsvFileSource(delimiter = ',', numLinesToSkip = 1, resources = "/param-test-data.csv")
void checkAllScoresTennisGame1(Integer player1Score, Integer player2Score, String expectedScore) {
TennisGame1 game = new TennisGame1("player1", "player2");
checkAllScores(player1Score, player2Score, expectedScore, game);
}
@Test
void checkForRealPlayerNames() {
String benoit_paire = "Benoit Paire";
String roger_federer = "Roger Federer";
TennisGame1 game = new TennisGame1(benoit_paire, roger_federer);
game.wonPoint(benoit_paire);
game.wonPoint(benoit_paire);
game.wonPoint(benoit_paire);
game.wonPoint(roger_federer);
game.wonPoint(roger_federer);
game.wonPoint(roger_federer);
game.wonPoint(roger_federer);
game.wonPoint(benoit_paire);
game.wonPoint(benoit_paire);
assertThat(game.score()).isEqualTo("Advantage Benoit Paire");
}
@ParameterizedTest
@CsvFileSource(delimiter = ',', numLinesToSkip = 1, resources = "/param-test-data.csv")
void checkAllScoresTennisGame2(Integer player1Score, Integer player2Score, String expectedScore) {
TennisGame2 game = new TennisGame2("player1", "player2");
checkAllScores(player1Score, player2Score, expectedScore, game);
}
@ParameterizedTest
@CsvFileSource(delimiter = ',', numLinesToSkip = 1, resources = "/param-test-data.csv")
void checkAllScoresTennisGame3(Integer player1Score, Integer player2Score, String expectedScore) {
TennisGame3 game = new TennisGame3("player1", "player2");
checkAllScores(player1Score, player2Score, expectedScore, game);
}
private void checkAllScores(Integer player1Score, Integer player2Score, String expectedScore, TennisGame game) {
int highestScore = Math.max(player1Score, player2Score);
for (int i = 0; i < highestScore; i++) {
if (i < player1Score)
game.wonPoint("player1");
if (i < player2Score)
game.wonPoint("player2");
}
assertThat(game.score()).isEqualTo(expectedScore);
}
}
player1Score,player2Score,ExpectedResult
0,0,Love-All
1,1,Fifteen-All
2,2,Thirty-All
3,3,Deuce
4,4,Deuce
1,0,Fifteen-Love
0,1,Love-Fifteen
2,0,Thirty-Love
0,2,Love-Thirty
3,0,Forty-Love
0,3,Love-Forty
4,0,Win for player1
0,4,Win for player2
2,1,Thirty-Fifteen
1,2,Fifteen-Thirty
3,1,Forty-Fifteen
1,3,Fifteen-Forty
4,1,Win for player1
1,4,Win for player2
3,2,Forty-Thirty
2,3,Thirty-Forty
4,2,Win for player1
2,4,Win for player2
4,3,Advantage player1
3,4,Advantage player2
5,4,Advantage player1
4,5,Advantage player2
15,14,Advantage player1
14,15,Advantage player2
6,4,Win for player1
4,6,Win for player2
16,14,Win for player1
14,16,Win for player2
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment