/*
 *	Spider
 *
 *	(c) Copyright 1989, Donald R. Woods and Sun Microsystems, Inc.
 *	(c) Copyright 1990, David Lemke and Network Computing Devices Inc.
 *
 *	See copyright.h for the terms of the copyright.
 *
 *	@(#)spider.c	2.4	91/05/09
 *
 */

/*
 * Spider card logic
 */

#include	"defs.h"
#include	"globals.h"
#include	<ctype.h>

static void	fix_coords();

int 	deltamod = 0;
Bool	squish = True;
int	cheat_count = 0;

/*
 * build all the cards, stacks, piles and the original deck
 */
card_init()
{
int	i;
Suit	suit;
Rank	rank;
CardPtr	tmp, tmp2;

	for (i = 0; i < NUM_STACKS; i++)	{
		stack[i] = (CardList) malloc(sizeof(CardListStruct));
		stack[i]->place = i + 11;
		stack[i]->cards = CARDNULL;
		stack[i]->card_delta = CARD_DELTA;
		stack[i]->x = STACK_LOC_X(stack[i]->place);
		stack[i]->y = STACK_LOC_Y;
	}

	for (i = 0; i < NUM_PILES; i++)	{
		piles[i] = (CardList) malloc(sizeof(CardListStruct));
		piles[i]->place = i + 1;
		piles[i]->cards = CARDNULL;
		piles[i]->card_delta = 0;
		piles[i]->x = PILE_LOC_X(piles[i]->place);
		piles[i]->y = PILE_LOC_Y;
	}
	deck = (CardList) malloc(sizeof(CardListStruct));
	deck->place = DECK;
	deck->x = DECK_X;
	deck->y = DECK_Y;
	deck->card_delta = 0;
	deck->cards = CARDNULL;
	tmp2 = CARDNULL;
	for (i = 0; i < NUM_DECKS; i++)	{
		for (suit = Spade; suit <= Club; suit++)	{
			for (rank = Ace; rank <= King; rank++)	{
				tmp = (CardPtr) calloc(sizeof(CardStruct), 1);
				add_card(tmp, tmp2, LOC_BEFORE, deck);
				tmp->rank = rank;
				tmp->suit = suit;
				tmp->type = Facedown;
				tmp2 = tmp;
			}
		}
	}
	deck->cards = tmp;
	srandom(getpid());
	shuffle_cards();
}

/*
 * randomizes order of deck list
 */

struct	shuffle {
	CardPtr	card;
	long	value;
} shuffled_cards[NUM_CARDS];

compare(a1, a2)
struct shuffle	*a1, *a2;
{
	return ((a2->value) - (a1->value));
}

/*
 * removes all the cards from the table and stcks them in a cache
 */
remove_all_cards(cache)
CardPtr	cache[NUM_CARDS];
{
CardPtr	tmp;
int	i, j;

	i = 0;
	while (deck->cards)	{
		tmp = deck->cards;
		remove_card(tmp);
		cache[i++] = tmp;
	}

	for (j = 0; j < NUM_PILES; j++)	{
		while (piles[j]->cards)	{
			tmp = piles[j]->cards;
			remove_card(tmp);
			cache[i++] = tmp;
		}
	}

	for (j = 0; j < NUM_STACKS; j++)	{
		while (stack[j]->cards)	{
			tmp = stack[j]->cards;
			remove_card(tmp);
			cache[i++] = tmp;
		}
	}

	assert(i == NUM_CARDS);
}

/*
 * shuffle the cards 
 */
shuffle_cards()
{
int	i;
CardPtr	cache[NUM_CARDS];
extern long	random();

	remove_all_cards(cache);

	for (i = 0; i < NUM_CARDS; i++)	{
		shuffled_cards[i].card = cache[i];
		shuffled_cards[i].value = random();
	}

	qsort((char *) shuffled_cards, NUM_CARDS, sizeof(struct shuffle), 
		compare);

	for (i = 0; i < NUM_CARDS; i++)	{
		shuffled_cards[i].card->type = Facedown;
		add_card(shuffled_cards[i].card, deck->cards, LOC_START, deck);
	}

	/* save the deal in the save cache */
	make_deck_cache();

	/* reset card spacing */
	for (i = 0; i < NUM_STACKS; i++)	{
		stack[i]->card_delta = CARD_DELTA;
	}

	/* force things to get fixed up */
	XClearArea(dpy, table, 0, 0, 0, 0, True);

	deal_number = 0;
	restart = False;
	cheat_count = 0;

	/* reset move log */
	init_cache();
}

/*
 * does orginal deal
 */
deal_cards()
{
int	i, j;
int	num;
	
	/*
	 * this is the way the original does deals -- weird, but
	 * thats compatibility
	 */
	for (i = 0; i < NUM_STACKS; i++)	{
		CardPtr	tmp[6];

		/* stacks 1, 4, 7, and 10 have 1 extra card */
		if (((i+1) % 3) == 1)	{
			num = 5;
		} else	{
			num = 4;
		}

		/* faceup first */
		tmp[num] = deck->cards;
		remove_card(tmp[num]);
		tmp[num]->type = Faceup;

		for (j = (num - 1); j >= 0; j--)	{
			tmp[j] = deck->cards;
			remove_card(tmp[j]);
			tmp[j]->type = Facedown;
		}
		for (j = 0; j <= num; j++)	{
			add_card(tmp[j], stack[i]->cards, LOC_END, stack[i]);
		}
	}
	deck_index -= 54;
	deal_number = 1;

	/*
	 * show the deal
	 */
	for (i = 0; i < NUM_STACKS; i++)	{
		show_list(stack[i], stack[i]->cards);
	}
}

/*
 * deal hand of 10
 */
deal_next_hand(log)
Bool	log;
{
int	i;
CardPtr	tmp;
char	buf[128];
int	old_delta;

	/* be sure all the spaces are filled */
	for (i = 0; i < NUM_STACKS; i++)	{
		if (stack[i]->cards == CARDNULL)	{
			show_message("Can't deal until all spaces are filled");
			spider_bell(dpy, 0);
			return;
		}
	}
	if (deck->cards == CARDNULL)	{
		/* dealt them all */
		show_message("No more cards in deck");
		spider_bell(dpy, 0);
		return;
	}
	/* deal face up cards */
	for (i = 0; i < NUM_STACKS; i++)	{
		old_delta = stack[i]->card_delta;
		tmp = deck->cards;
		remove_card(tmp);
		tmp->type = Faceup;
		add_card(tmp, stack[i]->cards, LOC_END, stack[i]);
		if (old_delta != stack[i]->card_delta)	{
			show_list(stack[i], stack[i]->cards);
		} else	{
			show_card(tmp);
		}
	}

	deck_index -= 10;

	/* force deck to repaint itself if its empty */
	if (deck->cards == CARDNULL)
		redraw_deck(0, 0, table_width, table_height);

	assert (deal_number >= 1);

	if (log)
		record (0, 0, 0, True);
	(void)sprintf(buf, "Dealt hand %d of 5", deal_number);
	show_message(buf);

	assert((deal_number < 5) || (deck_index == 0));

	deal_number++;
}

/*
 * change the state of a card
 */
flip_card(card, state)
CardPtr	card;
Type	state;
{
	card->type = state;
	show_card(card);
}

/*
 * place a card on a list
 *
 * expects 'new' to have been removed from any list
 */
add_card(new, old, location, list)
CardPtr	new, old;
int	location;
CardList	list;
{

	assert(new->prev == CARDNULL);
	assert(new->next == CARDNULL);
	assert((location == LOC_BEFORE) || (location == LOC_AFTER)
		|| (location == LOC_END) || (location == LOC_START));
	assert ((old == NULL) || (old->list == list));


	if (location == LOC_END)	{
		old = last_card(list);
		location = LOC_AFTER;
		/* let the later code do the work */
	} else if (location == LOC_START)	{
		old = list->cards;
		location = LOC_BEFORE;
		/* let the later code do the work */
	}

	assert((location == LOC_BEFORE) || (location == LOC_AFTER));

	/* fix the list */
	if (list->cards == CARDNULL)
		list->cards = new;
	new->list = list;

	if (location == LOC_BEFORE)	{
		if (old)	{
			if (list->cards == old)
				list->cards = new;
			if (old->prev)
				old->prev->next = new;
			new->prev = old->prev;
			new->next = old;
			old->prev = new;
		} else	{
			new->next = old;
		}
	} else 	{		/* LOC_AFTER */
		if (old)	{
			if (old->next)
				old->next->prev = new;
			new->prev = old;
			new->next = old->next;
			old->next = new;
		} else	{
			new->prev = old;
		}
	}

	fix_coords(new, list, False);
}

/*
 * see if cards exist, are faceup, and part of the same run
 */
#define	in_sequence(a, b)						\
	((a) && (b) &&							\
		((a)->type == Faceup) && ((b)->type == Faceup) &&	\
		((a)->suit == (b)->suit) && ((a)->rank == (b)->rank + 1))

/*
 * fix up the inter-card spacing for a stack
 */
static void
fix_coords(new, list, display)
CardPtr	new;
CardList	list;
Bool	display;
{
	/* fix the coords */
	new->x = list->x;
	if (new->prev)	{	/* added to bottom */
		if ((new->prev->y + list->card_delta + CARD_HEIGHT) > 
		     table_height)	{
			recompute_list_deltas(list);
			if (display)
				show_list(list, list->cards);
		}
		/* runs are coallesced */
		if (squish && in_sequence(new->prev, new) && 
			in_sequence(new->prev->prev, new->prev))	{
				new->y = new->prev->y + (list->card_delta >> 2);
		} else	{
			new->y = new->prev->y + list->card_delta;
		}
	} else 	{
		new->y = list->y;
	}
}

/*
 * compute the inter-card spacing for a stack
 */
void
recompute_list_deltas(list)
CardList	list;
{
CardPtr	tmp;
int	delta, num = 0;
	
	assert (list->place >= STACK_1);

	tmp = list->cards;
	while (tmp)	{
		num++;
		tmp = tmp->next;
	}

	/* don't do anything if 1 or fewer cards */
	if (num <= 1)	{
		delta = CARD_DELTA;
		return;
	}

	/* adjust 'size' of stack to limit the amount of redrawing */
	if (deltamod)
		num = (num + deltamod - 1)/deltamod * deltamod;

	delta = (table_height - (STACK_LOC_Y + 10 + CARD_HEIGHT))/(num - 1);

	if (delta > CARD_DELTA)
		delta = CARD_DELTA;

	if (list->card_delta != delta)	{
		list->card_delta = delta;
		tmp = list->cards;
		while (tmp)	{
			fix_coords(tmp, list, False);
			tmp = tmp->next;
		}
	}
}

/*
 * remove a card from a list
 */
remove_card(card)
CardPtr	card;
{
	/* fix card pointers */
	if (card->prev)
		card->prev->next = card->next;
	if (card->next)
		card->next->prev = card->prev;

	/* fix up card list */
	if (card->prev == CARDNULL)
		card->list->cards = card->next;

	/* clear pointers */
	card->next = CARDNULL;
	card->prev = CARDNULL;
	card->list = CARDLISTNULL;
}

/*
 * move an entire sublist to another list
 */
void
move_to_list(card, list, log)
CardPtr		card;
CardList	list;
Bool		log;
{
CardPtr	tmp;
int	from, dest;
int	count = 0;
Bool	exposed = False;
int	delta;

	/* fix old list */
	if (card->prev)	{
		card->prev->next = CARDNULL;
		if (card->prev->type == Facedown)	{
			exposed = True;
			card->prev->type = Faceup;
		}
	} else	{
		card->list->cards = CARDNULL;
	}

	/* shrink stack if necessary */
	if (card->list->place >= STACK_1 && 
	    card->list->card_delta != CARD_DELTA)	{
		recompute_list_deltas(card->list);
		show_list(card->list, card->list->cards);
	} else	{
		show_list(card->list, card->prev);
	}
	from = STACK_INDEX(card->list->place) + 1;

#ifdef DEBUG
	validate_card_list(card->list);
#endif

	tmp = last_card(list);
	if (tmp)	{
		assert(tmp->next == CARDNULL);
		tmp->next = card;
		card->prev = tmp;
	} else	{
		list->cards = card;
		card->prev = CARDNULL;
	}
	tmp = card;
	while (tmp)	{
		count++;
		tmp->list = list;
		delta = list->card_delta;
		fix_coords(tmp, list, True);
		/* only show card if fix_coords() didn't */
		if (delta == list->card_delta)
			show_card(tmp);
		tmp = tmp->next;
	}

#ifdef DEBUG
	validate_card_list(list);
#endif
	if (log)	{
		dest = (IS_PILE(list)) ? 0 : STACK_INDEX(list->place) + 1;
		record(from, dest, count, exposed);
	}
}

#ifdef DEBUG
print_list(list)
CardList	list;
{
CardPtr	tmp;
	
	tmp = list->cards;

	while (tmp)	{
		(void) fprintf(stderr,"card is %s of %s (%s)\n",
			rank_name(tmp->rank), 
			suit_name(tmp->suit), 
			type_name(tmp->type));
		tmp = tmp->next;
	}
}

validate_card_list(list)
CardList	list;
{
CardPtr	tmp;
	
	tmp = list->cards;
	if (tmp == CARDNULL)
		return;
	if (tmp->prev != CARDNULL)	{
		(void) fprintf(stderr,
			"validate list:  first card has non-null prev\n");
	}
	while (tmp->next)	{
		if (tmp->next->prev != tmp)
			(void) fprintf(stderr,"validate list: bad link\n");
		if (tmp->list != list)
			(void) fprintf(stderr,
				"validate list: card/list mismatch\n");
		tmp = tmp->next;
	}
}
#endif

/*
 * rank & suit value->string roputines
 */


static	char	*rnk_names[] =	{
	"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"
};

/*
 * shortened version for save files and info
 */
char	*
rnk_name(rank)
Rank	rank;
{
	assert(rank >= Ace && rank <= King);

	return (rnk_names[rank]);
}

static char	*rank_names[] =	{
	"Ace", "Deuce", "Three", "Four", "Five", "Six", "Seven",
	"Eight", "Nine", "Ten", "Jack", "Queen", "King"
};

char	*
rank_name(rank)
Rank	rank;
{
	assert(rank >= Ace && rank <= King);

	return (rank_names[rank]);
}

static char	*suit_names[] = {
	"Spades", "Hearts", "Diamonds", "Clubs"
}; 

char	*
suit_name(suit)
Suit	suit;
{
	assert (suit >= Spade && suit <= Club);

	return (suit_names[suit]);
}

#ifdef DEBUG
static char	*type_names[] =	{
	"Faceup", "Facedown", "Joker"
};

char	*
type_name(type)
Type	type;
{
	assert (type >= Faceup && type <= Joker);

	return (type_names[type]);
}
#endif DEBUG


/*
 * return the bottom-most card of a list
 */
CardPtr
last_card(list)
CardList	list;
{
CardPtr	tmp = CARDNULL;

	if ((list == CARDLISTNULL) || (list->cards == CARDNULL))
		return (CARDNULL);

	tmp = list->cards;
	while (tmp->next)	{
		tmp = tmp->next;
	}
	return (tmp);
}

/*
 * can only move card if there's a run of the same suit underneath it
 */
Bool
can_move(card)
CardPtr	card;
{
CardPtr	tmp;
Rank	last_rank;

	if (card->type != Faceup)
		return (False);
	last_rank = card->rank;
	tmp = card->next;
	while (tmp)	{
		if ((tmp->rank != (last_rank - 1)) || (tmp->suit != card->suit))
			return (False);
		last_rank = tmp->rank;
		tmp = tmp->next;
	}
	return (True);
}

/*
 * can 'card' go on to 'dest' ?
 */
Bool
can_move_to(card, list)
CardPtr	card;
CardList	list;
{
CardPtr	tmp;

	assert (can_move(card));
	tmp = last_card(list);
	if (tmp == CARDNULL)
		return (True);
	return (tmp->rank == (card->rank + 1));
}

/*
 * finds the best move for a specific card
 *
 * use the first 'next best' move so we choose them from right to left
 */
CardList
best_card_move(card)
CardPtr	card;
{
CardList	next_best = CARDLISTNULL;
CardList	space = CARDLISTNULL;
CardPtr	tmp;
int	i;

	/* iterate through the stacks */
	for (i = 0; i < NUM_STACKS; i++)	{
		/* don't look at our own stack */
		if (stack[i] == card->list)
			continue;
		tmp = last_card(stack[i]);
		if (tmp == CARDNULL)	{	/* spaces are ok */
			if (next_best == CARDLISTNULL)
				space = stack[i];
			continue;
		}
		/* rank & suit is optimal */
		if (tmp->rank == (card->rank + 1))	{
			if (tmp->suit == card->suit)
				return (tmp->list);
			/* just rank is the next best */
			if (next_best == CARDLISTNULL)
				next_best = tmp->list;
		}
	}
	if (next_best == CARDLISTNULL)
		next_best = space;

	return (next_best);
}

/*
 * performs the best move for an entire sub-list
 */
void
best_list_move(list, first_card)
CardList	list;
CardPtr		first_card;
{
CardPtr	tmp, tmp2;
CardList	best = CARDLISTNULL;

	if (first_card != CARDNULL)	{
		tmp = first_card;
	} else	{
		tmp = list->cards;
		if (tmp == CARDNULL)	{
			show_message("Empty list");
			spider_bell(dpy, 0);
			return;
		}
	}

	/*
	 * iterate through stack.  for each card that can move,
	 * try to find one.  return as soon as we find one
	 */
	while (tmp)	{
		if (can_move(tmp))	{
			/*
			 * special case full suits
			 */
			if (tmp->rank == King)	{
				tmp2 = last_card(list);
				if (tmp2->rank == Ace)	{
					move_to_pile(tmp);
					return;
				}
			}
			best = best_card_move(tmp);
			if (best)	{
				move_to_list(tmp, best, True);
			} else	{
				card_message("Nowhere to move the", tmp);
				spider_bell(dpy, 0);
			}
			return;
		}
		tmp = tmp->next;
	}
}

void
move_to_pile(card)
CardPtr	card;
{
int	i;

	for (i = 0; i < NUM_PILES; i++)	{
		if (piles[i]->cards == CARDNULL)
			break;
	}
	assert(i < NUM_PILES);

	move_to_list(card, piles[i], True);
}


/*
 * is card thru lastcard King - Ace?
 */
static Bool
is_sequence(card)
CardPtr	card;
{
CardPtr	tmp;

	if (card->rank != King)
		return (False);
	tmp = card;
	while (tmp->next)	{
		if (!(tmp->next && (tmp->suit == tmp->next->suit) &&
		    (tmp->rank == (tmp->next->rank + 1))))
			return (False);
		tmp = tmp->next;
	}
	if (tmp->rank == Ace)
		return (True);
	else
		return (False);
}

/*
 * Compute a somewhat arbitrary evaluation function for the position:
 *    2 point per card sitting atop next higher card in same suit
 *   10 per card turned face up
 *   15 extra for each column where all cards have been revealed
 *   50 per completed suit removed (note this costs 12*2 for cards in seq)
 * If all columns are either empty or contain completed suits, then those
 * suits also count 50 (including the 24 for the 12 cards that are atop
 * higher cards), plus an extra 2 for each suit after the first three.
 * Thus the only way to get 1000 points is to win with all eight suits
 * still in the tableau.
 */

int
compute_score()
{
int	score = 0;
int	i;
CardPtr	tmp;
int	num_piles = 0;

	if (deal_number == 0)
		return (0);

	score = 44 * 10;		/* score if all cards flipped */
	for (i = 0; i < NUM_PILES; i++)	{
		if (piles[i]->cards)
			score += 50;
	}

	for (i = 0; i < NUM_STACKS; i++)	{
		if (stack[i]->cards)	{
			if (stack[i]->cards->type == Faceup)	{
				score += 15;
				if (is_sequence(stack[i]->cards))	{
					score += 50;
					num_piles++;
					if (num_piles > 3)	{
						score += 2;
					}
					continue;
				}
			}
		} else	{
			score += 15;
		}

		tmp = stack[i]->cards;
		while (tmp)	{
			if (tmp->type == Faceup)	{
				if (tmp->prev)	{
					if ((tmp->prev->type == Faceup) &&
					    (tmp->rank == (tmp->prev->rank - 1))
					    && (tmp->suit == tmp->prev->suit))
						score += 2;
				}
			} else	{
				score -= 10;	/* still Facedown */
			}
			tmp = tmp->next;
		}
	}

	return (score);
}

/*
 * display which suits have all their cards visible
 */
show_full_suits()
{
char	showing[NUM_RANKS][NUM_SUITS];
Bool	all[NUM_SUITS];
int	num = 0;
int	i, j;
CardPtr	tmp;
char	buf[128];

	for (i = 0; i < NUM_RANKS; i++)
		for (j = 0; j < NUM_SUITS; j++)
			showing[i][j] = 0;

	for (i = 0; i < NUM_STACKS; i++)	{
		tmp = stack[i]->cards;
		while (tmp)	{
			if (tmp->type == Faceup)	{
				showing[tmp->rank][tmp->suit]++;
			}
			tmp = tmp->next;
		}
	}
	for (j = 0; j < NUM_SUITS; j++)	{
		all[j] = True;
		for (i = 0; i < NUM_RANKS; i++)	{
			if (showing[i][j] == 0)	{
				all[j] = False;
				break;
			}
		}
		if (all[j])
			num++;
	}
	if (num == 0)	{
		show_message("No suit has all 13 cards showing.");
	} else	{
		(void) strcpy(buf, 
			"Sufficient cards visible to form complete set of ");
		for (j = 0; j < NUM_SUITS; j++)	{
			if (all[j])	{
				(void)strcat(buf, suit_name((Suit) j));
				if (--num)	{
					(void)strcat(buf, ", ");
				} else	{
					(void)strcat(buf, ".");
				}
			}
		}
		show_message(buf);
	}
}

/*
 * print cards in list
 */
expand(list)
CardList	list;
{
CardPtr	tmp, last;
char	buf[512], buf2[10];
Bool	sequence = False;


	tmp = list->cards;
	if (tmp == CARDNULL)	{
		show_message("Empty column.");
		return;
	}
	(void)strcpy(buf, "Column contains:");
	last = CARDNULL;
	while (tmp)	{
		if (tmp->type != Faceup)	{
			tmp = tmp->next;
			continue;
		}
		if (last && last->suit == tmp->suit && 
			(last->rank == tmp->rank + 1))		{
			if (!sequence)	{
				sequence = True;
			}
		} else	{
			if (sequence)	{
				(void)sprintf(buf2, "-%s%c %s%c", 
					rnk_name(last->rank),
					tolower(*suit_name(last->suit)),
					rnk_name(tmp->rank),
					tolower(*suit_name(tmp->suit)));
				sequence = False;
			} else	{
				(void)sprintf(buf2, " %s%c",rnk_name(tmp->rank),
					tolower(*suit_name(tmp->suit)));
			}
			(void)strcat(buf, buf2);
		}
		last = tmp;
		tmp = tmp->next;
	}
	/* handle dangling sequences */
	if (sequence)	{
		(void)sprintf(buf2, "-%s%c", rnk_name(last->rank),
			tolower(*suit_name(last->suit)));
		(void)strcat(buf, buf2);
	}
	show_message(buf);
}

