index.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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. otherPlayerPoint: Point;
  23. };
  24. type Advantage = {
  25. kind: "advantage",
  26. player: Player
  27. };
  28. type Game = {
  29. kind: "game",
  30. player: Player
  31. // otherPlayerPoint: ? Not representable here.
  32. };
  33. type Score = Deuce | PointsData | FortyData | Advantage | Game;
  34. // Add the ignored _score parameter to ensure a type check.
  35. function scoreWhenDeuce(_score: Deuce, player: Player): Advantage {
  36. return {
  37. kind: "advantage",
  38. player
  39. };
  40. }
  41. function scoreWhenAdvantage(score: Advantage, player: Player): Game | Deuce {
  42. return score.player.kind === player.kind ?
  43. {
  44. kind: "game",
  45. player: score.player // == player.
  46. } :
  47. {
  48. kind: "deuce"
  49. };
  50. }
  51. function incrementPoint(point: Point): "15" | "30" | undefined {
  52. switch (point) {
  53. case "love":
  54. return "15";
  55. case "15":
  56. return "30";
  57. case "30":
  58. return undefined;
  59. }
  60. }
  61. function scoreWhenForty(score: FortyData, player: Player): Game | Deuce | FortyData {
  62. if (score.player.kind === player.kind) {
  63. return {
  64. kind: "game",
  65. player: player
  66. };
  67. } else {
  68. // "15" | "30" | undefined is a subset of Point | undefined
  69. const newPoints: Point | undefined = incrementPoint(score.otherPlayerPoint);
  70. return (newPoints === undefined) ?
  71. { kind: "deuce" } :
  72. {...score, otherPlayerPoint: newPoints};
  73. }
  74. }
  75. function updatePointsData(score: PointsData, newPoints: Point, player: Player): PointsData {
  76. // Discriminated union, with player.kind being the discriminant, player the
  77. // union, and the switch the typeGuard.
  78. switch (player.kind) {
  79. case "playerOne":
  80. return {...score, playerOnePoint: newPoints};
  81. case "playerTwo":
  82. return {...score, playerTwoPoint: newPoints};
  83. }
  84. }
  85. function createFortyData(score: PointsData, player: Player): FortyData {
  86. let otherPlayerPoint = player.kind === "playerOne"
  87. ? score.playerTwoPoint
  88. : score.playerOnePoint;
  89. return {
  90. kind: "forty",
  91. player,
  92. otherPlayerPoint
  93. };
  94. }
  95. function scoreWhenPoints(score: PointsData, player: Player): PointsData | FortyData {
  96. const newPoints: Point | undefined = player.kind === "playerOne" ?
  97. incrementPoint(score.playerOnePoint) :
  98. incrementPoint(score.playerTwoPoint);
  99. return newPoints === undefined ?
  100. createFortyData(score, player) :
  101. updatePointsData(score, newPoints, player);
  102. }
  103. function score(score: Score, player: Player): Score {
  104. // XXX: refactor to avoid the switch and just use a method map.
  105. switch (score.kind) {
  106. case "points":
  107. return scoreWhenPoints(score, player);
  108. case "forty":
  109. return scoreWhenForty(score, player);
  110. case "deuce":
  111. return scoreWhenDeuce(score, player);
  112. case "advantage":
  113. return scoreWhenAdvantage(score, player);
  114. case "game":
  115. return score;
  116. }
  117. }