index.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. type PlayerOne = {
  2. kind: "playerOne";
  3. name: string;
  4. };
  5. type PlayerTwo = {
  6. kind: "playerTwo";
  7. name: string;
  8. };
  9. type Player = PlayerOne | PlayerTwo;
  10. type Point = "love" | "15" | "30";
  11. type Deuce = {
  12. kind: "deuce";
  13. };
  14. type PointsData = {
  15. kind: "points";
  16. playerOnePoint: Point;
  17. playerTwoPoint: Point;
  18. };
  19. type FortyData = {
  20. kind: "forty";
  21. player: Player;
  22. otherPlayerPoints: Point;
  23. };
  24. type Advantage = {
  25. kind: "advantage",
  26. player: Player
  27. };
  28. type Game = {
  29. kind: "game",
  30. player: Player
  31. // otherPlayerPoints: ? Not representable here.
  32. };
  33. type Score = Deuce | PointsData | FortyData | Advantage | Game;
  34. function scoreWhenDeuce(player: Player): Advantage {
  35. return {
  36. kind: "advantage",
  37. player,
  38. }
  39. }
  40. function scoreWhenAdvantage(score: Advantage, player: Player): Game | Deuce {
  41. return score.player.kind === player.kind ? {
  42. kind: "game",
  43. player,
  44. } :
  45. {
  46. kind: "deuce",
  47. };
  48. }
  49. function incrementPoints(point: Point): "15" | "30" | undefined {
  50. switch (point) {
  51. case "love":
  52. return "15";
  53. case "15":
  54. return "30";
  55. case "30":
  56. return undefined;
  57. }
  58. }
  59. function scoreWhenForty(score: FortyData, player: Player): Game | Deuce | FortyData {
  60. if (score.player.kind === player.kind) {
  61. return {
  62. kind: "game",
  63. player,
  64. }
  65. }
  66. else {
  67. // "15" | "30" | undefined is a subset of Point | undefined
  68. const newPoints: Point | undefined = incrementPoints(score.otherPlayerPoints);
  69. return newPoints === undefined ?
  70. { kind: "deuce" } :
  71. { ...score, otherPlayerPoints: newPoints };
  72. }
  73. }
  74. function updatePointsData(score: PointsData, newPoints: Point, player: Player): PointsData {
  75. // Discriminated union, with player.kind being the discriminant, player the
  76. // union, and the switch the typeGuard.
  77. switch (player.kind) {
  78. case "playerOne":
  79. return { ...score, playerOnePoint: newPoints };
  80. case "playerTwo":
  81. return { ...score, playerTwoPoint: newPoints };
  82. }
  83. }
  84. function createFortyData(score: PointsData, player: Player): FortyData {
  85. let otherPlayerPoint = player.kind === "playerOne"
  86. ? score.playerTwoPoint
  87. : score.playerOnePoint;
  88. return {
  89. kind: "forty",
  90. player,
  91. otherPlayerPoints: otherPlayerPoint,
  92. }
  93. }
  94. function scoreWhenPoints(score: PointsData, player: Player): PointsData | FortyData {
  95. const newPoints: Point | undefined = player.kind === "playerOne"
  96. ? incrementPoints(score.playerOnePoint)
  97. : incrementPoints(score.playerTwoPoint);
  98. return (newPoints === undefined)
  99. ? createFortyData(score, player)
  100. : updatePointsData(score, newPoints, player);
  101. }
  102. function score(score: Score, player: Player): Score {
  103. switch (score.kind) {
  104. case "points":
  105. return scoreWhenPoints(score, player);
  106. case "forty":
  107. return scoreWhenForty(score, player);
  108. case "deuce":
  109. return scoreWhenDeuce(player);
  110. case "advantage":
  111. return scoreWhenAdvantage(score, player);
  112. case "game":
  113. return score;
  114. }
  115. }