Skip to content

Instantly share code, notes, and snippets.

@cboudereau
Last active April 10, 2026 07:59
Show Gist options
  • Select an option

  • Save cboudereau/f1a5796b70807c8a76a8c385dcf930c3 to your computer and use it in GitHub Desktop.

Select an option

Save cboudereau/f1a5796b70807c8a76a8c385dcf930c3 to your computer and use it in GitHub Desktop.
tennis kata for java with union types
package io.github.cboudereau;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
public class TennisKataTest {
record Tuple<L, R>(L fst, R snd) {
}
sealed interface Point permits Point.Love, Point.Fifteen, Point.Thirty, Point.Forty {
record Love() implements Point {
}
record Fifteen() implements Point {
}
record Thirty() implements Point {
}
record Forty() implements Point {
}
default Point next() {
return switch (this) {
case Love() -> new Fifteen();
case Fifteen() -> new Thirty();
default -> new Forty();
};
}
}
sealed interface Player permits Player.Left, Player.Right {
record Left() implements Player {
}
record Right() implements Player {
}
}
sealed interface Score
permits Score.Points, Score.FifteenAll, Score.ThirtyAll, Score.Deuce, Score.Advantage, Score.Game {
record Points(Point left, Point right) implements Score {
}
record FifteenAll() implements Score {
}
record ThirtyAll() implements Score {
}
record Deuce() implements Score {
}
record Advantage(Player player) implements Score {
}
record Game(Player player) implements Score {
}
static Score zero() {
return new Points(new Point.Love(), new Point.Love());
}
default Score win(final Player player) {
return switch (new Tuple<>(player, this)) {
case Tuple(_, Game game) -> game;
case Tuple(Player.Left l, Advantage(Player.Left ())) -> new Game(l);
case Tuple(Player.Left l, Points(Point.Forty(), _)) -> new Game(l);
case Tuple(Player.Right r, Advantage(Player.Right ())) -> new Game(r);
case Tuple(Player.Right r, Points(_, Point.Forty())) -> new Game(r);
case Tuple(Player.Left (), Points(Point.Love(), Point.Fifteen())),
Tuple(Player.Right (), Points(Point.Fifteen(), Point.Love())) -> new FifteenAll();
case Tuple(Player.Left (), FifteenAll()) -> new Points(new Point.Thirty(), new Point.Fifteen());
case Tuple(Player.Right (), FifteenAll()) -> new Points(new Point.Fifteen(), new Point.Thirty());
case Tuple(Player.Left (), Points(Point.Fifteen(), Point.Thirty())),
Tuple(Player.Right (), Points(Point.Thirty(), Point.Fifteen())) -> new ThirtyAll();
case Tuple(Player.Left (), ThirtyAll()) -> new Points(new Point.Forty(), new Point.Thirty());
case Tuple(Player.Right (), ThirtyAll()) -> new Points(new Point.Thirty(), new Point.Forty());
case Tuple(Player.Left (), Points(Point.Thirty(), Point.Forty())),
Tuple(Player.Right (), Points(Point.Forty(), Point.Thirty())),
Tuple(Player.Left (), Advantage(Player.Right())),
Tuple(Player.Right (), Advantage(Player.Left())) -> new Deuce();
case Tuple(var p, Deuce()) -> new Advantage(p);
case Tuple(Player.Left (), Points(var l, var r)) -> new Points(l.next(), r);
case Tuple(Player.Right (), Points(var l, var r)) -> new Points(l, r.next());
};
}
}
@Test
void left_player_always_wins_use_case() {
var score = Score.zero();
assertEquals(new Score.Points(new Point.Love(), new Point.Love()), score);
score = score.win(new Player.Left());
assertEquals(new Score.Points(new Point.Fifteen(), new Point.Love()), score);
score = score.win(new Player.Left());
assertEquals(new Score.Points(new Point.Thirty(), new Point.Love()), score);
score = score.win(new Player.Left());
assertEquals(new Score.Points(new Point.Forty(), new Point.Love()), score);
score = score.win(new Player.Left());
assertEquals(new Score.Game(new Player.Left()), score);
}
@Test
void left_wins_the_game_but_the_right_player_was_close_to_win_use_case() {
var score = Score.zero();
assertEquals(new Score.Points(new Point.Love(), new Point.Love()), score);
score = score.win(new Player.Left());
assertEquals(new Score.Points(new Point.Fifteen(), new Point.Love()), score);
score = score.win(new Player.Right());
assertEquals(new Score.FifteenAll(), score);
score = score.win(new Player.Left());
assertEquals(new Score.Points(new Point.Thirty(), new Point.Fifteen()), score);
score = score.win(new Player.Right());
assertEquals(new Score.ThirtyAll(), score);
score = score.win(new Player.Left());
assertEquals(new Score.Points(new Point.Forty(), new Point.Thirty()), score);
score = score.win(new Player.Right());
assertEquals(new Score.Deuce(), score);
score = score.win(new Player.Left());
assertEquals(new Score.Advantage(new Player.Left()), score);
score = score.win(new Player.Right());
assertEquals(new Score.Deuce(), score);
score = score.win(new Player.Right());
assertEquals(new Score.Advantage(new Player.Right()), score);
score = score.win(new Player.Left());
assertEquals(new Score.Deuce(), score);
score = score.win(new Player.Left());
assertEquals(new Score.Advantage(new Player.Left()), score);
score = score.win(new Player.Left());
assertEquals(new Score.Game(new Player.Left()), score);
}
@Test
void right_wins_the_game_but_the_left_player_was_close_to_win_use_case() {
var score = Score.zero();
assertEquals(new Score.Points(new Point.Love(), new Point.Love()), score);
score = score.win(new Player.Right());
assertEquals(new Score.Points(new Point.Love(), new Point.Fifteen()), score);
score = score.win(new Player.Left());
assertEquals(new Score.FifteenAll(), score);
score = score.win(new Player.Right());
assertEquals(new Score.Points(new Point.Fifteen(), new Point.Thirty()), score);
score = score.win(new Player.Left());
assertEquals(new Score.ThirtyAll(), score);
score = score.win(new Player.Right());
assertEquals(new Score.Points(new Point.Thirty(), new Point.Forty()), score);
score = score.win(new Player.Left());
assertEquals(new Score.Deuce(), score);
score = score.win(new Player.Right());
assertEquals(new Score.Advantage(new Player.Right()), score);
score = score.win(new Player.Left());
assertEquals(new Score.Deuce(), score);
score = score.win(new Player.Left());
assertEquals(new Score.Advantage(new Player.Left()), score);
score = score.win(new Player.Right());
assertEquals(new Score.Deuce(), score);
score = score.win(new Player.Right());
assertEquals(new Score.Advantage(new Player.Right()), score);
score = score.win(new Player.Right());
assertEquals(new Score.Game(new Player.Right()), score);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment