ftp.nice.ch/pub/next/games/network/MazeWar.2.0.NIHS.bs.tar.gz#/MazeWar/mazewar.c

This is mazewar.c in view mode; [Download] [Up]

/* $Header: mazewar.c,v 1.13 88/08/25 09:57:53 kent Exp $ */

/* 
 * mazewar.c - Rats in a maze
 * 
 * Author:	Christopher A. Kent
 * 		Western Research Laboratory
 * 		Digital Equipment Corporation
 * Date:	Wed Sep 24 1986
 */

/***********************************************************
Copyright 1986 by Digital Equipment Corporation, Maynard, Massachusetts,

                        All Rights Reserved

Permission to use, copy, modify, and distribute this software and its 
documentation for any purpose and without fee is hereby granted, 
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in 
supporting documentation, and that the names of Digital not be
used in advertising or publicity pertaining to disstribution of the
software without specific, written prior permission.  

DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

******************************************************************/

/* 
 * This is an adaptation of the Mazewar program built at Xerox PARC in
 * Mesa for Altos by Jim Guyton and others in the late 1970s and beyond.
 * Unlike other programs that have appeared from time to time, this
 * one attempts to remain faithful to the original spirit of the game,
 * with no fancy additions (like seeing your opponents or teleport
 * traps). I hope you enjoy it.
 */

/*
 * $Log:	mazewar.c,v $
 * Revision 1.13  88/08/25  09:57:53  kent
 * Copyright.
 * 
 * Revision 1.12  88/02/11  18:00:39  kent
 * Changes so the value of M.theSocket doesn't change (this makes the
 * window system code simpler).
 * 
 * Revision 1.11  88/02/11  17:52:41  kent
 * Move some code out of the play loop so it can be called by an "external" 
 * play loop (such as is needed for the X11 toolkit).
 * 
 * Revision 1.10  87/03/31  15:47:34  kent
 * Handle duplicated RAT_NEW packets while joining the game. If the 
 * guy is already in the game, just send him a status packet; previously,
 * he'd get added to the game again!
 * 
 * Revision 1.9  87/03/31  14:37:37  kent
 * Portability considerations, especially byteswapping to/from the net.
 * 
 * Revision 1.8  86/12/04  17:44:53  kent
 * Notify user also if shot; make sure non-dukes get notified on game joins.
 * 
 * Revision 1.7  86/12/03  18:13:10  kent
 * Cleaned up the shot handling code a bit. Was waiting two seconds
 * instead of one, and would only handle one shot off the queue
 * every time around the loop, instead of all applicable.
 * 
 * Also cleaned up a race in the port moving code for when the mover and
 * the quitter were on the same machine.
 * 
 * Revision 1.6  86/12/03  13:31:10  kent
 * 
 * 
 * Revision 1.5  86/12/03  10:15:03  kent
 * Only send location when moving, not every time screen needs updating.
 * 
 * Revision 1.4  86/12/03  10:00:19  kent
 * Changes to allow multiple players per machine.
 * 
 * Revision 1.3  86/12/01  23:44:42  kent
 * Housecleaning and documentation pass.
 * 
 * Revision 1.2  86/12/01  14:47:04  kent
 * Changes for a realistic implementation of shooting.
 * 
 * Revision 1.1  86/11/26  16:57:53  kent
 * Initial revision
 * 
 */

#ifndef	lint
static char rcs_ident[] = "$Header: mazewar.c,v 1.13 88/08/25 09:57:53 kent Exp $";
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <netinet/in.h>

#include <signal.h>
#include <stdio.h>
#include <strings.h>

#include "mazewar.h"

static int		i1 = 0;		/* random number hackery */
static int		i2 = 24;

static Boolean		updateView;	/* true if update needed */
static Boolean		sendLocation;	/* true if must send out location */

static MazeTypePtr	mp = M.maze;	/* easy access to Maze */

static RatHealth	ratHealth;	/* keep track of other players */

main(argc, argv)
char **argv;
{
	void	quit();

	signal(SIGHUP, quit);
	signal(SIGINT, quit);
	signal(SIGTERM, quit);
#ifdef NeXT
	mazerun();	/* NeXT Application Kit uses callbacks */
#else
	MazeInit(argc, argv);
	play();
#endif
}

