module slidegame // ************************************************************************************************** // // A simple slide game that uses bitmaps to show nice pictures. // On Macintosh: // * Make sure the application has sufficient 'Extra memory' (Application options) // * Select a PICT file; // On Windows(9x/NT): // * Make sure the application has sufficient 'Heap' (Application options) // * Select a BMP file. // // The program has been written in Clean 2.0 and uses the Clean Standard Object I/O library 1.2.2 // // ************************************************************************************************** import StdEnv, StdIO, Random from StdFunc import seqList /* openSlideGame first attempts to read in the bitmap. If successfull, openSlideGame then checks whether the given bitmap has proper dimensions. If this is the case then a window is opened that will contain the slide game. The initial positions of the slides are determined by shuffling them nr_shuffle times randomly. The local state of the window keeps track of the current position of the hole. */ :: WindowState = { curHole :: !Coord // The current position of the hole } :: Coord = { col :: !Int // The zero based column number , row :: !Int // The zero based row number } Start :: *World -> *World Start world # (maybeFile,world) = selectInputFile world | isNothing maybeFile = world # (maybeBitmap,world) = openBitmap (fromJust maybeFile) world | isNothing maybeBitmap = world # bitmap = fromJust maybeBitmap bitmapsize = getBitmapSize bitmap blocksize = {w=bitmapsize.w/4,h=bitmapsize.h/4} | not (ok_blocksize blocksize) = world | otherwise # (seed,world) = getNewRandomSeed world (okCoords,hole) = initlast [{col=col,row=row} \\ row<-[0..3],col<-[0..3]] (_,coords,hole) = iteraten nr_shuffle shuffle (seed,zip2 okCoords okCoords,hole) # (windowId,world) = openId world # (allcids, world) = openIds 15 world # (allr2ids,world) = openR2Ids 15 world = startIO SDI Void (openSlideWindow hole bitmap blocksize windowId allcids allr2ids coords) [ProcessClose closeProcess] world where nr_shuffle = 200 openSlideWindow hole bitmap blocksize windowId allcids allr2ids coords pState = snd (openWindow {curHole=hole} (window bitmap blocksize windowId allcids allr2ids coords) pState) ok_blocksize {w,h} = isbetween minblocksize.w w maxblocksize.w && isbetween minblocksize.h h maxblocksize.h where minblocksize = {w=20,h=20} maxblocksize = {w=maxFixedWindowSize.w/4,h=maxFixedWindowSize.h/4} shuffle :: (RandomSeed,[(Coord,Coord)],Coord) -> (RandomSeed,[(Coord,Coord)],Coord) shuffle (seed,coords,hole) # (candidates,others) = splitFilter (\(okCoord,coord)->distCoord coord hole==1) coords (random_nr,seed) = random seed (before,[(okCandidate,candidate):after]) = splitAt (random_nr rem (length candidates)) candidates = (seed,before++[(okCandidate,hole):after]++others,candidate) /* window defines the Window that shows the slide game. It contains a list of slide controls. Closing the window will terminate the program. */ window :: Bitmap Size Id [Id] [SlideR2Id] [(Coord,Coord)] -> Window (ListLS (AddLS (:+: CustomButtonControl (Receiver2 SlideMsgIn Bool)))) WindowState (PSt .l) window bitmap blocksize windowId allcids allr2ids coords = Window "SlideGame" ( ListLS (map (slideControl bitmap blocksize windowId allr2ids) coord_ids) ) [ WindowClose (noLS closeProcess) , WindowId windowId , WindowItemSpace 0 0 , WindowViewSize {w=4*blocksize.w,h=4*blocksize.h} , WindowPen [PenBack Grey] ] where coord_ids = zip2 coords (zip2 allcids allr2ids) /* slideControl defines one slide control of the slide game. A slide control consists of two components: * A custom button control: This control shows a part of the bitmap image. Selecting this control will swap places with the current hole iff it is adjacent to the hole. It checks whether all slide controls are at their desired locations, and if so disables the window. Note that disabling the window will disable all slide controls. The look of a slide control is such that in disabled state it will not frame its bitmap part, so the complete bitmap will be displayed. * A receiver control: This control handles external requests that inform whether the slide control is at its desired position. */ :: SlideState // The local state of a slide control = { curCoord :: Coord // The current location of the slide control } :: SlideMsgIn // The ingoing messages of the slide control = AreYouOk // Inform whether the control is currently at its desired location :: SlideMsgOut // The outgoing messages of the slide control :== Bool // True iff the control is currently at its desired location :: SlideR2Id // Shorthand for the receiver id of a slide control :== R2Id SlideMsgIn SlideMsgOut :: SlideControl ls pst // Shorthand for the slide control constructor type :== AddLS (:+: CustomButtonControl (Receiver2 SlideMsgIn SlideMsgOut)) ls pst slideControl :: Bitmap Size Id [SlideR2Id] ((Coord,Coord),(Id,SlideR2Id)) -> SlideControl WindowState (PSt .l) slideControl bitmap size windowId allr2ids ((okCoord,initCoord),(cid,r2id)) = { addLS = { curCoord=initCoord } , addDef = custombutton :+: receiver2 } where others = removeMember r2id allr2ids custombutton = CustomButtonControl size slideLook [ ControlPos (LeftTop,OffsetVector (offset initCoord)) , ControlFunction slideMove , ControlId cid ] slideLook select {newFrame} picture # picture = drawAt {x=0-okCoord.col*size.w,y=0-okCoord.row*size.h} bitmap picture | enabled select = draw newFrame picture | otherwise = picture offset {col,row}= {vx=size.w*col,vy=size.h*row} slideMove :: (*(SlideState,WindowState),PSt .l) -> (*(SlideState,WindowState),PSt .l) slideMove ((slide=:{curCoord},ls=:{curHole}),pst) | distCoord curCoord curHole<>1 = ((slide,ls),pst) # slide = {slide & curCoord=curHole } ls = {ls & curHole =curCoord} # (_,pst) = accPIO (setControlPos windowId [(cid,(LeftTop,OffsetVector (offset curHole)))]) pst # i_am_ok = curHole==okCoord | not i_am_ok = ((slide,ls),pst) # (others_ok,pst) = seqList (map areYouOk others) pst | and others_ok = ((slide,ls),appPIO (disableWindow windowId) pst) | otherwise = ((slide,ls),pst) areYouOk :: SlideR2Id (PSt .l) -> (Bool,PSt .l) areYouOk r2id pst # (response,pst) = syncSend2 r2id AreYouOk pst = (fromJust (snd response),pst) receiver2 = Receiver2 r2id receive2 [] receive2 :: SlideMsgIn *(*(SlideState,.ls),PSt .l) -> (SlideMsgOut,*(*(SlideState,.ls),PSt .l)) receive2 AreYouOk (slide=:({curCoord},_),pst) = (okCoord==curCoord,(slide,pst)) // The distance between two Coords: distCoord :: !Coord !Coord -> Int distCoord coord1 coord2 = abs (coord1.col-coord2.col) + abs (coord1.row-coord2.row) instance zero Coord where zero = {col=0,row=0} instance == Coord where (==) coord1 coord2 = distCoord coord1 coord2==0 // Generally useful functions: initlast :: ![.a] -> ([.a],.a) // (init xs,last xs) initlast [a] = ([],a) initlast [a:as] = ([a:init],last) where (init,last) = initlast as initlast [] = abort "initlast of []" splitFilter :: (a -> .Bool) !.[a] -> (.[a],.[a]) // (filter cond xs,filter (not o cond) xs) splitFilter f [a:as] | f a = ([a:yes],no) | otherwise = (yes,[a:no]) where (yes,no) = splitFilter f as splitFilter f [] = ([],[]) iteraten :: !Int (a -> a) a -> a iteraten n f x | n<=0 = x | otherwise = iteraten (n-1) f (f x) isbetween :: !a !a a -> Bool | Ord a isbetween low x up = low<=x && x<=up