module bounce // ************************************************************************************************** // // A program that creates two interactive processes that bounce balls in an open-ended barrel. // // The program has been written in Clean 2.0 and uses the Clean Standard Object I/O library 1.2.2 // // ************************************************************************************************** import StdEnv, StdIO import bounceDraw from StdFunc import seq :: Message // The message type: = BallsArrive [Ball] // balls that have crossed process border | BounceOpened // the other bounce process has been created | QuitBounce // quit the bounce process :: Local // The local program state: = { talkTo :: !RId Message // who to play with , barrel :: !Barrel // the shape of the barrel , balls :: ![Ball] // the balls in the barrel } :: *Bounce :== PSt Local // Synonym for PSt // Create the initial interactive process: Start :: *World -> *World Start world # (rIdA,world) = openRId world # (rIdB,world) = openRId world # (wIdA,world) = openId world # (wIdB,world) = openId world # (tIdA,world) = openId world # (tIdB,world) = openId world = startProcesses [ bounce wIdB tIdB rIdB rIdA "Bounce B" RightTop rightBarrelSetUp , bounce wIdA tIdA rIdA rIdB "Bounce A" LeftTop leftBarrelSetUp ] world bounce :: Id Id (RId Message) (RId Message) Title ItemLoc (Barrel,[Ball]) -> Process bounce wId tId me you name itemLoc (barrel,balls) = Process SDI initLocal initIO [ProcessClose quit] where barrelDomain = barrel.bDomain barrelSize = rectangleSize barrelDomain maxSize = maxFixedWindowSize windowSize = {w=min barrelSize.w (maxSize.w/2),h=min barrelSize.h (maxSize.h/2)} splitWalls = splitWallsInBarrel barrel (initLocal`,initLocal) = shareLocal {talkTo=you,barrel=barrel,balls=balls} initIO :: Bounce -> Bounce initIO pst # (error,pst) = openWindow undef window pst | error<>NoError = abort "bounce could not open window." # (error,pst) = openMenu undef menu pst | error<>NoError = abort "bounce could not open menu." # (error,pst) = openTimer undef timer pst | error<>NoError = abort "bounce could not open timer." # (error,pst) = openReceiver undef receiver pst | error<>NoError = abort "bounce could not open receiver." | otherwise = pst where // window defines the window that displays the barrel and the current balls. window = Window name NilLS [ WindowId wId , WindowLook False (updateBalls initLocal`) , WindowViewSize windowSize , WindowPos (itemLoc,NoOffset) ] // menu defines the bounce menu. It contains only the quit command to terminate the application. menu = Menu name ( MenuItem "About Bounce..." [MenuFunction (noLS bounceHelp)] :+: MenuSeparator [] :+: MenuItem "Quit" [MenuFunction (noLS quit),MenuShortKey 'q'] ) [] // timer defines the timer that calculates the movements of the current balls. timer = Timer 0 NilLS [ TimerId tId , TimerFunction (noLS1 (bounceBalls splitWalls)) ] where bounceBalls :: !(![SingleWall],![SingleWall]) NrOfIntervals Bounce -> Bounce bounceBalls splitWalls _ bounce=:{ls=local=:{talkTo,balls,barrel},io} # (windowSize,io) = getWindowViewSize wId io scale = scaleSize windowSize barrelSize eraseOld = map (eraseBall scale base) balls drawNew = map (drawBall scale base) ins local = {local & balls=ins} # (local`,local) = shareLocal local # io = appWindowPicture wId (seq (eraseOld++drawNew)) io # io = setWindowLook wId False (False,updateBalls local`) io # bounce = {bounce & ls=local,io=io} | isEmpty outs = bounce | otherwise = snd (syncSend talkTo (BallsArrive outs) bounce) where nextBallPos = nextBallPositions splitWalls balls ballsMoved = map moveBall nextBallPos domain = barrel.bDomain base = domain.corner1 barrelSize = rectangleSize domain (ins,outs) = splitBallsInBarrel domain ballsMoved // receiver defines the receiver that will receive new balls and termination requests. receiver :: *Receiver Message .ls Bounce receiver = Receiver me (noLS1 (receive splitWalls)) [] where receive :: !(![SingleWall],![SingleWall]) !Message !Bounce -> Bounce receive (horizontal,vertical) (BallsArrive newBalls) bounce=:{ls} #! newBalls = map correctBall newBalls = {bounce & ls={ls & balls=newBalls++ls.balls}} where correctBall :: !Ball -> Ball correctBall ball=:{bCenter,bSpeed} # ball = {ball & bCenter=movePoint (~bSpeed) bCenter} # ball = checkVerticalWalls vertical ball # ball = checkHorizontalWalls horizontal ball # ball = moveBall ball = ball receive _ BounceOpened bounce = appPIO (enableTimer tId) bounce receive _ QuitBounce bounce = closeProcess bounce updateBalls :: Local SelectState UpdateState *Picture -> *Picture updateBalls {balls,barrel} _ {oldFrame,newFrame,updArea} picture # picture = drawBarrel area scale barrel picture # picture = seq (map (drawBall scale domain.corner1) balls) picture = picture where domain = barrel.bDomain windowSize = rectangleSize newFrame barrelSize = rectangleSize domain scale = scaleSize windowSize barrelSize area = if (oldFrame==newFrame) updArea [newFrame] quit :: Bounce -> Bounce quit bounce=:{ls={talkTo}} = closeProcess (snd (syncSend talkTo QuitBounce bounce)) // bounceHelp opens a dialog that tells something about this application. bounceHelp :: Bounce -> Bounce bounceHelp bounce # (okId,bounce) = accPIO openId bounce # (dId, bounce) = accPIO openId bounce # dDef = Dialog "About bounce" ( TextControl "This is a Clean program" [ControlPos (Center,NoOffset)] :+: ButtonControl "Ok" [ControlId okId ,ControlPos (Center,NoOffset) ,ControlFunction (noLS (closeWindow dId)) ] ) [ WindowOk okId , WindowId dId ] # ((error,_),bounce)= openModalDialog undef dDef bounce | error<>NoError = abort "bounce could not open About bounce dialog." | otherwise = bounce // Determine which balls are inside and which are outside the barrel: splitBallsInBarrel :: !ViewDomain ![Ball] -> (![Ball],![Ball]) splitBallsInBarrel domain balls = seq (map (ballInOrOut domain) balls) ([],[]) where ballInOrOut :: !ViewDomain !Ball !(![Ball],![Ball]) -> (![Ball],![Ball]) ballInOrOut {corner1={x=left,y=top},corner2={x=right,y=bottom}} ball=:{bCenter} (ins,outs) | between bCenter.x left right && between bCenter.y top bottom = ([ball:ins],outs) | otherwise = (ins,[ball:outs]) nextBallPositions :: !(![SingleWall],![SingleWall]) ![Ball] -> [Ball] nextBallPositions (horizontal,vertical) balls = map (checkHorizontalWalls horizontal) ( map (checkVerticalWalls vertical) ( computeNextBallPositions [] balls)) where computeNextBallPositions :: ![Ball] ![Ball] -> [Ball] computeNextBallPositions ballsDone [ball:balls] = computeNextBallPositions [ballDone:newBallsDone] newBalls where (newBallsDone,newBalls,ballDone) = checkBallCollisions ballsDone balls ball checkBallCollisions :: ![Ball] ![Ball] !Ball -> (![Ball],![Ball],!Ball) checkBallCollisions balls1 balls2 ball = (newBalls1,newBalls2,ball2) where (newBalls1,ball1) = checkBallCollision balls1 ball (newBalls2,ball2) = checkBallCollision balls2 ball1 checkBallCollision :: ![Ball] !Ball -> (![Ball],!Ball) checkBallCollision [ball2=:{bCenter=center2,bRadius=radius2,bSpeed=step2}:others] ball1=:{bCenter=center1,bRadius=radius1,bSpeed=step1} | dist (moveBall ball1).bCenter center2<=toReal (radius1+radius2) # (others,ball1) = checkBallCollision others {ball1 & bSpeed=step2} = ([{ball2 & bSpeed=step1}:others],ball1) | otherwise # (others,ball1) = checkBallCollision others ball1 = ([ball2:others],ball1) checkBallCollision others ball = (others,ball) computeNextBallPositions ballsDone _ = ballsDone checkHorizontalWalls :: ![SingleWall] !Ball -> Ball checkHorizontalWalls [((a,b),interior):walls] ball=:{bCenter,bRadius,bSpeed} | interior<>startInterior = checkHorizontalWalls walls ball | not collision = checkHorizontalWalls walls ball1 | otherwise = ball1 where c = (moveBall ball).bCenter speed1 = if collision {bSpeed & vy=0-bSpeed.vy} bSpeed collision = (between c.x (a.x-bRadius) (b.x+bRadius)) && (sign bSpeed.vy<>interior) && (if posSign (a.y+signRadius>=c.y) (a.y+signRadius<=c.y)) signRadius = interior*bRadius posSign = interior>0 startInterior = sign (bCenter.y-a.y) ball1 = {ball & bSpeed=speed1} checkHorizontalWalls _ ball = ball checkVerticalWalls :: ![SingleWall] !Ball -> Ball checkVerticalWalls [((a,b),interior):walls] ball=:{bCenter,bRadius,bSpeed} | interior<>startInterior = checkVerticalWalls walls ball | not collision = checkVerticalWalls walls ball1 | otherwise = ball1 where c = (moveBall ball).bCenter speed1 = if collision {bSpeed & vx=0-bSpeed.vx} bSpeed collision = (between c.y (a.y-bRadius) (b.y+bRadius)) && ((sign bSpeed.vx<>interior) && (if posSign (a.x+signRadius>=c.x) (a.x+signRadius<=c.x))) signRadius = interior*bRadius posSign = interior>0 startInterior = sign (bCenter.x-a.x) ball1 = {ball & bSpeed=speed1} checkVerticalWalls _ ball = ball moveBall :: !Ball -> Ball moveBall ball=:{bCenter,bSpeed} = {ball & bCenter=movePoint bSpeed bCenter} shareLocal :: !Local -> (!Local,!*Local) shareLocal local=:{talkTo,barrel,balls} = (local, {talkTo=talkTo,barrel=barrel,balls=balls})