static int
col_locate(list, suit, rank, checksuit)
CardList	list;
Suit	suit;
Rank	rank;
Bool	checksuit;
{
CardPtr	tmp;
int	count = 0;

	tmp = list->cards;
	for (tmp = list->cards; tmp; tmp = tmp->next)	{
		if (tmp->type != Faceup)
			continue;
		/* we have a find if we asked for a suit and found it
		 * OR if we don't have a suit and are looking for a free
		 * card
		 */
		if (tmp->rank == rank &&
			((checksuit && tmp->suit == suit) ||
			(!checksuit && 
				(!tmp->next ||	/* end of stack */
				(tmp->next && 	/* free */
					tmp->next->rank != (tmp->rank - 1))))))
			count++;
	}
	return	count;
}

void
locate(str)
char	*str;
{
int	i, num;
Suit	suit;
Rank	rank;
char	buf[512], buf2[256], times[32];
Bool	found = False, checksuit = False;

	if (!str)
		return;
	/*
	 * assume that the string is well formed (probably stupid
	 * assumption) and treat accordingly
	 */
	for (i = 0; i < strlen(str); i++)	{
		switch(str[i])	{
		case	'D':
		case	'd':
			suit = Diamond;
			checksuit = True;
			break;
		case	'S':
		case	's':
			suit = Spade;
			checksuit = True;
			break;
		case	'H':
		case	'h':
			suit = Heart;
			checksuit = True;
			break;
		case	'C':
		case	'c':
			suit = Club;
			checksuit = True;
			break;
		case	'A':
		case	'a':
			rank = Ace;
			break;
		case	'T':
		case	't':
			rank = Ten;
			break;
		case	'J':
		case	'j':
			rank = Jack;
			break;
		case	'Q':
		case	'q':
			rank = Queen;
			break;
		case	'K':
		case	'k':
			rank = King;
			break;
		default:
			rank = atoi(str) - 1;
			if (rank < Deuce || rank > Ten)	{
				(void)sprintf(buf, 
					"Invalid card specification %s", str);
				show_message(buf);
				return;
			}
			break;
		}
	}

	if (checksuit)
		(void)sprintf(buf, "%s of %s ", 
			rank_name(rank), suit_name(suit));
	else
		(void)sprintf(buf, "Free %s ", rank_name(rank));
	for (i = 0; i < NUM_STACKS; i++)	{
		if (num = col_locate(stack[i], suit, rank, checksuit))	{
			if (found)	{
				(void)strcat(buf, ", ");
			}
			found = True;
			if (num == 1)	{
				(void)strcpy(times, "once");
			} else if (num == 2)	{
				(void)strcpy(times, "twice");
			} else	{
				(void)sprintf(times, "%d times", num);
			}

			(void)sprintf(buf2, "occurs in column %d %s ", i + 1,
						times);
			(void)strcat(buf, buf2);
		}
	}

	if (!found)
		(void)strcat(buf, "is not visible");
	show_message(buf);
}

/*
 * routines to give advice about best move.
 *
 * doing a really good job here may be impossible -- this is just a
 * rough attempt
 */

static void
advise_pile_move(list)
CardList	list;
{
char	buf[128];

	(void) sprintf(buf, "Remove King through Ace in pile %d", 
		STACK_INDEX(list->place) + 1);
	show_message(buf);
}

static void
advise_move(card, from, to)
CardPtr		card;
CardList	from, to;
{
char	buf[128];

	if (to->cards == CARDNULL)	{
		(void) sprintf(buf, "Move %s of %s from stack %d to space in stack %d",
			rank_name(card->rank), suit_name(card->suit),
			STACK_INDEX(from->place) + 1, 
			STACK_INDEX(to->place) + 1);
	} else	{
		(void) sprintf(buf, "Move %s of %s from stack %d to %s of %s on stack %d",
			rank_name(card->rank), suit_name(card->suit),
			STACK_INDEX(from->place) + 1, 
			rank_name(last_card(to)->rank), suit_name(last_card(to)->suit),
			STACK_INDEX(to->place) + 1);
	}
	show_message(buf);
}

