[comp.sources.games] v02i082: hearts - multiplayer card game, Part01/02

games-request@tekred.TEK.COM (11/05/87)

Submitted by: bob@reed (Mythical Bob Ankeney)
Comp.sources.games: Volume 2, Issue 82
Archive-name: hearts/Part01



#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 2)."
# Contents:  README MANIFEST NOTES dist.c heartsd.c opensock.c
# Wrapped by billr@tekred on Thu Nov  5 10:21:00 1987
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f README -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"README\"
else
echo shar: Extracting \"README\" \(1209 characters\)
sed "s/^X//" >README <<'END_OF_README'
X     This is the initial release of multi-player hearts.  Hearts is just
Xanother brain-damaged card game.  This version supports multiple players
Xacross a network (hopefully) and also multiple games.  Users can select
Xwhich game to join via a hearts distributor menu that shows games in
Xprogress and their current status.
X     Hearts has been tested on a 4.3 BSD system and on a 4.1 BSD-based
Xperversion of Unix.  Unfortunately, it has not been tested across a network
Xdue to lack of access to such an arrangement.  Oh, well...  I'm sure I'll
Xhear about it if it doesn't work.
X     In a future release I plan to rewrite the computer strategy and add a
Xhigh score list option to the menu.  Not too sure what the form of the high
Xscore list will take; perhaps top 10 averages for a given period or something.
XI'm open to suggestions on that.
X     I'm releasing this version now, to get any bug reports out of the way
Xbefore I release the next version, and because I'm not sure when I'll get
Xaround to implementing the above-mentioned mods.  I imagine it will partly
Xbe up to the response I receive from this version.
X
X     Send gripes, bug-fixes, amazing stories, etc. to:
X	Bob Ankeney
X	...!tektronix!reed!bob
X
END_OF_README
if test 1209 -ne `wc -c <README`; then
    echo shar: \"README\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f MANIFEST -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"MANIFEST\"
else
echo shar: Extracting \"MANIFEST\" \(624 characters\)
sed "s/^X//" >MANIFEST <<'END_OF_MANIFEST'
X   File Name		Archive #	Description
X-----------------------------------------------------------
X INSTALL                   2	
X MANIFEST                  1	This shipping list
X Makefile                  2	
X NOTES                     1	
X README                    1	
X connect.c                 2	
X defs.h                    2	
X dist.c                    1	
X hearts.c                  2	
X hearts.instr              2	
X heartsd.c                 1	
X local.proto               2	
X misc.h                    2	
X opensock.c                1	
X select.c                  2	
X sockio.c                  2	
X start_dist.c              2	
END_OF_MANIFEST
if test 624 -ne `wc -c <MANIFEST`; then
    echo shar: \"MANIFEST\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f NOTES -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"NOTES\"
else
echo shar: Extracting \"NOTES\" \(3578 characters\)
sed "s/^X//" >NOTES <<'END_OF_NOTES'
X     Hearts consists of basically 3 parts:
X1) The hearts distributor which keeps track of games in progress, and
X   invokes new games (dealers) upon request.
X2) The hearts dealer, which is the smarts of hearts, handling the actual
X   deal, play, and computer strategy.
X3) The hearts player, which is essentially an ignorant interface for each
X   human player to the hearts dealer.  The dealer passes messages to the
X   player about what to display, and the player has a curses implemented
X   windowing interface for displaying those messages.  Messages include
X   such information as what's in your hand, what's been played, the current
X   score, etc.  Messages passed back to the dealer include what card to play,
X   and messages to send to other players.
X
X
X     The initial invocation of hearts forks off the distributor, which hangs
Xaround till nobody needs it.  The distributor passes info to the select_game
Xportion of the hearts player showing games in progress, which are updated as
Xgames progress.  The player may join any existing game, or start a new game,
Xat which point the distributor forks off a new dealer, complete with a good
Xsocket port to listen for hearts players on.  The port # of the desired game
Xis passed back to the hearts player, which then connects to the dealer, and
Xplay begins.
X
X     Modifications to dump replace the cursed curses implementation should be
Xeasy as all the code is inside hearts.c.  Perhaps if I ever get X windows
Xrunning on my workstation...
X
X			Possible bugs and problems:
X
X     If 2 people invoke hearts at the same time, it is possible that they will
Xboth try to invoke the distributor.  The second one will fail, at which point
Xan error message will be printed.  I've considered a few methods of dealing
Xwith this.  For one thing, once a player has connected to the dealer, the
Xdistributor socket is closed.  When all dealers and players are gone, the
Xdistributor exits.  Otherwise it is sitting in a select loop waiting for new
Xplayer connections, or status information from the dealers (hand and round
Xnumbers, players joined or left).  Perhaps the distributor should hang around.
XMaybe have it time out after, say, 30 minutes, then leave.  Or, the fix I'm
Xconsidering is having each hearts player do an exclusive lock on a file when
Xstarted.  When successful, the player will try to connect to the distributor.
XIf successful, fine, else the distributor is invoked, and when ready for
Xconnections, then free the lock.  Thus is guaranteed no two players will try
Xto start the distributor.  Suggestions for how best to deal with this problem
Xare welcome!
X     The strategy for the game came from an old Pascal program by Don Backus
Xof Oregon Software, with "improvements" by Jeff Hemmerling of Tessi.  I'm not
Xtoo sure the improvements help that much in that they do fix some bad play
Xproblems, but at the same time make it a bit easier to shoot, and seem to
Xreduce the incidence of the computer accidentally shooting the moon.  There
Xat present is no algorithm for the computer to shoot, tho miraculously, it
Xon occasion succeeds!  I'm not to familiar with the rules of hearts, but I
Xsee no rules regarding dumping hearts the first round or leading hearts when
Xnot broken.  The latter causes a potential problem if hearts have not been
Xbroken yet, and the leader has only hearts left.  If these rules are common,
Xor desirable, let me know as I'm considering tossing them from the new
Xstrategy I'm writing.
X
X     I've been too lazy to write a man page for it.  Maybe by next release...
X
X	Bob Ankeney
X	...!tektronix!reed!bob
X
END_OF_NOTES
if test 3578 -ne `wc -c <NOTES`; then
    echo shar: \"NOTES\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f dist.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"dist.c\"
else
echo shar: Extracting \"dist.c\" \(9988 characters\)
sed "s/^X//" >dist.c <<'END_OF_dist.c'
X/*
X * hearts_dist - distributor for hearts
X *
X * Keeps track of games in progress and allows new players to select which
X * game to join or start a new game.
X *
X *
X *	Commands to hearts player:
X *  tn		Following info regards table n (n is unique identifier).
X *  hn		Now playing hand n.
X *  rn		Now playing round n.
X *  pn<name>	Player n is <name>.
X *
X *  xn		Table n has exited (game over).
X *  g		Get table number to join.
X *  cn		Connect to dealer on port n.
X *
X *	Commands from hearts player:
X *  jn		Join table n.
X *  n		Start new game.
X *
X *	Commands to dealer:
X * none yet?!?
X *
X *	Commands from dealer:
X *  hn		Now playing hand n.
X *  rn		Now playing round n.
X *  pn<name>	Player n is <name>.
X *
X */
X
X#include "misc.h"
X#include "defs.h"
X#include "local.h"
X
Xtypedef struct table *table_ptr;
X
Xstruct table {
X	table_ptr	next_table;		/* Points to next table entry */
X	int		table_id;		/* Unique identifier */
X	char		player_name[4][9];	/* Name of players at table */
X	int		hand;			/* Current dealer hand (1..4) */
X	int		round;			/* Current round (1..13) */
X	int		port_num;		/* Port # assigned */
X	int		socket;			/* File descriptor for dealer */
X	int		pid;			/* Process id for dealer */
X};
X
Xtypedef struct empty_table *empty_ptr;
X
Xstruct empty_table {
X	empty_ptr	next_empty;
X	int		port_num;		/* Available port # */
X};
X
Xtypedef struct new_comer *new_ptr;
X
Xstruct new_comer {
X	new_ptr		next_new_comer;
X	int		socket;			/* File descriptor */
X};
X
Xtable_ptr	first_table;
Xempty_ptr	first_empty;
Xnew_ptr		first_new_comer;
X
Xint	main_sock,
X	next_port,				/* Next port to assign */
X	unique_id;
X
X/*
X * clear_table - dealer went away; drop that table.
X */
Xclear_table(tbl_ptr)
Xtable_ptr	tbl_ptr;
X{
X	table_ptr	prev_ptr = NULL;
X	table_ptr	cur_ptr;
X	empty_ptr	temp_empty, cur_empty;
X	new_ptr		cur_new_comer;
X	char		buf[16];
X	char		*malloc();
X
X	while (wait(0) != tbl_ptr->pid)		/* Wait for process to die */
X		;
X	(void) close(tbl_ptr->socket);
X	/*
X	 * Inform new-comers
X	 */
X	(void) sprintf(buf, "x%d", tbl_ptr->table_id);
X	for (cur_new_comer = first_new_comer; cur_new_comer;
X			cur_new_comer = cur_new_comer->next_new_comer)
X		write_socket(cur_new_comer->socket, buf);
X	for (cur_ptr = first_table; cur_ptr != tbl_ptr;
X					cur_ptr = cur_ptr->next_table)
X		prev_ptr = cur_ptr;
X	if (prev_ptr)
X		prev_ptr->next_table = tbl_ptr->next_table;
X	else
X		if ((first_table = tbl_ptr->next_table) == NULL)
X			exit(0);	/* No more dealers */
X	temp_empty = (empty_ptr) malloc(sizeof(struct empty_table));
X	temp_empty->next_empty = NULL;
X	temp_empty->port_num = tbl_ptr->port_num;
X	free((char *) tbl_ptr);
X	if (first_empty) {
X		for (cur_empty = first_empty; cur_empty->next_empty;
X					cur_empty = cur_empty->next_empty) ;
X		cur_empty->next_empty = temp_empty;
X	}
X	else
X		first_empty = temp_empty;
X}
X
X/*
X * drop_new_comer - New comer exited
X */
Xdrop_new_comer(dead_new_comer)
Xnew_ptr	dead_new_comer;
X{
X	new_ptr	cur_new_comer,
X		prev_new_comer = NULL;
X
X	(void) close(dead_new_comer->socket);
X	for (cur_new_comer = first_new_comer; cur_new_comer != dead_new_comer;
X			cur_new_comer = cur_new_comer->next_new_comer)
X		prev_new_comer = cur_new_comer;
X	if (prev_new_comer)
X		prev_new_comer->next_new_comer = dead_new_comer->next_new_comer;
X	else
X		first_new_comer = dead_new_comer->next_new_comer;
X	free((char *) dead_new_comer);
X	if ((first_table == NULL) && (first_new_comer == NULL))
X		exit(0);		/* Nobody connected */
X}
X
X/*
X * new_player - New player has connected.  Inform of games in progress and
X *		request game to be joined.
X */
Xnew_player()
X{
X	int	new_socket;		/* new file descriptor */
X	char	buf[64];
X	struct	sockaddr_in sockad;
X	int	ssize;			/* makes accept happy */
X	int	i;
X	new_ptr		cur_new_comer, temp_new_comer;
X	table_ptr	cur_ptr;
X	char	*malloc();
X
X	/*
X	 * add whoever's waiting
X	 */
X	ssize = sizeof (sockad);
X	if ((new_socket = accept(main_sock, &sockad, &ssize)) == -1) {
X		perror("accept");
X		exit(-1);
X	}
X	/*
X	 * add to list of new_comers
X	 */
X	temp_new_comer = (new_ptr) malloc(sizeof(struct new_comer));
X	temp_new_comer->next_new_comer = NULL;
X	temp_new_comer->socket = new_socket;
X	if (first_new_comer) {
X		for (cur_new_comer = first_new_comer;
X			cur_new_comer->next_new_comer;
X			cur_new_comer = cur_new_comer->next_new_comer) ;
X		cur_new_comer->next_new_comer = temp_new_comer;
X	}
X	else
X		first_new_comer = temp_new_comer;
X	/*
X	 * send info on games in progress
X	 */
X	for (cur_ptr = first_table; cur_ptr; cur_ptr = cur_ptr->next_table) {
X		(void) sprintf(buf, "t%d", cur_ptr->table_id);
X		write_socket(new_socket, buf);
X		(void) sprintf(buf, "h%d", cur_ptr->hand);
X		write_socket(new_socket, buf);
X		(void) sprintf(buf, "r%d", cur_ptr->round);
X		write_socket(new_socket, buf);
X		for (i = 0; i < 4; i++) {
X			(void) sprintf(buf, "p%d%s", i, cur_ptr->player_name[i]);
X			write_socket(new_socket, buf);
X		}
X	}
X	write_socket(new_socket, "g");
X}
X
Xtell_new_comers(buf)
Xchar	*buf;
X{
X	new_ptr	cur_new;
X
X	for (cur_new = first_new_comer; cur_new;
X			cur_new = cur_new->next_new_comer)
X		write_socket(cur_new->socket, buf);
X}
X
X/*
X * join - join an existing table.  table_num is unique identifier for table.
X */
Xjoin(table_num, socket)
Xint	table_num, socket;
X{
X	table_ptr	cur_ptr;
X	char	buf[16];
X
X	for (cur_ptr = first_table; cur_ptr && cur_ptr->table_id != table_num;
X					cur_ptr = cur_ptr->next_table) ;
X	if (cur_ptr) {
X		(void) sprintf(buf, "c%d", cur_ptr->port_num);
X		write_socket(socket, buf);
X	}
X	else
X		write_socket(socket, "g");	/* Table doesn't exist?!? */
X}
X
X/*
X * new_table - start new hearts game.
X */
Xnew_table(cur_new_comer)
Xnew_ptr	cur_new_comer;
X{
X	table_ptr	cur_table, new_tbl_ptr;
X	empty_ptr	tmp_empty;
X	int		i, socks[2];
X	int		dealer_port, dealer_socket, retry;
X	char	buf[16];
X	char	*malloc();
X
X	new_tbl_ptr = (table_ptr) malloc(sizeof(struct table));
X	new_tbl_ptr->next_table = NULL;
X	new_tbl_ptr->table_id = ++unique_id;
X	for (i = 0; i < 4; i++)
X		(void) strcpy(new_tbl_ptr->player_name[i], "<empty>");
X	/*
X	 * Assign a port.  Reassign a used port if available.
X	 */
X	do {
X		if (first_empty) {
X			dealer_port = first_empty->port_num;
X			tmp_empty = first_empty;
X			first_empty = first_empty->next_empty;
X			free((char *) tmp_empty);
X		}
X		else
X			dealer_port = next_port++;
X		/*
X		 * Make sure port is ok before assigning.
X		 */
X		if ((dealer_socket = open_socket(dealer_port)) == 0)
X			if (++ retry == 20) {
X				fputs("Can't open a dealer port!\n", stderr);
X				exit(1);
X			}
X	}
X	while (dealer_socket == 0);
X	new_tbl_ptr->port_num = dealer_port;
X	/*
X	 * Open a socket pair to talk to dealer on.
X	 */
X	if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) == -1) {
X		perror("socketpair");
X		exit(1);
X	}
X	switch (new_tbl_ptr->pid = fork()) {
X	case 0:
X		(void) setpgrp (0, getpid());
X		(void) close(socks[0]);
X		if (socks[1] != 3)
X			(void) dup2(socks[1], 3);	/* Remap to fd 3 */
X		if (dealer_socket != 4)
X			(void) dup2(dealer_socket, 4);	/* Remap to fd 4 */
X		execl (HEARTSD, "heartsd", 0);
X		exit(1);
X	case -1:
X		perror("fork");
X		exit(1);
X	}
X	(void) close(socks[1]);
X	(void) close(dealer_socket);
X
X	new_tbl_ptr->socket = socks[0];
X	if (first_table) {
X		for (cur_table = first_table; cur_table->next_table;
X			cur_table = cur_table->next_table)
X			;
X		cur_table->next_table = new_tbl_ptr;
X	}
X	else
X		first_table = new_tbl_ptr;
X	/*
X	 * Inform hearts player of port # to connect on.
X	 */
X	(void) sprintf(buf, "c%d", dealer_port);
X	write_socket(cur_new_comer->socket, buf);
X}
X
Xmain(argc, argv)
Xchar **argv;
X{
X	fd_type		read_fd;
X	table_ptr	cur_table;
X	new_ptr		cur_new_comer;
X	char		buf[64], tmp_buf[64];
X	int		ret;
X	int		table_num;
X	char		debug;
X
X	while (*++argv) {
X		if (**argv == '-') {
X			while (*++*argv) {
X				switch (**argv) {
X				case 'd':
X					debug = TRUE;
X					break;
X
X				default:
X					fprintf (stderr, "usage: hearts_dist [-d]\n");
X					exit (1);
X					break;
X				}
X			}
X		}
X	}
X
X	first_table = NULL;
X	first_empty = NULL;
X	first_new_comer = NULL;
X	next_port = DIST_PORT;
X	unique_id = 0;
X
X	if ((main_sock = open_socket(PORT)) == 0) {
X		fputs("Distributor port in use!\n", stderr);
X		exit(1);
X	}
X	if (!debug) {
X		/*
X		 * Fork off and die.  Thus hearts invoker wait() returns.
X		 * This signals ready to connect.
X		 */
X		if (fork())
X			exit (0);
X	}
X
X	for (;;) {
X		fd_init(main_sock, &read_fd);
X		/*
X		 * Build mask for dealers
X		 */
X		for (cur_table = first_table; cur_table;
X				cur_table = cur_table->next_table)
X			fd_set(cur_table->socket, &read_fd);
X		/*
X		 * Build mask for new_comers
X		 */
X		for (cur_new_comer = first_new_comer; cur_new_comer;
X				cur_new_comer = cur_new_comer->next_new_comer)
X			fd_set(cur_new_comer->socket, &read_fd);
X
X		/*
X		 * Wait for something to happen
X		 */
X		if (select(WIDTH, &read_fd, (fd_type *) 0, (fd_type *) 0,
X				(struct timeval *) 0)) {
X			if (fd_isset(main_sock, read_fd))
X				new_player();
X			for (cur_table = first_table; cur_table;
X					cur_table = cur_table->next_table)
X				if (fd_isset(cur_table->socket, read_fd)) {
X					/*
X					 * Message from dealer
X					 */
X					ret = read_socket(cur_table->socket, buf);
X					if (!ret)
X						clear_table(cur_table);
X					else {
X						switch (buf[0]) {
X						case 'h' :
X							(void) sscanf(buf + 1, "%d", &cur_table->hand);
X							break;
X						case 'r' :
X							(void) sscanf(buf + 1, "%d", &cur_table->round);
X							break;
X						case 'p' :
X							(void) strcpy(cur_table->player_name[buf[1] - '0'], buf + 2);
X						}
X						(void) sprintf(tmp_buf, "t%d", cur_table->table_id);
X						tell_new_comers(tmp_buf);
X						tell_new_comers(buf);
X					}
X				}
X			for (cur_new_comer = first_new_comer; cur_new_comer;
X				cur_new_comer = cur_new_comer->next_new_comer)
X				if (fd_isset(cur_new_comer->socket, read_fd)) {
X					/*
X					 * Message from newcomer
X					 */
X					ret = read_socket(cur_new_comer->socket, buf);
X					if (ret)
X						switch (buf[0]) {
X						case 'j' :
X							(void) sscanf(buf + 1, "%d", &table_num);
X							join(table_num, cur_new_comer->socket);
X							break;
X
X						case 'n' :
X							new_table(cur_new_comer);
X						}
X					else
X						drop_new_comer(cur_new_comer);
X				}
X		}
X	}
X}
X
END_OF_dist.c
if test 9988 -ne `wc -c <dist.c`; then
    echo shar: \"dist.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f heartsd.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"heartsd.c\"
else
echo shar: Extracting \"heartsd.c\" \(34560 characters\)
sed "s/^X//" >heartsd.c <<'END_OF_heartsd.c'
X/*
X * dealer - more commonly known as heartsd
X *
X * Smarts of hearts program.  Runs in background when invoked by initial
X * caller of hearts.  Contains all logic for communicating with other
X * hearts players.  Handles all computer players, and communicates play
X * of others to other players.
X *
X * Computer strategy originally written in Pascal by:
X *	Don Baccus of Oregon Software.
X * Strategy mods by:
X *	Jeff Hemmerling of Test Systems Strategies, Inc.
X *
X * Converted strategy to C, added curses and multi-player (sockets)
X * interface by:
X *	Bob Ankeney of Generic Computer Products
X *
X * Thanks to Keith Packard, for his invaluable (is that the same as
X * unvaluable?) assistance as a unix guru, for design suggestions, and
X * (I suppose) for convincing me to rewrite the bloody thing.
X *
X * Bug reports to Bob Ankeney at:
X * ...!tektronix!reed!bob
X *
X */
X
X#include "misc.h"
X#include "defs.h"
X#include "local.h"
X
X#define	BUF_SIZE	64
X#define FUNNY_FACTOR	2
X#define NPASS		3	/* # cards to pass */
X#define PLAYERS		4	/* # players total */
X#define NCARDS		(52 / PLAYERS)
X
X#define ACE		13
X#define KING		12
X#define QUEEN		11
X#define JACK		10
X
X#define HEARTS_PANIC	4
X#define PANIC_RATIO	0.85
X
X#define COMPUTER	1
X#define HUMAN		2
X#define VOYEUR		3
X
Xint	debug = 0;
X
Xint	num_humans,			/* # HUMAN players */
X	human_type,			/* HUMAN or VOYEUR */
X	player_mask;		/* Bit n+1 is set for each human player n */
X
Xchar	someone_is_shooting, possibly_shooting,
X	queen_played,
X	safe_suit[MAX_SUIT + 1];
X
Xtypedef	struct node *ptr;
X
Xstruct	card {
X	int suit, rank;
X};
X
Xstruct	node {
X	ptr llink, rlink;
X	int rank;
X};
X
Xstruct	suit_list {
X	ptr head, tail;
X	int length;
X};
X
Xtypedef struct suit_list card_hand[MAX_SUIT + 1];
X
Xstruct player_status {
X	card_hand	cards;
X	int		pts, totalpts;
X	int		passer;
X	struct card	passed_cards[NPASS + 1];
X	int		num_passed;
X	int		player_kind;
X	int		socket;
X	char		name[9];
X};
X
Xstruct	player_status	card_table[PLAYERS + 1];
X
Xcard_hand	cards_played;
X
Xstruct	card	deck[53];
X
Xstruct	sockaddr_in	sockaddr;
X
X
Xint	player_count, leader,
X	round, hand,
X	points_played, shooter;
X
Xchar	hearts_broken;
X
Xstruct	{
X	int	card_count, highest_played, high_player, suit_led, pts;
X	char	any_hearts;
X	struct card played[PLAYERS + 1];
X} trick;
X
Xchar	*snames[] = {
X	    "",
X	    "clubs",
X	    "diamonds",
X	    "hearts",
X	    "spades"
X	},
X	rnames[] = " 23456789TJQKA",
X	*comp_names[] = {
X	    "",
X	    "zeppo",
X	    "chico",
X	    "harpo",
X	    "groucho"
X	};
X
X
Xrnd(num)
X{
X	extern long random();
X
X	return (random() % num);
X}
X
X
Xinit_deck()
X{
X	int	suit_temp, rank_temp, j;
X
X	j = 0;
X	for (rank_temp = MIN_RANK; rank_temp <= MAX_RANK ; rank_temp++)
X		for (suit_temp = CLUBS; suit_temp <= SPADES; suit_temp++) {
X			deck[++j].suit = suit_temp;
X			deck[j].rank = rank_temp;
X		}
X}
X
Xinit_suit(list)
Xstruct suit_list *list;
X{
X	char *malloc();
X	ptr	p, p1;
X
X	list->length = 0;
X	p = list->head = (ptr) malloc(sizeof(*p));
X	p1 = list->tail = (ptr) malloc(sizeof(*p1));
X	p->llink = NULL;	  
X	p1->rlink = NULL;
X	p->rlink = p1;	  
X	p1->llink = p;
X	p->rank = MAX_RANK + 1; 
X	p1->rank = 0;
X}
X
Xinit()
X{
X	int	player;
X
X	srandom(getpid());	/* Init random number gen */
X	for (player = 1; player <= PLAYERS; player++) {
X		card_table[player].player_kind = COMPUTER;
X		(void) strcpy(card_table[player].name, comp_names[player]);
X		card_table[player].socket = -1;
X	}
X	num_humans = 0;
X}
X
Xnew_game()
X{
X	int	suit, player;
X
X	init_deck();
X	for (player = 1; player <= PLAYERS; player++) {
X		card_table[player].totalpts = 0;
X		for (suit = CLUBS; suit <= SPADES; suit++)
X			init_suit(&card_table[player].cards[suit]);
X	}
X	for (suit = CLUBS; suit <= SPADES; suit++)
X		init_suit(&cards_played[suit]);
X}
X
Xget_rank(rch)
Xchar	rch;
X{
X	int i;
X
X	for (i = 1; i <= MAX_RANK; i++)
X		if (rch == rnames[i])
X			return(i);
X	return(0);
X}
X
Xget_suit(sch)
Xchar	sch;
X{
X	int i;
X
X	for (i = 1; i <= MAX_SUIT; i++)
X		if (sch == *snames[i])
X			return(i);
X	return(0);
X}
X
Xchar *
Xget_name(player_num)
Xint	player_num;
X{
X	int	i;
X	char	unique_name = TRUE;
X	static char pname[16];
X
X	for (i = 1; i <= PLAYERS; i++)
X		if ((i != player_num) &&
X		    !strcmp(card_table[i].name, card_table[player_num].name))
X			unique_name = FALSE;
X	if (unique_name)
X		(void) strcpy(pname, card_table[player_num].name);
X	else
X		(void) sprintf(pname, "%s (%d)", card_table[player_num].name,
X								player_num);
X	return(pname);
X}
X
X/*
X * Get a card from somebody.  get_mask is a bit mask specifying who is
X * acceptable to get a card from (i.e., bit n states player n can send
X * a card.)  Scans all sockets for input, handling messages and player
X * leaving signals.  Returns when one of the specified players passes
X * a card.  Returns 0 if one of the specified players exits the game.
X */
Xget_card(get_mask, from, buf)
Xint	get_mask, *from;
Xchar	*buf;
X{
X	int	i, player, ret;
X	char	mesg_buf[64];
X	fd_type read_fd;
X
X	for (;;) {
X		fd_init(4, &read_fd);		/* Watch for new arrivals */
X		for (player = 1; player <= PLAYERS; player++)
X			if (card_table[player].player_kind != COMPUTER)
X				fd_set(card_table[player].socket, &read_fd);
X		if (select(WIDTH, &read_fd, (fd_type *) 0, (fd_type *) 0,
X				(struct timeval *) 0)) {
X			if (fd_isset(4, read_fd))	/* New arrival? */
X				add_player();
X			for (player = 1; player <= PLAYERS; player++)	/* This player send? */
X				if (fd_isset(card_table[player].socket, read_fd)) {
X					ret = read_socket(card_table[player].socket, buf);
X					*from = player;
X					if (ret && (buf[0] == 'M')) {
X						(void) sprintf(mesg_buf, "M%d%s: %s", MESG_WINDOW,
X						get_name(player), buf + 1);
X						send_to_all(mesg_buf);		/* Message to everyone */
X					} 
X					else {
X						if (!ret) {
X							if (card_table[player].player_kind == HUMAN)
X								player_mask &= ~(1 << player);		/* player went away */
X							card_table[player].player_kind = COMPUTER;
X							(void) close(card_table[player].socket);
X							if (--num_humans == 0)
X								exit (0);	/* give up if nobody wants to play (sniff) */
X							for (i = 1; i <= PLAYERS; i++) {
X								if (card_table[i].player_kind != COMPUTER) {
X									(void) sprintf(buf,
X									"M%d%s vanishes in a puff of greasy black smoke!",
X									MESG_WINDOW, get_name(player));	/* Announce deletion */
X									send_buf(i, buf);		/* to others */
X									(void) sprintf(buf,
X									/*
X									 * Inform dealer.
X									 */
X									"p%d<empty>", player - 1);
X									write_socket(3, buf);
X								}
X							}
X							(void) strcpy(card_table[player].name, comp_names[player]);
X							send_all_totals();
X						}
X						if (get_mask & (1 << player))
X							return(ret);
X					}
X				}
X		}
X	}
X}
X
Xtoss_card(player, card_to_toss, suit_to_toss)
Xint	player, suit_to_toss;
Xptr	card_to_toss;
X{
X	char	buf[BUF_SIZE];
X
X	(void) sprintf(buf, "R%c%c",
X	rnames[card_to_toss->rank], *snames[suit_to_toss]);
X	send_buf(player, buf);		/* Tell player to remove card */
X}
X
Xread_card(read_mask, player, card_to_play, suit_to_play)
Xint	read_mask, *player, *suit_to_play;
Xptr	*card_to_play;
X{
X	char	buf[BUF_SIZE];
X	int	rank, suit;
X	ptr p;
X	char	rank_ch, suit_ch;
X
X	if (get_card(read_mask, player, buf)) {
X		do {
X			rank_ch = buf[1];
X			suit_ch = buf[2];
X			rank = get_rank(rank_ch);
X			suit = get_suit(suit_ch);
X			erase_window(*player, TEXT_WINDOW);
X			p = card_table[*player].cards[suit].head->rlink;
X			while (p && (p->rank != rank))
X				p = p->rlink;
X			if (p == NULL) {
X				(void) sprintf(buf, "M%dPlay a card you have:",
X								TEXT_WINDOW);
X				send_buf(*player, buf);
X				send_buf(*player, "G");
X				if (!get_card(read_mask, player, buf))
X					return(0);
X			}
X		} 
X		while (p == NULL);
X		*card_to_play = p;
X		*suit_to_play = suit;
X		return(1);
X	}
X	return(0);
X}
X
Xsend_card(player, rank, suit)
Xint	player, rank, suit;
X{
X	char	buf[BUF_SIZE];
X
X	(void) sprintf(buf, "A%c%c", rnames[rank], *snames[suit]);
X	send_buf(player, buf);
X}
X
X/*
X *  send cards in hand to player
X */
Xsend_hand(player)
Xint	player;
X{
X	int	suit;
X	ptr	p;
X
X	for (suit = CLUBS; suit <= SPADES; suit++) {	/* send cards in hand */
X		p = card_table[player].cards[suit].head->rlink;
X		while (p->rank) {
X			send_card(player, p->rank, suit);
X			p = p->rlink;
X		}
X	}
X}
X
Xget_first_player()
X{
X	fd_type read_fd;		/* main socket bit mask */
X
X	/*
X	 *  wait for new player to show up
X	 */
X	fd_init(4, &read_fd);		/* Wait for someone to show up */
X	if (select(WIDTH, &read_fd, (fd_type *) 0, (fd_type *) 0,
X	(struct timeval *) 0) == -1) {
X		perror("select");
X		exit(1);
X	}
X}
X
Xadd_player()
X{
X	int new_socket;			/* new file descriptor */
X	char pname[128], buf[128];
X	struct sockaddr_in sockad;
X	int ssize;			/* to make accept happy */
X	int new_player = 0;
X	int i;
X
X	/*
X	 *  add whoever's waiting
X	 */
X	ssize = sizeof (sockad);
X	if ((new_socket = accept(4, &sockad, &ssize)) == -1) {
X		perror("accept");
X		exit(1);
X	}
X	/* get user name */
X	if (read_socket(new_socket, pname) > 0) {
X		pname[8] = '\0';	/* Name must be less than 9 chars */
X		for (i = PLAYERS; i >= 1; i--)
X			if (card_table[i].player_kind == COMPUTER)
X				new_player = i;
X	}
X	if (new_player) {
X		/*
X		 * Inform distributor
X		 */
X		(void) sprintf(buf, "p%d%s", new_player - 1, pname);
X		write_socket(3, buf);
X		card_table[new_player].player_kind = human_type;
X		card_table[new_player].socket = new_socket;
X		(void) strcpy(card_table[new_player].name, pname);
X		++num_humans;
X		if (human_type == VOYEUR) {
X			(void) sprintf(buf,
X				"M%d    Game in progress...", PLAY_WINDOW);
X			send_buf(new_player, buf);
X			(void) sprintf(buf,
X			       "M%dYou may watch till next hand.", MESG_WINDOW);
X			send_buf(new_player, buf);
X		} 
X		else
X			player_mask |= 1 << new_player;
X		for (i = 1; i <= PLAYERS; i++) {
X			if (card_table[i].player_kind != COMPUTER) {
X				(void) sprintf(buf,
X					"M%d%s added as player %d", MESG_WINDOW,
X					pname, new_player);
X				if (i != new_player)	/* Announce addition */
X					send_buf(i, buf);
X				send_totals(i);
X			}
X		}
X		send_hand(new_player);
X	}
X	else {
X		(void) sprintf(buf, "M%dAll seats are full!", MESG_WINDOW);
X		write_socket(new_socket, buf);
X		write_socket(new_socket, "X");
X		(void) close(new_socket);
X	}
X}
X
Xerase_window(player, which_window)
Xint	player, which_window;
X{
X	char	buf[BUF_SIZE];
X
X	(void) sprintf(buf, "E%d", which_window);
X	send_buf(player, buf);
X}
X
Xerase_all_window(which_window)
Xint	which_window;
X{
X	int	player;
X
X	for (player = 1; player <= PLAYERS; player++)
X		if (card_table[player].player_kind != COMPUTER)
X			erase_window(player, which_window);
X}
X
Xsend_totals(player)
Xint	player;
X{
X	int	i;
X	char	buf[BUF_SIZE];
X
X	if (card_table[player].player_kind != COMPUTER) {
X		for (i = 1; i <= PLAYERS; i++) {
X			(void) sprintf(buf, "S%d%02d%02d%s", i,
X				card_table[i].pts,
X				card_table[i].totalpts, card_table[i].name);
X			send_buf(player, buf);
X		}
X	}
X}
X
Xsend_all_totals()
X{
X	int	player;
X
X	for (player = 1; player <= PLAYERS; player++)
X		send_totals(player);
X}
X
Xsend_all_winner()
X{
X	int	max_score,
X	player, shooter;
X	char	buf[BUF_SIZE];
X
X	(void) sprintf(buf, "M%d--------------------------------------------------------", MESG_WINDOW);
X	send_to_all(buf);
X	max_score = 0;  
X	for (player = 1; player <= PLAYERS; player++) {
X		if (card_table[player].pts > max_score) {
X			max_score = card_table[player].pts;
X			shooter = player;
X		}
X	}
X	if (max_score == 26) {
X		for (player = 1; player <= PLAYERS; player++)
X			if (player != shooter)
X				card_table[player].totalpts += 26;
X	} 
X	else {
X		for (player = 1; player <= PLAYERS; player++)
X			card_table[player].totalpts += card_table[player].pts;
X	}
X	if (max_score == 26)
X		(void) sprintf(buf, "M %s wins by shooting the moon!",
X		get_name(shooter));
X	else {
X		get_winner(buf, FALSE);
X		(void) strcat(buf, ".");
X	}
X	buf[1] = MESG_WINDOW + '0';
X	send_to_all(buf);
X}
X
Xsend_final_winner()
X{
X	char	buf[64];
X
X	get_winner(buf, TRUE);
X	buf[1] = PLAY_WINDOW + '0';
X	(void) strcat(buf, " total!");
X	erase_all_window(PLAY_WINDOW);
X	send_to_all(buf);
X}
X
X/*
X * Get buffer of form:	"M groucho wins with nn points"
X *		   or:	"M groucho and chico tie with nn points"
X */
Xget_winner(buf, use_totalpts)
Xchar	*buf;
Xchar	use_totalpts;
X{
X	int	player, winning_score, temp,
X	scores[PLAYERS + 1], players[PLAYERS + 1];
X	char	sorted;
X
X	for (player = 1; player <= PLAYERS; player++) {
X		scores[player] = use_totalpts ? card_table[player].totalpts
X		    : card_table[player].pts;
X		players[player] = player;
X	}
X	do {				/* Bubble sort the bloody thing */
X		sorted = TRUE;
X		for (player = 1; player < PLAYERS; player++)
X			if (scores[player + 1] < scores[player]) {
X				temp = scores[player];
X				scores[player] = scores[player + 1];
X				scores[player + 1] = temp;
X				temp = players[player];
X				players[player] = players[player + 1];
X				players[player + 1] = temp;
X				sorted = FALSE;
X			}
X	} 
X	while (!sorted);
X	winning_score = scores[1];
X	(void) sprintf(buf, "M %s", card_table[players[1]].name);
X	for (player = 2;
X	    (player <= PLAYERS) && (scores[player] == winning_score); player++)
X		    (void) sprintf(buf + strlen(buf), " and %s",
X		card_table[players[player]].name);
X	if (scores[2] == winning_score)
X		(void) sprintf(buf + strlen(buf),
X		" tie with %d point", winning_score);
X	else
X		(void) sprintf(buf + strlen(buf),
X		" wins with %d point", winning_score);
X	if (winning_score != 1)
X		(void) strcat(buf, "s");
X}
X
Xsend_buf(player, buf)
Xint	player;
Xchar	*buf;
X{
X	write_socket(card_table[player].socket, buf);
X}
X
Xsend_to_all(buf)
Xchar	*buf;
X{
X	int	player;
X
X	for (player = 1; player <= PLAYERS; player++)
X		if (card_table[player].player_kind != COMPUTER)
X			send_buf(player, buf);
X}
X
Xnew_round()
X{
X	char	buf[BUF_SIZE];
X
X	trick.pts = 0;
X	trick.any_hearts = FALSE;
X	player_count = 0;
X	erase_all_window(PLAY_WINDOW);
X	/*
X	 * Inform distributor
X	 */
X	(void) sprintf(buf, "r%d", round);
X	write_socket(3, buf);
X	(void) sprintf(buf, "M%dHand: %d   Round: %d",
X				ROUND_WINDOW, hand, round);
X	send_to_all(buf);
X}
X
Xenter_card(which_card, which_hand)
Xstruct	card	which_card;
Xcard_hand	which_hand;
X
X{
X	ptr p, p1;
X
X	p = which_hand[which_card.suit].head;
X	++which_hand[which_card.suit].length;
X	while (p->rank > which_card.rank)
X		p = p->rlink;
X	p1 = (ptr) malloc(sizeof(*p1));
X	p1->llink = p->llink;
X	p1->llink->rlink = p1;
X	p->llink = p1;
X	p1->rlink = p;
X	p1->rank = which_card.rank;
X}
X
Xremove_node(p)
Xptr	p;
X
X{
X	p->llink->rlink = p->rlink;
X	p->rlink->llink = p->llink;
X	free((char *) p);
X}
X
Xclear_hand(which_hand)
Xcard_hand which_hand;
X{
X	int suit;
X	ptr p, p1;
X
X	for (suit = CLUBS; suit <= SPADES; suit++) {
X		safe_suit[suit] = TRUE;
X		which_hand[suit].length = 0;
X		p = which_hand[suit].head->rlink;
X		while (p->rank > 0) {
X			p1 = p;
X			p = p->rlink;
X			remove_node(p1);
X		}
X	}
X	safe_suit[HEARTS] = FALSE;
X}
X
Xshuffle()
X{
X	int	j, k;
X	struct card t;
X
X	for (j = 52; j >= 1; j--) {
X		k = rnd(j) + 1;
X		t = deck[k]; 
X		deck[k] = deck[j]; 
X		deck[j] = t;
X	}
X}
X
Xdeal()
X{
X	int i, j, player;
X
X	for (player = 1; player <= PLAYERS; player++) {
X		j = NCARDS * (player - 1);
X		for (i = 1; i <= NCARDS; i++)
X			enter_card(deck[i+j], card_table[player].cards);
X		if (card_table[player].player_kind != COMPUTER)
X			send_hand(player);
X	}
X}
X
Xnew_hand()
X{
X	int	player;
X	char	buf[BUF_SIZE];
X
X	points_played = 0;
X	someone_is_shooting = possibly_shooting = FALSE;
X	queen_played = hearts_broken = FALSE;
X	trick.suit_led = CLUBS;
X	for (player = 1; player <= PLAYERS; player++) {
X		card_table[player].pts = 0;
X		clear_hand(card_table[player].cards);
X	}
X	clear_hand(cards_played);
X	erase_all_window(PLAY_WINDOW);
X	send_all_totals();
X	/*
X	 * Inform distributor
X	 */
X	(void) sprintf(buf, "h%d", hand);
X	write_socket(3, buf);
X	write_socket(3, "r0");
X	(void) sprintf(buf, "M%dNew hand...", ROUND_WINDOW);
X	send_to_all(buf);
X	shuffle();			/* Shuffle three times! */
X	shuffle();			/* (just like vegas!)   */
X	shuffle();
X	deal();
X}
X
Xptr
Xnext_highest(player, rank, suit_to_play)
Xint	player, rank, suit_to_play;
X{
X	ptr	p;
X
X	p = card_table[player].cards[suit_to_play].head->rlink;
X	while (p && p->rank)
X		if (p->rank < rank)
X			return(p);
X		else
X			p = p->rlink;
X	return(NULL);
X}
X
X#define highest(player, suit) (card_table[player].cards[suit].head->rlink)
X
X#define lowest(player, suit) (card_table[player].cards[suit].tail->llink)
X
X#define cards_out(suit) (MAX_SUIT - cards_played[suit].length)
X
X#define spades_safe(player) ((((cards_out(SPADES) / PLAYERS) + 1) <	\
Xcard_table[player].cards[SPADES].length) && safe_suit[SPADES])
X
X#define diamonds_safe(player) (( (float) (cards_out(DIAMONDS) -	\
Xcard_table[player].cards[DIAMONDS].length) / (PLAYERS - 1.0) >= 2.0) &&\
X	safe_suit[DIAMONDS])
X
X#define clubs_safe(player) (( (float) (cards_out(CLUBS) -		\
Xcard_table[player].cards[CLUBS].length) / (PLAYERS - 1.0) >= 2.0) &&\
X	safe_suit[CLUBS])
X
X#define only_hearts(player) ((14 - round) ==			\
Xcard_table[player].cards[HEARTS].length)
X
Xfind_high_card(player, card_to_play, suit_to_play, play_points)
Xint	player, *suit_to_play;
Xchar	play_points;
Xptr	*card_to_play;
X{
X	int	high_card, suit;
X	ptr	p;
X
X	high_card = 0;
X	*card_to_play = NULL;
X	for (suit = CLUBS; suit <= SPADES; suit++) {
X		p = card_table[player].cards[suit].head->rlink;
X		if ((p->rank > high_card) &&
X		    (play_points || (point_value(p->rank, suit) == 0))) {
X			high_card = p->rank;
X			*card_to_play = p;
X			*suit_to_play = suit;
X		}
X	}
X	if (*card_to_play == NULL) {
X		/*
X		 * No clubs or diamonds.  Q spades is highest spade, or no
X		 * spades at all.  Try to play next highest spade.
X		 */
X		*card_to_play = next_highest(player, QUEEN, SPADES);
X		if (*card_to_play)
X		    *suit_to_play = SPADES;
X		else {
X			/* Play highest heart */
X			*card_to_play = highest(player, HEARTS);
X			*suit_to_play = HEARTS;
X		}
X	}
X}
X
Xfind_low_card(player, card_to_play, suit_to_play)
Xint	player, *suit_to_play;
Xptr	*card_to_play;
X{
X	int	low_card, suit;
X	ptr	p;
X
X	low_card = MAX_RANK + 1;
X	*card_to_play = NULL;
X	for (suit = CLUBS; suit <= SPADES; suit++) {
X		p = card_table[player].cards[suit].tail->llink;
X		if ((p->rank < low_card) && (suit != HEARTS)) {
X			low_card = p->rank;
X			*card_to_play = p;
X			*suit_to_play = suit;
X		}
X		if (*card_to_play == NULL) {
X			*suit_to_play = HEARTS;
X			*card_to_play = card_table[player].cards[HEARTS].tail->llink;
X		}
X	}
X}
X
Xfind_low_club(leader)
Xint	*leader;
X{
X	int	player, low_club, rank;
X
X	low_club = MAX_RANK;
X	for (player = 1; player <= PLAYERS; player++)
X		if ((rank = card_table[player].cards[CLUBS].tail->llink->rank) < low_club) {
X			*leader = player;
X			low_club = rank;
X		}
X}
X
Xfind_queen(player, card_to_play)
Xint	player;
Xptr	*card_to_play;
X{
X	ptr	p;
X
X	*card_to_play = NULL;
X	p = card_table[player].cards[SPADES].head->rlink;
X	while (p)
X	    if (p->rank == QUEEN) {
X		*card_to_play = p;
X		p = NULL;
X	} 
X	else
X		p = p->rlink;
X}
X
Xprint_pass(player)
Xint	player;
X{
X	int	i;
X	char	buf[BUF_SIZE];
X	struct card cd;
X
X	erase_window(player, PLAY_WINDOW);
X	(void) sprintf(buf, "M%d%s passes you:", LEAD_WINDOW,
X	get_name(card_table[player].passer));
X	send_buf(player, buf);
X	for (i = 1; i <= NPASS; i++) {
X		cd = card_table[player].passed_cards[i];
X		send_card(player, cd.rank, cd.suit);
X		(void) sprintf(buf, "P%d%c%c", i, rnames[cd.rank], *snames[cd.suit]);
X		send_buf(player, buf);
X	}
X}
X
X/*
X * Get card to pass from computer player
X */
Xget_pass(from, which_pass, card_to_pass, suit_to_pass)
Xint	from, which_pass, *suit_to_pass;
Xptr	*card_to_pass;
X{
X	ptr	p1;
X
X	p1 = highest(from, SPADES);
X	if (!spades_safe(from) && (p1->rank >= QUEEN) &&
X	    card_table[from].cards[SPADES].length) {
X		*card_to_pass = p1;
X		*suit_to_pass = SPADES;
X	} 
X	else
X		if (((NPASS - which_pass) >= card_table[from].cards[DIAMONDS].length - 1) &&
X		    (card_table[from].cards[DIAMONDS].length > 0)) {
X			*card_to_pass = highest(from, DIAMONDS);
X			*suit_to_pass = DIAMONDS;
X		} 
X		else
X			if (((NPASS - which_pass) >= card_table[from].cards[CLUBS].length - 2) &&
X			    (card_table[from].cards[CLUBS].length > 1)) {
X				*card_to_pass = highest(from, CLUBS);
X				*suit_to_pass = CLUBS;
X			} 
X			else
X				if (card_table[from].cards[HEARTS].length) {
X					*card_to_pass = highest(from, HEARTS);
X					*suit_to_pass = HEARTS;
X				} 
X				else
X					find_high_card(from, card_to_pass, suit_to_pass, TRUE);
X}
X
X/*
X * Pass card to player
X */
Xpass_to(from, who_to, which_pass, card_to_pass, suit_to_pass)
Xint	from, who_to, which_pass;
Xptr	card_to_pass;
Xchar	suit_to_pass;
X{
X	char	buf[BUF_SIZE];
X
X	card_table[who_to].passed_cards[which_pass].rank = card_to_pass->rank;
X	card_table[who_to].passed_cards[which_pass].suit = suit_to_pass;
X	--card_table[from].cards[suit_to_pass].length;
X	card_table[who_to].passer = from;
X	remove_node(card_to_pass);
X	if (card_table[from].player_kind != COMPUTER) {
X		erase_window(from, INP_WINDOW);
X		(void) sprintf(buf, "P%d%c%c", which_pass,
X		rnames[card_to_pass->rank], *snames[suit_to_pass]);
X		send_buf(from, buf);
X		toss_card(from, card_to_pass, suit_to_pass);
X	}
X}
X
X#define map_player(leader, player) (((leader + player - 1) % PLAYERS) + 1)
X
Xplayer_pass(player)
Xint	player;
X{
X	char	buf[BUF_SIZE];
X
X	(void) sprintf(buf, "M%dPass %d to %s:",
X	TEXT_WINDOW, NPASS, get_name(map_player(player, hand)));
X	send_buf(player, buf);
X	(void) sprintf(buf, "M%dCard 1:", LEAD_WINDOW);
X	send_buf(player, buf);
X	send_buf(player, "G");
X}
X
Xpass_cards()
X{
X	int	player, pass, suit, old_mask;
X	int	passed_mask = 0;
X	ptr	p;
X	char	buf[BUF_SIZE];
X
X	human_type = HUMAN;
X	for (player = 1; player <= PLAYERS; player++) {
X		card_table[player].num_passed = 0;
X		if (card_table[player].player_kind == HUMAN)
X		    player_pass(player);
X	}
X	while (player_mask != passed_mask) {
X		old_mask = player_mask;
X		if (read_card(player_mask, &player, &p, &suit)) {
X			pass_to(player, map_player(player, hand),
X			++card_table[player].num_passed, p, suit);
X			if (card_table[player].num_passed == NPASS)
X				/*
X				 * Done passing
X				 */
X				passed_mask |= 1 << player;
X			else {
X				(void) sprintf(buf, "M%dCard %d:", LEAD_WINDOW,
X				card_table[player].num_passed + 1);
X				/*
X				 * Ask for next card
X				 */
X				send_buf(player, buf);
X				send_buf(player, "G");
X			}
X		} 
X		else
X			/*
X			 * Player left game
X			 */
X			passed_mask &= ~(1 << player);
X		/*
X		 * Player added?
X		 */
X		for (player = 1; player <= PLAYERS; player++)
X			if (~old_mask & player_mask & (1 << player)) {
X				card_table[player].num_passed = 0;
X				player_pass(player);
X			}
X	}
X	erase_all_window(LEAD_WINDOW);
X	human_type = VOYEUR;
X	/*
X	 * Let computer pass.
X	 */
X	for (player = 1; player <= PLAYERS; player++)
X		if (card_table[player].player_kind != HUMAN)
X		    for (pass = ++card_table[player].num_passed;
X					pass <= NPASS; pass++) {
X			get_pass(player, pass, &p, &suit);
X			pass_to(player, map_player(player, hand), pass, p, suit);
X		}
X	for (player = 1; player <= PLAYERS; player++) {
X		for (pass = 1; pass <= NPASS; pass++)
X			enter_card(card_table[player].passed_cards[pass],
X			card_table[player].cards);
X		if (card_table[player].player_kind != COMPUTER)
X		    print_pass(player);
X	}
X}
X
Xpoint_value(rank, suit)
Xint	rank, suit;
X{
X	if (suit == HEARTS)
X	    return(1);
X	else
X		if ((suit == SPADES) && (rank == QUEEN))
X		    return(13);
X	return(0);
X}
X
Xshow(player, card_to_play, suit_to_play)
Xint	player, suit_to_play;
Xptr	card_to_play;
X{
X	int	rank_to_play;
X	char	buf[BUF_SIZE];
X
X	rank_to_play = card_to_play->rank;
X	remove_node(card_to_play);
X	if (card_table[player].player_kind != COMPUTER) {
X		erase_window(player, INP_WINDOW);
X		erase_window(player, LEAD_WINDOW);
X		toss_card(player, card_to_play, suit_to_play);
X	}
X	--card_table[player].cards[suit_to_play].length;
X	if (suit_to_play == HEARTS)
X	    trick.any_hearts = TRUE;
X	else {
X		if ((suit_to_play == SPADES) && (rank_to_play == QUEEN))
X		    queen_played = TRUE;
X	}
X	if ((suit_to_play == trick.suit_led) &&
X	    (rank_to_play > trick.highest_played)) {
X		trick.highest_played = rank_to_play;
X		trick.high_player = player;
X	}
X	trick.played[player].rank = rank_to_play;
X	trick.played[player].suit = suit_to_play;
X	/*
X		 * If points were dumped or someone couldn't follow suit,
X		 * then the suit is unsafe.
X		 */
X	if ((trick.pts += point_value(rank_to_play, suit_to_play)) ||
X	    (suit_to_play != trick.suit_led))
X	    safe_suit[trick.suit_led] = FALSE;
X	enter_card(trick.played[player], cards_played);
X	++trick.card_count;
X	(void) sprintf(buf, "P%d%c%c%s", player, rnames[rank_to_play],
X	*snames[suit_to_play], card_table[player].name);
X	send_to_all(buf);
X}
X
Xreally_safe(suit)
Xint	suit;
X{
X	char	safe;
X	int	losers_out, low_rank;
X	ptr	p;
X
X	p = cards_played[suit].head->rlink;
X	low_rank = card_table[leader].cards[suit].tail->llink->rank;
X	/*
X		 * Count # cards of _suit_ played that would be losers
X		 * to leaders lowest of _suit_.
X		 */
X	losers_out = 0;
X	while (p->rank) {
X		if (p->rank < low_rank)
X		    ++losers_out;
X		p = p->rlink;
X	}
X	/*
X	 * Old strategy.
X	  losers_out = low_rank - 2 - losers_out;
X	  safe = (((card_table[leader].cards[suit].length +
X			  cards_played[suit].length + losers_out) != 13) ||
X			  safe_suit[suit]);
X	 */
X	/*
X	 * Card is guaranteed to lose the trick if:
X	 * -- All cards of suit lower than low_rank have been played;
X	 * -- There is at least one other card of the suit unplayed.
X	 *
X 	 * Using this strategy means safe_suit[] is unused.
X	 */
X	safe = (losers_out == (low_rank - 1)) &&
X	    ((card_table[leader].cards[suit].length + cards_played[suit].length) != 13);
X	if (suit == SPADES)
X	    safe &= ((card_table[leader].cards[SPADES].tail->llink->rank <
X	    QUEEN) || queen_played);
X	return(safe);
X}
X
Xfind_low_lead(card_to_play, suit_to_play, play_hearts)
Xptr	*card_to_play;
Xint	*suit_to_play;
Xchar	play_hearts;
X{
X	char	last_gasp;
X	int	low_card, suit;
X	ptr	p;
X
X	low_card = MAX_RANK + 1;
X	last_gasp = FALSE;
X	*card_to_play = NULL;
X	while (*card_to_play == NULL) {
X		for (suit = CLUBS; suit <= SPADES; suit++) {
X			p = card_table[leader].cards[suit].tail->llink;
X			if ((p->rank < low_card) &&
X			    (play_hearts || (suit != HEARTS)) &&
X			    (really_safe(suit) || last_gasp)) {
X				low_card = p->rank;
X				*card_to_play = p;
X				*suit_to_play = suit;
X			}
X		}
X		last_gasp = TRUE;
X	}
X}
X
Xshow_lead(card_to_play, suit_to_play)
Xint	suit_to_play;
Xptr	card_to_play;
X{
X	trick.card_count = 1;
X	trick.highest_played = card_to_play->rank;
X	trick.suit_led = suit_to_play;
X	trick.high_player = leader;
X	show(leader, card_to_play, suit_to_play);
X}
X
Xread_lead(leader, card_to_play, suit_to_play)
Xint	leader, *suit_to_play;
Xptr	*card_to_play;
X{
X	char	good, alive, buf[64];
X	int	t;	/* Ignored */
X
X	do {
X		send_buf(leader, "G");
X		if (alive = read_card(1 << leader, &t, card_to_play, suit_to_play)) {
X			good = (((round != 1) ||
X			    (*card_to_play == card_table[leader].cards[CLUBS].tail->llink)) &&
X			    ((*suit_to_play != HEARTS) || hearts_broken || only_hearts(leader)));
X			if (!good) {
X				if (*suit_to_play == HEARTS)
X				    (void) sprintf(buf,"M%dHearts not broken yet!", TEXT_WINDOW);
X				else
X					(void) sprintf(buf,"M%dLead your lowest club!", TEXT_WINDOW);
X				send_buf(leader, buf);
X				send_buf(leader, "G");
X			}
X		}
X	} 
X	while (alive && !good);
X}
X
Xread_next_card(player, card_to_play, suit_to_play)
Xint	player, *suit_to_play;
Xptr	*card_to_play;
X{
X	char	good, good_pts, alive, buf[64];
X	int	t;	/* Ignored */
X
X	do {
X		send_buf(player, "G");
X		if (alive = read_card(1 << player, &t, card_to_play, suit_to_play)) {
X			good_pts = (((*suit_to_play != SPADES) ||
X			    ((*card_to_play)->rank != QUEEN))
X			    && ((*suit_to_play != HEARTS) || only_hearts(player)) ||
X			    (round != 1));
X			good = (((*suit_to_play == trick.suit_led) ||
X			    (card_table[player].cards[trick.suit_led].length == 0)) && good_pts);
X			if (!good) {
X				if (!good_pts)
X				    (void) sprintf(buf, "M%dCan't dump points yet!", TEXT_WINDOW);
X				else
X					(void) sprintf(buf, "M%dTry following suit!", TEXT_WINDOW);
X				send_buf(player, buf);
X				send_buf(player, "G");
X			}
X		}
X	} 
X	while (alive && !good);
X}
X
Xcomputer_lead(card_to_play, suit_to_play)
Xint	*suit_to_play;
Xptr	*card_to_play;
X{
X	ptr	p;
X
X	if (round == 1) {
X		/*
X		 * Lead the 2 of clubs
X		 */
X		*card_to_play = card_table[leader].cards[CLUBS].tail->llink;
X		*suit_to_play = CLUBS;
X	} 
X	else {
X		find_queen(leader, &p);
X		if (!queen_played && (p == NULL) && spades_safe(leader) &&
X		    card_table[leader].cards[SPADES].length) {
X			*suit_to_play = SPADES;
X			*card_to_play = next_highest(leader, QUEEN, SPADES);
X		} 
X		else {
X			*card_to_play = NULL;
X/*
X * Old strategy
X			      if (diamonds_safe(leader) &&
X				 (card_table[leader].cards[DIAMONDS].length)) {
X				*suit_to_play = DIAMONDS;
X				*card_to_play = highest(leader, DIAMONDS);
X			      } else
X			      if (clubs_safe(leader) && (card_table[leader].cards[CLUBS].length)) {
X				*suit_to_play = CLUBS;
X				*card_to_play = highest(leader, CLUBS);
X			      }
X */
X		}
X		if (*card_to_play == NULL)
X		    find_low_lead(card_to_play, suit_to_play,
X		(hearts_broken || only_hearts(leader)));
X	}
X}
X
X#define max(a,b) ((a > b) ? a : b)
X
Xpick_a_loser(player, card_to_play, suit_to_play)
Xint	player, *suit_to_play;
Xptr	*card_to_play;
X{
X	ptr	p;
X
X	if (card_table[player].cards[trick.suit_led].length) {
X		*suit_to_play = trick.suit_led;
X		if ((trick.suit_led != SPADES) && (trick.pts == 0) &&
X		    (trick.card_count == PLAYERS))
X		/*
X		 * Play the highest card of suit led if player is last
X		 * and there are no points in current trick.
X		 */
X		*card_to_play = highest(player, trick.suit_led);
X		else
X			switch (trick.suit_led) {
X			case SPADES:
X				if ((trick.card_count == PLAYERS) && (trick.pts == 0)) {
X				/*
X				 * Play the highest spade.  Don't play queen if
X				 * it will win the trick.
X				 */
X					*card_to_play = highest(player, SPADES);
X					if (((*card_to_play)->rank == QUEEN) &&
X					    (QUEEN > trick.highest_played))
X					    *card_to_play = next_highest(player, QUEEN, SPADES);
X				} 
X				else
X					if (spades_safe(player) && !queen_played) {
X						find_queen(player, &p);
X						*card_to_play = highest(player, SPADES);
X						if ((p == NULL) || (p == *card_to_play))
X						    *card_to_play = next_highest(player,
X						max(trick.highest_played, QUEEN), SPADES); 
X					}
X				break;
X
X			case CLUBS:
X				if (clubs_safe(player))
X				    *card_to_play = highest(player, CLUBS);
X				break;
X
X			case DIAMONDS:
X				if (diamonds_safe(player))
X				    *card_to_play = highest(player,DIAMONDS);
X				break;
X
X			case HEARTS:
X				break;
X			}
X		if (*card_to_play == NULL)
X		    *card_to_play = next_highest(player, trick.highest_played,
X		*suit_to_play);
X		if (*card_to_play == NULL) {
X			*card_to_play = card_table[player].cards[trick.suit_led].tail->llink;
X			/*
X			 * Don't play the Q spades if:
X			 *   1. The Queen is your lowest spade and
X			 *   2. You have a higher spade.
X			 */
X			if (((*card_to_play)->rank == QUEEN) && (trick.suit_led == SPADES) &&
X			    (card_table[player].cards[trick.suit_led].length > 1))
X			    *card_to_play = card_table[player].cards[trick.suit_led].head->rlink;
X		}
X	} 
X	else {
X		if (round != 1) {
X			/*
X			 * Play the queen of spades if player has it.
X			 */
X			if (*card_to_play)
X			    *suit_to_play = SPADES;
X			if ((*card_to_play == NULL) &&
X			    (card_table[player].cards[HEARTS].length)) {
X				if (possibly_shooting) {
X					*card_to_play = next_highest(player, JACK, HEARTS);
X					if (*card_to_play == NULL)
X					    *card_to_play = lowest(player, HEARTS);
X				} 
X				else
X					*card_to_play = highest(player, HEARTS);
X				*suit_to_play = HEARTS;
X			}
X		}
X		if (*card_to_play == NULL)
X		    find_high_card(player, card_to_play, suit_to_play, (round != 1));
X	}
X}
X
Xstop_the_bastard(player, card_to_play, suit_to_play)
Xint	player, *suit_to_play;
Xptr	*card_to_play;
X{
X	if (card_table[player].cards[trick.suit_led].length) {
X		*suit_to_play = trick.suit_led;
X		if ((trick.suit_led == SPADES) && (!queen_played)) {
X			*card_to_play = next_highest(player, QUEEN, SPADES);
X			if (*card_to_play == NULL)
X			    *card_to_play = lowest(player, SPADES);
X		} 
X		else
X			*card_to_play = highest(player, trick.suit_led);
X		if ((trick.pts < 13) &&
X		    !(((cards_out(trick.suit_led) < (PLAYERS - player_count)) ||
X		    trick.any_hearts) &&
X		    ((*card_to_play)->rank > trick.highest_played) &&
X		    (shooter == trick.high_player)))
X		    *card_to_play = lowest(player, trick.suit_led);
X	} 
X	else
X		if ((shooter != trick.high_player) &&
X		    (card_table[player].cards[HEARTS].length > 0)) {
X			*card_to_play = highest(player, HEARTS);
X			*suit_to_play = HEARTS;
X		} 
X		else
X			find_low_card(player, card_to_play, suit_to_play);
X}
X
Xcomputer_pick(player, card_to_play, suit_to_play)
Xint	player, *suit_to_play;
Xptr	*card_to_play;
X{
X	*card_to_play = NULL;
X	if (someone_is_shooting && (player != shooter))
X	    stop_the_bastard(player, card_to_play, suit_to_play);
X	else
X		pick_a_loser(player, card_to_play, suit_to_play);
X}
X
Xlead(leader)
Xint	leader;
X{
X	int	suit_to_play;
X	ptr	card_to_play;
X	char	buf[BUF_SIZE];
X
X	if (card_table[leader].player_kind == HUMAN) {
X		(void) sprintf(buf, "M%dYour lead:", LEAD_WINDOW);
X		send_buf(leader, buf);
X		read_lead(leader, &card_to_play, &suit_to_play);
X	}
X	if (card_table[leader].player_kind != HUMAN)	/* If player left */
X	    computer_lead(&card_to_play, &suit_to_play);
X	show_lead(card_to_play, suit_to_play);
X}
X
Xplay_next_card(player)
Xint	player;
X{
X	int	suit_to_play;
X	ptr	card_to_play;
X	char	buf[BUF_SIZE];
X
X	if (card_table[player].player_kind == HUMAN) {
X		(void) sprintf(buf, "M%dYour play:", LEAD_WINDOW);
X		send_buf(player, buf);
X		read_next_card(player, &card_to_play, &suit_to_play);
X	}
X	if (card_table[player].player_kind != HUMAN)	/* If player left */
X	    computer_pick(player, &card_to_play, &suit_to_play);
X	show(player, card_to_play, suit_to_play);
X}
X
Xfind_winner(winner)
Xint	*winner;
X{
X	*winner = trick.high_player;
X	if (trick.any_hearts)
X	    hearts_broken = TRUE;
X	points_played += trick.pts;
X	card_table[*winner].pts += trick.pts;
X	possibly_shooting = ((points_played > 0) &&
X	    (card_table[*winner].pts == points_played));
X	someone_is_shooting = (possibly_shooting &&
X	    (card_table[*winner].pts >= HEARTS_PANIC));
X	if (someone_is_shooting)
X	    shooter = *winner;
X}
X
X
Xmain()
X{
X	int	player;
X
X	init();
X	new_game();
X
X	human_type = HUMAN;
X	get_first_player();
X	add_player();
X
X	for (;;) {
X		for (hand = 1; hand <= PLAYERS; hand++) {
X			new_hand();
X			if (hand != PLAYERS)
X				pass_cards();
X			find_low_club(&leader);
X			for (round = 1; round <= NCARDS; round++) {
X				new_round();
X				lead(leader);
X				for (player_count = 1; player_count <= PLAYERS - 1; player_count++)
X					play_next_card(map_player(leader, player_count));
X				find_winner(&leader);
X				send_all_totals();
X			}
X			send_all_winner();
X			for (player = 1; player <= PLAYERS; player++)
X				if (card_table[player].player_kind == VOYEUR) {
X					card_table[player].player_kind = HUMAN;
X					player_mask |= 1 << player;
X				}
X		}
X		send_all_totals();
X		send_final_winner();
X		send_to_all("X");
X		new_game();
X	}
X}
END_OF_heartsd.c
if test 34560 -ne `wc -c <heartsd.c`; then
    echo shar: \"heartsd.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f opensock.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"opensock.c\"
else
echo shar: Extracting \"opensock.c\" \(1587 characters\)
sed "s/^X//" >opensock.c <<'END_OF_opensock.c'
X/*
X * opensock.c
X *
X * server creates a socket to listen for connections
X */
X
X#include <stdio.h>
X#include "defs.h"
X#include "local.h"
X
X
X/*
X * Open main socket on given port and set sock_num to it.
X * If port is 0, get port from /etc/services.
X */
Xopen_socket(port)
Xint	port;
X{
X	struct	sockaddr_in sockaddr;
X	struct	hostent *host;
X	struct	servent *distributor;
X	int	sock_num;
X	char	hostnm[SLEN];
X
X	(void) gethostname(hostnm, SLEN);
X	if ((sock_num = socket(AF_INET, SOCK_STREAM, 0)) < 0)  {
X		perror("socket");
X		exit(1);
X	}
X	if ((host = gethostbyname(hostnm)) == NULL)  {
X		perror("gethostbyname");
X		exit(1);
X	}
X	bzero((char *) &sockaddr, sizeof (sockaddr));
X	bcopy(host->h_addr, (char *) &sockaddr.sin_addr, host->h_length);
X	sockaddr.sin_family = AF_INET;
X	if (port)
X		sockaddr.sin_port = htons(port);
X	else {
X		if ((distributor = getservbyname(SERVICE, PROTO)) == NULL)  {
X			fputs("hearts: service not found\n", stderr);
X			exit(1);
X		}
X		sockaddr.sin_port = distributor->s_port;
X	}
X	/*
X	 * Allow reuse of local addresses.
X	 * Speeds up reinvocation of distributor.
X	 */
X	(void) setsockopt(sock_num, SOL_SOCKET, SO_REUSEADDR, (char *) 0, 0);
X	/*
X	 * Don't hang around if socket in use.
X	 */
X#ifdef SO_DONTLINGER
X	(void) setsockopt(sock_num, SOL_SOCKET, SO_DONTLINGER, (char *) 0, 0);
X#else
X	{
X		struct linger ling;
X
X		(void) setsockopt(sock_num, SOL_SOCKET, SO_LINGER, (char *) ling, 0);
X	}
X#endif
X	if (bind(sock_num, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) < 0)  {
X		return(0);
X	}
X	if ((listen(sock_num, 5)) == -1)  {
X		perror("listen");
X		exit(1);
X	}
X	return(sock_num);
X}
END_OF_opensock.c
if test 1587 -ne `wc -c <opensock.c`; then
    echo shar: \"opensock.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of archive 1 \(of 2\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 2 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked both archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive1: case 3