/* MIKEY: notes, todo */

/* shooting scoring messed up */
/* improve passing */
/*   keep high cards for jd trick at end? only if don't have jd */
/*   => take all tricks for jd at end if qs gone */
/* computer shoot attempt? */
/* jdvoided */

/*
 * 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.
 *
 * JD variant addition and strategy mods by:
 *      Mike Yang of Silicon Graphics (mikey@sgi.com)
 *
 * 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"
#include <sys/resource.h>

/* #define DEBUG	 uncomment to enable play logging */
  
#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,
  queen_played,
  jack_played;

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;

int mikeyshooter,MIKEYJder,MIKEYJdintrick;

int privatemsgs = 1;

int voided[MAX_SUIT+1][PLAYERS+1], hasqs[PLAYERS+1][PLAYERS+1];
/* FALSE: No info, assume not
   TRUE:  Yes
   MAYBE: Maybe, based on play */
#define MAYBE 2
#define NONE 666

FILE *fp;

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"
    };

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

#define queen_safe(player) (queen_played || (find_true(hasqs[player]) == FALSE))

#define safe_suit(suit) ((suit != HEARTS) && (find_true(voided[suit]) == FALSE))

#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_RANK - cards_played[suit].length)

#define spades_safe(player) (( (float) (cards_out(SPADES) - \
card_table[player].cards[SPADES].length) / (PLAYERS - 1.0) >= 2.3) && \
			     safe_suit(SPADES))

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

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

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

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

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+64];
  
  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);
  if (strlen(pname) >= 16) {
    pname[15] = '\0';
  }
  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+16];
  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);
	    if (strlen(mesg_buf) >= 64) {
	      mesg_buf[63] = '\0';
	    }
	    send_to_all(mesg_buf);		/* Message to everyone */
	  } 
	  else if (ret && (buf[0] == '~')) {
	    if (privatemsgs)
	      privatemsgs = FALSE;
	    else
	      privatemsgs = TRUE;
	  }
	  else if (ret && (buf[0] == ';')) {
	    if ((1 <= buf[1]-'0') &&
		privatemsgs &&
		(buf[1]-'0' <= PLAYERS) &&
		(card_table[buf[1]-'0'].player_kind != COMPUTER)) {
	      (void) sprintf(mesg_buf, "M%d%s; %s", MESG_WINDOW,
			     get_name(player), buf + 2);
	      if (strlen(mesg_buf) >= 64) {
		mesg_buf[63] = '\0';
	      }
	      send_buf(buf[1]-'0',mesg_buf);
	      send_buf(player,mesg_buf);
	    }
	  } 
	  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];
      if (rank_ch == '?') {
	if (round == 0)
	  get_pass(*player, card_table[*player].num_passed+1,
		   &p, &suit);
	else
	  if (leader == *player)
	    computer_lead(&p,&suit);
	  else
	    computer_pick(*player,&p,&suit);
	erase_window(*player, TEXT_WINDOW);
      } else {
	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, "Z");
    (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%03d%03d%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];
  char    mikeysh;
  
  (void) sprintf(buf, "M%d--------------------------------------------------------", MESG_WINDOW);
  send_to_all(buf);
  max_score = 0;  
  mikeysh = TRUE;
  for (player = 1; player <= PLAYERS; player++) {
    if ((max_score != 0) &&
	(card_table[player].pts != 0) &&
	(card_table[player].pts != -10))
      mikeysh = FALSE;
    if ((card_table[player].pts > max_score) &&
	(card_table[player].pts != -10) &&
	(card_table[player].pts != 0)) {
      max_score = card_table[player].pts;
      shooter = player;
    }
  }
  mikeysh = mikeysh && (mikeyshooter == shooter);
  
  if (mikeysh) {
    for (player = 1; player <= PLAYERS; player++) {
      if (player != shooter)
	card_table[player].totalpts += 26;
      if ((player == MIKEYJder) && MIKEYJ)
	card_table[player].totalpts -= 10;
    }
  } 
  else {
    for (player = 1; player <= PLAYERS; player++)
      card_table[player].totalpts += card_table[player].pts;
  }
  if (mikeysh)
    (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];
  int     mikey;
  
  for(mikey = 1;mikey <= PLAYERS;mikey++) {
    trick.played[mikey].rank = 0;
    trick.played[mikey].suit = 0;
  }
  for(mikey=CLUBS; mikey<=SPADES; mikey++) {
    if (cards_out(mikey) <= 1.5*PLAYERS) {
      int each;
      
      for (each=1; each<=PLAYERS; each++) {
	if (voided[mikey][each] == FALSE) {
	  voided[mikey][each] = MAYBE;
	}
      }
    }
  }
  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++) {
    which_hand[suit].length = 0;
    p = which_hand[suit].head->rlink;
    while (p->rank > 0) {
      p1 = p;
      p = p->rlink;
      remove_node(p1);
    }
  }
}

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];
  
  mikeyshooter = 0;
  MIKEYJder = 0;
  MIKEYJdintrick = FALSE;
  points_played = 0;
  someone_is_shooting = FALSE;
  jack_played = queen_played = hearts_broken = FALSE;
  trick.suit_led = CLUBS;
  for (player = 1; player <= PLAYERS; player++) {
    int each;
    
    card_table[player].pts = 0;
    clear_hand(card_table[player].cards);
    voided[CLUBS][player] = voided[DIAMONDS][player] =
      voided[HEARTS][player] = voided[SPADES][player] = FALSE;
    for (each=1; each<=PLAYERS; each++) {
      hasqs[player][each] = TRUE;
    }
  }
  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
  find_card(player,rank,suit)
int player,rank,suit;
{
  ptr p;
  
  p = card_table[player].cards[suit].head->rlink;
  while (p && p->rank)
    if (p->rank == rank)
      return(p);
    else
      p = p->rlink;
  return(NULL);
}

char
  not_in_trick(rank,suit)
int rank,suit;
{
  int mikey;
  
  for(mikey = 1;mikey <= PLAYERS;mikey++) {
    if ((trick.played[mikey].rank == rank) &&
	(trick.played[mikey].suit == suit))
      return(FALSE);
  }
  return(TRUE);
}

ptr
  effective_high(player,rank,suit)
int player,rank,suit;
{
  ptr card,p,q;
  int r;
  
  r = rank-1;
  card = find_card(player,rank,suit);
  p = card->rlink;
  q = cards_played[suit].head->rlink;
  while (q->rank > r)
    q = q->rlink;
  while (r && (!MIKEYJ || (suit != DIAMONDS) || (r != JACK)) &&
	 ((suit != SPADES) || (r != QUEEN)) &&
	 ((p->rank == r) || ((q->rank == r) &&
			     (not_in_trick(r,suit))))) {
    if (p->rank == r) {
      card = p;
      p = p->rlink;
    } else {
      q = q->rlink;
    }
    r--;
  }
  return(card);
}


ptr
  effective_low(player,rank,suit)
int player,rank,suit;
{
  ptr card,p,q;
  int r;
  
  r = rank+1;
  card = find_card(player,rank,suit);
  p = card->llink;
  q = cards_played[suit].tail->llink;
  while (q->rank < r)
    q = q->llink;
  while ((r <= MAX_RANK) &&
	 (!MIKEYJ || (suit != DIAMONDS) || (r != JACK)) &&
	 ((suit != SPADES) || (r != QUEEN)) &&
	 ((p->rank == r) || ((q->rank == r) &&
			     (not_in_trick(r,suit))))) {
    if (p->rank == r) {
      card = p;
      p = p->llink;
    } else {
      q = q->llink;
    }
    r++;
  }
  return(card);
}


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(effective_high(player,p->rank,suit_to_play));
    else
      p = p->rlink;
  return(NULL);
}

int
  highest_unplayed(suit, player)
int suit, player;
{
  ptr p, q;
  int last;
  
  p = cards_played[suit].head->rlink;
  q = card_table[player].cards[suit].head->rlink;
  last = MAX_RANK+1;
  while (last) {
    if ((p->rank != last-1) && (q->rank != last-1)) {
      return(last-1);
    } else {
      if (q->rank == last-1) {
	q = q->rlink;
      } else {
	p = p->rlink;
      }
      last--;
    }
  }
  return(0);
}

int
  lowest_unplayed(suit, player)
int suit, player;
{
  ptr p, q;
  int last;
  
  p = cards_played[suit].tail->llink;
  q = card_table[player].cards[suit].tail->llink;
  last = 0;
  while (last != MAX_RANK) {
    if ((p->rank != last+1) && (q->rank != last+1)) {
      return(last+1);
    } else {
      if (q->rank == last+1) {
	q = q->rlink;
      } else {
	p = p->rlink;
      }
      last++;
    }
  }
  return(MAX_RANK+1);
}

int
  count_losers(rank, suit)
int rank, suit;
{
  ptr p;
  int losers;
  
  p = cards_played[suit].head->rlink;
  losers = 0;
  while (p->rank) {
    if (p->rank < rank)
      losers++;
    p = p->rlink;
  }
  return(losers);
}

int
  find_true(array)
int array[PLAYERS+1];
{
  int each, result;
  
  result = FALSE;
  for (each=trick.card_count+1; each<=PLAYERS; each++) {
    if (array[map_player(leader,each)] == TRUE)
      result = TRUE;
    if ((array[map_player(leader,each)] == MAYBE) && (result != TRUE))
      result = MAYBE;
  }
  return(result);
}

int
  count_array(array, value)
int array[PLAYERS+1], value;
{
  int each, result;
  
  result = 0;
  for (each=1; each<=PLAYERS; each++) {
    if (array[each] == value)
      result++;
  }
  return(result);
}

int
  find_false(array)
int array[PLAYERS+1];
{
  int each, result;
  
  result = TRUE;
  for (each=trick.card_count+1; each<=PLAYERS; each++) {
    if (array[map_player(leader,each)] == FALSE)
      result = FALSE;
    if ((array[map_player(leader,each)] == MAYBE) && (result != FALSE))
      result = MAYBE;
  }
  return(result);
}

ptr
  effective_highest(player, suit)
int player, suit;
{
  ptr p;
  
  p = highest(player,suit);
  if (p->rank)
    return(effective_high(player,p->rank,suit));
  else
    return(p);
}

ptr
  effective_lowest(player, suit)
int player, suit;
{
  ptr p;
  
  p = lowest(player,suit);
  if (p->rank != MAX_RANK+1)
    return(effective_low(player,p->rank,suit));
  else
    return(p);
}

int
  lowestp(rank,suit)
int rank, suit;
{
  ptr p;
  int each;
  
  p = cards_played[suit].tail->llink;
  for (each=1; each<rank; each++) {
    if (p->rank == each) {
      p = p->llink;
    } else {
      return(FALSE);
    }
  }
  return(TRUE);
}


find_high_card(player, card_to_play, suit_to_play, play_points, play_spades, play_diamonds)
     int player, *suit_to_play;
     char play_points, play_spades, play_diamonds;
     ptr *card_to_play;
{
  int high_card, suit;
  ptr p;
  
  high_card = -1;
  *card_to_play = NULL;
  for (suit = CLUBS; suit <= SPADES; suit++)  {
    p = card_table[player].cards[suit].head->rlink;
    
    if ((suit == DIAMONDS) && (p->rank >= JACK) && MIKEYJ) {
      p=p->rlink;
    }
    
    if ((p->rank != 0) &&
	(p->rank-count_losers(p->rank,suit) > high_card) &&
	(play_spades ||
	 (suit != SPADES)) &&
	(play_diamonds ||
	 (suit != DIAMONDS)) &&
	(play_points ||
	 (point_value(p->rank, suit) == 0))) {
      high_card = p->rank-count_losers(p->rank,suit);
      *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.
     */
    
    /*
     * May have Jack of Diamonds also, if MIKEYJ
     */
    if (play_spades)
      *card_to_play = next_highest(player, QUEEN, SPADES);
    if (*card_to_play)
      *suit_to_play = SPADES;
    else if (play_points &&
	     (card_table[player].cards[HEARTS].length)) {
      /* Play highest heart */
      *card_to_play = effective_highest(player, HEARTS);
      *suit_to_play = HEARTS;
    }
    /* otherwise, try lowest diamond */
    if ((*card_to_play == NULL) &&
	card_table[player].cards[DIAMONDS].length) {
      *card_to_play = lowest(player, DIAMONDS);
      *suit_to_play = DIAMONDS;
    }
    if ((*card_to_play == NULL) &&
	card_table[player].cards[SPADES].length) {
      *card_to_play = effective_highest(player, SPADES);
      *suit_to_play = SPADES;
    }
    /* shouldn't ever need the following
       if (*card_to_play == NULL) {
       *card_to_play = effective_highest(player, CLUBS);
       *suit_to_play = CLUBS;
       if (((*card_to_play)->rank) == 0)
       *card_to_play = NULL;
       }
       */
  }
}

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 (MIKEYJ && (p->rank == JACK) && (suit == DIAMONDS))
      p = p->llink;
    if ((p->rank < low_card) &&
	(point_value(p->rank, suit) == 0)) {
      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;
    if ((*card_to_play)->rank == MAX_RANK+1) {
      *suit_to_play = DIAMONDS;
      *card_to_play = find_card(player,JACK,DIAMONDS);
    }
    if (!(*card_to_play) || (*card_to_play)->rank == MAX_RANK+1) {
      *suit_to_play = SPADES;
      *card_to_play = find_card(player,QUEEN,SPADES);
    }
  }
}

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;
  int each;
  
  p1 = highest(from, SPADES);
  if ((((p1->rank > QUEEN) &&
	(!find_card(from,QUEEN,SPADES) ||
	 (card_table[from].cards[SPADES].length <= 4))) ||
       ((p1->rank == QUEEN) &&
	(card_table[from].cards[SPADES].length <= 4))) &&
      card_table[from].cards[SPADES].length) {
    *card_to_pass = p1;
    *suit_to_pass = SPADES;
    if (p1->rank == QUEEN) {
      /* remember who has the QS */
      for (each=1; each<=PLAYERS; each++) {
	hasqs[from][each] = FALSE;
      }
      hasqs[from][map_player(from, hand)] = TRUE;
    }
  } else if (card_table[from].cards[DIAMONDS].length &&
	     !find_card(from,JACK,DIAMONDS) &&
	     (highest(from,DIAMONDS)->rank >= 4) &&
	     (card_table[from].cards[DIAMONDS].length <= NPASS-which_pass+1)) {
    /* create void in DIAMONDS */
    *card_to_pass = highest(from, DIAMONDS);
    *suit_to_pass = DIAMONDS;
  } else if (card_table[from].cards[CLUBS].length &&
	     (highest(from,CLUBS)->rank >= 4) &&
	     (card_table[from].cards[CLUBS].length <= NPASS-which_pass+1)) {
    /* create void in CLUBS */
    *card_to_pass = highest(from, CLUBS);
    *suit_to_pass = CLUBS;
  } else if (card_table[from].cards[HEARTS].length &&
	     (highest(from,HEARTS)->rank >= 7) &&
	     (card_table[from].cards[HEARTS].length <= NPASS-which_pass+1)) {
    /* create void in HEARTS */
    *card_to_pass = highest(from, HEARTS);
    *suit_to_pass = HEARTS;
  } else if (card_table[from].cards[HEARTS].length &&
	     ((card_table[from].cards[HEARTS].length <= 2) ||
	      ((card_table[from].cards[HEARTS].length == 3) &&
	       (highest(from,HEARTS)->rlink->rank > 8))) &&
	     (highest(from,HEARTS)->rank > JACK)) {
    /* get rid of high HEART */
    *card_to_pass = highest(from, HEARTS);
    *suit_to_pass = HEARTS;
  } else if ((card_table[from].cards[CLUBS].length > 2) &&
	     (lowest(from,CLUBS)->rank >= 8)) {
    /* get rid of high CLUBS */
    *card_to_pass = highest(from, CLUBS);
    *suit_to_pass = CLUBS;
  } else if ((card_table[from].cards[HEARTS].length > 2) &&
	     (lowest(from,HEARTS)->rank >= 8)) {
    /* get rid of high HEARTS */
    *card_to_pass = highest(from, HEARTS);
    *suit_to_pass = HEARTS;
  } else if (card_table[from].cards[CLUBS].length &&
	     (highest(from,CLUBS)->rank >= 7) &&
	     (card_table[from].cards[CLUBS].length <= NPASS-which_pass+2)) {
    /* create almost void in CLUBS */
    *card_to_pass = find_card(from,1,CLUBS);
    if (!*card_to_pass)
      *card_to_pass = highest(from, CLUBS);
    *suit_to_pass = CLUBS;
  } else if (card_table[from].cards[HEARTS].length &&
	     (card_table[from].cards[HEARTS].length >= 5) &&
	     (highest(from,HEARTS)->rlink->rank >= 7)) {
    /* get rid of some HEARTS */
    if (highest(from, HEARTS)->rank >= JACK)
      *card_to_pass = highest(from, HEARTS)->rlink;
    else
      *card_to_pass = highest(from, HEARTS);
    *suit_to_pass = HEARTS;
  } else if (card_table[from].cards[CLUBS].length &&
	     (card_table[from].cards[CLUBS].length >= 5)) {
    /* get rid of some CLUBS */
    *card_to_pass = find_card(from,1,CLUBS);
    if (!*card_to_pass)
      *card_to_pass = highest(from, CLUBS);
    *suit_to_pass = CLUBS;
  } else if (card_table[from].cards[DIAMONDS].length &&
	     !find_card(from,JACK,DIAMONDS) &&
	     (card_table[from].cards[DIAMONDS].length >= 5)) {
    /* get rid of some DIAMONDS */
    *card_to_pass = highest(from, DIAMONDS);
    *suit_to_pass = DIAMONDS;
  } else if (card_table[from].cards[SPADES].length &&
	     !find_card(from,QUEEN,SPADES) &&
	     (card_table[from].cards[SPADES].length >= 5)) {
    /* get rid of some SPADES */
    *card_to_pass = highest(from, SPADES);
    *suit_to_pass = SPADES;
  } else if (find_card(from,1,CLUBS)) {
    *card_to_pass = find_card(from,1,CLUBS);
    *suit_to_pass = CLUBS;
  } else {
    find_high_card(from, card_to_pass, suit_to_pass, FALSE, TRUE, 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;
  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);
  }
  remove_node(card_to_pass);
}

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);
    else
      if ((suit == DIAMONDS) && (rank == JACK) && MIKEYJ)
	return(-10);
  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;
  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);
  }
  remove_node(card_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;
    else
      if ((suit_to_play == DIAMONDS) &&
	  (rank_to_play == JACK) &&
	  MIKEYJ) {
	jack_played = TRUE;
	MIKEYJdintrick = 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;
  trick.pts += point_value(rank_to_play, suit_to_play);
  
  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;
  
  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 = count_losers(low_rank,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.
   *
   */
  safe = (losers_out == (low_rank - 1)) &&
    (card_table[leader].cards[suit].length +
     cards_played[suit].length != 13);
  safe &= (find_true(voided[suit]) == FALSE);
  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;
{
  int pass, low_card, suit, f, last_gasp;
  ptr p, q;
  
  last_gasp = FALSE;
  *card_to_play = NULL;
  pass = 0;
  q = find_card(leader,QUEEN,SPADES);
  while (*card_to_play == NULL) {
    pass++;
    if (pass > 20)
      last_gasp = TRUE;
    low_card = MAX_RANK+1;
    for (suit = CLUBS; suit <= SPADES; suit++) {
      p = card_table[leader].cards[suit].tail->llink;
      if ((((p->rank == JACK) && (suit == DIAMONDS) && MIKEYJ) ||
	   ((p->rank == QUEEN) && (suit == SPADES))) &&
	  (card_table[leader].cards[suit].length > 1)) {
	p = p->llink;
      }
      if ((p->rank <= MAX_RANK) &&
	  (p->rank-count_losers(p->rank,suit) < low_card) &&
	  (play_hearts || (suit != HEARTS) || last_gasp)) {
	f = TRUE;
	switch(pass) {
	case 1:
	  f = ((cards_out(suit) == card_table[leader].cards[suit].length+1) &&
	       (!q || (suit != SPADES)) &&
	       (highest_unplayed(suit,leader) > p->rank));
	  break;
	case 2:
	  f = (lowestp(p->rank, suit) &&
	       (!q || (suit != SPADES)) &&
	       (highest_unplayed(suit,leader) > p->rank));
	  break;
	case 3:
	  f = ((count_array(voided[suit],TRUE) == 2) &&
	       (!q || (suit != SPADES)) &&
	       (highest_unplayed(suit,leader) > p->rank) &&
	       (lowest_unplayed(suit,leader) > p->rank));
	  break;
	case 4:
	  f = (safe_suit(suit) &&
	       (!q || (suit != SPADES) ||
		(card_table[leader].cards[suit].length > 3)) &&
	       (highest_unplayed(suit,leader) > p->rank) &&
	       (count_array(voided[suit],TRUE) < 2) &&
	       !((MIKEYJ && (suit == DIAMONDS) && (p->rank == JACK)) ||
		 ((suit == SPADES) && (p->rank == QUEEN))));
	  break;
	case 5:
	  f = ((find_true(voided[suit]) == MAYBE) &&
	       (!q || (suit != SPADES) ||
		(card_table[leader].cards[suit].length > 2)) &&
	       (highest_unplayed(suit,leader) > p->rank) &&
	       (count_array(voided[suit],TRUE) < 2) &&
	       !((MIKEYJ && (suit == DIAMONDS) && (p->rank == JACK)) ||
		 ((suit == SPADES) && (p->rank == QUEEN))));
	  break;
	case 6:
	  f = ((highest_unplayed(suit,leader) > p->rank) &&
	       (count_array(voided[suit],TRUE) < 2) &&
	       (!q || (suit != SPADES) ||
		(card_table[leader].cards[suit].length > 2)) &&
	       !((MIKEYJ && (suit == DIAMONDS) && (p->rank == JACK)) ||
		 ((suit == SPADES) && (p->rank == QUEEN))));
	  break;
	case 7:
	  f = ((highest_unplayed(suit,leader) > p->rank) &&
	       (count_array(voided[suit],TRUE) < 2) &&
	       !((MIKEYJ && (suit == DIAMONDS) && (p->rank == JACK)) ||
		 ((suit == SPADES) && (p->rank == QUEEN))));
	  break;
	case 8:
	  f = ((highest_unplayed(suit,leader) > p->rank) &&
	       (count_array(voided[suit],TRUE) < 2) &&
	       ((suit != SPADES) || (p->rank != QUEEN)));
	  break;
	case 9:
	  f = ((highest_unplayed(suit,leader) > p->rank) &&
	       (count_array(voided[suit],TRUE) < 2));
	  break;
	}
	if (f) {
	  low_card = p->rank-count_losers(p->rank,suit);
	  *card_to_play = p;
	  *suit_to_play = suit;
	}
      }
    }
  }
#ifdef DEBUG
  fprintf(fp, "%2d ",pass);
#endif
}


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);
}

int
  num_lower(player, rank, suit_to_play)
int player, rank, suit_to_play;
{
  ptr p;
  int     mikey;
  
  mikey = 0;
  p = card_table[player].cards[suit_to_play].head->rlink;
  while (p && p->rank) {
    if (p->rank < rank)
      mikey++;
    p = p->rlink;
  }
  return(mikey);
}

char flushdiamonds(player)
     int player;
{
  ptr mikey;
  
  mikey = find_card(player,JACK,DIAMONDS);
  if (MIKEYJ && mikey && (mikey->rank == JACK) &&
      (safe_suit(DIAMONDS) || queen_played) &&
      (cards_out(DIAMONDS) > PLAYERS) &&
      ((safe_suit(DIAMONDS) &&
	(card_table[player].cards[DIAMONDS].length >
	 ((cards_out(DIAMONDS)/PLAYERS)+2))) ||
       (num_lower(player,JACK,DIAMONDS) >=
	(cards_out(DIAMONDS) - card_table[player].cards[DIAMONDS].length))))
    return(TRUE);
  else
    return(FALSE);
}

char gotthejack(player)
     int player;
{
  ptr p,q;
  
  if (card_table[player].cards[DIAMONDS].length > 0)
    p = card_table[player].cards[DIAMONDS].head->rlink;
  else
    p = NULL;
  if (cards_played[DIAMONDS].length > 0)
    q = cards_played[DIAMONDS].head->rlink;
  else
    q = NULL;
  
  if (!((p&&(p->rank == ACE)) || ((q&&(q->rank == ACE)&&
				   (not_in_trick(ACE,DIAMONDS))))))
    return(FALSE);
  if ((p&&(p->rank == ACE)))
    p = p->rlink;
  else
    q = q->rlink;
  if (!((p&&(p->rank == KING)) || ((q&&(q->rank == KING)&&
				    (not_in_trick(KING,DIAMONDS))))))
    return(FALSE);
  if ((p&&(p->rank == KING)))
    p = p->rlink;
  else
    q = q->rlink;
  if (!((p&&(p->rank == QUEEN)) || ((q&&(q->rank == QUEEN)&&
				     (not_in_trick(QUEEN,DIAMONDS))))))
    return(FALSE);
  if ((p&&(p->rank == QUEEN)))
    p = p->rlink;
  else
    q = q->rlink;
  if (p&&(p->rank == JACK))
    return(TRUE);
  else
    return(FALSE);
}

computer_lead(card_to_play, suit_to_play)
     int *suit_to_play;
     ptr *card_to_play;
{
  ptr p;
  
  find_queen(leader, &p);
  if (round == 1) {
    /* Lead the 2 of clubs */
    *card_to_play = card_table[leader].cards[CLUBS].tail->llink;
    *suit_to_play = CLUBS;
  } else if (MIKEYJ && gotthejack(leader) &&
	     (safe_suit(DIAMONDS) || queen_played)) {
    *card_to_play = find_card(leader,JACK,DIAMONDS);
    *suit_to_play = DIAMONDS;
  } else if (!queen_played && !p && spades_safe(leader) &&
	     next_highest(leader,QUEEN,SPADES) &&
	     (highest(leader,SPADES)->rank < QUEEN)) {
    /* try and flush the queen out */
    *suit_to_play = SPADES;
    *card_to_play = next_highest(leader, QUEEN, SPADES);
  } else if (flushdiamonds(leader)) {
    *suit_to_play = DIAMONDS;
    if (diamonds_safe(leader) || queen_safe(leader)) {
      *card_to_play = next_highest(leader,JACK,DIAMONDS);
      if (*card_to_play == NULL)
	*card_to_play = highest(leader,DIAMONDS);
    } else {
      *card_to_play = lowest(leader,DIAMONDS);
      if ((*card_to_play)->rank == JACK)
	*card_to_play = highest(leader,DIAMONDS);
    }
  } else {
    find_low_lead(card_to_play, suit_to_play,
		  (hearts_broken || only_hearts(leader)));
  }
}


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 > 0) {
    *card_to_play = NULL;
    *suit_to_play = trick.suit_led;
    if ((trick.suit_led != SPADES) &&
	(trick.pts == 0) &&
	(!MIKEYJ || jack_played || (trick.suit_led != DIAMONDS) ||
	 (((highest(player,DIAMONDS))->rank) < JACK)) &&
	((trick.card_count == PLAYERS) ||
	 (round == 1)))
      /*
       * Play the highest card of suit led if player is last
       * and there are no points in current trick.
       */
      *card_to_play = effective_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 = effective_highest(player, SPADES);
	  if (((*card_to_play)->rank == QUEEN) &&
	      (QUEEN > trick.highest_played))
	    *card_to_play = next_highest(player, QUEEN, SPADES);
	} else {
	  find_queen(player,&p);
	  if (p && (trick.highest_played > QUEEN))
	    *card_to_play = p;
	  else
	    if (!queen_played &&
		(find_true(hasqs[player]) == FALSE) &&
		(spades_safe(player) ||
		 ((highest(player, SPADES)->rank > QUEEN) &&
		  (trick.pts < PLAYERS)))) {
	      *card_to_play = effective_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) && (trick.pts <= 0))
	  *card_to_play = effective_highest(player, CLUBS);
	break;
	
      case DIAMONDS:
	if (MIKEYJ && (trick.pts < PLAYERS) && (trick.highest_played < JACK) &&
	    find_card(player,JACK,DIAMONDS) && queen_safe(player) &&
	    ((trick.card_count == PLAYERS) ||
	     (find_false(voided[DIAMONDS]) == TRUE) ||
	     gotthejack(player))) {
	  *card_to_play = find_card(player,JACK,DIAMONDS);
	} else if (MIKEYJ && (trick.pts < 0)) {
	  /* dump highest diamond if jd in trick */
	  *card_to_play = highest(player,DIAMONDS);
	} else if (MIKEYJ && !jack_played && diamonds_safe(player)) {
	  *card_to_play = next_highest(player,JACK,DIAMONDS);
	} else if ((!MIKEYJ || jack_played) && diamonds_safe(player)) {
	  *card_to_play = highest(player,DIAMONDS);
	} else if (MIKEYJ && !jack_played && (trick.pts == 0)) {
	  *card_to_play = next_highest(player,JACK,DIAMONDS);
 	  if ((*card_to_play == NULL) && find_card(player,JACK,DIAMONDS))
	    *card_to_play = highest(player,DIAMONDS);
	} else if ((next_highest(player, trick.highest_played,
				 DIAMONDS) == NULL) &&
		   MIKEYJ &&
		   ((trick.card_count == PLAYERS) ||
		    (find_false(voided[DIAMONDS]) == TRUE)) &&
		   find_card(player,JACK,DIAMONDS)) {
	  *card_to_play = find_card(player,JACK,DIAMONDS);
	} else if ((next_highest(player, trick.highest_played,
				 DIAMONDS) == NULL) &&
		   (card_table[player].cards[DIAMONDS].length > 1) &&
		   (lowest(player,DIAMONDS)->rank == JACK) &&
		   MIKEYJ) {
	  *card_to_play = lowest(player,DIAMONDS)->llink;
	}
	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) &&
	(find_false(voided[*suit_to_play]) == TRUE))
      *card_to_play = highest(player, *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;
    }
    if (((*card_to_play)->rank > trick.highest_played) &&
	(*suit_to_play == trick.suit_led) &&
	((*suit_to_play != SPADES) ||
	 ((card_table[player].cards[trick.suit_led].head->rlink)->rank !=
	  QUEEN)) &&
	((trick.card_count == PLAYERS) ||
	 (find_false(voided[trick.suit_led]) == TRUE)) &&
	((*suit_to_play != DIAMONDS) || !MIKEYJ || jack_played))
      *card_to_play = card_table[player].cards[trick.suit_led].head->rlink;
  } else {
    /* void */
    if (round != 1) {
      /*
       * Play the queen of spades if player has it.
       */
      find_queen(player,card_to_play);
      if (*card_to_play)
	*suit_to_play = SPADES;
      if ((*card_to_play == NULL) &&
	  !queen_played &&
	  (card_table[player].cards[SPADES].length) &&
	  ((effective_highest(player,SPADES))->rank > QUEEN)) {
	*card_to_play = effective_highest(player,SPADES);
	*suit_to_play = SPADES;
      }
      if ((*card_to_play == NULL) &&
	  (card_table[player].cards[HEARTS].length)) {
	/* MIKEY: does this work? */
	if (someone_is_shooting &&
	    (mikeyshooter != NONE) &&
	    (mikeyshooter != player)) {
	  if ((trick.high_player != mikeyshooter) &&
	      trick.played[mikeyshooter].suit)
	    *card_to_play = highest(player, HEARTS);
	  else
	    *card_to_play = next_highest(player, JACK, HEARTS);
	  if (*card_to_play == NULL)
	    *card_to_play = effective_lowest(player, HEARTS);
	} else if ((mikeyshooter != NONE) &&
		   (mikeyshooter != player) &&
		   someone_is_shooting &&
		   (card_table[player].cards[HEARTS].length > 3)) {
	  *card_to_play = highest(player, HEARTS)->rlink;
	} else {
	  *card_to_play = effective_highest(player, HEARTS);
	}
	*suit_to_play = HEARTS;
      }
    }
    if (*card_to_play == NULL)
      find_high_card(player, card_to_play, suit_to_play,
		     (round != 1),
		     (find_card(player,QUEEN,SPADES) != NULL),
		     (!MIKEYJ || jack_played));
  }
}

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_safe(player)) {
      *card_to_play = next_highest(player, QUEEN, SPADES);
      if (*card_to_play == NULL)
	*card_to_play = effective_lowest(player, SPADES);
    } 
    else
      *card_to_play = effective_highest(player, trick.suit_led);
    if ((trick.pts < PLAYERS) &&
	!(((cards_out(trick.suit_led) <
	    (PLAYERS - player_count)) ||
	   trick.any_hearts) &&
	  ((*card_to_play)->rank > trick.highest_played) &&
	  (mikeyshooter == trick.high_player)))
      *card_to_play = effective_lowest(player, trick.suit_led);
  } 
  else
    if ((mikeyshooter != trick.high_player) &&
	(mikeyshooter != NONE) &&
	someone_is_shooting &&
	(card_table[player].cards[HEARTS].length > 0)) {
      *card_to_play = effective_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;
  
  /* MIKEY: computer too stupid to shoot */
  if (someone_is_shooting && (player == mikeyshooter))
    someone_is_shooting = FALSE;
  
  if (someone_is_shooting && (player != mikeyshooter))
    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);
  
  if (suit_to_play != trick.suit_led) {
    int each;
    
    voided[trick.suit_led][player] = TRUE;
    if (trick.suit_led == SPADES) {
      for (each=1; each<=PLAYERS; each++) {
	hasqs[each][player] = FALSE;
      }
    }
  } else if ((card_to_play->rank >= JACK) && (trick.pts > 0) &&
	     (trick.card_count != PLAYERS)) {
    voided[suit_to_play][player] = MAYBE;
  }
  if ((suit_to_play == trick.suit_led) &&
      (suit_to_play == DIAMONDS) &&
      (card_to_play->rank > JACK) &&
      !jack_played) {
    voided[DIAMONDS][player] = MAYBE;
  }
  
  if (trick.suit_led == SPADES &&
      ((suit_to_play != SPADES) ||
       ((trick.highest_played > QUEEN) &&
	(card_to_play->rank != QUEEN)))) {
    int each;
    
    for (each=1; each<=PLAYERS; each++) {
      hasqs[each][player] = FALSE;
    }
  }
  
  show(player, card_to_play, suit_to_play);
}

find_winner(winner)
     int *winner;
{
  char    mikeyshoot;
  int     maxscore, player, possibly_shooting;
  
#ifdef DEBUG
  for (mikey=1; mikey<=PLAYERS; mikey++) {
    fprintf(fp, "%c%c  ", rnames[trick.played[mikey].rank],
	    *snames[trick.played[mikey].suit]);
  }
  fprintf(fp, "\n %d ", trick.high_player);
#endif
  
  *winner = trick.high_player;
  if (trick.any_hearts)
    hearts_broken = TRUE;
  points_played += trick.pts;
  card_table[*winner].pts += trick.pts;
  if (MIKEYJdintrick) {
    MIKEYJder = *winner;
    MIKEYJdintrick = FALSE;
  }
  if ((trick.pts != 0) && (trick.pts != -10))
    if ((mikeyshooter == 0) || (mikeyshooter == *winner))
      mikeyshooter = *winner;
    else
      mikeyshooter = NONE;
  
  possibly_shooting = ((mikeyshooter != NONE) && (mikeyshooter != 0));
  if (MIKEYJ && jack_played && !queen_played)
    someone_is_shooting = (possibly_shooting &&
			   (card_table[*winner].pts >= HEARTS_PANIC-10));
  else if (!jack_played && queen_played)
    someone_is_shooting = (possibly_shooting &&
			   (card_table[*winner].pts >= HEARTS_PANIC+13));
  else
    someone_is_shooting = (possibly_shooting &&
			   (card_table[*winner].pts >= HEARTS_PANIC));
  
  /* or if lead void */
  trick.card_count = 1;
  if ((*winner == leader) &&
      (find_false(voided[trick.suit_led]) == TRUE))
    someone_is_shooting = possibly_shooting;
  
  mikeyshoot = TRUE;
  maxscore = 0;
  for (player = 1; player <= PLAYERS; player++) {
    if ((maxscore != 0) && (card_table[player].pts != 0))
      mikeyshoot = FALSE;
    if ((card_table[player].pts != 0) &&
	(card_table[player].pts != -10) &&
	(maxscore == 0)) {
      maxscore = card_table[player].pts;
    }
  }
  someone_is_shooting = someone_is_shooting && mikeyshoot;
}


main()
{
  int player;
  int     mikeytemp;
  
  init();
  new_game();
  
  if (debug)
    human_type = VOYEUR;
  else
#ifdef DEBUG
    human_type = VOYEUR;
#else
  human_type = HUMAN;
#endif
  
  if (debug)
    mikeytemp = 100;
  else
    mikeytemp = PLAYERS;
  
  get_first_player();
  add_player();
  
#ifdef DEBUG
  fp = fopen("hearts.log", "w");
  if (fp == NULL) {
    fprintf(stderr, "Can't open file hearts.log for writing\n");
    exit(1);
  }
  for (mikeytemp=3; mikeytemp<=3; mikeytemp++) {
#else
    for (;;) {
#endif
      for (hand = 1; hand <= mikeytemp; hand++) {
	int mikey;
	
#ifdef DEBUG
	fprintf(fp,"\n--- New Hand ---\n      ");
#endif
	for(mikey = 1;mikey <= PLAYERS;mikey++) {
	  trick.played[mikey].rank = 0;
	  trick.played[mikey].suit = 0;
	}
	round = 0;
	new_hand();
	if (hand != PLAYERS)
	  pass_cards();
	find_low_club(&leader);
	for (mikey=1; mikey<=PLAYERS; mikey++) {
	  if (find_card(mikey,QUEEN,SPADES)) {
	    int each;
	    
	    for (each=1; each<=PLAYERS; each++) {
	      hasqs[mikey][each] = (each == mikey);
	    }
	  }
	}
	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();
#ifndef DEBUG
	if (!debug)
	  for (player = 1; player <= PLAYERS; player++)
	    if (card_table[player].player_kind == VOYEUR) {
	      card_table[player].player_kind = HUMAN;
	      player_mask |= 1 << player;
	    }
#endif
      }
      send_all_totals();
      send_final_winner();
      send_to_all("X");
      new_game();
    }
#ifdef DEBUG
    fclose(fp);
#endif
  }
