[comp.sys.mac.programmer] Dragging playing pieces on a playing field

franco@bbn.com (Frank A. Lonigro) (12/08/88)

[ Go away you line eater you! ]

---

Thanks again to all who responded to my previous posting with subject line
	"Thanks! (was "Question about GrafPort transfer modes...")"

You all helped me to solve my problem.  So, I'm now going to share with
all of you how I solved my problem.

For those of you who don't remember my previous postings, I was having a
problem with transfer modes.  What I wanted to do was to draw a picture,
which is a game playing piece, onto a playing field(a checker board) without
the rectangular shaped box around it wiping out the background.  I was
using DrawPicture, which was my problem.  The solution was to use CopyBits.
CopyBits worked fine in srcXor mode except I didn't like the result.  What
I wanted to achieve instead was to have a 1 or 2 pixel white border in the
shape of the playing piece around the playing piece.  I solved this by using
ICON's instead of PICT's, by defining a mask for every icon which was 1 or 2
pixels bigger around the outer rim of the piece.  Then using CopyMask to
draw the ICON and MASK onto the play field.  This was the most exciting
programming discovery yet and I got really enthusiastic about reaching my
next goal, to drag the playing piece around without wiping out the playing
field background and I have you guys to thank for this one.

To drag the playing piece around you need to keep track of what the background
looked like before the playing piece was copied there.  So moving the piece
around is simply:
	put back the saved background; /* This makes the piece disappear
                                        * for a while until you draw it in
                                        * its new spot.  This causes the
                                        * piece to "flicker" which I have a
                                        * problem with and I wish to solve.
                                        */
	save the background where the piece is going to move to;
	copymask the icon and mask to the new spot;

This method works very well, except for the "flicker".  My next question:
"Is their a way to eliminate this "flicker"?". I guess what I'm really
asking for is a better way of restoring the background under where the
piece was.  Any clues?????

Well anyway, for now I can deal with the "flicker".  So here is sample "C"
code for how to drag a playing piece around on a playing field.  Actually
code fragments that should get you started.

/* Sample code starts here	*/

/*
  This is how I defined my playing piece.  In actuality you will probably want
  an array of playing pieces, but this will do for an example.
 */
struct piece {
	Rect	iconR, /* an offscreen rect that contains the ICON */
		maskR, /*       "       "         "       the Mask */
		saveR, /* what the background looked like	*/
		destR; /* where the piece is now on the board	*/
	int	icon,  /* ICON resource ID #		*/
		mask,  /* ICON Mask resource ID #	*/
		square;/* the number of the square the piece is on */
} aPiece;

/* The procedure to initialize the piece data structure */
SetUpPiece ()
{
        int     itemType;
        Handle  itemHandle;
        Rect    itemRect;

        aPiece.square = boardD4;
        aPiece.icon = bknightICON;
        aPiece.mask = mknightICON;

        GetDItem (theDialog, aPiece.square, &itemType, &itemHandle, &itemRect);

        SetRect (&aPiece.destR, itemRect.left+5, itemRect.top+2,
                itemRect.right-5, itemRect.bottom-2); /* 32 x 32 Dest Rect */

	/* should use offscreen rects	*/
        SetRect (&aPiece.maskR, 400, 30, 432, 62);      /* Mask */
        SetRect (&aPiece.iconR, 432, 30, 464, 62);      /* ICON */
        SetRect (&aPiece.saveR, 464, 30, 496, 62);      /* old  */
}

/* procedure to draw the piece at its current location on the playing field */
DrawPiece ()
{
	Handle	theIcon, theMask;
	GrafPtr	curPort;

	GetPort (&curPort);
	SetPort (thePlayField);

	/* the ICON and Mask are stored off screen */
	theIcon = GetIcon (aPiece.icon);
	theMask = GetIcon (aPiece.mask);
	PlotIcon (&aPiece.maskR, theMask);
	PlotIcon (&aPiece.iconR, theIcon);

	/* Save copy of old position off screen	*/
	CopyBits (&(thePlayField)->portBits, &(thePlayField)->portBits,
		  &aPiece.destR, &aPiece.saveR, srcCopy, nil);

	/* Copy in the ICON and Mask to the playing field(destR) */
	CopyMask (&(thePlayField)->portBits, /* theIcon		*/
		  &(thePlayField)->portBits, /* theMask		*/
		  &(thePlayField)->portBits, /* destination	*/
		  &aPiece.iconR, &aPiece.maskR, &aPiece.destR);

	SetPort (curPort);
}

/*
  So now that the playing piece is set up and drawn on the board, to drag it
  around you need to act accordingly to a mouseDown event.  Here is how I
  handled this:
 */
	case mouseDown :
		SetPt (&evPoint, theEvent->where.h, theEvent->where.v);
		where = FindWindow (evPoint, &whichWindow);
		switch (where) {
		case inContent :
			if (whichWindow == thePlayField) {
				if (PtInRect (evPoint, &aPiece.destR))
					DragPiece (aPiece, evPoint);
				else
					SysBeep (1); /* Not moving a piece */
				break;
			}
			/* Handle other windows */
			break;
		}
		break; /* out of mouseDown */

/*
  HERE IS THE ROUTINE YOU ALL HAVE BEEN WAITING FOR:
 */
DragPiece (apiece, from)
struct piece apiece;
Point from;
{
	Point	current, to;
	GrafPtr	curPort;

	GetPort (&curPort);
	SetPort (thePlayField);

	/* GlobalToLocal (&from);
	 * you may need this, I didn't for some reason
	 */

	while ( StillDown() ) { /* mouse is still down */
		GetMouse (&current);
		if ( ! EqualPt(from, current) ) { /* mouse moved */

			/* Get dh, dv */
			SetPt (&to, current.h, current.v);
			SubPt (from, &to);

			/* Put back the background	*/
			CopyBits (&(thePlayField)->portBits,
				  &(thePlayField)->portBits,
				  &aPiece.saveR, &aPiece.destR, srcCopy, nil);

			/* Move the piece	*/
			OffsetRect (&aPiece.destR, to.h, to.v);

			/* Save copy of new background	*/
			CopyBits (&(thePlayField)->portBits,
				  &(thePlayField)->portBits,
				  &aPiece.destR, &aPiece.saveR, srcCopy, nil);

			/* Copy in the ICON and Mask	*/
			CopyMask (&(thePlayField)->portBits,
				  &(thePlayField)->portBits,
				  &(thePlayField)->portBits,
				  &aPiece.iconR, &aPiece.maskR, &aPiece.destR);
		}
		/* Make from the current location */
		SetPt (&from, current.h, current.v);
	}
	SetPort (curPort);
}

/* End of sample code	*/

I hope this helps out anyone who is trying to achieve playing piece animation.
I look forward to your comments and any other helpful hints you can provide.
I may be needing more help sooner than you think.

Thanks again,
-franco

franco@bbn.com
franco%bbn.com@relay.cs.net
...!harvard!bbn!franco

PS.  I don't pretend to claim that what I presented is an example of good
or excelent programming practice, I only claim it to be a simple but effect
learning example in achieving animation for those of you, who like me, didn't
have a clue as to how to achieve it.

oster@dewey.soe.berkeley.edu (David Phillip Oster) (12/08/88)

I am the author of Shepard Tones, among many others. The reason this
solution flickers is:
1.) you restore the background, erasing the old position of the piece
2.) you draw the pice in its new location.
in between the two draws, there is a split second where the background is
visible, because you haven't drawn the new piece yet. to get this to be
smooth, you need to add the following:

if the old and new positions alllow any part of the old and new objects to
overlap, then do all of the above to an off-screen bitmap, then copy the
result to the screen, This gets around the transient background problem.
Note, this offscreen bitmap can be quite small: at most twice the size of
the object you are moving.