Skip to content

Instantly share code, notes, and snippets.

@martinei
Created October 24, 2013 19:00
Show Gist options
  • Save martinei/7143036 to your computer and use it in GitHub Desktop.
Save martinei/7143036 to your computer and use it in GitHub Desktop.
KataTennis
object Tennis {
// A Typesafe solution for http://codingdojo.org/cgi-bin/wiki.pl?KataTennis
sealed trait Player
case class Player1() extends Player
case class Player2() extends Player
sealed trait Score
case class NScore [+P <: Score]() extends Score()
type Score0 = NScore[Score]
type Score15 = NScore[Score0]
type Score30 = NScore[Score15]
type Score40 = NScore[Score30]
case class State[S1 <: Score, S2 <: Score]()
val start = State[Score0, Score0]()
trait Player1MayScore[S1 <: Score, S2<:Score] {
def score(p : Player1) : State[NScore[S1], S2] = State[NScore[S1],S2]()
}
trait Player2MayScore[S1 <: Score, S2<:Score] {
def score(p : Player2) : State[S1,NScore[S2]] = State[S1,NScore[S2]]()
}
case class Normal [S1 <: Score, S2 <: Score]() extends Player1MayScore[S1,S2] with Player2MayScore[S1,S2]
implicit def pimpToNormal[ S1 >: Score30 <: Score, S2 >: Score15 <: Score] (s : State[S1,S2]) : Normal[S1,S2]= Normal()
implicit def pimpToNormal2[ S1 >: Score15 <: Score, S2 >: Score30 <: Score30] (s : State[S1,S2]) : Normal[S1,S2]= Normal()
case class Won[P <: Player]()
case class AdvantageP1() {
def score(p : Player1) : Won[Player1] = Won()
def score (p : Player2) : Deuce = Deuce()
}
case class AdvantageP2() {
def score(p : Player2) : Won[Player2] = Won()
def score (p : Player1) : Deuce = Deuce()
}
case class Deuce(){
def score(p : Player1) : AdvantageP1 = AdvantageP1()
def score (p : Player2) :AdvantageP2 = AdvantageP2()
}
implicit def pimpToDeuce (s : State[Score30,Score30]) = Deuce()
start.score(Player1())
val oneScored = start.score(Player1())
// Player1 can score three times in a row, now he has won!
start.score(Player1()).score(Player1()).score(Player1())
// Scoring four times is not possible
//start.score(Player1()).score(Player1()).score(Player1()).score(Player1())
// Same for Player2
start.score(Player2()).score(Player2()).score(Player2())
//start.score(Player1()).score(Player1()).score(Player1()).score(Player2())
// a Mixed Game
val p1Won = start.score(Player1()).score(Player2()).score(Player1()).score(Player1())
// Now lets Enter an Deuce
val deuce = start.score(Player1()).score(Player1()).score(Player2()).score(Player2())
// Let Player One win
deuce.score(Player1()).score(Player1())
// Cant do this:\
//deuce.score(Player1()).score(Player1()).score(Player1())
// To Advantage and Back to Deuce:
deuce.score(Player1()).score(Player2())
// And another Point is Possible
deuce.score(Player1()).score(Player2()).score(Player1())
}
@martinei
Copy link
Author

I think one can actually simplify furhter by having a "target types T1,T2" in Player1MayScore hat defaults to NScore[S1] , S2 and are used in the return type of score.

The Advantage and Deuce Classes would then be subclasses of Normale with different Target Types, thus that
State[Score30,Score30] represent Deuce, State[Score40,Score30] and State[Score30,Score40] the Advantage for on player. Scores would thatn move back into on of the other existing states.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment