The two games use the same underlying combinators for implementing the board. The first is a button that can display changing graphics:
whereboardButtonF :: (ColorGen bgcolor, Graphic gfx) => bgcolor -> Size -> F gfx Click boardButtonF bg size = buttonF'' (setBgColor bg) (g (blankD size)) >=^< Left . setLabel . g
buttonF''is the dynamically customisable (see Section 30.3) version of
setLabelis a customiser that changes the button label.
The second combinator is
which, given the size of the board and a function from the coordinates of a square to a fudget implementing that square, creates a parallel composition of square fudgets with the appropriate layout. The square fudgets are addressed with their coordinates.type Coord = (Int,Int) boardF :: Coord -> (Coord -> F a b) -> F (Coord,a) (Coord,b) boardF (w,h) squareF = placerF (matrixP w) $ listF [((x,y),sqF (x,y)) | y<-[0..h-1],x<-[0..w-1]]
Figure 98. The Explode game.
Figure 99. The Othello game.
In the Explode game, two players take turns placing stones, or atoms, in the squares of a board. A player can not place atoms in a square that already contains atoms from the opponent. When a square is full, that is, contains as many atoms as it has neighbours, it explodes, sending one atom to each neighbour. All atoms of the invaded square change color to the invading atom's color. Invaded squares may become full and explode in turn. When the board has settled, a new move can be entered. When the board starts to get full of atoms, placing a new atom may cause an infinite chain reaction. When this happens, the game is over and the player who caused it is the winner.
main = fudlogue (shellF "Explode" explodeBoardF) explodeBoardF = loopF (absF routeSP >==< boardF boardSize boardSize atomsSquareF) where routeSP = concatMapAccumlSP route White route side (src,msg) = case msg of ClickedWhileEmpty -> (otherSide side, [(src,side)]) ClickedWithColor side' -> if side'==side then (otherSide side, [(src,side)]) else (side, ) -- illegal move Explode dsts side' -> (side, [(dst,side')|dst<-dsts]) atomsSquareF :: Coord -> F AtomColor (SquareEvent Coord) atomsSquareF (x,y) = loopThroughRightF (absF ctrlSP) atomsButtonF where ctrlSP = concatMapAccumlSP ctrl (0,Nothing) ctrl s@(oldcnt,oldside) msg = case msg of Left Click -> (s,[Right (case oldside of Just side -> ClickedWithColor side Nothing -> ClickedWhileEmpty)]) Right side -> let cnt=oldcnt+1 in if cnt>=size then become (cnt-size) side (explodemsgs side) else become cnt side  become cnt side msgs = ((cnt,optside),Left (cnt,side):msgs) where optside = if cnt==0 then Nothing else Just side size = length neighbours explodemsgs = (:) . Right . Explode neighbours neighbours = filter inside2d [(x-1,y),(x+1,y),(x,y-1),(x,y+1)] inside2d (x,y) = inside x && inside y inside x = 0<=x && x<boardSize atomsButtonF :: F (NumberOfAtoms,AtomColor) Click atomsButtonF = boardButtonF bgColor sqsize >=^< drawAtoms drawAtoms (count,color) = ... -- 20 lines
Figure 100. Fudgets implementation of the Explode game.
routeSPallow all square fudgets to communicate with each other. However, each square knows its coordinates and send messages only to its neighbours. The actual communication structure is thus not directly reflected in the program structure.
routeSPalso acts as a referee. It keeps track of whose turn it is, to be able to discard illegal moves. This is also where you would put a test for explosions involving all squares (which should end the game). Otherwise, all the work is done by the squares themselves.
where the messages meandata SquareEvent dest = ClickedWhileEmpty | ClickedWithColor AtomColor | Explode [dest] AtomColor
ClickedWhileEmpty"I was clicked and I was empty".
routeSPthen replies with an atom of the appropriate color (depending on whose turn it is).
ClickedWithColor color: "I was clicked and my color was color". If the color matches with color of the current player,
routeSPreplies with an atom of that color, otherwise the message is ignored (some indication of an illegal move could be produced).
Explode square color: "I explode and invade square with color".
routeSPforwards the message to the square at square. 2-4 messages of this kind are sent when a square explodes.
atomsButtonFproduces the appropriate graphical image for a square, using the types
Drawingdescribed in Chapter 27.
In the Fudgets solution, the squares are in effect connected to all other squares, whereas in the Gadgets solution, each square process is connected through wires only to its neighbours. As noted in [NR95], combining fudgets to achieve exactly this connectivity would be difficult, and it would probably also be difficult to add new processes to such a solution.
As described above, in the current Fudgets solution, each square fudget knows its coordinates and computes the coordinates of its neighbours. It would of course be possible to parameterise the square fudgets by an abstract representation of the neighbours instead,
and letatomsSquareF :: [address] -> F AtomColor (SquareEvent address)
routeSPcompute the concrete addresses of the neighbours. This perhaps makes the communication pattern more visible in the program text and prevents errors in the implementation of
atomsSquareFfrom breaking the pattern.
Since the Gadgets system uses indeterministic process scheduling, it is necessary to explicitly keep track of when an explosion is in progress and when the board is stable, since moves are allowed to be entered only when the board is stable. The implementation of Fudgets is deterministic and internal communication has priority over external communication, so user input is automatically queued until an explosion has subsided.
In the implementation of the Explode game, we used a distributed
solution: the work of computing the effects of the users' moves is
handled almost entirely by the square fudgets. In the
implementation of Othello, we have taken the opposite approach:
the board fudget only displays the board and receives user
input. The checking of the validity of a move and computation of
its effect is handled by a stream processor attached in the
typical way with the
loopThroughRightF combinator (see
Section 18.2). The structure of the program is:
The functionmain = fudlogue (shellF "Othello" ottoF) ottoF = displayF >==< loopThroughRightF (absF newGameSP) ottoBoardF >==< buttonF "New Game" ottoBoardF :: F BoardReq Coord ottoBoardF = ... newGameSP = playSP (reptree moves startpos) playSP :: GameTree -> SP (Either Coord Click) (Either BoardReq String) playSP current_position = ...
reptree(lazily) computes a game tree which has
startposas the root node and where the children of a node are obtained by applying the function
The stream processor
playSP checks the current position for
whose turn it is to play, and then either waits for the user to
enter a move or computes a good move for the computer using
standard alfa-beta game-tree search.