DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T h

⟦c291924e7⟧ TextFile

    Length: 34560 (0x8700)
    Types: TextFile
    Names: »heartsd.c«

Derivation

└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987
    └─⟦this⟧ »EUUGD18/General/Hearts/heartsd.c« 

TextFile

/*
 * dealer - more commonly known as heartsd
 *
 * Smarts of hearts program.  Runs in background when invoked by initial
 * caller of hearts.  Contains all logic for communicating with other
 * hearts players.  Handles all computer players, and communicates play
 * of others to other players.
 *
 * Computer strategy originally written in Pascal by:
 *	Don Baccus of Oregon Software.
 * Strategy mods by:
 *	Jeff Hemmerling of Test Systems Strategies, Inc.
 *
 * Converted strategy to C, added curses and multi-player (sockets)
 * interface by:
 *	Bob Ankeney of Generic Computer Products
 *
 * Thanks to Keith Packard, for his invaluable (is that the same as
 * unvaluable?) assistance as a unix guru, for design suggestions, and
 * (I suppose) for convincing me to rewrite the bloody thing.
 *
 * Bug reports to Bob Ankeney at:
 * ...!tektronix!reed!bob
 *
 */

#include "misc.h"
#include "defs.h"
#include "local.h"

#define	BUF_SIZE	64
#define FUNNY_FACTOR	2
#define NPASS		3	/* # cards to pass */
#define PLAYERS		4	/* # players total */
#define NCARDS		(52 / PLAYERS)

#define ACE		13
#define KING		12
#define QUEEN		11
#define JACK		10

#define HEARTS_PANIC	4
#define PANIC_RATIO	0.85

#define COMPUTER	1
#define HUMAN		2
#define VOYEUR		3

int	debug = 0;

int	num_humans,			/* # HUMAN players */
	human_type,			/* HUMAN or VOYEUR */
	player_mask;		/* Bit n+1 is set for each human player n */

char	someone_is_shooting, possibly_shooting,
	queen_played,
	safe_suit[MAX_SUIT + 1];

typedef	struct node *ptr;

struct	card {
	int suit, rank;
};

struct	node {
	ptr llink, rlink;
	int rank;
};

struct	suit_list {
	ptr head, tail;
	int length;
};

typedef struct suit_list card_hand[MAX_SUIT + 1];

struct player_status {
	card_hand	cards;
	int		pts, totalpts;
	int		passer;
	struct card	passed_cards[NPASS + 1];
	int		num_passed;
	int		player_kind;
	int		socket;
	char		name[9];
};

struct	player_status	card_table[PLAYERS + 1];

card_hand	cards_played;

struct	card	deck[53];

struct	sockaddr_in	sockaddr;


int	player_count, leader,
	round, hand,
	points_played, shooter;

char	hearts_broken;

struct	{
	int	card_count, highest_played, high_player, suit_led, pts;
	char	any_hearts;
	struct card played[PLAYERS + 1];
} trick;

char	*snames[] = {
	    "",
	    "clubs",
	    "diamonds",
	    "hearts",
	    "spades"
	},
	rnames[] = " 23456789TJQKA",
	*comp_names[] = {
	    "",
	    "zeppo",
	    "chico",
	    "harpo",
	    "groucho"
	};


rnd(num)
{
	extern long random();

	return (random() % num);
}


init_deck()
{
	int	suit_temp, rank_temp, j;

	j = 0;
	for (rank_temp = MIN_RANK; rank_temp <= MAX_RANK ; rank_temp++)
		for (suit_temp = CLUBS; suit_temp <= SPADES; suit_temp++) {
			deck[++j].suit = suit_temp;
			deck[j].rank = rank_temp;
		}
}

init_suit(list)
struct suit_list *list;
{
	char *malloc();
	ptr	p, p1;

	list->length = 0;
	p = list->head = (ptr) malloc(sizeof(*p));
	p1 = list->tail = (ptr) malloc(sizeof(*p1));
	p->llink = NULL;	  
	p1->rlink = NULL;
	p->rlink = p1;	  
	p1->llink = p;
	p->rank = MAX_RANK + 1; 
	p1->rank = 0;
}

init()
{
	int	player;

	srandom(getpid());	/* Init random number gen */
	for (player = 1; player <= PLAYERS; player++) {
		card_table[player].player_kind = COMPUTER;
		(void) strcpy(card_table[player].name, comp_names[player]);
		card_table[player].socket = -1;
	}
	num_humans = 0;
}

new_game()
{
	int	suit, player;

	init_deck();
	for (player = 1; player <= PLAYERS; player++) {
		card_table[player].totalpts = 0;
		for (suit = CLUBS; suit <= SPADES; suit++)
			init_suit(&card_table[player].cards[suit]);
	}
	for (suit = CLUBS; suit <= SPADES; suit++)
		init_suit(&cards_played[suit]);
}

get_rank(rch)
char	rch;
{
	int i;

	for (i = 1; i <= MAX_RANK; i++)
		if (rch == rnames[i])
			return(i);
	return(0);
}

get_suit(sch)
char	sch;
{
	int i;

	for (i = 1; i <= MAX_SUIT; i++)
		if (sch == *snames[i])
			return(i);
	return(0);
}

char *
get_name(player_num)
int	player_num;
{
	int	i;
	char	unique_name = TRUE;
	static char pname[16];

	for (i = 1; i <= PLAYERS; i++)
		if ((i != player_num) &&
		    !strcmp(card_table[i].name, card_table[player_num].name))
			unique_name = FALSE;
	if (unique_name)
		(void) strcpy(pname, card_table[player_num].name);
	else
		(void) sprintf(pname, "%s (%d)", card_table[player_num].name,
								player_num);
	return(pname);
}

/*
 * Get a card from somebody.  get_mask is a bit mask specifying who is
 * acceptable to get a card from (i.e., bit n states player n can send
 * a card.)  Scans all sockets for input, handling messages and player
 * leaving signals.  Returns when one of the specified players passes
 * a card.  Returns 0 if one of the specified players exits the game.
 */
get_card(get_mask, from, buf)
int	get_mask, *from;
char	*buf;
{
	int	i, player, ret;
	char	mesg_buf[64];
	fd_type read_fd;

	for (;;) {
		fd_init(4, &read_fd);		/* Watch for new arrivals */
		for (player = 1; player <= PLAYERS; player++)
			if (card_table[player].player_kind != COMPUTER)
				fd_set(card_table[player].socket, &read_fd);
		if (select(WIDTH, &read_fd, (fd_type *) 0, (fd_type *) 0,
				(struct timeval *) 0)) {
			if (fd_isset(4, read_fd))	/* New arrival? */
				add_player();
			for (player = 1; player <= PLAYERS; player++)	/* This player send? */
				if (fd_isset(card_table[player].socket, read_fd)) {
					ret = read_socket(card_table[player].socket, buf);
					*from = player;
					if (ret && (buf[0] == 'M')) {
						(void) sprintf(mesg_buf, "M%d%s: %s", MESG_WINDOW,
						get_name(player), buf + 1);
						send_to_all(mesg_buf);		/* Message to everyone */
					} 
					else {
						if (!ret) {
							if (card_table[player].player_kind == HUMAN)
								player_mask &= ~(1 << player);		/* player went away */
							card_table[player].player_kind = COMPUTER;
							(void) close(card_table[player].socket);
							if (--num_humans == 0)
								exit (0);	/* give up if nobody wants to play (sniff) */
							for (i = 1; i <= PLAYERS; i++) {
								if (card_table[i].player_kind != COMPUTER) {
									(void) sprintf(buf,
									"M%d%s vanishes in a puff of greasy black smoke!",
									MESG_WINDOW, get_name(player));	/* Announce deletion */
									send_buf(i, buf);		/* to others */
									(void) sprintf(buf,
									/*
									 * Inform dealer.
									 */
									"p%d<empty>", player - 1);
									write_socket(3, buf);
								}
							}
							(void) strcpy(card_table[player].name, comp_names[player]);
							send_all_totals();
						}
						if (get_mask & (1 << player))
							return(ret);
					}
				}
		}
	}
}

toss_card(player, card_to_toss, suit_to_toss)
int	player, suit_to_toss;
ptr	card_to_toss;
{
	char	buf[BUF_SIZE];

	(void) sprintf(buf, "R%c%c",
	rnames[card_to_toss->rank], *snames[suit_to_toss]);
	send_buf(player, buf);		/* Tell player to remove card */
}

read_card(read_mask, player, card_to_play, suit_to_play)
int	read_mask, *player, *suit_to_play;
ptr	*card_to_play;
{
	char	buf[BUF_SIZE];
	int	rank, suit;
	ptr p;
	char	rank_ch, suit_ch;

	if (get_card(read_mask, player, buf)) {
		do {
			rank_ch = buf[1];
			suit_ch = buf[2];
			rank = get_rank(rank_ch);
			suit = get_suit(suit_ch);
			erase_window(*player, TEXT_WINDOW);
			p = card_table[*player].cards[suit].head->rlink;
			while (p && (p->rank != rank))
				p = p->rlink;
			if (p == NULL) {
				(void) sprintf(buf, "M%dPlay a card you have:",
								TEXT_WINDOW);
				send_buf(*player, buf);
				send_buf(*player, "G");
				if (!get_card(read_mask, player, buf))
					return(0);
			}
		} 
		while (p == NULL);
		*card_to_play = p;
		*suit_to_play = suit;
		return(1);
	}
	return(0);
}

send_card(player, rank, suit)
int	player, rank, suit;
{
	char	buf[BUF_SIZE];

	(void) sprintf(buf, "A%c%c", rnames[rank], *snames[suit]);
	send_buf(player, buf);
}

/*
 *  send cards in hand to player
 */
send_hand(player)
int	player;
{
	int	suit;
	ptr	p;

	for (suit = CLUBS; suit <= SPADES; suit++) {	/* send cards in hand */
		p = card_table[player].cards[suit].head->rlink;
		while (p->rank) {
			send_card(player, p->rank, suit);
			p = p->rlink;
		}
	}
}

get_first_player()
{
	fd_type read_fd;		/* main socket bit mask */

	/*
	 *  wait for new player to show up
	 */
	fd_init(4, &read_fd);		/* Wait for someone to show up */
	if (select(WIDTH, &read_fd, (fd_type *) 0, (fd_type *) 0,
	(struct timeval *) 0) == -1) {
		perror("select");
		exit(1);
	}
}

add_player()
{
	int new_socket;			/* new file descriptor */
	char pname[128], buf[128];
	struct sockaddr_in sockad;
	int ssize;			/* to make accept happy */
	int new_player = 0;
	int i;

	/*
	 *  add whoever's waiting
	 */
	ssize = sizeof (sockad);
	if ((new_socket = accept(4, &sockad, &ssize)) == -1) {
		perror("accept");
		exit(1);
	}
	/* get user name */
	if (read_socket(new_socket, pname) > 0) {
		pname[8] = '\0';	/* Name must be less than 9 chars */
		for (i = PLAYERS; i >= 1; i--)
			if (card_table[i].player_kind == COMPUTER)
				new_player = i;
	}
	if (new_player) {
		/*
		 * Inform distributor
		 */
		(void) sprintf(buf, "p%d%s", new_player - 1, pname);
		write_socket(3, buf);
		card_table[new_player].player_kind = human_type;
		card_table[new_player].socket = new_socket;
		(void) strcpy(card_table[new_player].name, pname);
		++num_humans;
		if (human_type == VOYEUR) {
			(void) sprintf(buf,
				"M%d    Game in progress...", PLAY_WINDOW);
			send_buf(new_player, buf);
			(void) sprintf(buf,
			       "M%dYou may watch till next hand.", MESG_WINDOW);
			send_buf(new_player, buf);
		} 
		else
			player_mask |= 1 << new_player;
		for (i = 1; i <= PLAYERS; i++) {
			if (card_table[i].player_kind != COMPUTER) {
				(void) sprintf(buf,
					"M%d%s added as player %d", MESG_WINDOW,
					pname, new_player);
				if (i != new_player)	/* Announce addition */
					send_buf(i, buf);
				send_totals(i);
			}
		}
		send_hand(new_player);
	}
	else {
		(void) sprintf(buf, "M%dAll seats are full!", MESG_WINDOW);
		write_socket(new_socket, buf);
		write_socket(new_socket, "X");
		(void) close(new_socket);
	}
}

erase_window(player, which_window)
int	player, which_window;
{
	char	buf[BUF_SIZE];

	(void) sprintf(buf, "E%d", which_window);
	send_buf(player, buf);
}

erase_all_window(which_window)
int	which_window;
{
	int	player;

	for (player = 1; player <= PLAYERS; player++)
		if (card_table[player].player_kind != COMPUTER)
			erase_window(player, which_window);
}

send_totals(player)
int	player;
{
	int	i;
	char	buf[BUF_SIZE];

	if (card_table[player].player_kind != COMPUTER) {
		for (i = 1; i <= PLAYERS; i++) {
			(void) sprintf(buf, "S%d%02d%02d%s", i,
				card_table[i].pts,
				card_table[i].totalpts, card_table[i].name);
			send_buf(player, buf);
		}
	}
}

send_all_totals()
{
	int	player;

	for (player = 1; player <= PLAYERS; player++)
		send_totals(player);
}

send_all_winner()
{
	int	max_score,
	player, shooter;
	char	buf[BUF_SIZE];

	(void) sprintf(buf, "M%d--------------------------------------------------------", MESG_WINDOW);
	send_to_all(buf);
	max_score = 0;  
	for (player = 1; player <= PLAYERS; player++) {
		if (card_table[player].pts > max_score) {
			max_score = card_table[player].pts;
			shooter = player;
		}
	}
	if (max_score == 26) {
		for (player = 1; player <= PLAYERS; player++)
			if (player != shooter)
				card_table[player].totalpts += 26;
	} 
	else {
		for (player = 1; player <= PLAYERS; player++)
			card_table[player].totalpts += card_table[player].pts;
	}
	if (max_score == 26)
		(void) sprintf(buf, "M %s wins by shooting the moon!",
		get_name(shooter));
	else {
		get_winner(buf, FALSE);
		(void) strcat(buf, ".");
	}
	buf[1] = MESG_WINDOW + '0';
	send_to_all(buf);
}

send_final_winner()
{
	char	buf[64];

	get_winner(buf, TRUE);
	buf[1] = PLAY_WINDOW + '0';
	(void) strcat(buf, " total!");
	erase_all_window(PLAY_WINDOW);
	send_to_all(buf);
}

/*
 * Get buffer of form:	"M groucho wins with nn points"
 *		   or:	"M groucho and chico tie with nn points"
 */
get_winner(buf, use_totalpts)
char	*buf;
char	use_totalpts;
{
	int	player, winning_score, temp,
	scores[PLAYERS + 1], players[PLAYERS + 1];
	char	sorted;

	for (player = 1; player <= PLAYERS; player++) {
		scores[player] = use_totalpts ? card_table[player].totalpts
		    : card_table[player].pts;
		players[player] = player;
	}
	do {				/* Bubble sort the bloody thing */
		sorted = TRUE;
		for (player = 1; player < PLAYERS; player++)
			if (scores[player + 1] < scores[player]) {
				temp = scores[player];
				scores[player] = scores[player + 1];
				scores[player + 1] = temp;
				temp = players[player];
				players[player] = players[player + 1];
				players[player + 1] = temp;
				sorted = FALSE;
			}
	} 
	while (!sorted);
	winning_score = scores[1];
	(void) sprintf(buf, "M %s", card_table[players[1]].name);
	for (player = 2;
	    (player <= PLAYERS) && (scores[player] == winning_score); player++)
		    (void) sprintf(buf + strlen(buf), " and %s",
		card_table[players[player]].name);
	if (scores[2] == winning_score)
		(void) sprintf(buf + strlen(buf),
		" tie with %d point", winning_score);
	else
		(void) sprintf(buf + strlen(buf),
		" wins with %d point", winning_score);
	if (winning_score != 1)
		(void) strcat(buf, "s");
}

send_buf(player, buf)
int	player;
char	*buf;
{
	write_socket(card_table[player].socket, buf);
}

send_to_all(buf)
char	*buf;
{
	int	player;

	for (player = 1; player <= PLAYERS; player++)
		if (card_table[player].player_kind != COMPUTER)
			send_buf(player, buf);
}

new_round()
{
	char	buf[BUF_SIZE];

	trick.pts = 0;
	trick.any_hearts = FALSE;
	player_count = 0;
	erase_all_window(PLAY_WINDOW);
	/*
	 * Inform distributor
	 */
	(void) sprintf(buf, "r%d", round);
	write_socket(3, buf);
	(void) sprintf(buf, "M%dHand: %d   Round: %d",
				ROUND_WINDOW, hand, round);
	send_to_all(buf);
}

enter_card(which_card, which_hand)
struct	card	which_card;
card_hand	which_hand;

{
	ptr p, p1;

	p = which_hand[which_card.suit].head;
	++which_hand[which_card.suit].length;
	while (p->rank > which_card.rank)
		p = p->rlink;
	p1 = (ptr) malloc(sizeof(*p1));
	p1->llink = p->llink;
	p1->llink->rlink = p1;
	p->llink = p1;
	p1->rlink = p;
	p1->rank = which_card.rank;
}

remove_node(p)
ptr	p;

{
	p->llink->rlink = p->rlink;
	p->rlink->llink = p->llink;
	free((char *) p);
}

clear_hand(which_hand)
card_hand which_hand;
{
	int suit;
	ptr p, p1;

	for (suit = CLUBS; suit <= SPADES; suit++) {
		safe_suit[suit] = TRUE;
		which_hand[suit].length = 0;
		p = which_hand[suit].head->rlink;
		while (p->rank > 0) {
			p1 = p;
			p = p->rlink;
			remove_node(p1);
		}
	}
	safe_suit[HEARTS] = FALSE;
}

shuffle()
{
	int	j, k;
	struct card t;

	for (j = 52; j >= 1; j--) {
		k = rnd(j) + 1;
		t = deck[k]; 
		deck[k] = deck[j]; 
		deck[j] = t;
	}
}

deal()
{
	int i, j, player;

	for (player = 1; player <= PLAYERS; player++) {
		j = NCARDS * (player - 1);
		for (i = 1; i <= NCARDS; i++)
			enter_card(deck[i+j], card_table[player].cards);
		if (card_table[player].player_kind != COMPUTER)
			send_hand(player);
	}
}

new_hand()
{
	int	player;
	char	buf[BUF_SIZE];

	points_played = 0;
	someone_is_shooting = possibly_shooting = FALSE;
	queen_played = hearts_broken = FALSE;
	trick.suit_led = CLUBS;
	for (player = 1; player <= PLAYERS; player++) {
		card_table[player].pts = 0;
		clear_hand(card_table[player].cards);
	}
	clear_hand(cards_played);
	erase_all_window(PLAY_WINDOW);
	send_all_totals();
	/*
	 * Inform distributor
	 */
	(void) sprintf(buf, "h%d", hand);
	write_socket(3, buf);
	write_socket(3, "r0");
	(void) sprintf(buf, "M%dNew hand...", ROUND_WINDOW);
	send_to_all(buf);
	shuffle();			/* Shuffle three times! */
	shuffle();			/* (just like vegas!)   */
	shuffle();
	deal();
}

ptr
next_highest(player, rank, suit_to_play)
int	player, rank, suit_to_play;
{
	ptr	p;

	p = card_table[player].cards[suit_to_play].head->rlink;
	while (p && p->rank)
		if (p->rank < rank)
			return(p);
		else
			p = p->rlink;
	return(NULL);
}

#define highest(player, suit) (card_table[player].cards[suit].head->rlink)

#define lowest(player, suit) (card_table[player].cards[suit].tail->llink)

#define cards_out(suit) (MAX_SUIT - cards_played[suit].length)

#define spades_safe(player) ((((cards_out(SPADES) / PLAYERS) + 1) <	\
card_table[player].cards[SPADES].length) && safe_suit[SPADES])

#define diamonds_safe(player) (( (float) (cards_out(DIAMONDS) -	\
card_table[player].cards[DIAMONDS].length) / (PLAYERS - 1.0) >= 2.0) &&\
	safe_suit[DIAMONDS])

#define clubs_safe(player) (( (float) (cards_out(CLUBS) -		\
card_table[player].cards[CLUBS].length) / (PLAYERS - 1.0) >= 2.0) &&\
	safe_suit[CLUBS])

#define only_hearts(player) ((14 - round) ==			\
card_table[player].cards[HEARTS].length)

find_high_card(player, card_to_play, suit_to_play, play_points)
int	player, *suit_to_play;
char	play_points;
ptr	*card_to_play;
{
	int	high_card, suit;
	ptr	p;

	high_card = 0;
	*card_to_play = NULL;
	for (suit = CLUBS; suit <= SPADES; suit++) {
		p = card_table[player].cards[suit].head->rlink;
		if ((p->rank > high_card) &&
		    (play_points || (point_value(p->rank, suit) == 0))) {
			high_card = p->rank;
			*card_to_play = p;
			*suit_to_play = suit;
		}
	}
	if (*card_to_play == NULL) {
		/*
		 * No clubs or diamonds.  Q spades is highest spade, or no
		 * spades at all.  Try to play next highest spade.
		 */
		*card_to_play = next_highest(player, QUEEN, SPADES);
		if (*card_to_play)
		    *suit_to_play = SPADES;
		else {
			/* Play highest heart */
			*card_to_play = highest(player, HEARTS);
			*suit_to_play = HEARTS;
		}
	}
}

find_low_card(player, card_to_play, suit_to_play)
int	player, *suit_to_play;
ptr	*card_to_play;
{
	int	low_card, suit;
	ptr	p;

	low_card = MAX_RANK + 1;
	*card_to_play = NULL;
	for (suit = CLUBS; suit <= SPADES; suit++) {
		p = card_table[player].cards[suit].tail->llink;
		if ((p->rank < low_card) && (suit != HEARTS)) {
			low_card = p->rank;
			*card_to_play = p;
			*suit_to_play = suit;
		}
		if (*card_to_play == NULL) {
			*suit_to_play = HEARTS;
			*card_to_play = card_table[player].cards[HEARTS].tail->llink;
		}
	}
}

find_low_club(leader)
int	*leader;
{
	int	player, low_club, rank;

	low_club = MAX_RANK;
	for (player = 1; player <= PLAYERS; player++)
		if ((rank = card_table[player].cards[CLUBS].tail->llink->rank) < low_club) {
			*leader = player;
			low_club = rank;
		}
}

find_queen(player, card_to_play)
int	player;
ptr	*card_to_play;
{
	ptr	p;

	*card_to_play = NULL;
	p = card_table[player].cards[SPADES].head->rlink;
	while (p)
	    if (p->rank == QUEEN) {
		*card_to_play = p;
		p = NULL;
	} 
	else
		p = p->rlink;
}

print_pass(player)
int	player;
{
	int	i;
	char	buf[BUF_SIZE];
	struct card cd;

	erase_window(player, PLAY_WINDOW);
	(void) sprintf(buf, "M%d%s passes you:", LEAD_WINDOW,
	get_name(card_table[player].passer));
	send_buf(player, buf);
	for (i = 1; i <= NPASS; i++) {
		cd = card_table[player].passed_cards[i];
		send_card(player, cd.rank, cd.suit);
		(void) sprintf(buf, "P%d%c%c", i, rnames[cd.rank], *snames[cd.suit]);
		send_buf(player, buf);
	}
}

/*
 * Get card to pass from computer player
 */
get_pass(from, which_pass, card_to_pass, suit_to_pass)
int	from, which_pass, *suit_to_pass;
ptr	*card_to_pass;
{
	ptr	p1;

	p1 = highest(from, SPADES);
	if (!spades_safe(from) && (p1->rank >= QUEEN) &&
	    card_table[from].cards[SPADES].length) {
		*card_to_pass = p1;
		*suit_to_pass = SPADES;
	} 
	else
		if (((NPASS - which_pass) >= card_table[from].cards[DIAMONDS].length - 1) &&
		    (card_table[from].cards[DIAMONDS].length > 0)) {
			*card_to_pass = highest(from, DIAMONDS);
			*suit_to_pass = DIAMONDS;
		} 
		else
			if (((NPASS - which_pass) >= card_table[from].cards[CLUBS].length - 2) &&
			    (card_table[from].cards[CLUBS].length > 1)) {
				*card_to_pass = highest(from, CLUBS);
				*suit_to_pass = CLUBS;
			} 
			else
				if (card_table[from].cards[HEARTS].length) {
					*card_to_pass = highest(from, HEARTS);
					*suit_to_pass = HEARTS;
				} 
				else
					find_high_card(from, card_to_pass, suit_to_pass, TRUE);
}

/*
 * Pass card to player
 */
pass_to(from, who_to, which_pass, card_to_pass, suit_to_pass)
int	from, who_to, which_pass;
ptr	card_to_pass;
char	suit_to_pass;
{
	char	buf[BUF_SIZE];

	card_table[who_to].passed_cards[which_pass].rank = card_to_pass->rank;
	card_table[who_to].passed_cards[which_pass].suit = suit_to_pass;
	--card_table[from].cards[suit_to_pass].length;
	card_table[who_to].passer = from;
	remove_node(card_to_pass);
	if (card_table[from].player_kind != COMPUTER) {
		erase_window(from, INP_WINDOW);
		(void) sprintf(buf, "P%d%c%c", which_pass,
		rnames[card_to_pass->rank], *snames[suit_to_pass]);
		send_buf(from, buf);
		toss_card(from, card_to_pass, suit_to_pass);
	}
}

#define map_player(leader, player) (((leader + player - 1) % PLAYERS) + 1)

player_pass(player)
int	player;
{
	char	buf[BUF_SIZE];

	(void) sprintf(buf, "M%dPass %d to %s:",
	TEXT_WINDOW, NPASS, get_name(map_player(player, hand)));
	send_buf(player, buf);
	(void) sprintf(buf, "M%dCard 1:", LEAD_WINDOW);
	send_buf(player, buf);
	send_buf(player, "G");
}

pass_cards()
{
	int	player, pass, suit, old_mask;
	int	passed_mask = 0;
	ptr	p;
	char	buf[BUF_SIZE];

	human_type = HUMAN;
	for (player = 1; player <= PLAYERS; player++) {
		card_table[player].num_passed = 0;
		if (card_table[player].player_kind == HUMAN)
		    player_pass(player);
	}
	while (player_mask != passed_mask) {
		old_mask = player_mask;
		if (read_card(player_mask, &player, &p, &suit)) {
			pass_to(player, map_player(player, hand),
			++card_table[player].num_passed, p, suit);
			if (card_table[player].num_passed == NPASS)
				/*
				 * Done passing
				 */
				passed_mask |= 1 << player;
			else {
				(void) sprintf(buf, "M%dCard %d:", LEAD_WINDOW,
				card_table[player].num_passed + 1);
				/*
				 * Ask for next card
				 */
				send_buf(player, buf);
				send_buf(player, "G");
			}
		} 
		else
			/*
			 * Player left game
			 */
			passed_mask &= ~(1 << player);
		/*
		 * Player added?
		 */
		for (player = 1; player <= PLAYERS; player++)
			if (~old_mask & player_mask & (1 << player)) {
				card_table[player].num_passed = 0;
				player_pass(player);
			}
	}
	erase_all_window(LEAD_WINDOW);
	human_type = VOYEUR;
	/*
	 * Let computer pass.
	 */
	for (player = 1; player <= PLAYERS; player++)
		if (card_table[player].player_kind != HUMAN)
		    for (pass = ++card_table[player].num_passed;
					pass <= NPASS; pass++) {
			get_pass(player, pass, &p, &suit);
			pass_to(player, map_player(player, hand), pass, p, suit);
		}
	for (player = 1; player <= PLAYERS; player++) {
		for (pass = 1; pass <= NPASS; pass++)
			enter_card(card_table[player].passed_cards[pass],
			card_table[player].cards);
		if (card_table[player].player_kind != COMPUTER)
		    print_pass(player);
	}
}

point_value(rank, suit)
int	rank, suit;
{
	if (suit == HEARTS)
	    return(1);
	else
		if ((suit == SPADES) && (rank == QUEEN))
		    return(13);
	return(0);
}

show(player, card_to_play, suit_to_play)
int	player, suit_to_play;
ptr	card_to_play;
{
	int	rank_to_play;
	char	buf[BUF_SIZE];

	rank_to_play = card_to_play->rank;
	remove_node(card_to_play);
	if (card_table[player].player_kind != COMPUTER) {
		erase_window(player, INP_WINDOW);
		erase_window(player, LEAD_WINDOW);
		toss_card(player, card_to_play, suit_to_play);
	}
	--card_table[player].cards[suit_to_play].length;
	if (suit_to_play == HEARTS)
	    trick.any_hearts = TRUE;
	else {
		if ((suit_to_play == SPADES) && (rank_to_play == QUEEN))
		    queen_played = TRUE;
	}
	if ((suit_to_play == trick.suit_led) &&
	    (rank_to_play > trick.highest_played)) {
		trick.highest_played = rank_to_play;
		trick.high_player = player;
	}
	trick.played[player].rank = rank_to_play;
	trick.played[player].suit = suit_to_play;
	/*
		 * If points were dumped or someone couldn't follow suit,
		 * then the suit is unsafe.
		 */
	if ((trick.pts += point_value(rank_to_play, suit_to_play)) ||
	    (suit_to_play != trick.suit_led))
	    safe_suit[trick.suit_led] = FALSE;
	enter_card(trick.played[player], cards_played);
	++trick.card_count;
	(void) sprintf(buf, "P%d%c%c%s", player, rnames[rank_to_play],
	*snames[suit_to_play], card_table[player].name);
	send_to_all(buf);
}

really_safe(suit)
int	suit;
{
	char	safe;
	int	losers_out, low_rank;
	ptr	p;

	p = cards_played[suit].head->rlink;
	low_rank = card_table[leader].cards[suit].tail->llink->rank;
	/*
		 * Count # cards of _suit_ played that would be losers
		 * to leaders lowest of _suit_.
		 */
	losers_out = 0;
	while (p->rank) {
		if (p->rank < low_rank)
		    ++losers_out;
		p = p->rlink;
	}
	/*
	 * Old strategy.
	  losers_out = low_rank - 2 - losers_out;
	  safe = (((card_table[leader].cards[suit].length +
			  cards_played[suit].length + losers_out) != 13) ||
			  safe_suit[suit]);
	 */
	/*
	 * Card is guaranteed to lose the trick if:
	 * -- All cards of suit lower than low_rank have been played;
	 * -- There is at least one other card of the suit unplayed.
	 *
 	 * Using this strategy means safe_suit[] is unused.
	 */
	safe = (losers_out == (low_rank - 1)) &&
	    ((card_table[leader].cards[suit].length + cards_played[suit].length) != 13);
	if (suit == SPADES)
	    safe &= ((card_table[leader].cards[SPADES].tail->llink->rank <
	    QUEEN) || queen_played);
	return(safe);
}

find_low_lead(card_to_play, suit_to_play, play_hearts)
ptr	*card_to_play;
int	*suit_to_play;
char	play_hearts;
{
	char	last_gasp;
	int	low_card, suit;
	ptr	p;

	low_card = MAX_RANK + 1;
	last_gasp = FALSE;
	*card_to_play = NULL;
	while (*card_to_play == NULL) {
		for (suit = CLUBS; suit <= SPADES; suit++) {
			p = card_table[leader].cards[suit].tail->llink;
			if ((p->rank < low_card) &&
			    (play_hearts || (suit != HEARTS)) &&
			    (really_safe(suit) || last_gasp)) {
				low_card = p->rank;
				*card_to_play = p;
				*suit_to_play = suit;
			}
		}
		last_gasp = TRUE;
	}
}

show_lead(card_to_play, suit_to_play)
int	suit_to_play;
ptr	card_to_play;
{
	trick.card_count = 1;
	trick.highest_played = card_to_play->rank;
	trick.suit_led = suit_to_play;
	trick.high_player = leader;
	show(leader, card_to_play, suit_to_play);
}

read_lead(leader, card_to_play, suit_to_play)
int	leader, *suit_to_play;
ptr	*card_to_play;
{
	char	good, alive, buf[64];
	int	t;	/* Ignored */

	do {
		send_buf(leader, "G");
		if (alive = read_card(1 << leader, &t, card_to_play, suit_to_play)) {
			good = (((round != 1) ||
			    (*card_to_play == card_table[leader].cards[CLUBS].tail->llink)) &&
			    ((*suit_to_play != HEARTS) || hearts_broken || only_hearts(leader)));
			if (!good) {
				if (*suit_to_play == HEARTS)
				    (void) sprintf(buf,"M%dHearts not broken yet!", TEXT_WINDOW);
				else
					(void) sprintf(buf,"M%dLead your lowest club!", TEXT_WINDOW);
				send_buf(leader, buf);
				send_buf(leader, "G");
			}
		}
	} 
	while (alive && !good);
}

read_next_card(player, card_to_play, suit_to_play)
int	player, *suit_to_play;
ptr	*card_to_play;
{
	char	good, good_pts, alive, buf[64];
	int	t;	/* Ignored */

	do {
		send_buf(player, "G");
		if (alive = read_card(1 << player, &t, card_to_play, suit_to_play)) {
			good_pts = (((*suit_to_play != SPADES) ||
			    ((*card_to_play)->rank != QUEEN))
			    && ((*suit_to_play != HEARTS) || only_hearts(player)) ||
			    (round != 1));
			good = (((*suit_to_play == trick.suit_led) ||
			    (card_table[player].cards[trick.suit_led].length == 0)) && good_pts);
			if (!good) {
				if (!good_pts)
				    (void) sprintf(buf, "M%dCan't dump points yet!", TEXT_WINDOW);
				else
					(void) sprintf(buf, "M%dTry following suit!", TEXT_WINDOW);
				send_buf(player, buf);
				send_buf(player, "G");
			}
		}
	} 
	while (alive && !good);
}

computer_lead(card_to_play, suit_to_play)
int	*suit_to_play;
ptr	*card_to_play;
{
	ptr	p;

	if (round == 1) {
		/*
		 * Lead the 2 of clubs
		 */
		*card_to_play = card_table[leader].cards[CLUBS].tail->llink;
		*suit_to_play = CLUBS;
	} 
	else {
		find_queen(leader, &p);
		if (!queen_played && (p == NULL) && spades_safe(leader) &&
		    card_table[leader].cards[SPADES].length) {
			*suit_to_play = SPADES;
			*card_to_play = next_highest(leader, QUEEN, SPADES);
		} 
		else {
			*card_to_play = NULL;
/*
 * Old strategy
			      if (diamonds_safe(leader) &&
				 (card_table[leader].cards[DIAMONDS].length)) {
				*suit_to_play = DIAMONDS;
				*card_to_play = highest(leader, DIAMONDS);
			      } else
			      if (clubs_safe(leader) && (card_table[leader].cards[CLUBS].length)) {
				*suit_to_play = CLUBS;
				*card_to_play = highest(leader, CLUBS);
			      }
 */
		}
		if (*card_to_play == NULL)
		    find_low_lead(card_to_play, suit_to_play,
		(hearts_broken || only_hearts(leader)));
	}
}

#define max(a,b) ((a > b) ? a : b)

pick_a_loser(player, card_to_play, suit_to_play)
int	player, *suit_to_play;
ptr	*card_to_play;
{
	ptr	p;

	if (card_table[player].cards[trick.suit_led].length) {
		*suit_to_play = trick.suit_led;
		if ((trick.suit_led != SPADES) && (trick.pts == 0) &&
		    (trick.card_count == PLAYERS))
		/*
		 * Play the highest card of suit led if player is last
		 * and there are no points in current trick.
		 */
		*card_to_play = highest(player, trick.suit_led);
		else
			switch (trick.suit_led) {
			case SPADES:
				if ((trick.card_count == PLAYERS) && (trick.pts == 0)) {
				/*
				 * Play the highest spade.  Don't play queen if
				 * it will win the trick.
				 */
					*card_to_play = highest(player, SPADES);
					if (((*card_to_play)->rank == QUEEN) &&
					    (QUEEN > trick.highest_played))
					    *card_to_play = next_highest(player, QUEEN, SPADES);
				} 
				else
					if (spades_safe(player) && !queen_played) {
						find_queen(player, &p);
						*card_to_play = highest(player, SPADES);
						if ((p == NULL) || (p == *card_to_play))
						    *card_to_play = next_highest(player,
						max(trick.highest_played, QUEEN), SPADES); 
					}
				break;

			case CLUBS:
				if (clubs_safe(player))
				    *card_to_play = highest(player, CLUBS);
				break;

			case DIAMONDS:
				if (diamonds_safe(player))
				    *card_to_play = highest(player,DIAMONDS);
				break;

			case HEARTS:
				break;
			}
		if (*card_to_play == NULL)
		    *card_to_play = next_highest(player, trick.highest_played,
		*suit_to_play);
		if (*card_to_play == NULL) {
			*card_to_play = card_table[player].cards[trick.suit_led].tail->llink;
			/*
			 * Don't play the Q spades if:
			 *   1. The Queen is your lowest spade and
			 *   2. You have a higher spade.
			 */
			if (((*card_to_play)->rank == QUEEN) && (trick.suit_led == SPADES) &&
			    (card_table[player].cards[trick.suit_led].length > 1))
			    *card_to_play = card_table[player].cards[trick.suit_led].head->rlink;
		}
	} 
	else {
		if (round != 1) {
			/*
			 * Play the queen of spades if player has it.
			 */
			if (*card_to_play)
			    *suit_to_play = SPADES;
			if ((*card_to_play == NULL) &&
			    (card_table[player].cards[HEARTS].length)) {
				if (possibly_shooting) {
					*card_to_play = next_highest(player, JACK, HEARTS);
					if (*card_to_play == NULL)
					    *card_to_play = lowest(player, HEARTS);
				} 
				else
					*card_to_play = highest(player, HEARTS);
				*suit_to_play = HEARTS;
			}
		}
		if (*card_to_play == NULL)
		    find_high_card(player, card_to_play, suit_to_play, (round != 1));
	}
}

stop_the_bastard(player, card_to_play, suit_to_play)
int	player, *suit_to_play;
ptr	*card_to_play;
{
	if (card_table[player].cards[trick.suit_led].length) {
		*suit_to_play = trick.suit_led;
		if ((trick.suit_led == SPADES) && (!queen_played)) {
			*card_to_play = next_highest(player, QUEEN, SPADES);
			if (*card_to_play == NULL)
			    *card_to_play = lowest(player, SPADES);
		} 
		else
			*card_to_play = highest(player, trick.suit_led);
		if ((trick.pts < 13) &&
		    !(((cards_out(trick.suit_led) < (PLAYERS - player_count)) ||
		    trick.any_hearts) &&
		    ((*card_to_play)->rank > trick.highest_played) &&
		    (shooter == trick.high_player)))
		    *card_to_play = lowest(player, trick.suit_led);
	} 
	else
		if ((shooter != trick.high_player) &&
		    (card_table[player].cards[HEARTS].length > 0)) {
			*card_to_play = highest(player, HEARTS);
			*suit_to_play = HEARTS;
		} 
		else
			find_low_card(player, card_to_play, suit_to_play);
}

computer_pick(player, card_to_play, suit_to_play)
int	player, *suit_to_play;
ptr	*card_to_play;
{
	*card_to_play = NULL;
	if (someone_is_shooting && (player != shooter))
	    stop_the_bastard(player, card_to_play, suit_to_play);
	else
		pick_a_loser(player, card_to_play, suit_to_play);
}

lead(leader)
int	leader;
{
	int	suit_to_play;
	ptr	card_to_play;
	char	buf[BUF_SIZE];

	if (card_table[leader].player_kind == HUMAN) {
		(void) sprintf(buf, "M%dYour lead:", LEAD_WINDOW);
		send_buf(leader, buf);
		read_lead(leader, &card_to_play, &suit_to_play);
	}
	if (card_table[leader].player_kind != HUMAN)	/* If player left */
	    computer_lead(&card_to_play, &suit_to_play);
	show_lead(card_to_play, suit_to_play);
}

play_next_card(player)
int	player;
{
	int	suit_to_play;
	ptr	card_to_play;
	char	buf[BUF_SIZE];

	if (card_table[player].player_kind == HUMAN) {
		(void) sprintf(buf, "M%dYour play:", LEAD_WINDOW);
		send_buf(player, buf);
		read_next_card(player, &card_to_play, &suit_to_play);
	}
	if (card_table[player].player_kind != HUMAN)	/* If player left */
	    computer_pick(player, &card_to_play, &suit_to_play);
	show(player, card_to_play, suit_to_play);
}

find_winner(winner)
int	*winner;
{
	*winner = trick.high_player;
	if (trick.any_hearts)
	    hearts_broken = TRUE;
	points_played += trick.pts;
	card_table[*winner].pts += trick.pts;
	possibly_shooting = ((points_played > 0) &&
	    (card_table[*winner].pts == points_played));
	someone_is_shooting = (possibly_shooting &&
	    (card_table[*winner].pts >= HEARTS_PANIC));
	if (someone_is_shooting)
	    shooter = *winner;
}


main()
{
	int	player;

	init();
	new_game();

	human_type = HUMAN;
	get_first_player();
	add_player();

	for (;;) {
		for (hand = 1; hand <= PLAYERS; hand++) {
			new_hand();
			if (hand != PLAYERS)
				pass_cards();
			find_low_club(&leader);
			for (round = 1; round <= NCARDS; round++) {
				new_round();
				lead(leader);
				for (player_count = 1; player_count <= PLAYERS - 1; player_count++)
					play_next_card(map_player(leader, player_count));
				find_winner(&leader);
				send_all_totals();
			}
			send_all_winner();
			for (player = 1; player <= PLAYERS; player++)
				if (card_table[player].player_kind == VOYEUR) {
					card_table[player].player_kind = HUMAN;
					player_mask |= 1 << player;
				}
		}
		send_all_totals();
		send_final_winner();
		send_to_all("X");
		new_game();
	}
}