[mod.mac.sources] BlobDemo3 - Blob Manager Demo source

macintosh@felix.UUCP (01/31/87)

#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	BlobDemo/DemoPong.c
#	BlobDemo/DemoPyramid.c
#	BlobDemo/DemoRadix.c
mkdir BlobDemo
sed 's/^X//' << 'SHAR_EOF' > BlobDemo/DemoPong.c
X/*
X	Blob Manager Demonstration:  Pong hau k'i module
X
X	This is an oriental game played on a 5-position board.  Each player
X	has 2 pieces, and the goal is to position to pieces so that the
X	other player cannot move.  Pieces may be moved onto the empty
X	position only if the two are connected by a line.
X
X	*     *
X	|\  / |
X	|  *  |
X	|/  \ |
X	*-----*
X
X	Since it's so simple, the game gives no feedback.  You simply
X	cannot move if the game is over.  The reset "button" can be
X	used to start over.
X
X	5 August 1986		Paul DuBois
X*/
X
X
X# include	"BlobDemo.h"
X# include	<ToolboxUtil.h>
X
X
X# define	pieceSize	20		/* size of each piece */
X# define	hOff		15		/* top left corner of playing area */
X# define	vOff		20
X# define	hSize		80		/* size of playing area */
X# define	vSize		60
X# define	vMesg		100
X
X
Xstatic GrafPtr			pongPort;
X
Xstatic BlobSetHandle	boardBlobs = nil;
Xstatic BlobHandle		board[5];
Xstatic BlobSetHandle	donors = nil;
Xstatic BlobHandle		current;
Xstatic BlobHandle		previous;
Xstatic BlobSetHandle	misc;
Xstatic BlobHandle		resetBlob;	/* simulated control */
Xstatic Str255			statusStr;
Xstatic BlobHandle		statusBlob;
X
X
X/*
X	Define center points of each position
X*/
X
Xstatic Point			intersection[5] =
X{
X	{ vOff, hOff },
X	{ vOff, hOff + hSize },
X	{ vOff + vSize/2, hOff + hSize/2 },
X	{ vOff + vSize, hOff },
X	{ vOff + vSize, hOff + hSize }
X};
X
X/*
X	Define connections between positions
X*/
X
Xstatic Boolean			connected[5][5] =
X{
X	{ false,	false,	true,	true,	false },
X	{ false,	false,	true,	false,	true },
X	{ true,	true,	false,	true,	true },
X	{ true,	false,	true,	false,	true },
X	{ false,	true,	true,	true,	false } 
X};
X	
X
Xstatic StatusMesg (s, b)
XStr255		s;
XBlobHandle	b;
X{
XRect	r;
X
X	SetRect (&r, 0, vMesg, 60, vMesg + 20);
X	TextBox (s+1, (long) s[0], &r, 2);
X	GlueGlob (b, GetBlobHandle (misc, 0));
X	StrCpy (statusStr, s);
X	statusBlob = b;
X}
X
X
X/*
X	The donor set consists of two blobs, each filled with a different
X	pattern
X*/
X
Xstatic MakeDonors ()
X{
XBlobHandle	b;
XRect		r;
XRgnHandle	rgn;
XPattern		p;
Xint			i;
X
X	donors = NewBlobSet ();
X	for (i = 9; i <= 16; i += 7)	/* use patterns 9, 16 */
X	{
X		rgn = NewRgn ();
X		OpenRgn ();
X		SetRect (&r, 0, 0, pieceSize, pieceSize);
X		FrameOval (&r);
X		CloseRgn (rgn);
X
X		b = NewBlob (donors, false, infiniteGlue, false, 0L);
X		OpenBlob ();
X		GetIndPattern (&p, sysPatListID, i);
X		FillOval (&r, p);
X		FrameOval (&r);
X		CloseRgnBlob (b, rgn, rgn);
X		DisposeRgn (rgn);
X	}
X}
X
X
Xstatic BlobHandle MakeReceptor (bSet, h, v)
XBlobSetHandle	bSet;
Xint				h, v;
X{
XBlobHandle	b;
XRect		r;
XRgnHandle	rgn;
X
X	SetRect (&r, h, v, h + pieceSize, v + pieceSize);
X	rgn = NewRgn ();
X	OpenRgn ();
X	FrameOval (&r);
X	CloseRgn (rgn);
X
X	b = NewBlob (bSet, false, 0, false, 0L);
X	OpenBlob ();
X	EraseOval (&r);
X	FrameOval (&r);
X	CloseRgnBlob (b, rgn, rgn);
X	DisposeRgn (rgn);
X	return (b);
X}
X
X
Xstatic MakeBoard ()
X{
Xint			i;
XRect		r;
X
X	boardBlobs = NewBlobSet ();
X	for (i = 0; i < 5; i++)
X	{
X		board[i] = MakeReceptor (boardBlobs,
X									intersection[i].h - pieceSize/2,
X									intersection[i].v - pieceSize/2);
X	}
X
X	misc = NewBlobSet ();
X	(void) MakeReceptor (misc, 60, vMesg - 2);
X	SetRect (&r, 0, 0, 20, 90);
X	OffsetRect (&r, pongPort->portRect.right - 25, 5);
X	resetBlob = NewVButtonBlob (misc, &r, "\pReset", false, 0L);
X}
X
X
X/*
X	Set the board to the initial configuration
X*/
X
Xstatic Reset ()
X{
Xint		i;
X
X	UnglueGlobSet (boardBlobs);		/* clear all globs */
X	GlueGlob (GetBlobHandle (donors, 0), board[0]);
X	GlueGlob (GetBlobHandle (donors, 0), board[1]);
X	GlueGlob (GetBlobHandle (donors, 1), board[3]);
X	GlueGlob (GetBlobHandle (donors, 1), board[4]);
X	current = FirstBlob (donors);
X	previous = NextBlob (current);
X	StatusMesg ("\pMove:", current);
X	HiliteBlob (resetBlob, inDragBlob, dimDraw);
X}
X
X
X/*
X	Given a blob handle, find the board position that corresponds to it.
X*/
X
Xstatic BoardPos (b)
XBlobHandle	b;
X{
Xint		i;
X
X	for (i = 0; i < 5; ++i)
X	{
X		if (board[i] == b)
X			return (i);
X	}
X	/* shouldn't ever get here */
X}
X
X
X/*
X	Test whether a piece can move or not.  This is true only if
X	the current position is connected to the empty position (of which
X	there is only one).
X*/
X
Xstatic Boolean CanMove (n)
Xint		n;
X{
Xint		i;
X
X	for (i = 0; i < 5; ++i)
X	{
X		if (i != n && connected [i][n] && BGlob (board[i]) == nil)
X			return (true);
X	}
X	return (false);
X}
X
X
X/*
X	See whether the game is over or not.  It's over if no piece 
X	of the current player can move.
X*/
X
Xstatic Boolean GameOver ()
X{
Xint		i;
X
X	for (i = 0; i < 5; ++i)
X	{
X		if (BGlob (board[i]) == current && CanMove (i))
X			return (false);
X	}
X	return (true);
X}
X
X
X
Xstatic Mouse (pt, t, mods)
XPoint	pt;
Xlong	t;
Xint		mods;
X{
XBlobHandle	b;
Xint			i;
X
X	if (TestBlob (resetBlob, pt) == inDragBlob)
X	{
X		if (BTrackMouse (resetBlob, pt, inFullBlob))
X			Reset ();
X	}
X	else
X	{
X		BlobClick (pt, t, nil, boardBlobs);
X		if (BClickResult () == bcXfer)
X		{
X			b = current;				/* switch player */
X			current = previous;
X			previous = b;
X			if (GameOver ())			/* see if board is locked */
X			{
X				StatusMesg ("\pWins:", previous);
X				HiliteBlob (resetBlob, inDragBlob, normalDraw);
X			}
X			else
X				StatusMesg ("\pMove:", current);
X		}
X	}
X}
X
X
X
X/*
X	When a piece is clicked on, the advisory checks whether the piece
X	belongs to the correct player and whether the piece has
X	any legal moves available to it.  If so, it returns true, so that
X	BlobClick is allowed to drag the piece.  After the piece has been
X	dragged, the advisory checks whether the position it was dragged to
X	is legal (connected to original position), and returns true if so.
X
X	Messages passed to the advisory follow the pattern
X
X	{ { advRClick advRClick* } advXfer* }*
X
X	where * means 0 or more instances of the thing *'ed.  In particular,
X	the advXfer message is never seen without a preceding advRClick.
X*/
X
Xstatic Boolean Advisory (mesg, b)
Xint			mesg;
XBlobHandle	b;
X{
Xstatic int	n;		/* static to save board position of click on piece */
X
X	switch (mesg)
X	{
X
X	case advRClick:	/* first click on piece */
X
X		if (BGlob (b) != current)
X			return (false);			/* can only move current piece(s) */
X		n = BoardPos (b);			/* find where it is */
X		return (CanMove (n));
X
X	case advXfer:	/* Mouse released after dragging piece */
X
X		return (connected [n][BoardPos (b)]);
X	}
X}
X
X
X
X/*
X	Activate the window:  Set the advisory function and set the permissions
X	to transfer-only.  Clear, replace, duplication and swap are off.
X	Since replace is off, the advisory doesn't have to check whether
X	the dragged piece was dragged onto a blob that already has a glob.
X
X	Deactivate the window:  Clear the advisory.
X*/
X
Xstatic Activate (active)
XBoolean	active;
X{
X
X	if (active)
X	{
X		SetDragRects (pongPort);
X		SetBCPermissions (false, true, false, false, false);	/* xfer only */
X		SetBCAdvisory (Advisory);
X	}
X	else
X	{
X		SetBCAdvisory (nil);
X	}
X}
X
X
X
X
Xstatic Update (resized)
XBoolean	resized;
X{
X	MoveTo (intersection[3].h, intersection[3].v);	/* draw board */
X	LineTo (intersection[0].h, intersection[0].v);
X	LineTo (intersection[4].h, intersection[4].v);
X	LineTo (intersection[1].h, intersection[1].v);
X	LineTo (intersection[3].h, intersection[3].v);
X	LineTo (intersection[4].h, intersection[4].v);
X	DrawBlobSet (boardBlobs);
X	DrawBlobSet (misc);
X	StatusMesg (statusStr, statusBlob);
X}
X
X
XPongInit	()
X{
X	SkelWindow (pongPort = GetDemoWind (pongWindRes),
X				Mouse,			/* mouse clicks */
X				nil,			/* key clicks */
X				Update,			/* updates */
X				Activate,		/* activate/deactivate events */
X				nil,			/* close window */
X				DoWClobber,		/* dispose of window */
X				nil,			/* idle proc */
X				false);			/* irrelevant, since no idle proc */
X
X	MakeDonors ();
X	MakeBoard ();
X	Reset ();
X	EnableBlobSet (boardBlobs);
X	EnableBlobSet (misc);
X	Update ();
X	ValidRect (&pongPort->portRect);
X}
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > BlobDemo/DemoPyramid.c
X/*
X	Blob Manager Demonstration:  Pyramid module
X
X	This is played on a checkerboard.  Each player has 10 pieces
X	arranged in a pyramid.  Moves are into adjacent diagonal squares,
X	in either direction.  Jumps are also allowed, over either side's
X	pieces.  The first player to reassemble a pyramid on the side
X	opposite to that which he started on wins.
X
X	I haven't thought about deadlock detection yet.
X
X	11 August 1986		Paul DuBois
X*/
X
X
X# include	"BlobDemo.h"
X
X
X# define	rows		8		/* number of rows on board */
X# define	columns		8		/* number of columns on board */
X# define	pieceSize	20		/* size of each piece */
X# define	vMessage	165		/* vertical position of message */
X
X
Xstatic GrafPtr			pyrPort;
X
X
Xstatic BlobSetHandle	boardBlobs = nil;
Xstatic BlobHandle		board[columns][rows];
Xstatic BlobSetHandle	donors = nil;
Xstatic BlobHandle		current;
Xstatic BlobHandle		pBlack;
Xstatic BlobHandle		pWhite;
Xstatic BlobHandle		lastXferPiece;
Xstatic Boolean			lastWasJump;
Xstatic BlobSetHandle	misc;
Xstatic BlobHandle		resetBlob;	/* simulated control */
X
Xstatic int				hMid;
Xstatic Boolean			paused;
Xstatic Str255			statusStr = "\p";
X
X/*
X	Initial configuration
X*/
X
Xstatic int	layout[rows][columns] =	/* 0 = black, 1 = white, 2 = none */
X{
X	0,	2,	2,	2,	2,	2,	2,	2,
X	2,	0,	2,	2,	2,	2,	2,	1,
X	0,	2,	0,	2,	2,	2,	1,	2,
X	2,	0,	2,	0,	2,	1,	2,	1,
X	0,	2,	0,	2,	1,	2,	1,	2,
X	2,	0,	2,	2,	2,	1,	2,	1,
X	0,	2,	2,	2,	2,	2,	1,	2,
X	2,	2,	2,	2,	2,	2,	2,	1
X};
X
X
Xstatic StatusMesg (s)
XStr255	s;
X{
XRect	r;
X
X	SetRect (&r, 0, vMessage, pyrPort->portRect.right - 25, vMessage + 12);
X	TextBox	(s+1, (long) s[0], &r, 1);
X	StrCpy (statusStr, s);
X}
X
X
X/*
X	The donor set consists of two blobs, a white piece and a black
X	piece.
X*/
X
Xstatic MakeDonors ()
X{
XRect		r;
X
X	donors = NewBlobSet ();
X	SetRect (&r, 0, 0, pieceSize - 1, pieceSize - 1);
X	pBlack = NewBlob (donors, false, infiniteGlue, false, 0L);
X	OpenBlob ();
X	PaintRect (&r);
X	PenMode (patBic);
X	FrameOval (&r);
X	PenNormal ();
X	CloseRectBlob (pBlack, &r, &r);
X	pWhite = NewBlob (donors, false, infiniteGlue, false, 0L);
X	OpenBlob ();
X	PaintRect (&r);
X	EraseOval (&r);
X	CloseRectBlob (pWhite, &r, &r);
X}
X
X
X/*
X	Board position drawing procedure.  All positions are drawn the
X	same - a black rectangle.  However, the unused positions are set
X	dimmed by InitBoard, so the drawing mechanisms make them gray.
X	For the drag region of used positions,
X	this proc is only called if the position has no piece (i.e., no
X	glob) because the pieces are picture blobs.
X
X	For these reasons, bSrc and bDst are always equal when
X	DrawPos is called.
X*/
X
Xstatic DrawPos (bSrc, bDst, partCode)
XBlobHandle	bSrc, bDst;
Xint			partCode;		/* ignored - always draw entire blob */
X{
XRect	r;
X
X	r = BStatBox (bDst);
X	PaintRect (&r);
X}
X
X
X/*
X	Build the board, which consists of alternating black and gray
X	squares.  They are actually the same as far as the drawing proc
X	goes, but the gray squares are set to be dimmed so that the
X	normal drawing mechanisms do the work of making the two types
X	of square look distinct.  The fact that the gray squares are
X	dimmed also makes the hit-testing mechanisms ignore them.
X*/
X
Xstatic InitBoard ()
X{
Xint			h, v;
XRect		r, r2;
XBlobHandle	b;
X
X	boardBlobs = NewBlobSet ();
X	for (v = 0; v < rows; v++)
X	{
X		for (h = 0; h < columns; h++)
X		{
X			b = NewBlob (boardBlobs, false, 0, false, 0L);
X			board[h][v] = b;
X			SetRect (&r, 0, 0, pieceSize, pieceSize);
X			OffsetRect (&r, h * pieceSize, v * pieceSize);
X			r2 = r;
X			InsetRect (&r2, 1, 1);
X			SetProcRectBlob (b, DrawPos, &r2, &r);
X
X			/*
X				All unused postions have coordinates that sum to an
X				odd number.
X			*/
X
X			if ((h + v) % 2 == 1)
X				SetBDrawMode (b, inFullBlob, dimDraw);
X		}
X	}
X	ShowBlobSet (boardBlobs);
X}
X
X
X/*
X	Set the board to the initial configuration
X*/
X
Xstatic Reset ()
X{
Xint			i, j, val;
XBlobHandle	b;
X
X	UnglueGlobSet (boardBlobs);		/* clear all globs */
X	for (i = 0; i < columns; ++i)
X	{
X		for (j = 0; j < rows; ++j)
X		{
X			val = layout[i][j];
X			b = board[i][j];
X			DisposeBlobMatchSet (b);
X			if (val != 2)
X			{
X				GlueGlob (GetBlobHandle (donors, val), b);
X				NewBlobMatch (GetBlobHandle (donors, 1 - val), b);
X			}
X		}
X	}
X	current = pWhite;
X	StatusMesg ("\p");
X	paused = false;
X	lastWasJump = false;	/* last move wasn't a jump */
X}
X
X
Xstatic Pause (msg)
XStringPtr	msg;
X{
X	StatusMesg (msg);
X	paused = true;
X}
X
X
X/*
X	Given a blob handle, find the board position that corresponds to it.
X	This is used to map hits in the blob set (a list) to the position in
X	the board (a 2-d array).
X*/
X
Xstatic BoardPos (b, h, v)
XBlobHandle	b;
Xint			*h, *v;
X{
Xint		i, j;
X
X	for (i = 0; i < columns; ++i)
X	{
X		for (j = 0; j < rows; ++j)
X		{
X			if (board[i][j] == b)
X			{
X				*h = i;
X				*v = j;
X				return;
X			}
X		}
X	}
X	/* shouldn't ever get here */
X}
X
X
X/*
X	Check whether a board position is empty.  The coordinates must be
X	legal, the position must be used in the current configuration,
X	and the position must have a piece on it.
X*/
X
Xstatic Boolean PosEmpty (h, v)
Xint		h, v;
X{
X	return (h >= 0 && h < columns && v >= 0 && v < rows
X			&& (h + v) % 2 == 0
X			&& BGlob (board[h][v]) == nil);
X}
X
X
Xstatic Boolean PosFilled (h, v)
Xint		h, v;
X{
X	return (h >= 0 && h < columns && v >= 0 && v < rows
X			&& (h + v) % 2 == 0
X			&& BGlob (board[h][v]) != nil);
X}
X
X
X/*
X	Test whether a piece can move or not.  It can move if there's an
X	empty adjacent diagonal, or a piece adjacent with an empty square
X	on the other side of it.  If the last move was a jump, and the
X	piece is the same one that jumped, then can only move if can
X	keep jumping.
X*/
X
Xstatic Boolean CanMove (h, v)
Xint		h, v;
X{
X	if (   (PosFilled (h - 1, v - 1) && PosEmpty (h - 2, v - 2))
X		|| (PosFilled (h + 1, v - 1) && PosEmpty (h + 2, v - 2))
X		|| (PosFilled (h - 1, v + 1) && PosEmpty (h - 2, v + 2))
X		|| (PosFilled (h + 1, v + 1) && PosEmpty (h + 2, v + 2)))
X		return (true);
X
X	if (board[h][v] == lastXferPiece && lastWasJump)
X		return (false);
X
X	return (   PosEmpty (h - 1, v - 1)
X			|| PosEmpty (h + 1, v - 1)
X			|| PosEmpty (h - 1, v + 1)
X			|| PosEmpty (h + 1, v + 1) );
X}
X
X
X/*
X	Test for a win.  Pass the upper and lower limits on the rows to
X	test (either the top or botton half of the board).
X*/
X
X
Xstatic Boolean TestWin (loRow)
Xint		loRow;
X{
Xint			i, j;
XMatchHandle	m;
X
X	for (i = loRow; i < loRow + rows / 2; ++i)
X	{
X		for (j = 0; j < columns; ++j)
X		{
X			if ((m = (**board[j][i]).matches) != nil
X				&& BGlob (board[j][i]) != (**m).mBlob)
X				return (false);			/* at least one mismatch */
X		}
X	}
X	return (true);
X}
X
X
X
Xstatic Mouse (pt, t, mods)
XPoint	pt;
Xlong	t;
Xint		mods;
X{
X
X
X	if (TestBlob (resetBlob, pt))
X	{
X		if (BTrackMouse (resetBlob, pt, inFullBlob))
X			Reset ();
X	}
X	else if (!paused)
X	{
X		BlobClick (pt, t, nil, boardBlobs);
X		if (BClickResult () == bcXfer)
X		{
X			if (TestWin (0))
X				Pause ("\pWhite Wins");
X			else if (TestWin (rows / 2))
X				Pause ("\pBlack Wins");
X			else if (BGlob (lastXferPiece) == current)	/* switch if didn't move prev player */
X				current = (current == pBlack ? pWhite : pBlack);
X		}
X	}
X}
X
X
X
X/*
X	When a piece is clicked on, the advisory checks whether the piece has
X	any legal moves available to it.  If so, it returns true, so that
X	BlobClick is allowed to drag the piece.  After the piece has been
X	dragged, the advisory checks whether the position it was dragged to
X	is legal, and returns true if so.
X
X	Messages passed to the advisory follow the pattern
X
X	{ { advRClick advRClick* } advXfer* }*
X
X	where * means 0 or more instances of the thing *'ed.  In particular,
X	the advXfer message is never seen without a preceding advRClick.
X*/
X
Xstatic Boolean Advisory (mesg, b)
Xint			mesg;
XBlobHandle	b;
X{
Xstatic int	h, v;	/* static to save board position of click on piece */
Xint			h2, v2;
XBoolean		result;
X
X	switch (mesg)
X	{
X
X	case advRClick:	/* first click on piece */
X		if (BGlob (b) != current && !(b == lastXferPiece && lastWasJump))
X			return (false);
X		BoardPos (b, &h, &v);	/* find where it is */
X		return (CanMove (h, v));
X
X	case advXfer:	/* Mouse released after dragging piece */
X
X		BoardPos (b, &h2, &v2);
X		result =   (h2 == h - 1 || h2 == h + 1)
X				&& (v2 == v - 1 || v2 == v + 1);
X		if (result == true)
X			lastWasJump = false;
X		else
X		{
X			result =   (h2 == h - 2 || h2 == h + 2)
X					&& (v2 == v - 2 || v2 == v + 2)
X					&& PosFilled ((h + h2) / 2, (v + v2) / 2);
X			if (result == true)
X				lastWasJump = true;
X		}
X		if (result == true)
X			lastXferPiece = b;
X		return (result);
X	}
X}
X
X
X
X/*
X	Activate the window:  Set the advisory function and set the permissions
X	to transfer-only.  Clear, replace, duplication and swap are off.
X	Since replace is off, the advisory doesn't have to check whether
X	the dragged piece was dragged onto a blob that already has a glob.
X
X	Deactivate the window:  Clear the advisory.
X*/
X
Xstatic Activate (active)
XBoolean	active;
X{
X
X	if (active)
X	{
X		SetDragRects (pyrPort);
X		SetBCPermissions (false, true, false, false, false);	/* xfer only */
X		SetBCAdvisory (Advisory);
X	}
X	else
X	{
X		SetBCAdvisory (nil);
X	}
X}
X
X
Xstatic Update (resized)
XBoolean	resized;
X{
X	DrawBlobSet (boardBlobs);
X	DrawBlobSet (misc);
X	StatusMesg (statusStr);
X}
X
X
XPyrInit	()
X{
XRect	r;
X
X	SkelWindow (pyrPort = GetDemoWind (pyrWindRes),
X				Mouse,			/* mouse clicks */
X				nil,			/* key clicks */
X				Update,			/* updates */
X				Activate,		/* activate/deactivate events */
X				nil,			/* close window */
X				DoWClobber,		/* dispose of window */
X				nil,			/* idle proc */
X				false);			/* irrelevant, since no idle proc */
X
X	hMid = pyrPort->portRect.right / 2;
X
X	MakeDonors ();
X	InitBoard ();
X	misc = NewBlobSet ();
X	SetRect (&r, 0, 0, 20, 90);
X	OffsetRect (&r, pyrPort->portRect.right - 25,
X					(pyrPort->portRect.bottom - 90 - 25) / 2);
X	resetBlob = NewVButtonBlob (misc, &r, "\pReset", true, 0L);
X	Reset ();
X	ValidRect (&pyrPort->portRect);
X}
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > BlobDemo/DemoRadix.c
X
X/*
X	Blob Manager Demonstration:  Arithmetic problem module
X
X	Displays arithmetic problems to solve.  Allows radix to be set
X	anywhere from base 2 to base 10.
X
X	This module demonstrate a simple use of the BlobClick advisory
X	function.
X
X	26 July 1986		Paul DuBois
X*/
X
X
X# include	"BlobDemo.h"
X# include	<MenuMgr.h>
X# include	<ControlMgr.h>
X
X
X/*
X	Blobs types, used to distinguish different parts of problem
X*/
X
Xenum
X{
X	donorBlob,		/* for donor digits */
X	carryBlob,		/* for carry digits */
X	addendBlob,		/* for addend digits */
X	sumBlob,		/* for sum digits */
X	operBlob		/* for the plus sign */
X};
X
X
X# define	carryPos	10	/* horizontal position of carry digits */
X							/* addend and sum pos's are relative to this */
X# define	digitPos	110	/* position of donor digits */
X# define	digitSize	18	/* size of digit blobs */
X# define	digitGap	2	/* gap between blobs */
X# define	absMaxLen	10	/* absolute max number of digits in addends */
X
X
Xstatic GrafPtr			radixPort;
Xstatic MenuHandle		radixMenu;
Xstatic ControlHandle	checkAns;
Xstatic ControlHandle	giveUp;
Xstatic ControlHandle	nextProb;
X
Xstatic int				radix = 10;		/* initially decimal */
X
Xstatic BlobSetHandle	digits = nil;	/* donor blobs */
Xstatic BlobSetHandle	problem = nil;	/* receptor blobs */
Xstatic BlobHandle		firstAddendBlob;
Xstatic BlobHandle		firstSumBlob;
Xstatic BlobHandle		plusSignBlob;
Xstatic int				maxLen = 6;		/* current max digits in addends */
Xstatic int				rightEdge;		/* right edge of problem display */
Xstatic int				lineX;			/* pos and length of line */
Xstatic int				lineY;			/* between lower addend and sum */
Xstatic int				lineLen;
X
Xstatic Boolean			paused;
X
X
X/*
X	Pick a problem digit.  Don't pick zero if canBeZero is false.
X*/
X
Xstatic PickDigit (canBeZero)
XBoolean	canBeZero;
X{
Xint		n;
X
X	do {
X		n = BlobRand (radix-1);		/* use Blob Manager rand function */
X	} while (!canBeZero && n == 0);
X	return (n);
X}
X
X
X/*
X	Make a blob.  Pass the x and y coordinates, the character drawn in the
X	blob, whether it needs an explicit match or not.  Carry digits don't
X	need an explicit match, digits of the sum do - except that the leftmost
X	sum blob might be unnecessary - so it can be zero or nothing.
X*/
X
Xstatic BlobHandle MakeBlob (bSet, x, y, c, needMatch, type)
XBlobSetHandle	bSet;
Xint				x, y;
Xint				c;
XBoolean			needMatch;
Xint				type;
X{
XBlobHandle		b;
X
X	b = MakeCharBlob (bSet, false, infiniteGlue, needMatch,
X				x, y, c);
X	SetBRefCon (b, c + ((long) type << 16));
X	return (b);
X}
X
X
X/*
X	Return blob type, encoded as the high word of the reference
X	constant.
X*/
X
Xstatic GetBlobType (b)
XBlobHandle	b;
X{
X	return (HiWord (GetBRefCon (b)));
X}
X
X
X/*
X	Generate a new problem to solve.  The addends are at least two digits
X	long.  The max number of digits in the sum will be 1 greater than the
X	longest addend.  The max number of carry digits is the same as the length
X	of the longest addend.
X
X	Digits in the addend and sum arrays are numbered from right to left.
X*/
X
Xstatic GenerateProblem ()
X{
Xint			addend1[absMaxLen];
Xint			addend2[absMaxLen];
Xint			carry[absMaxLen];
Xint			sum[absMaxLen+1];
Xint			add1Len;			/* number of digits in addend 1 */
Xint			add2Len;			/* number of digits in addend 2 */
Xint			sumLen;				/* number of digits in sum */
Xint			carryLen;			/* number of carry digits */
XBlobHandle	b;
Xint			i;
Xint			x, y;
X
X	add1Len = BlobRand (maxLen - 2) + 2;	/* addends are at least 2 digits */
X	add2Len = BlobRand (maxLen - 2) + 2;
X	carryLen = (add1Len > add2Len ? add1Len : add2Len);
X	sumLen = carryLen + 1;
X
X	/* zero the addends and the carry digits */
X
X	for (i = 0; i < absMaxLen; ++i)
X	{
X		addend1[i] = 0;
X		addend2[i] = 0;
X		carry[i] = 0;
X	}
X
X	/* generate digits for the addends and determine the sum. */
X	/* the leftmost digit of the addends should not be zero */
X
X	for (i = 0; i < add1Len; ++i)
X		addend1[i] = PickDigit (i != add1Len - 1);
X	for (i = 0; i < add2Len; ++i)
X		addend2[i] = PickDigit (i != add2Len - 1);
X	for (i = 0; i < sumLen; ++i)
X	{
X		sum[i] = addend1[i] + addend2[i];
X		if (i > 0 && sum[i-1] >= radix)
X		{
X			sum[i-1] -= radix;
X			++sum[i];
X			++carry[i-1];
X		}
X	}
X
X	/* get rid of any old problem blob set */
X
X	if (problem != nil)
X	{
X		HideBlobSet (problem);
X		DisposeBlobSet (problem);
X	}
X	problem = NewBlobSet ();
X
X	/* generate blobs for carry digits - they don't need an explicit */
X	/* match.  carry digits are placed over every addend digit */
X	/* except the rightmost */
X
X	x = rightEdge - digitSize;
X	y = carryPos;
X	for (i = 0; i < carryLen; ++i)
X	{
X		x -= digitSize + digitGap;
X		b = MakeBlob (problem, x, y, ' ', false, carryBlob);
X		NewBlobMatch (GetBlobHandle (digits, carry[i]), b);
X	}
X
X	/* generate blobs for addends.  addend blobs are given a non-zero */
X	/* reference value so the advisory function can distinguish them */
X	/* from carry and sum blobs easily. */
X
X	x = rightEdge - digitSize;
X	y += digitSize + digitGap;
X	for (i = 0; i < add1Len; ++i)
X	{
X		b = MakeBlob (problem, x, y, ' ', false, addendBlob);
X		GlueGlob (GetBlobHandle (digits, addend1[i]), b);
X		if (i == 0)
X			firstAddendBlob = b;
X		x -= digitSize + digitGap;
X	}
X
X	x = rightEdge - digitSize;
X	y += digitSize + digitGap;
X	for (i = 0; i < add2Len; ++i)
X	{
X		b = MakeBlob (problem, x, y, ' ', false, addendBlob);
X		GlueGlob (GetBlobHandle (digits, addend2[i]), b);
X		x -= digitSize + digitGap;
X	}
X	
X	/* add the plus sign */
X
X	plusSignBlob = MakeBlob (problem, x, y, '+', false, operBlob);
X	FreezeBlob (plusSignBlob);
X
X	/* figure out length and position of line 'tween addend2 and sum */
X
X	y += digitSize + digitGap;
X	lineLen = sumLen * (digitSize + digitGap) - digitGap;
X	lineX = rightEdge - lineLen;
X	lineY = y;
X
X	/* make sum digits.  these must be matched explicitly - except possibly */
X	/* the leftmost one. */
X
X	x = rightEdge - digitSize;
X	y += digitGap + 2;
X	for (i = 0; i < sumLen; ++i)
X	{
X		b = MakeBlob (problem, x, y, ' ', true, sumBlob);
X		if (i == sumLen - 1 && sum[i] == 0)
X			ClearBlobFlags (b, bNeedGlobMask);	/* explicit match unneeded */
X		NewBlobMatch (GetBlobHandle (digits, sum[i]), b);
X		if (i == 0)
X			firstSumBlob = b;
X		x -= digitSize + digitGap;
X	}
X}
X
X
Xstatic DrawLine ()
X{
X	MoveTo (lineX, lineY);
X	LineTo (rightEdge, lineY);
X}
X
X
Xstatic NextProblem ()
X{
X	InvalRect (&radixPort->portRect);
X	PenMode (patBic);
X	DrawLine ();					/* erase line */
X	PenNormal ();
X	SetCTitle (checkAns, "\pCheck");
X	HiliteControl (checkAns, 0);	/* make sure these buttons are on */
X	HiliteControl (giveUp, 0);
X	paused = false;
X	/* choose numbers */
X	GenerateProblem ();
X	ShowBlobSet (problem);
X	DrawLine ();
X	ValidRect (&radixPort->portRect);
X}
X
X
X/*
X	Make digit set - creates only the digits are legal for the
X	current radix.
X*/
X
Xstatic MakeDigits ()
X{
Xint			i, x;
X
X	if (digits != nil)
X	{
X		HideBlobSet (digits);
X		DisposeBlobSet (digits);
X	}
X	digits = NewBlobSet ();
X	x = rightEdge - radix * (digitSize + digitGap) + digitGap;
X	for (i = 0; i < radix; ++i)
X	{
X		(void) MakeBlob (digits, x, digitPos, i + '0', false, donorBlob);
X		x += digitSize + digitGap;
X	}
X	ShowBlobSet (digits);
X}
X
X
X/*
X	Radix menu handler
X
X	Change the radix and choose a new problem.
X*/
X
Xstatic ChooseRadix (item)
Xint		item;
X{
X	CheckItem (radixMenu, radix-1, false);
X	radix = item + 1;	/* menu items start with Base 2 */
X	CheckItem (radixMenu, radix-1, true);
X	MakeDigits ();
X	NextProblem ();
X}
X
X
Xstatic Mouse (pt, t, mods)
XPoint	pt;
Xlong	t;
Xint		mods;
X{
XBlobHandle		b;
Xint				type;
XControlHandle	ctl;
X
X	if (FindControl	(pt, radixPort, &ctl))
X	{
X		if (TrackControl (ctl, pt, nil))	/* any button hit? */
X		{
X			if (ctl == nextProb)
X				NextProblem ();
X			else if (ctl == checkAns)
X			{
X				if (!paused)
X				{
X					/* give feedback (this freezes the receptors) */
X					BlobFeedback (problem, normalDraw, dimDraw);
X					SetCTitle (checkAns, "\pResume");
X					paused = true;
X				}
X				else
X				{
X					/* allow user to continue working */
X					SetCTitle (checkAns, "\pCheck");
X					paused = false;
X					ThawBlobSet (problem);		/* thaw entire problem */
X					FreezeBlob (plusSignBlob);	/* except plus sign */
X				}
X			}
X			else if (ctl == giveUp)
X			{
X			/*
X				Show answer.  Show only the non-zero carry digits,
X				and the sum digits.  Show the leftmost sum digit only
X				if it's non-zero.  Have to that the problem first,
X				because some of it may have been dimmed by Check.
X			*/
X				ThawBlobSet (problem);	/* might be frozen from Check */
X				for (b = FirstBlob (problem); NextBlob (b) != nil; b = NextBlob (b))
X				{
X					type = GetBlobType (b);
X					if (type == carryBlob)
X					{
X						UnglueGlob (b);	/* clear any glob it might have */
X						if (FirstBMatch (b) == GetBlobHandle (digits, 1))
X							ZGlueGlob (FirstBMatch (b), b);
X					}
X					else if (type == sumBlob)
X						ZGlueGlob (FirstBMatch (b), b);
X				}
X				/*
X					for loop leaves b pointing at last blob in problem set,
X					i.e., the leftmost sum digit.
X				*/
X				if (FirstBMatch (b) != GetBlobHandle (digits, 0))
X					ZGlueGlob (FirstBMatch (b), b);
X
X				HiliteControl (checkAns, 255);	/* make inactive */
X				HiliteControl (giveUp, 255);
X				paused = true;
X			}
X		}
X	}
X	else if (!paused)
X	{
X		BlobClick (pt, t, digits, problem);
X		if (BlobSetQuiet (problem))		/* done yet? */
X		{
X			HiliteControl (checkAns, 255);
X			HiliteControl (giveUp, 255);
X			paused = true;
X		}
X	}
X}
X
X
X/*
X	The purpose of the advisory is to allow digits from the addends
X	to be duplicated onto carry or sum digits, but to prevent addend
X	blobs from being cleared or duplicated onto, and to prevent donors
X	from being glued to them.  So, whenever a clear, glue, or duplicate
X	message if received, return false if the blob involved is an addend
X	to make BlobClick abort.  Addends are easily distinguished since
X	they have non-zero reference values.
X	
X	Transfer and swap messages will never be received since the
X	permissions are set to disallow those transaction types.
X	For any other message, return true to continue normal processing.
X*/
X
Xstatic Boolean Advisory (mesg, b)
Xint			mesg;
XBlobHandle	b;
X{
X	switch (mesg)
X	{
X		case advGlue:
X		case advUnglue:
X		case advDup:
X			if (GetBlobType (b) == addendBlob)
X				return (false);
X	}
X	return (true);
X}
X
X
Xstatic Activate (active)
XBoolean	active;
X{
X	if (active)
X	{
X		SetDragRects (radixPort);
X		SetBCPermissions (true, true, true, false, true);
X		SetBCAdvisory (Advisory);
X		radixMenu = GetMenu (radixMenuRes);
X		SkelMenu (radixMenu, ChooseRadix, DoMClobber);
X		CheckItem (radixMenu, radix-1, true);
X	}
X	else
X	{
X		SkelRmveMenu (radixMenu);	/* destroy handler */
X		SetBCAdvisory (nil);
X	}
X}
X
X
Xstatic Update ()
X{
X	DrawControls (radixPort);
X	DrawBlobSet (problem);
X	DrawLine ();
X	DrawBlobSet (digits);
X}
X
X
XRadixInit ()
X{
XRect	r;
X
X	SkelWindow (radixPort = GetDemoWind (radixWindRes),
X				Mouse,			/* mouse clicks */
X				nil,			/* key clicks */
X				Update,			/* updates */
X				Activate,		/* activate/deactivate events */
X				nil,			/* close window */
X				DoWClobber,		/* dispose of window */
X				nil,			/* idle proc */
X				false);			/* irrelevant, since no idle proc */
X
X	SetRect (&r, 0, 25, 70, 45);
X	OffsetRect (&r, radixPort->portRect.right - 80, 0);
X	checkAns = NewControl (radixPort, &r, "\pCheck", true, 0, 0, 0,
X							pushButProc, nil);
X	OffsetRect (&r, 0, 30);
X	giveUp = NewControl (radixPort, &r, "\pUncle", true, 0, 0, 0,
X							pushButProc, nil);
X	OffsetRect (&r, 0, 30);
X	nextProb = NewControl (radixPort, &r, "\pNext", true, 0, 0, 0,
X							pushButProc, nil);
X
X	rightEdge = radixPort->portRect.right - 100;
X	SetCharBlobSize (digitSize);
X	MakeDigits ();
X	NextProblem ();
X}
SHAR_EOF
exit