/*
 * calculate the relative worth of a move
 *
 * this is by no means an optimal algorithm, since there's no lookahead,
 * but it should be good enough to get a beginner started
 */
/*
 * value is:
 *	head of sublist rank + 100	(best to move the high cards first)
 *    + number of cards			(move as many as possible)
 *    + 200 if show a new card		(dig out cards)
 *    + 400 if show a new space		(dig out cards)
 *    + 800 if same suit		(same suits is preferable)
 *
 * the constants are arbitrary values large enough not to be reached
 * by the rank or count modifiers
 */
#define	RANK_MOVE	100
#define	NEW_CARD_MOVE	200
#define	SPACE_MOVE	400
#define	SAME_SUIT_MOVE	800

/* ARGSUSED */
static int
value_move(card, from, to)
CardPtr		card;
CardList	from, to;
{
int	value;;

	value = card->rank + RANK_MOVE;      /* higher cards are worth more */

	if (!card->prev)	{
		value += SPACE_MOVE;
	} else if (card->prev->type == Facedown)	{
		value += NEW_CARD_MOVE;
	}
	/* avoid moving to a space */
	if (last_card(to) == CARDNULL)	{
		/* don't space hop */
		if (card->prev == CARDNULL)	{
			value = 0;
		} else	{
			value /= 2;
		}
	/* same suit is worth a lot more */
	} else if (card->suit == last_card(to)->suit)	{
		value += SAME_SUIT_MOVE;
	/* avoid jumping back & forth from two equal moves */
	} else if (card->prev && card->prev->type == Faceup &&
		(card->prev->rank == (card->rank + 1)))	{
		value = 0;
	}
	return (value);
}

/*
 * finds the 'best' move and displays it
 */
void
advise_best_move()
{
CardPtr		tmp, tmp2, bestcard;
CardList	move = CARDLISTNULL,
		bestfrom = CARDLISTNULL,
		bestto = CARDLISTNULL;
int		best_value = 0, val;
CardList	list;
int		i;

	for (i = 0; i < NUM_STACKS; i++)	{
		list = stack[i];
		tmp = list->cards;
		if (tmp == CARDNULL)	{
			continue;
		}

		/*
		 * iterate through stack.  for each card that can move,
		 * calculate the move value
		 */
		while (tmp)	{
			if (can_move(tmp))	{
				/*
				 * special case full suits
				 */
				if (tmp->rank == King)	{
					tmp2 = last_card(list);
					if (tmp2->rank == Ace)	{
						advise_pile_move(list);
						return;
					}
				}
				move = best_card_move(tmp);
				if (move)	{
					val = value_move(tmp, list, move);
					if (val > best_value)	{
						bestfrom = list;
						bestto = move;
						bestcard = tmp;
						best_value = val;
					}
				}
				break;	/* finished with this stack */
			}
			tmp = tmp->next;
		}
	}
	if (bestfrom)	{
		advise_move(bestcard, bestfrom, bestto);
	} else	{
		if (deck->cards == CARDNULL)	{
			show_message("Its all over.");
		} else	{
			show_message("Deal the next hand.");
		}
	}
}

/*
 * fix up the inter-card spacing when resource value "squish" changes.
 */
void
fix_up_card_spacing()
{
	int i, maxy;
	CardPtr tmp, last;
	CardList list;

	for (i = 0; i < NUM_STACKS; i++) {
		list = stack[i];
		tmp = list->cards;
		last = last_card(list);
		maxy = last->y;
		while(tmp) {
			fix_coords(tmp, list, False);
			tmp = tmp->next;
		}
		if (maxy != last->y) {
			show_list(list, list->cards);
		}
	}
}
