Posted By

codernator on 12/28/12


Tagged

api functionalprogramming


Versions (?)

Who likes this?

1 person have marked this snippet as a favorite

thastyle


Tony Morris Compile-Check API Challenge


 / Published in: C#
 

URL: http://blog.tmorris.net/understanding-practical-api-design-static-typing-and-functional-programming/

This is a first stab at defining a tic-tac-toe API that is compile-time safe, as outlined in Tony's blog: "Understanding Practical API Design, Static Typing and Functional Programming" (http://blog.tmorris.net/understanding-practical-api-design-static-typing-and-functional-programming/).

Although the game engine API is strongly typed, it's design implicitly introduces potential run-time errors in its consumption. Still, I like where it is going.

The trick to this is the use of Nullable. Since the output of the "move" function can't simultaneously satisfy the input types of most API functions (move, whoWon, TakeBack), I deemed a conversion function necessary. Using structs to define board state and Nullable, I could guarantee that a type-safe, non-null value is passed to the each of the API methods. What I can't guarantee is that the API consumer won't misuse the Nullable instances. Presumably, the IGameEngine API implementation is buried in a DLL and the setters on Board, WinnerBoard, PlayableBoard, and UndoBoard are internal only, so a consumer of the API can't construct invalid instances of those data types.

  1. public struct Player
  2. {
  3. int playerindex; //values range from 1-2
  4.  
  5.  
  6. public static implicit operator Player(int value)
  7. {
  8. return new Player { playerindex = System.Math.Abs(value) % 2 + 1 };
  9. }
  10.  
  11. public static Player operator +(Player first, Player second)
  12. {
  13. Player p = first.playerindex + second.playerindex;
  14. return p;
  15. }
  16.  
  17. /* ... etc */
  18. }
  19.  
  20. public struct Space
  21. {
  22. int spaceindex;
  23.  
  24. public static implicit operator Space(int value)
  25. {
  26. return new Space { spaceindex = System.Math.Abs(value) % 9 };
  27. }
  28.  
  29. /* ... etc */
  30. }
  31.  
  32.  
  33. public struct Board { /* game state, etc. */ }
  34. public struct UndoBoard { /* maybe contains a board - maybe is implemented same as board */ }
  35. public struct PlayableBoard { /* maybe contains a board - maybe is implemented same as board */ }
  36. public struct WinnerBoard { /* maybe contains a board - maybe is implemented same as board */ }
  37.  
  38. public interface IGameEngine
  39. {
  40. PlayableBoard? GetPlayableBoard(Board board);
  41. WinnerBoard? GetWinnerBoard(Board board);
  42. UndoBoard? GetUndoBoard(Board board);
  43.  
  44. // move: takes a tic-tac-toe board and position and moves to that position (if not occupied) returning
  45. // a new board. This function can only be called on a board that is in-play. Calling move on a game
  46. // board that is finished is a compile-time type error.
  47. Board Move(PlayableBoard board, Space position);
  48.  
  49. bool ValidateMove(PlayableBoard board, Space position);
  50.  
  51. // whoWon: takes a tic-tac-toe board and returns the player that won the game (or a draw if neither).
  52. // This function can only be called on a board that is finished. Calling move on a game board that is
  53. // in-play is a compile-time type error.
  54. Player WhoWon(WinnerBoard board);
  55.  
  56. // takeBack: takes either a finished board or a board in-play that has had at least one move and returns
  57. // a board in-play. It is a compile-time type error to call this function on an empty board.
  58. Board TakeBack(UndoBoard board);
  59.  
  60. // playerAt: takes a tic-tac-toe board and position and returns the (possible) player at a given position.
  61. // This function works on any type of board.
  62. Player PlayerAt(Board board, Space position);
  63. }
  64.  
  65. public interface IUserInterface
  66. {
  67. Space RequestMove(PlayableBoard board, Player player);
  68. void ReportInvalid(Player player, Space move);
  69. void ReportWin(Player player);
  70. void ReportDraw();
  71. }
  72.  
  73.  
  74. class Program
  75. {
  76. private void Play(IGameEngine engine, IUserInterface userInterface)
  77. {
  78. // Use of engine.TakeBack and engine.PlayerAt is academic, and not included here.
  79.  
  80. Board board = new Board();
  81. PlayableBoard? maybeplayable = engine.GetPlayableBoard(board);
  82. Player player = 1;
  83. while (maybeplayable.HasValue)
  84. {
  85. Space move;
  86. PlayableBoard playable = maybeplayable.Value;
  87.  
  88. while (true)
  89. {
  90. move = userInterface.RequestMove(playable, player);
  91. if (engine.ValidateMove(playable, move))
  92. break;
  93. userInterface.ReportInvalid(player, move);
  94. }
  95.  
  96. board = engine.Move(playable, move);
  97. maybeplayable = engine.GetPlayableBoard(board);
  98.  
  99. player += 1;
  100. }
  101.  
  102. WinnerBoard? maybewinner = engine.GetWinnerBoard(board);
  103. if (maybewinner.HasValue)
  104. {
  105. Player winner = engine.WhoWon(maybewinner.Value);
  106. userInterface.ReportWin(winner);
  107. }
  108. else
  109. {
  110. userInterface.ReportDraw();
  111. }
  112. }
  113.  
  114. static void Main(string[] args)
  115. {
  116. /* Spin up an implementation of IGameEngine and IUserInterface. Play */
  117. }
  118. }

Report this snippet  

You need to login to post a comment.