Last active
April 10, 2026 07:59
-
-
Save cboudereau/f1a5796b70807c8a76a8c385dcf930c3 to your computer and use it in GitHub Desktop.
tennis kata for java with union types
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
| 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