#ifdef NeXT
play(eventp)
MWEvent *eventp;
#define event (*eventp)
{
	void	quit();
	static int ratDoctor();
#else
play()
{
	MWEvent		event;
	RatPacket	incoming;

	Boolean		KBEventPending();

	event.eventDetail = &incoming;

	while (1) {
		NextEvent(&event);
#endif
		if (!M.peeking)
			switch(event.eventType) {
			case EVENT_A:
				aboutFace();
				break;

			case EVENT_S:
				leftTurn();
				break;

			case EVENT_D:
				forward();
				break;

			case EVENT_F:
				rightTurn();
				break;

			case EVENT_BAR:
				backward();
				break;

			case EVENT_I:
				makeInvincible(TRUE);
				break;

			case EVENT_K:
				makeInvincible(FALSE);
				break;

			case EVENT_O:
				makeOmniscient(TRUE);
				break;

			case EVENT_L:
				makeOmniscient(FALSE);
				break;

			case EVENT_LEFT_D:
				peekLeft();
				break;

			case EVENT_MIDDLE_D:
				shoot();
				break;

			case EVENT_RIGHT_D:
				peekRight();
				break;

			case EVENT_NETWORK:
				readRats(&event);
				break;

			case EVENT_INT:
				quit();
				break;

			}
		else
			switch (event.eventType) {
			case EVENT_RIGHT_U:
			case EVENT_LEFT_U:
				peekStop();
				break;

			case EVENT_NETWORK:
				readRats(&event);
				break;
			}
			
		ratDoctor();		/* clean house */

		DoRatKillQ();
		
		DoViewUpdate();
		
		SendLocation();
#ifndef NeXT
	}
#endif
}

static	Direction	_aboutFace[NDIRECTION] ={SOUTH, NORTH, WEST, EAST};
static	Direction	_leftTurn[NDIRECTION] =	{WEST, EAST, NORTH, SOUTH};
static	Direction	_rightTurn[NDIRECTION] ={EAST, WEST, SOUTH, NORTH};

aboutFace()
{
	M.dir = _aboutFace[M.dir];
	updateView = TRUE;
	sendLocation = TRUE;
}

leftTurn()
{
	M.dir = _leftTurn[M.dir];
	updateView = TRUE;
	sendLocation = TRUE;
}

rightTurn()
{
	M.dir = _rightTurn[M.dir];
	updateView = TRUE;
	sendLocation = TRUE;
}

/* remember ... "North" is to the right ... positive X motion */

forward()
{
	register int	tx = M.xloc;
	register int	ty = M.yloc;

	switch(M.dir) {
	case NORTH:	if (!mp[tx+1].y[ty])	tx++; break;
	case SOUTH:	if (!mp[tx-1].y[ty])	tx--; break;
	case EAST:	if (!mp[tx].y[ty+1])	ty++; break;
	case WEST:	if (!mp[tx].y[ty-1])	ty--; break;
	default:
		MWError("bad direction in Forward");
	}
	if ((M.xloc != tx) || (M.yloc != ty)) {
		M.xloc = tx; M.yloc = ty;
		updateView = TRUE;
		sendLocation = TRUE;
	}
}

backward()
{
	register int	tx = M.xloc;
	register int	ty = M.yloc;

	switch(M.dir) {
	case NORTH:	if (!mp[tx-1].y[ty])	tx--; break;
	case SOUTH:	if (!mp[tx+1].y[ty])	tx++; break;
	case EAST:	if (!mp[tx].y[ty-1])	ty--; break;
	case WEST:	if (!mp[tx].y[ty+1])	ty++; break;
	default:
		MWError("bad direction in Backward");
	}
	if ((M.xloc != tx) || (M.yloc != ty)) {
		M.xloc = tx; M.yloc = ty;
		updateView = TRUE;
		sendLocation = TRUE;
	}
}

makeInvincible(neverDie)
Boolean neverDie;
{
	M.invincible = neverDie;
	ShowPosition(M.xloc, M.yloc, M.invincible, M.dir);
}

makeOmniscient(allSeeing)
Boolean allSeeing;
{
	M.omniscient = allSeeing;
	ShowAllPositions();
}

peekLeft()
{
	M.xPeek = M.xloc;
	M.yPeek = M.yloc;
	M.dirPeek = M.dir;

	switch(M.dir) {
	case NORTH:	if (!mp[M.xloc+1].y[M.yloc]) {
				M.xPeek = M.xloc + 1;
				M.dirPeek = WEST;
			}
			break;

	case SOUTH:	if (!mp[M.xloc-1].y[M.yloc]) {
				M.xPeek = M.xloc - 1;
				M.dirPeek = EAST;
			}
			break;

	case EAST:	if (!mp[M.xloc].y[M.yloc+1]) {
				M.yPeek = M.yloc + 1;
				M.dirPeek = NORTH;
			}
			break;

	case WEST:	if (!mp[M.xloc].y[M.yloc-1]) {
				M.yPeek = M.yloc - 1;
				M.dirPeek = SOUTH;
			}
			break;

	default:
			MWError("bad direction in PeekLeft");
	}

	/* if any change, display the new view without moving! */

	if ((M.xPeek != M.xloc) || (M.yPeek != M.yloc)) {
		M.peeking = TRUE;
		updateView = TRUE;
	}
}

peekRight()
{
	M.xPeek = M.xloc;
	M.yPeek = M.yloc;
	M.dirPeek = M.dir;

	switch(M.dir) {
	case NORTH:	if (!mp[M.xloc+1].y[M.yloc]) {
				M.xPeek = M.xloc + 1;
				M.dirPeek = EAST;
			}
			break;

	case SOUTH:	if (!mp[M.xloc-1].y[M.yloc]) {
				M.xPeek = M.xloc - 1;
				M.dirPeek = WEST;
			}
			break;

	case EAST:	if (!mp[M.xloc].y[M.yloc+1]) {
				M.yPeek = M.yloc + 1;
				M.dirPeek = SOUTH;
			}
			break;

	case WEST:	if (!mp[M.xloc].y[M.yloc-1]) {
				M.yPeek = M.yloc - 1;
				M.dirPeek = NORTH;
			}
			break;

	default:
			MWError("bad direction in PeekRight");
	}

	/* if any change, display the new view without moving! */

	if ((M.xPeek != M.xloc) || (M.yPeek != M.yloc)) {
		M.peeking = TRUE;
		updateView = TRUE;
	}
}

peekStop()
{
	M.peeking = FALSE;
	updateView = TRUE;
}

DoViewUpdate()
{
	if (updateView) {	/* paint the screen */
		ShowPosition(M.xloc, M.yloc, M.invincible, M.dir);
		if (M.peeking)
			ShowView(M.xPeek, M.yPeek, M.dirPeek);
		else
			ShowView(M.xloc, M.yloc, M.dir);
		updateView = FALSE;
	}
}

shoot()
{
	M.score--;
	M.ratcb.rats[M.myRatId].score--;
	UpdateScoreCard(M.myRatId);
	sendKill();
}

/* 
 * add the shot to the shot queue. It'll be processed later.
 */

holdBreath(ratKill)
RatKill	ratKill;
{
	RatKillQ_t	rkp;
	struct timeval	now;
	
	DeadRatCursor();
	gettimeofday(&now, NULL);

	if (RatKillQ == NULL) {
		RatKillQ = (RatKillQ_t) malloc(sizeof(AqRatKillQ));
		rkp = RatKillQ;
	} else {
		for (rkp = RatKillQ; rkp->nextOne != NULL; rkp = rkp->nextOne)
			;
		rkp->nextOne = (RatKillQ_t) malloc(sizeof(AqRatKillQ));
		rkp = rkp->nextOne;
	}

	bcopy((char *)ratKill, (char *)&rkp->thisOne, sizeof(AqRatKill));
	rkp->nextOne = NULL;
	rkp->shotHits = now;
	rkp->shotHits.tv_sec++;
}

/* 
 * finally see if the shot hit home.
 */

handleKill(tx, ty, td, ratId)
Loc	tx, ty;
Direction td;
RatId	ratId;
{
	while (!M.maze[tx].y[ty]) {
		switch (td) {
		case NORTH:	tx++; break;
		case SOUTH:	tx--; break;
		case EAST:	ty++; break;
		case WEST:	ty--; break;
		}
		if ((M.xloc == tx) && (M.yloc == ty)) {	/* Oh oh... */
			sendDead(ratId);
			NewPosition();	/* avoid multiple hits */
			FlashScreen();
			M.score -= 5;	/* minus 5 points for getting killed */
			M.ratcb.rats[M.myRatId].score = M.score;
			UpdateScoreCard(M.myRatId);
			NotifyPlayer();
			updateView = TRUE;
			sendLocation = TRUE;
		}
	}
	RatCursor();
}

/*
 * Process the pending shots, if any.
 */

DoRatKillQ()
{
	struct timeval	now;
	RatKillQ_t	rkp = RatKillQ;
	RatKill		ratKill;

	if (RatKillQ != NULL) {
		gettimeofday(&now, NULL);
		while (rkp != NULL) {
#ifdef ORIGINAL
			if (now.tv_sec >= rkp->shotHits.tv_sec) {
				if (now.tv_usec >=
				    rkp->shotHits.tv_usec) {
#else
			if (now.tv_sec > rkp->shotHits.tv_sec ||
(now.tv_sec == rkp->shotHits.tv_sec && now.tv_usec >= rkp->shotHits.tv_usec)
				) {
#endif
					ratKill = &RatKillQ->thisOne;
					handleKill(ratKill->xLoc,
						   ratKill->yLoc,
						   ratKill->dir,
						   ratKill->ratId);
					RatKillQ = RatKillQ->nextOne;
					free((char *) rkp);
					rkp = RatKillQ;
#ifdef ORIGINAL
				} else
					break;
#endif
			} else
				break;
		}
		if (RatKillQ == NULL)
			RatCursor();
	}
}
		
/* 
 * Convert the contents of a packet to network order before sending.
 */

ConvertOutgoing(p)
RatPacket *p;
{
	char		buf[64];
	RatId		ratId;
	RatLocation	ratLoc;
	RatKill		ratKill;
	RatDead		ratDead;
	RatStatus	ratStatus;
	RatNew		ratNew;
	RatGone		ratGone;
	RatQuery	ratQuery;
	RatAlive	ratAlive;
	RatMove		ratMove;

	switch(p->type) {
	case RAT_LOCATION:
		ratLoc = (RatLocation) &p->body;
		ratLoc->ratId = htonl(ratLoc->ratId);
		ratLoc->xLoc =  htons(ratLoc->xLoc);
		ratLoc->yLoc =  htons(ratLoc->yLoc);
		ratLoc->dir =   htons(ratLoc->dir);
		ratLoc->score = htonl(ratLoc->score);
		break;

	case RAT_KILL:
		ratKill = (RatKill) &p->body;
		ratKill->ratId = htonl(ratKill->ratId);
		ratKill->xLoc  = htons(ratKill->xLoc);
		ratKill->yLoc  = htons(ratKill->yLoc);
		ratKill->dir   = htons(ratKill->dir);
		break;

	case RAT_DEAD:
		ratDead = (RatDead) &p->body;
		ratDead->ratId = htonl(ratDead->ratId);
		ratDead->killedBy = htonl(ratDead->killedBy);
		break;

	case RAT_STATUS:
		ratStatus = (RatStatus) &p->body;
		ratStatus->dukeRat = htonl(ratStatus->dukeRat);
		for (ratId = 0; ratId < MAXRATS; ratId++) {
			RatInfo	ratInfo;

			ratInfo = &ratStatus->rats[ratId];
			ratInfo->playing = htons(ratInfo->playing);
			ratInfo->xLoc = htons(ratInfo->xLoc);
			ratInfo->yLoc = htons(ratInfo->yLoc);
			ratInfo->dir = htons(ratInfo->dir);
			ratInfo->score = htonl(ratInfo->score);
			ratInfo->addr.sin_family =
				ntohs(ratInfo->addr.sin_family);
			/* don't touch address or name */
		}
		break;

	case RAT_NEW:
		ratNew = (RatNew) &p->body;
		ratNew->pass = htons(ratNew->pass);
		ratNew->xLoc = htons(ratNew->xLoc);
		ratNew->yLoc = htons(ratNew->yLoc);
		ratNew->dir  = htons(ratNew->dir);
		ratNew->addr.sin_family =
			htons(ratNew->addr.sin_family);
		/* don't touch address or name */
		break;

	case RAT_GOING:
		ratGone = (RatGone) &p->body;
		ratGone->ratId = htonl(ratGone->ratId);
		break;

	case RAT_QUERY:
		ratQuery = (RatQuery) &p->body;
		ratQuery->ratId = htonl(ratQuery->ratId);
		break;

	case RAT_ALIVE:
		ratAlive = (RatAlive) &p->body;
		ratAlive->ratId = htonl(ratAlive->ratId);
		break;

	case RAT_SURVEY:
		ratNew = (RatNew) &p->body;
		ratNew->pass = htons(ratNew->pass);
		ratNew->xLoc = htons(ratNew->xLoc);
		ratNew->yLoc = htons(ratNew->yLoc);
		ratNew->dir  = htons(ratNew->dir);
		/* don't touch address or name */
		break;

	case RAT_MOVE:
		ratMove = (RatMove) &p->body;
		ratMove->ratId = htonl(ratMove->ratId);
		break;

	default:
		sprintf(buf, "ConvertOutgoing bad type %d (%d)",
			p->type, htons(p->type));
		MWError(buf);
	}
	p->type = htonl(p->type);
}

/* 
 * Convert the contents of a packet to host order after ConvertIncoming.
 */

ConvertIncoming(p)
RatPacket *p;
{
	char		buf[64];
	RatId		ratId;
	RatLocation	ratLoc;
	RatKill		ratKill;
	RatDead		ratDead;
	RatStatus	ratStatus;
	RatNew		ratNew;
	RatGone		ratGone;
	RatQuery	ratQuery;
	RatAlive	ratAlive;
	RatMove		ratMove;

	p->type = ntohl(p->type);
	switch(p->type) {
	case RAT_LOCATION:
		ratLoc = (RatLocation) &p->body;
		ratLoc->ratId = ntohl(ratLoc->ratId);
		ratLoc->xLoc =  ntohs(ratLoc->xLoc);
		ratLoc->yLoc =  ntohs(ratLoc->yLoc);
		ratLoc->dir =   ntohs(ratLoc->dir);
		ratLoc->score = ntohl(ratLoc->score);
		break;

	case RAT_KILL:
		ratKill = (RatKill) &p->body;
		ratKill->ratId = ntohl(ratKill->ratId);
		ratKill->xLoc  = ntohs(ratKill->xLoc);
		ratKill->yLoc  = ntohs(ratKill->yLoc);
		ratKill->dir   = ntohs(ratKill->dir);
		break;

	case RAT_DEAD:
		ratDead = (RatDead) &p->body;
		ratDead->ratId = ntohl(ratDead->ratId);
		ratDead->killedBy = ntohl(ratDead->killedBy);
		break;

	case RAT_STATUS:
		ratStatus = (RatStatus) &p->body;
		ratStatus->dukeRat = ntohl(ratStatus->dukeRat);
		for (ratId = 0; ratId < MAXRATS; ratId++) {
			RatInfo	ratInfo;

			ratInfo = &ratStatus->rats[ratId];
			ratInfo->playing = ntohs(ratInfo->playing);
			ratInfo->xLoc = ntohs(ratInfo->xLoc);
			ratInfo->yLoc = ntohs(ratInfo->yLoc);
			ratInfo->dir = ntohs(ratInfo->dir);
			ratInfo->score = ntohl(ratInfo->score);
			ratInfo->addr.sin_family =
				ntohs(ratInfo->addr.sin_family);
			/* don't touch address or name */
		}
		break;

	case RAT_NEW:
		ratNew = (RatNew) &p->body;
		ratNew->pass = ntohs(ratNew->pass);
		ratNew->xLoc = ntohs(ratNew->xLoc);
		ratNew->yLoc = ntohs(ratNew->yLoc);
		ratNew->dir  = ntohs(ratNew->dir);
		ratNew->addr.sin_family =
			ntohs(ratNew->addr.sin_family);
		/* don't touch address or name */
		break;

	case RAT_GOING:
		ratGone = (RatGone) &p->body;
		ratGone->ratId = ntohl(ratGone->ratId);
		break;

	case RAT_QUERY:
		ratQuery = (RatQuery) &p->body;
		ratQuery->ratId = ntohl(ratQuery->ratId);
		break;

	case RAT_ALIVE:
		ratAlive = (RatAlive) &p->body;
		ratAlive->ratId = ntohl(ratAlive->ratId);
		break;

	case RAT_SURVEY:
		ratNew = (RatNew) &p->body;
		ratNew->pass = ntohs(ratNew->pass);
		ratNew->xLoc = ntohs(ratNew->xLoc);
		ratNew->yLoc = ntohs(ratNew->yLoc);
		ratNew->dir  = ntohs(ratNew->dir);
		/* don't touch address or name */
		break;

	case RAT_MOVE:
		ratMove = (RatMove) &p->body;
		ratMove->ratId = ntohl(ratMove->ratId);
		break;

	default:
		sprintf(buf, "ConvertIncoming bad type %d (%d)",
			p->type, ntohs(p->type));
		MWError(buf);
	}
}

#ifdef	PACKET_TRACE
static char	*packTypes[] = {
	"RAT_LOCATION",
	"RAT_KILL",
	"RAT_DEAD",
	"RAT_STATUS",
	"RAT_NEW",
	"RAT_GOING",
	"RAT_QUERY",
	"RAT_ALIVE",
	"RAT_SURVEY",
	"RAT_MOVE",
	0
};
#endif	PACKET_TRACE

readRats(evp)
MWEvent *evp;
{
#ifdef NeXT
	extern void beginNetwork(), endNetwork();
#endif
	register RatLocation	ratLoc;
	register RatLook	ratLook;
	register RatAlive	ratAlive;
	RatPacket	*pack = evp->eventDetail;
	RatInfo		ratInfo;
	Boolean		oldVisible;
	RatId		ratId;
	RatStatus	status;
	RatNew		ratNew;
	RatGone		ratGone;
	RatKill		ratKill;
	RatDead		ratDead;
	RatQuery	ratQuery;
	RatMove		ratMove;
	Sockaddr	nullAddr;
	char		buf[32];
	int		newSocket;

#ifdef	PACKET_TRACE
	printf("received %s (%d)\n",
		packTypes[pack->type - 1], pack->type);
#endif	PACKET_TRACE

	switch(pack->type) {
	case RAT_LOCATION:		/* someone moved */
		ratLoc = (RatLocation) &pack->body;
		ratLook = &R2d2[ratLoc->ratId];
		if ((oldVisible = ratLook->visible) == TRUE)
#ifdef NeXT
			updateView = TRUE;
#else
			XORToken(ratLoc->ratId);
#endif
		ratInfo = &M.ratcb.rats[ratLoc->ratId];
		ratInfo->xLoc = ratLoc->xLoc;
		ratInfo->yLoc = ratLoc->yLoc;
		ratInfo->dir  = ratLoc->dir;
		DisplayOthersPosition(ratLoc->ratId, ratLoc->xLoc,
				      ratLoc->yLoc, ratLoc->dir);
		TokenVisible(ratLoc->ratId);
		if (ratLook->visible == TRUE)
#ifdef NeXT
			updateView = TRUE;
#else
			XORToken(ratLoc->ratId);
#endif
		if ((oldVisible != ratLook->visible) ||
		    (ratInfo->score != ratLoc->score)) {
			ratInfo->score = ratLoc->score;
			UpdateScoreCard(ratLoc->ratId);
		}
		ratHealth[ratLoc->ratId].rcvd = TRUE;
		break;

	case RAT_KILL:			/* someone shot at me */
		if (!M.invincible) {
			ratKill = (RatKill) &pack->body;
			holdBreath(ratKill);
		}
		break;

	case RAT_DEAD:			/* I hit someone */
		ratDead = (RatDead) &pack->body;
		if (ratDead->killedBy == M.myRatId) {
			FlashTop();	/* got him! */
			M.score += 10;	/* 10 points for a kill */
			M.score += 1;	/* make up for shot cost */
			M.ratcb.rats[M.myRatId].score = M.score;
			UpdateScoreCard(M.myRatId);
			sendLocToAll();
		}
		break;

	case RAT_STATUS:		/* new info about everyone */
		status = (RatStatus) &pack->body;
		if (bcmp(&status->rats[M.myRatId].addr, &M.myAddr,
			  sizeof(M.myAddr)) != 0)
			break;		/* not for me, from another game */
					/* perhaps left over from findDuke() */

		/* Have a new table, turn off any visible opponents */

		for (ratId = 0; ratId < MAXRATS; ratId++)
			if (R2d2[ratId].visible == TRUE)
#ifdef NeXT
				updateView = TRUE;
#else
				XORToken(ratId);
#endif
		bcopy((char *)status, (char *)&M.ratcb, sizeof(RatCb));
		if (M.ratcb.dukeRat == M.myRatId)
			M.duke = TRUE;
		for (ratId = 0; ratId < MAXRATS; ratId++) {
			TokenVisible(ratId);
			if (R2d2[ratId].visible == TRUE)
#ifdef NeXT
				updateView = TRUE;
#else
				XORToken(ratId);
#endif
		}
		NewScoreCard();
		ratInfo = &M.ratcb.rats[M.myRatId];
		if ((ratInfo->xLoc != M.xloc) ||
		    (ratInfo->yLoc != M.yloc) ||
		    (ratInfo->dir  != M.dir))
			sendLocToAll();
		
		break;

	case RAT_NEW:			/* new player */
		ratNew = (RatNew) &pack->body;
		if (ratNew->pass == RAT_PASSWORD) {
			if (M.duke) {
				/* 
				 * need to check for duplicates here;
				 * a previous reply might have been
				 * lost. Can't let the same guy in the
				 * game twice.
				 */

				register RatId	id;

				for (id = 0; id < MAXRATS; id++)
					if (M.ratcb.rats[id].playing &&
					    !bcmp(&M.ratcb.rats[id].addr,
						  &evp->eventSource,
						  sizeof (Sockaddr))) {

						/* already there */
						sendStatus(evp->eventSource);
						break;
					}
				if (id >= MAXRATS) {	/* fell through */
					allocateNewRat(ratNew);
					sendAllStatus();
				}
			} else
				sendStatus(evp->eventSource);
		}
		break;

	case RAT_GOING:		       /* player leaving, only rcvd if duke */
		ratGone = (RatGone) &pack->body;
		ratLeft(ratGone->ratId);
		break;

	case RAT_QUERY:			/* are you alive? */
		/*
		 * register their net address, in case they got a
		 * RAT_MOVE, moved, and the change got lost somewhere.
		 */
		ratQuery = (RatQuery) &pack->body;
		M.ratcb.rats[ratQuery->ratId].addr = evp->eventSource;

		sendAlive();
		break;

	case RAT_ALIVE:			/* I am alive */
		ratAlive = (RatAlive) &pack->body;
		ratHealth[ratAlive->ratId].rcvd = TRUE;
		break;

	case RAT_SURVEY:		/* who's out there? */
		ratNew = (RatNew) &pack->body;
		if (ratNew->pass == RAT_PASSWORD)
			sendStatus(evp->eventSource);
		break;

	case RAT_MOVE:			/* move to M.mazePort */
		ratMove = (RatMove) &pack->body;
		if (ratMove->ratId != M.ratcb.dukeRat)
			MWError("bogus RAT_MOVE");
#ifdef NeXT
		endNetwork();
#endif
		close(M.theSocket);

		/* 
		 * If the socket being closed is on this machine,
		 * leave some time for the socket to close down before
		 * I try to grab it.
		 */

		if (bcmp((char *) &evp->eventSource.sin_addr,
			  (char *) &M.myAddr.sin_addr,
			  sizeof(M.myAddr.sin_addr)) == 0)
			sleep(1);

		/* grab the socket */

		newSocket = socket(AF_INET, SOCK_DGRAM, 0);
		if (newSocket < 0)
			MWError("RAT_MOVE lost socket");
		if (dup2(newSocket, M.theSocket) < 0)
			MWError("RAT_MOVE dup2 failed");
#ifdef NeXT
		if (newSocket != M.theSocket) close(newSocket);
#endif
		if (setsockopt(M.theSocket, SOL_SOCKET, SO_REUSEADDR,
			       NULL, 0) < 0)
			MWError("RAT_MOVE can't reuse addresses");
		M.myAddr.sin_port = M.mazePort;
		nullAddr = M.myAddr;	/* see netInit() */
		bzero((char *) &nullAddr.sin_addr, sizeof(nullAddr.sin_addr));
		if (bind(M.theSocket, (struct sockaddr *)&nullAddr, sizeof(nullAddr)) < 0)
			MWError("RAT_MOVE can't bind");
		M.ratcb.rats[M.myRatId].addr = M.myAddr;
#ifdef NeXT
		beginNetwork();
#endif
		sendAllStatus();
		break;

	default:
		sprintf(buf, "readRats bad packet type 0x%x", pack->type);
		MWError(buf);
	}
}

/* 
 * In order to reduce the network traffic, only send out the location change if
 * there's no keyboard input pending.
 */

SendLocation()
{
#ifdef NeXT
	Boolean		KBEventPending();
#endif
	if (!KBEventPending())
		if (sendLocation) {
			sendLocToAll();
			sendLocation = FALSE;
		}
}

/* 
 * Let everyone know I've moved.
 */

sendLocToAll()
{
	RatPacket	pack;
	RatLocation	ratloc;
	RatId		i;
	RatInfo		ratInfo = &M.ratcb.rats[M.myRatId];

	ratInfo->xLoc = M.xloc;		/* update my table, too */
	ratInfo->yLoc = M.yloc;
	ratInfo->dir  = M.dir;
	ratInfo->score = M.score;

	pack.type = RAT_LOCATION;
	ratloc = (RatLocation) &pack.body;
	ratloc->ratId = M.myRatId;
	ratloc->xLoc = ratInfo->xLoc;
	ratloc->yLoc = ratInfo->yLoc;
	ratloc->dir = ratInfo->dir;
	ratloc->score = ratInfo->score;
	ConvertOutgoing(&pack);
	
	/* 
	 * Would really like this to be asynchronous, so play could
	 * continue while the packets are being sent. Of course, then,
	 * the data might change in the midst of all this...
	 */

	for (i = 0; i < MAXRATS; i++)
		if ((i != M.myRatId) && (M.ratcb.rats[i].playing))
			if (sendto(M.theSocket, &pack, sizeof(pack), 0,
				  (struct sockaddr *)&M.ratcb.rats[i].addr, sizeof(Sockaddr)) < 0)
				MWError("sendLocToAll");
}

sendAllStatus()
{
	RatId	i;

	for (i = 0; i < MAXRATS; i++)
		if ((i != M.myRatId) && (M.ratcb.rats[i].playing))
			sendStatus(M.ratcb.rats[i].addr);
}

/* 
 * Send the entire status data to a rat.
 */

sendStatus(to)
Sockaddr to;
{
	RatPacket	pack;

	pack.type = RAT_STATUS;
	pack.body = M.ratcb;
	ConvertOutgoing(&pack);
	if (sendto(M.theSocket, &pack, sizeof(pack), 0, (struct sockaddr *)&to, sizeof(to)) < 0)
		MWError("sendStatus");
}

/* 
 * Tell a player he's being shot at.
 */

sendKill()
{
	RatPacket	pack;
	RatKill		ratKill;
	RatId		ixRatId;

	for (ixRatId = 0; ixRatId < MAXRATS; ixRatId++) {
		if (ixRatId == M.myRatId)
			continue;
		if (R2d2[ixRatId].visible) {
			pack.type = RAT_KILL;
			ratKill = (RatKill) &pack.body;
			ratKill->ratId = M.myRatId;
			ratKill->xLoc = M.xloc;
			ratKill->yLoc = M.yloc;
			ratKill->dir  = M.dir;
			ConvertOutgoing(&pack);
			if (sendto(M.theSocket, &pack, sizeof(pack), 0,
				   (struct sockaddr *)&M.ratcb.rats[ixRatId].addr,
				   sizeof(M.ratcb.rats[ixRatId].addr)) < 0)
				MWError("sendKill");
		}
	}
}

/* 
 * Tell a shooter he hit me.
 */

sendDead(killerRatId)
RatId	killerRatId;
{
	RatPacket	pack;
	RatDead		ratDead;

	pack.type = RAT_DEAD;
	ratDead = (RatDead) &pack.body;
	ratDead->ratId = M.myRatId;
	ratDead->killedBy = killerRatId;
	ConvertOutgoing(&pack);
	if (sendto(M.theSocket, &pack, sizeof(pack), 0,
		  (struct sockaddr *)&M.ratcb.rats[killerRatId].addr, sizeof(Sockaddr)) < 0)
		MWError("sendDead");
}

/* 
 * Tell the duke I'm leaving.
 */

sendGoing()
{
	RatPacket	pack;
	RatGone		ratGone;

	pack.type = RAT_GOING;
	ratGone = (RatGone) &pack.body;
	ratGone->ratId = M.myRatId;
	ConvertOutgoing(&pack);
	if (sendto(M.theSocket, &pack, sizeof(pack), 0,
		  (struct sockaddr *)&M.ratcb.rats[M.ratcb.dukeRat].addr, sizeof(Sockaddr)) < 0)
		MWError("sendGoing");
}

/* 
 * Ask the silent types if they're alive.
 */

sendQuery()
{
	RatPacket	pack;
	RatId		ratId;
	RatQuery	ratQuery;

	for (ratId = 0; ratId < MAXRATS; ratId++)
		if (ratHealth[ratId].send) {
			pack.type = RAT_QUERY;
			ratQuery = (RatQuery) &pack.body;
			ratQuery->ratId = M.myRatId;
			ratHealth[ratId].send = FALSE;
			ConvertOutgoing(&pack);
			if (sendto(M.theSocket, &pack, sizeof(pack), 0,
				  (struct sockaddr *)&M.ratcb.rats[ratId].addr,
				  sizeof(Sockaddr)) < 0)
				MWError("sendQuery");
		}
}

/* 
 * Register someone as alive, and let them know we are, too. 
 */

sendAlive()
{
	RatPacket	pack;
	RatId		ratId;
	RatAlive	ratAlive;

	for (ratId = 0; ratId < MAXRATS; ratId++) {
		if ((ratId == M.myRatId) ||
		    !M.ratcb.rats[ratId].playing)
			continue;
		pack.type = RAT_ALIVE;
		ratAlive = (RatAlive) &pack.body;
		ratAlive->ratId = M.myRatId;
		ConvertOutgoing(&pack);
		if (sendto(M.theSocket, &pack, sizeof(pack), 0,
		      (struct sockaddr *)&M.ratcb.rats[ratId].addr, sizeof(Sockaddr)) < 0)
			MWError("sendAlive");
	}
}

/* 
 * Let a new player in the game.
 */

allocateNewRat(ratNew)
RatNew	ratNew;
{
	RatId	ratId;
	RatInfo	ratInfo;

	for (ratId = 0; ratId < MAXRATS; ratId++) {
		ratInfo = &M.ratcb.rats[ratId];
		if (!ratInfo->playing) {
			ratInfo->playing = TRUE;
			ratInfo->xLoc = ratNew->xLoc;
			ratInfo->yLoc = ratNew->yLoc;
			ratInfo->dir  = ratNew->dir;
			ratInfo->score = 0;
			ratInfo->addr = ratNew->addr;
			strncpy(ratInfo->name, ratNew->name, NAMESIZE);
			TokenVisible(ratId);
			UpdateScoreCard(ratId);
			if (R2d2[ratId].visible == TRUE)
#ifdef NeXT
				updateView = TRUE;
#else
				XORToken(ratId);
#endif
			AddNewPlayer(ratId, ratNew->xLoc, ratNew->yLoc,
					ratNew->dir);
			return;
		}
	}
}

/* 
 * I wanna go home!
 */

void quit()
{
	RatId	ratId;

	if (!M.duke)
		sendGoing();
	else {				/* oh oh, I'm the duke rat */
		M.ratcb.rats[M.myRatId].playing = FALSE;
		for (ratId = 0; ratId < MAXRATS; ratId++)
			if (M.ratcb.rats[ratId].playing) {
				M.ratcb.dukeRat = ratId;
				sendAllStatus();
				break;
			}
		moveSomeone(M.myRatId);
	}
	StopWindow();
	exit(0);
}

/* 
 * Clean up after someone who has left. Let everyone else know, too.
 */

ratLeft(ratId)
RatId	ratId;
{
	if (R2d2[ratId].visible == TRUE)
#ifdef NeXT
		updateView = TRUE;
#else
		XORToken(ratId);
#endif
	R2d2[ratId].visible = FALSE;
	M.ratcb.rats[ratId].playing = FALSE;
	ExitPlayer(ratId);
	UpdateScoreCard(ratId);
	sendAllStatus();
	moveSomeone(ratId);
}

/* 
 * See if the guy leaving has vacated the reserved port. If so, try to
 * find someone else on that machine and tell him to move there.
 */

moveSomeone(ratId)
RatId	ratId;
{
#ifdef NeXT
	extern void endNetwork();
#endif
	Sockaddr	hisAddr;
	RatId		newRat;
	RatPacket	pack;
	RatMove		ratMove;

	hisAddr = M.ratcb.rats[ratId].addr;
	if (hisAddr.sin_port != M.mazePort)
		return;

	for (newRat = 0; newRat < MAXRATS; newRat++) {
		if (newRat == ratId)
			continue;
		if (M.ratcb.rats[newRat].playing == FALSE)
			continue;
		if (!bcmp(&M.ratcb.rats[newRat].addr.sin_addr,
			  &hisAddr.sin_addr, sizeof(hisAddr.sin_addr))) {
			pack.type = RAT_MOVE;
			ratMove = (RatMove) &pack.body;
			ratMove->ratId = M.ratcb.dukeRat;
			ConvertOutgoing(&pack);
			if (sendto(M.theSocket, &pack, sizeof(pack), 0,
				  (struct sockaddr *)&M.ratcb.rats[newRat].addr,
				  sizeof(Sockaddr)) < 0)
				MWError("moveSomeone");

			/* 
			 * If I'm the one leaving, must free up my port.
			 */

			if (ratId == M.myRatId)
#ifdef NeXT
				endNetwork(),
#endif
				close(M.theSocket);
			break;
		}
	}
		
}

/* 
 * Make sure nobody has died unnoticed.
 */

static Boolean		started = FALSE;
struct timeval	waitStart;
static Boolean		runDoctor = TRUE;

static
ratDoctor()
{
	RatId	ratId, nextRatId();
	struct timeval	now;

	if (!runDoctor)
		return;

	if (started == FALSE) {
		gettimeofday(&waitStart, NULL);
		for (ratId = 0; ratId < MAXRATS; ratId++) {
			ratHealth[ratId].count = 0;
			ratHealth[ratId].send = FALSE;
			ratHealth[ratId].rcvd = FALSE;
		}
		started = TRUE;
		return;
	} else {
		gettimeofday(&now, NULL);
		if ((now.tv_sec - waitStart.tv_sec) < 5)
			return;
		for (ratId = 0; ratId < MAXRATS; ratId++) {
			if ((!M.ratcb.rats[ratId].playing) ||
			    (ratId == M.myRatId))
				continue;
			if (ratHealth[ratId].rcvd) {
				ratHealth[ratId].count = 0;
				ratHealth[ratId].rcvd = FALSE;
				continue;
			}
			if (++ratHealth[ratId].count < 5) {
				ratHealth[ratId].send = TRUE;
				continue;
			}
			if (M.duke ||
			    ((M.ratcb.dukeRat == ratId) &&
			     (nextRatId(ratId) == M.myRatId))) {
				M.duke = TRUE;
				M.ratcb.dukeRat = M.myRatId;
				ratLeft(ratId);
			}
		}
		sendQuery();
		gettimeofday(&waitStart, NULL);
	}
	
}

RatId
nextRatId(ratId)
RatId	ratId;
{
	RatId	ixRatId;

	for (ixRatId = 0; ixRatId < MAXRATS; ixRatId++)
		if (M.ratcb.rats[ixRatId].playing &&
		    (ixRatId != ratId))
			return ixRatId;
	return ixRatId;
}

NewPosition()
{
	register	rndCnt = 0;

	M.xloc = M.yloc = 0;		/* start on occupied square */
	while (mp[M.xloc].y[M.yloc]) {
		M.xloc = random(MAZEXMAX);
		M.yloc = random(MAZEYMAX);
		if (++rndCnt == 100) {
			rndCnt = 0;
			InitRandom();
		}
	}

	/* prevent a blank wall at first glimpse */

	if (!M.maze[M.xloc+1].y[M.yloc]) M.dir = NORTH;
	if (!M.maze[M.xloc-1].y[M.yloc]) M.dir = SOUTH;
	if (!M.maze[M.xloc].y[M.yloc+1]) M.dir = EAST;
	if (!M.maze[M.xloc].y[M.yloc-1]) M.dir = WEST;

	return;
}

/* re-initialize the maze randomization vector */
InitRandom()
{
	struct timeval	t;
	struct timezone	tz;
	register int	i;

	gettimeofday(&t, &tz);
	for (i = 0; i < VECTORSIZE; i++)
		M.randomVector[i] = M.randomVector[i] + t.tv_sec & 0xffff;
}

random(limit)
register int	limit;
{
	register unsigned int	ret;

	ret = M.randomVector[i1] = M.randomVector[i1] + M.randomVector[i2];
	if (++i1 >= VECTORSIZE)
		i1 = 0;
	if (++i2 >= VECTORSIZE)
		i2 = 0;
	return ret%limit;
}

#ifndef NeXT
MWError(s)
char *s;
{
	StopWindow();
	fprintf(stderr, "MazeWar: %s\n", s);
	perror("MazeWar");
	exit(-1);
}
#endif

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.