Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design of interface abstraction

Currently, I try to write a small game program (Skat) as a hobby project. Skat is a trick-taking game were two players play against a single player. As there are different kinds of players (lokal player, network player, computer, etc.), I wanted to abstract the interface to the player.

My basic idea is to use a typeclass Player, that defines all kind of things, a player have to do and to know (playing a card, get notified about who won the trick, etc). Then, the whole game is just done by a function playSkat :: (Player a, Player b, Player c) => a -> b -> c -> IO () where a, b and c might be different kinds of players. A player might then react in a implementation defined way. A lokal player would get some message on his terminal, a network player might send some information over the network and a computer player might calculate a new strategy.

Because the player might want to do some IO and definitly want to have some kind of state to track private things, it has to live in some kind of Monad. So I thought about defining the Player class like this:

class Player p where
  playCard :: [Card] -> p -> IO (Card,p)
  notifyFoo :: Event -> p -> IO p
  ...

This pattern seems to be quite similar to a state transformer, but I am not shure how to handle it. If I would write it as an extra monad-transformer on top of IO, I had three different monads at the end of the day. How can I write this abstraction in a good way?

To clarify, what I need, here is how a usual control flow should look like:
When playing a trick, the first player plays a card, then the second, and finally the third. To do this, the logic needs to execute the function playCard trice for each player. Afterwards, the logic decides, which player wins the trick and sends the information who won to all players.

like image 753
fuz Avatar asked Apr 24 '11 09:04

fuz


2 Answers

First of all, keep in mind that the main purpose of type classes is to permit overloading of functions, i.e. that you can use a single function at different types. You don't really require that, so you are better off with a record type along the lines of

data Player = Player { playCard :: [Card] -> IO (Card, Player), ... }


Second, the problem of some players needing IO and some not can be solved with a custom monad. I have written corresponding example code for a game of TicTacToe, which is part of my operational package.

like image 63
Heinrich Apfelmus Avatar answered Oct 19 '22 01:10

Heinrich Apfelmus


A much better design would be not to have IO as part of any Player type. Why does the player need to do IO? The player probably needs to get information and send information. Make an interface that reflects that. If/when IO is needed it will be performed by playSkat.

If you do it this what you can have other versions of playSkat that don't do any IO and you can also test your players much more easily since they only interact via the class methods and not through IO.

like image 21
augustss Avatar answered Oct 19 '22 03:10

augustss