/*
 *	$Source: /mit/sipb/src/src/mboggle/RCS/boggle.c,v $
 *	$Header: /mit/sipb/src/src/mboggle/RCS/boggle.c,v 1.24 1995/06/04 18:25:09 ghudson Exp $
 */

#ifndef lint
static char *rcsid_boggle_c = "$Header: /mit/sipb/src/src/mboggle/RCS/boggle.c,v 1.24 1995/06/04 18:25:09 ghudson Exp $";
#endif lint


#include <netdb.h>
#ifdef HESIOD
#include <hesiod.h>
#endif
#include <errno.h>
#include <strings.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <curses.h>
#include <pwd.h>
#include <sys/ioctl.h>
#ifdef _AIX
#include <sys/select.h>
#endif

#ifdef __NetBSD__
#define _maxx maxx
#define _maxy maxy
#define _curx curx
#define _cury cury
#define _y lines
#endif

#include "boggle.h"
#include "window.h"

#define LIST_LENGTH 32

void get_scored_word(), clear_scored_words(), highlight_words_in_window(),
     highlight_word_in_window(), add_word_to_scored_window(),
     add_word_to_window_hl();
char *malloc(), *realloc(), *free();
#define my_realloc(ptr, num) ((ptr) ? realloc(ptr, num) : malloc(num))
#define my_free(ptr) if (ptr) free(ptr)

WINDOW *time_window = (WINDOW *) NULL, *input_window = (WINDOW *) NULL,
     *message_window = (WINDOW *) NULL, *tray_window = (WINDOW *) NULL,
     *word_window = (WINDOW *) NULL, *score_window = (WINDOW *) NULL;
WordWindow final_ws;
Dimensions time_d, input_d, message_d, tray_d, word_d, score_d, final_d;

int seconds_remaining, num_of_players, end_time;

struct player_ext players_ext[MAX_NUM_OF_PLAYERS];

struct pair {
	int x, y;
};

/* These names have to be matched with the definitions in boggle.h */

char *state_names[12] = {"", "done", "playing", "votes yes", "votes no",
			"abstains", "deciding", "ready", "viewing",
			"--------", "passing", "invited"};

static unsigned long nlen;		/* for network byte order */

main(argc, argv)
int argc;
char *argv[];
{
    int game, len;
    struct passwd *pw;
    char *name, hostname[MAX_NAME_LENGTH], namebuf[128];
    u_short port = 0;
    char *host = 0;
    int c;
    extern char *optarg;

#ifdef MOTDFILE
    FILE *motdfile;
    char buf[BUFSIZ];
    int charsread;

    motdfile = fopen(MOTDFILE, "r");
    if (motdfile) {
	 while (charsread = fread(buf, 1, BUFSIZ, motdfile))
	      fwrite(buf, 1, charsread, stdout);
	 fclose(motdfile);
    }
#endif

    while ((c = getopt(argc, argv, "h:p:")) != EOF) {
	 switch(c) {
	 case 'h':
	      host = optarg;
	      break;
	 case 'p':
	      port = htons(atoi(optarg));
	      break;
	 default:
	      fprintf(stderr, "Usage: mboggle [ -h host ] [ -p port ]\n");
	      exit(1);
	 }
    }

    /* He'll expect us to send him our name. */
    pw = getpwuid(getuid());
    if (gethostname(hostname,MAX_NAME_LENGTH - strlen(pw->pw_name) - 2) < 0){
	fprintf(stderr, "boggle: couldn't get hostname\n");
	exit(1);
    }
    hostname[MAX_NAME_LENGTH-strlen(pw->pw_name) - 2] = '\0';
    
    printf("Your name? [%s@%s]: ",pw->pw_name, hostname);
    if (! fgets(namebuf, 128, stdin)) {
	 write(1, "\n", 1);
	 exit(0);
    }
    if (name = index(namebuf, '\n'))
	 *name = '\0';
    if (strlen(namebuf) == 0)
	 sprintf(namebuf,"%s@%s",pw->pw_name,hostname);
    name = namebuf;
    /* Insure that the name, including its trailing null, will be short enough
       for the bogglemaster. */
    if ((len = strlen(name) + 1) > MAX_NAME_LENGTH) {
	len = MAX_NAME_LENGTH;
	name[MAX_NAME_LENGTH - 1] = '\0';
    }    

    /* Note that if we successfully join a game he will want to know our name
       immediately.  There shouldn't be anything long between the find_game
       and the writes.  This could be changed later, but would require changes
       to the bogglemaster. */

    if ((game = find_game_socket(host, port)) < 0) {
	fprintf(stderr, "boggle: couldn't join or create game\n");
	exit(1);
    }
    nlen = htonl(len);
    write(game, (char *)&nlen, sizeof(nlen));
    write(game, name, len);
    play(game);
}

find_game_socket(server_host, port)
char *server_host;
u_short port;
{
     struct sockaddr_in addr;
     int sock;
     struct servent *service;
     char **hosts;
     struct hostent *hostentry;

#ifdef HESIOD
     if (port == 0) {
	  service = hes_getservbyname("mboggle", "tcp");
	  if (service) {
	       port = service->s_port;
	  }
     }

     if (server_host == 0) {
	  hosts = hes_resolve("mboggle", "sloc");
	  if (hosts && *hosts) {
	       server_host = *hosts;
	  }
     }
#endif

     if (port == 0) {
	  service = getservbyname("mboggle", "tcp");
	  if (service) {
	       port = service->s_port;
	  }
     }

#ifdef MBOGGLE_PORT
     if (port == 0) {
	  port = htons(MBOGGLE_PORT);
     }
#endif

#ifdef MBOGGLE_SERVER
     if (server_host == 0) {
	  server_host = MBOGGLE_SERVER;
     }
#endif

     if (! port) {
	  fprintf(stderr, "mboggle: couldn't determine game port\n");
	  exit(1);
     }

     if (! server_host) {
	  fprintf(stderr, "mboggle: couldn't determine game host\n");
	  exit(1);
     }

     hostentry = gethostbyname(server_host);
     if (! hostentry) {
	  fprintf(stderr, "mboggle: couldn't resolve server host \"%s\"\n",
		  server_host);
	  exit(1);
     }

     bzero((char *)&addr, sizeof(addr));
     addr.sin_family = AF_INET;
     addr.sin_port = port;
     bcopy(hostentry->h_addr_list[0], (char *) &addr.sin_addr,
	   hostentry->h_length);

     sock = socket(AF_INET, SOCK_STREAM, 0);
     if (sock < 0) {
	  fprintf(stderr, "mboggle: ");
	  perror("socket");
	  exit(1);
     }

     if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
	  fprintf(stderr, "mboggle: ");
	  perror("connect");
	  exit(1);
     }

     return(sock);
}

     
WINDOW *dowin(old, dim)
WINDOW *old;
Dimensions dim;
{
     WINDOW *new_win;
     
     new_win = subwin(stdscr, dim.h, dim.w, dim.y, dim.x);
     if (old) {
	  overwrite(old, new_win);
	  delwin(old);
     }

     return(new_win);
}

	  

exit_gracefully()
{
     resetty();
     write(1, "\n", 1);
     exit(0);
}


figure_out_dimensions()
{
     int leftover;
     
     /* The window with the clock in it is in a constant location. */
     time_d.w = 4;
     time_d.h = 1;
     time_d.x = 8;
     time_d.y = 2;
     
     /* The window with the board in it is also in a constant */
     /* location. */
     tray_d.w = 15;
     tray_d.h = 5;
     tray_d.x = 20;
     tray_d.y = 0;

     /* The input window has a constant location and height, but the */
     /* width depends on the width of the window. */
     /* COLS is a predefined curses symbol. */
     input_d.w = COLS - 41;
     input_d.h = 1;
     input_d.x = 40;
     input_d.y = 3;

     /* The message window also has a constant location and height and */
     /* a variable width.  It goes right above the input window. */
     message_d.w = COLS - 41;
     message_d.h = 1;
     message_d.x = 40;
     message_d.y = 1;

     /* There are three windows left: The word window goes directly */
     /* below the tray, and displays the user's words as he types them */
     /* in.  The score window goes below that and displays the scores */
     /* of all players.  The final window goes below that and displays */
     /* the final list of words that were scored for all users. */

     /* All three windows are the width of the screen and touching the */
     /* left side of the screen. */
     score_d.w = word_d.w = final_d.w = COLS;
     score_d.x = word_d.x = final_d.x = 0;
     
     /* The size of the score window is fixed -- its width is the */
     /* width of the screen, and its height is the maximum number of */
     /* players plus two. */
     score_d.h = MAX_NUM_OF_PLAYERS + 2;
     
     /* The remaining space is divided equally between the user word */
     /* window and the final words window. */
     /* LINES is a predefined curses symbol. */
     /* The constant 3 at the end is for the space below the tray, the */
     /* space below the word window, and the space below the score */
     /* window. */
     leftover = LINES - tray_d.h - score_d.h - 3;
     leftover = (leftover > 1) ? leftover : 2;
     word_d.h = leftover / 2;

     /* There will probably be more final words that user words, so we */
     /* give the possible extra odd 1 line (if leftover is odd) to */
     /* final rather than to word. */
     final_d.h = leftover / 2 + leftover % 2;

     /* Word right below the tray (with a blank space before it). */
     word_d.y = tray_d.y + tray_d.h + 1;

     /* Score right below word (with a blank space above it). */
     score_d.y = word_d.y + word_d.h + 1;

     /* Final right below word (with a blank space above it). */
     final_d.y = score_d.y + score_d.h + 1;

     /* We need to check to see if their window is big enough, and exit */
     /* if not. */
     if ((LINES < 5) || (COLS < 72)) {
	  resetty();
	  fprintf(stderr, "%s\n%s 72 columns by 5 rows.\n",
		  "Your screen is not big enough to play mboggle.",
		  "To play mboggle, you need a screen at least");
	  sleep(3);
	  exit_gracefully();
     }

     dowin_ww(stdscr, &final_ws, final_d);

     word_window = dowin(word_window, word_d);
     score_window = dowin(score_window, score_d);
     tray_window = dowin(tray_window, tray_d);
     time_window = dowin(time_window, time_d);
     input_window = dowin(input_window, input_d);
     message_window = dowin(message_window, message_d);

     refresh();
}


play(game)
int game;
{
    extern WINDOW *time_window, *input_window, *message_window, *tray_window,
        *word_window, *score_window;
    extern WordWindow final_ws;
    extern int alrm_handler();
    char tray[5][5];
    char words[MAX_NUM_OF_WORDS][MAX_WORD_LENGTH];
    int num_of_words;

    signal(SIGALRM, alrm_handler);
    signal(SIGINT, exit_gracefully);
    signal(SIGHUP, exit_gracefully);
    signal(SIGQUIT, exit_gracefully);
    
    initscr();
    savetty();
    crmode();
    noecho();
    init_ww(&final_ws);
    figure_out_dimensions();
    print_score_header();

    num_of_words = 0;
    while (1) {
	 vote_until_offer_to_play(game, tray, words, num_of_words, &final_ws);
	if (wait_until_player_is_ready(game)) {
	    wait_for_game_to_start(game);
	    num_of_words = play_one_game(game, tray, words, &final_ws);
       }
    }
}

vote_until_offer_to_play(game, tray, words, num_of_words, scored)
int game;
char tray[5][5];
char words[MAX_NUM_OF_WORDS][MAX_WORD_LENGTH];
int num_of_words;
WordWindow *scored;
{
    extern WINDOW *input_window, *message_window;

    fd_set selectmask, readfds;
    int maxfds, voting;
    char chr, boggle_code;

    werase(input_window);
#ifndef AUTO_TOUCH
    wrefresh(input_window);    
#endif
    FD_ZERO(&selectmask);
    FD_SET(game, &selectmask);
    FD_SET(0, &selectmask);
    maxfds = game + 1;
    voting = 0;
    while (1) {
	my_refresh();
	readfds = selectmask;
	while (select(maxfds, &readfds, NULL, NULL, NULL) < 0) {
	     readfds = selectmask;
	}
	if (FD_ISSET(game, &readfds)) {
	    if (safe_read(game, &boggle_code, sizeof(boggle_code)) < 0) {
		fprintf(stderr, "boggle: bogglemaster died 12\n");
		resetty();
		exit(1);
	    }

	    switch (boggle_code) {
		case GAME_STATE:
		    get_game_state(game);
		    break;
		case STATE_UPDATE:
		    get_state_update(game);
		    break;
		case WANT_TO_PLAY:
		    return;
		case CURRENT_TRAY:
		    werase(message_window);
		    waddstr(message_window, "game in progress...");
#ifndef AUTO_TOUCH
		    wrefresh(message_window);
#endif
		    get_tray(game, tray);
		    print_tray(tray);
		    break;
		case WE_ARE_VOTING:
		    get_voting(game, tray);
		    werase(input_window);
		    waddstr(input_window, "Your vote? ");
		    voting = 1;
		    flush_typeahead();
		    break;
		case THEY_ARE_VOTING:
		    get_voting(game, tray);
		    werase(input_window);
		    voting = 0;
		    break;
		case WINNING_WORD:
		    get_winning_word(game, words, num_of_words);
		    break;
	       case SCORED_WORD:
		    get_scored_word(game, scored, words, num_of_words);
		    break;
		default:
		    werase(message_window);
		    waddstr(message_window,
		        "The bogglemaster is being weird!!!");
#ifndef AUTO_TOUCH
		    wrefresh(message_window);
#endif
		    break;
	    }
	}
	if (FD_ISSET(0, &readfds)) {
	    if (read(0, &chr, sizeof(chr)) <= 0) {
		endwin();
		resetty();
		exit(0);
	    }
	    switch (chr) {
		case 'y':  case 'Y':  case '\n':  case '\r':  case ' ':
		    if (voting) {
			boggle_code = PLAYER_VOTES_YES;
			write(game, &boggle_code, sizeof(boggle_code));
		    }
		    break;
	        case 'n':  case 'N':  case '\b':  case '\177':
		    if (voting) {
			boggle_code = PLAYER_VOTES_NO;
			write(game, &boggle_code, sizeof(boggle_code));
		    }
		    break;
		case 'a':  case 'A':  case '?':
		    if (voting) {
			boggle_code = PLAYER_ABSTAINS;
			write(game, &boggle_code, sizeof(boggle_code));
		    }
		    break;
		case '\f':
		    wrefresh(curscr);
		    break;
	    }
	}
    }
}
		    
int wait_until_player_is_ready(game)
int game;
{
    extern WINDOW *message_window, *input_window;

    fd_set selectmask, readfds;
    int maxfds;
    char boggle_code, chr;

    FD_ZERO(&selectmask);
    FD_SET(game, &selectmask);
    FD_SET(0, &selectmask);
    maxfds = game + 1;

    werase(message_window);
#ifndef AUTO_TOUCH
    wrefresh(message_window);
#endif
    flush_typeahead();
    werase(input_window);
    waddstr(input_window, "Ready? ");
    while (1) {
	my_refresh();
	readfds = selectmask;
	while (select(maxfds, &readfds, NULL, NULL, NULL) < 0) {
	     readfds = selectmask;
	}
	if (FD_ISSET(game, &readfds)) {
	    if (safe_read(game, &boggle_code, sizeof(boggle_code)) < 0) {
		fprintf(stderr, "boggle: bogglemaster died 2\n");
		resetty();
		exit(1);
	    }

	    switch (boggle_code) {
		case GAME_STATE:
		    get_game_state(game);
		    break;
		case STATE_UPDATE:
		    get_state_update(game);
		    break;
		case NEW_GAME:
		    werase(message_window);
		    waddstr(message_window,
		        "The game is starting without us!!!");
#ifndef AUTO_TOUCH
		    wrefresh(message_window);
#endif
		    break;
		default:
		    werase(message_window);
		    waddstr(message_window,
		        "The bogglemaster is sending garbage!!!");
#ifndef AUTO_TOUCH
		    wrefresh(message_window);
#endif
		    break;
	    }
	}
	if (FD_ISSET(0, &readfds)) {
	    if (read(0, &chr, 1) <= 0) {
		endwin();
		resetty();
		exit(0);		
	    }
	    switch (chr) {
		case ' ':
		case 'y':	case 'Y':
		case '\n':	case '\r':
		    boggle_code = PLAYER_READY;
		    write(game, &boggle_code, sizeof(boggle_code));
		    return(1);
		case 'p':	case 'P':
		case 'n':	case 'N':
		case '\177':	case '\b':
		    boggle_code = PLAYER_PASSING;
		    write(game, &boggle_code, sizeof(boggle_code));
		    return(0);
		case 'q':	case 'Q':
		    endwin();
		    resetty();
		    exit(0);
		case '\f':
		    wrefresh(curscr);
		    break;
	    }
	}
    }
}

wait_for_game_to_start(game)
int game;
{
    extern WINDOW *message_window, *input_window;

    char boggle_code;

    werase(message_window);
    waddstr(message_window, "waiting to start...");
#ifndef AUTO_TOUCH
    wrefresh(message_window);
#endif
    werase(input_window);
    while (1) {
	my_refresh();
	/* Don't use safe_read here because we don't want to time out. */
	if (read(game, &boggle_code, sizeof(boggle_code)) <= 0) {
	    fprintf(stderr, "boggle: bogglemaster died 3\n");
	    resetty();
	    exit(1);
	}

	switch (boggle_code) {
	    case GAME_STATE:
	        get_game_state(game);
		break;
            case STATE_UPDATE:
		get_state_update(game);
		break;
	    case NEW_GAME:
		return;
	    default:
	        werase(message_window);
		waddstr(message_window,
		    "The bogglemaster is sending garbage!!!");
#ifndef AUTO_TOUCH
		wrefresh(message_window);
#endif
		break;
	}
    }
}

int play_one_game(game, tray, words, scored)
int game;
char tray[5][5];
char words[MAX_NUM_OF_WORDS][MAX_WORD_LENGTH];
WordWindow *scored;
{
    extern WINDOW *message_window, *word_window;

    int len, num_of_words;
    char word[MAX_WORD_LENGTH];

    werase(message_window);
    werase(word_window);
#ifndef AUTO_TOUCH
    wrefresh(message_window);
    wrefresh(word_window);
#endif	
    num_of_words = 0;
    clear_ww(scored);
    
    get_tray(game, tray);
    flush_typeahead();
    print_tray(tray);
#ifdef SHORT_TIMER
    start_timer(SHORT_TIMER);
#else
    start_timer(180);
#endif
    while (seconds_remaining > 0) {
	if (get_word(word, tray, game)) {
	    downcase_word(word);
	    len = strlen(word);
	    if (len < 1) continue;
	    if (len < 4) {
		werase(message_window);
		wprintw(message_window, "\"%s\" is too short.", word);
#ifndef AUTO_TOUCH
		wrefresh(message_window);
#endif
		continue;
	    }
	    if (word_used_already(word, words, num_of_words)) {
		werase(message_window);
		wprintw(message_window, "You've used \"%s\" already.", word);
#ifndef AUTO_TOUCH
		wrefresh(message_window);
#endif
		continue;
	    }
	    if (! word_is_legal(word, tray)) {
		werase(message_window);
		wprintw(message_window, "\"%s\" is not in the tray.", word);
#ifndef AUTO_TOUCH
		wrefresh(message_window);
#endif
		continue;
	    }
	    strcpy(words[num_of_words++], word);
	    sort_words(words, num_of_words);
	    print_words_in_window(word_window, words, num_of_words);
	}
    }

    werase(input_window);
#ifndef AUTO_TOUCH
    wrefresh(input_window);
#endif
    sleep(1);

    send_words_to_bogglemaster(game, words, num_of_words);

    return(num_of_words);

}

send_words_to_bogglemaster(game, words, num_of_words)
int game;
char words[MAX_NUM_OF_WORDS][MAX_WORD_LENGTH];
int num_of_words;
{
    extern WINDOW *input_window, *message_window;

    char boggle_code;

    boggle_code = PLAYER_DONE;
    write(game, &boggle_code, sizeof(boggle_code));

    while (1) {
	my_refresh();
	if (read(game, &boggle_code, sizeof(boggle_code)) <= 0) {
	    fprintf(stderr, "boggle: bogglemaster died 4\n");
	    resetty();
	    exit(1);
	}

	switch (boggle_code) {
	    case GAME_STATE:
	        get_game_state(game);
		break;
	    case STATE_UPDATE:
		get_state_update(game);
		break;
	   case YOURE_DONE:
		break;
	    case SEND_WORDS:
		sort_words(words, num_of_words);
		nlen = htonl(num_of_words);
		write(game, (char *)&nlen, sizeof(nlen));
		write(game, (char *)words, num_of_words * MAX_WORD_LENGTH);
		return;
	    default:
	        werase(message_window);
		waddstr(message_window, "The bogglemaster is being weird!!!");
#ifndef AUTO_TOUCH
		wrefresh(message_window);
#endif
		break;
	}
    }
}


get_game_state(game)
int game;
{
    extern struct player_ext players_ext[];
    extern int num_of_players;

    if (safe_read(game, (char *)&nlen, sizeof(nlen)) < 0) {
	fprintf(stderr, "boggle: bogglemaster died 6\n");
	resetty();
	exit(1);
    }
    
    num_of_players = ntohl(nlen);
    if (num_of_players > 0) {
	if (safe_read(game, (char *) players_ext,
	    num_of_players * sizeof(struct player_ext)) < 0) {
	    fprintf(stderr, "boggle: bogglemaster died 7\n");
	    resetty();
	    exit(1);
	}
    }
    fix_from_net(players_ext, num_of_players);
    print_scores(players_ext, num_of_players);
}


/* ARGSUSED */
fix_from_net(players, num)
struct player_ext players[];
int num;
{
	extern int num_of_players;
	register int i;

	for (i = 0; i < num_of_players; i++) {
		players[i].ease_rating = ntohl(players[i].ease_rating);
		players[i].tray_score = ntohs(players[i].tray_score);
		players[i].total_score = ntohs(players[i].total_score);
	}
}

get_state_update(game)
int game;
{
    extern WINDOW *score_window;
    extern struct player_ext players_ext[];

    char player_num, new_state;

    if (safe_read(game, (char *)&player_num, sizeof(player_num)) < 0) {
	fprintf(stderr, "boggle: bogglemaster died in get_state_update\n");
	resetty();
	exit(1);
    }
    if (safe_read(game, (char *)&new_state, sizeof(new_state)) < 0) {
	fprintf(stderr, "boggle: bogglemaster died in get_state_update\n");
	resetty();
	exit(1);
    }
    players_ext[player_num].state = new_state;
    mvwprintw(score_window, player_num + 2, 56, "%-15s", state_names[new_state]);
#ifndef AUTO_TOUCH
    wrefresh(score_window);
#endif
}    
    

get_voting(game, tray)
int game;
char tray[5][5];
{
    extern WINDOW *message_window;

    char name[MAX_NAME_LENGTH], word[MAX_WORD_LENGTH];
    int wordlen;

    if (safe_read(game, (char *)name, MAX_NAME_LENGTH) < 0) {
	fprintf(stderr, "boggle: bogglemaster died 9\n");
	resetty();
	exit(1);
    }
    if (safe_read(game, (char *)&nlen, sizeof(nlen)) < 0) {
	fprintf(stderr, "boggle: bogglemaster died 10\n");
	resetty();
	exit(1);
    }
    wordlen = ntohl(nlen);
    if (safe_read(game, (char *)word, wordlen) < 0) {
	fprintf(stderr, "boggle: bogglemaster died 11\n");
	resetty();
	exit(1);
    }
    werase(message_window);
    wprintw(message_window, "%s: %s", name, word);
#ifndef AUTO_TOUCH
    wrefresh(message_window);
#endif
    highlight_word(word, tray);
}

get_winning_word(game, words, num_of_words)
int game;
char words[MAX_NUM_OF_WORDS][MAX_WORD_LENGTH];
int num_of_words;
{
    extern WINDOW *word_window;

    char word[MAX_WORD_LENGTH];
    register int i, x, y, len;
    int wordlen;

    if (safe_read(game, (char *)&nlen, sizeof(nlen)) < 0) {
	fprintf(stderr, "boggle: bogglemaster died 10\n");
	resetty();
	exit(1);
    }
    wordlen = ntohl(nlen);
    if (safe_read(game, (char *)word, wordlen) < 0) {
	fprintf(stderr, "boggle: bogglemaster died 11\n");
	resetty();
	exit(1);
    }

    x = 0; y = 0;
    for (i = 0; i < num_of_words; i++) {
	len = strlen(words[i]);
	if (strcmp(word, words[i]) == 0) {
	    wstandout(word_window);
	    if ((x + len) > word_window->_maxx)
		mvwaddstr(word_window, y + 1, 0, word);
	    else
		mvwaddstr(word_window, y, x, word);
	    wstandend(word_window);
#ifndef AUTO_TOUCH
	    wrefresh(word_window);
#endif
	    return;
	}
	if ((x + len) > word_window->_maxx) {
	    y++;  x = len + 1;
	}
	else if ((x + len + 1) > word_window->_maxx) {
	    y++;  x = 0;
	}
	else {
	    x += len + 1;
	}
    }
}

int get_word(buf, tray, game)
char *buf;
char tray[5][5];
int game;
{
    extern WINDOW *input_window, *message_window;
    extern int seconds_remaining;

    fd_set selectmask, readfds;
    int maxfds;
    static struct timeval word_timeout = {1, 0};
    char boggle_code;
    register char *ptr, chr;
    register int x, y;

    werase(input_window);
    ptr = buf;
    FD_ZERO(&selectmask);
    FD_SET(0, &selectmask);
    FD_SET(game, &selectmask);
    maxfds = game + 1;
    while (1) {
	display_time(seconds_remaining);
	my_refresh();
        readfds = selectmask;
	while (select(maxfds, &readfds, NULL, NULL, &word_timeout) <= 0) {
	    display_time(seconds_remaining);
	    if ((seconds_remaining <= 0) && (buf == ptr)) return(0);
	    readfds = selectmask;
	    my_refresh();
	}
	if (FD_ISSET(game, &readfds)) {
	    if (safe_read(game, (char *)&boggle_code,
	        sizeof(boggle_code)) < 0) {
		fprintf(stderr, "boggle: bogglemaster died\n");
		resetty();
		exit(1);
	    }
	    switch (boggle_code) {
		case GAME_STATE:
		    get_game_state(game);
		    break;
		case STATE_UPDATE:
		    get_state_update(game);
		    break;
	       case YOURE_DONE:
		    seconds_remaining = 0;
		    return(0);
		    break;
	       default:
		    werase(message_window);
		    waddstr(message_window, "bogglemaster is babbling");
#ifndef AUTO_TOUCH
		    wrefresh(message_window);
#endif
		    break;
	    }
	}
	if (FD_ISSET(0, &readfds)) {
	    chr = getch();
	    switch (chr) {
		case ' ':
		case '\n':
		case '\r':	*ptr = '\0';
				werase(message_window);
#ifndef AUTO_TOUCH
				wrefresh(message_window);
#endif
				return(1);
	        case '\b':
		case '\177':	if (ptr != buf) {
		    		    getyx(input_window, y, x);
				    mvwaddch(input_window, y, --x, ' ');
				    wmove(input_window, y, x);
				    ptr--;
				}
				break;
		/* ^U */
	        case '\025':	werase(input_window);
				ptr = buf;
				break;
		/* ^L */
	        case '\f':	werase(message_window);
				overwrite(message_window, curscr);
				wrefresh(curscr);
				break;
		/* ^F */
	        case '\006':	flip_tray(tray);
				print_tray(tray);
				werase(message_window);
#ifndef AUTO_TOUCH
				wrefresh(message_window);
#endif
				break;
		/* ^R */
		case '\022':	rotate_tray(tray);
	    			print_tray(tray);
				werase(message_window);
#ifndef AUTO_TOUCH
				wrefresh(message_window);
#endif
				break;
	        case 'a':  case 'A':
	        case 'b':  case 'B':
	        case 'c':  case 'C':
	        case 'd':  case 'D':
	        case 'e':  case 'E':
	        case 'f':  case 'F':
	        case 'g':  case 'G':
	        case 'h':  case 'H':
	        case 'i':  case 'I':
	        case 'j':  case 'J':
	        case 'k':  case 'K':
	        case 'l':  case 'L':
	        case 'm':  case 'M':
	        case 'n':  case 'N':
	        case 'o':  case 'O':
	        case 'p':  case 'P':
	        case 'q':  case 'Q':
	        case 'r':  case 'R':
	        case 's':  case 'S':
	        case 't':  case 'T':
	        case 'u':  case 'U':
	        case 'v':  case 'V':
	        case 'w':  case 'W':
	        case 'x':  case 'X':
	        case 'y':  case 'Y':
	        case 'z':  case 'Z':	waddch(input_window, chr);
					*(ptr++) = chr;
					break;
	    }
	}
    }
}

/* The three-minute timer. */


start_timer(secs)
int secs;
{
    extern int seconds_remaining;

    static struct itimerval my_start_timer = {{1, 0}, {1, 0}};

    end_time = time(0) + secs;
    display_time(seconds_remaining = secs);
    write(1, "", 1);
    setitimer(ITIMER_REAL, &my_start_timer, NULL);
}

display_time(secs)
int secs;
{
    extern WINDOW *time_window;

    mvwprintw(time_window, 0, 0, "%1d:%1d%1d", secs / 60, (secs % 60) / 10,
        secs % 10);
#ifndef AUTO_TOUCH
    wrefresh(time_window);
#endif
}

alrm_handler()
{
    extern int seconds_remaining, end_time;

    static struct itimerval my_stop_timer = {{0, 0}, {0, 0}};

    /* Even though it's tempting to put a calls to display_time and
       refresh here, don't.  If they get invoked while something else
       is happening in a window, the world can get into an inconsistent
       state. */

    seconds_remaining = end_time - time(0);
    if (seconds_remaining < 0) {
	 seconds_remaining = 0;
    }

    if (seconds_remaining == 0) {
        setitimer(ITIMER_REAL, &my_stop_timer, NULL);
	write(1, "", 1);
    }
#ifdef _AIX
    signal(SIGALRM, alrm_handler);
#endif
}

/*  The score_window is designed to look like this:

player            tray   total   games   avg   rating   state
=======================================================================
12345678901234567  123   1234     12    12.3    12.3    123456789012345

*/


print_score_header()
{
    extern WINDOW *score_window;

    wmove(score_window, 0, 0);
    waddstr(score_window,
        "player            tray   total   games   avg   rating   state");
    waddstr(score_window,
        "\n=======================================================================");
#ifndef AUTO_TOUCH
    wrefresh(score_window);
#endif
}


print_scores(players, num_of_players)
struct player_ext players[];
int num_of_players;
{
    extern WINDOW *score_window;
    extern char *state_names[];

    struct player_ext *p;
    register int i;

    for (i = 0; i < num_of_players; i++) {
	p = &players[i];
	mvwprintw(score_window, i + 2, 0,
	    "%-17s  %3d   %4d     %2d    %4.1f    %4.1f    %-15s",
	    p->name, p->tray_score, p->total_score, p->num_of_games,
	    (p->num_of_games) ?
	    ((float)p->total_score / p->num_of_games) : 0.0,	
    (p->ease_rating == 0) ?
	    0.0 : ((float)p->total_score / ((float) p->ease_rating / 100.0)),
	    state_names[p->state]);
    }
    /* if the list got shorter, this will erase the old extra line */
    wclrtobot(score_window);
#ifndef AUTO_TOUCH
    wrefresh(score_window);
#endif
}

/* Routines to manipulate the list of words found. */


sort_words(words, num_of_words)
char words[MAX_NUM_OF_WORDS][MAX_WORD_LENGTH];
int num_of_words;
{
    qsort((char *)words, num_of_words, MAX_WORD_LENGTH, strcmp);
}


print_word(word)
char *word;
{
    extern WINDOW *word_window;

    register int len, x, y;

    len = strlen(word);
    getyx(word_window, y, x);
    if ((x + len) > word_window->_maxx) {
	mvwprintw(word_window, y + 1, 0, "%s ", word);
    }
    else if ((x + len + 1) > word_window->_maxx) {
	waddstr(word_window, word);
	wmove(word_window, y + 1, 0);
    }
    else {
	wprintw(word_window, "%s ", word);
    }
#ifndef AUTO_TOUCH
    wrefresh(word_window);
#endif
}


print_words_in_window(window, words, num_of_words)
WINDOW *window;
char words[][MAX_WORD_LENGTH];
int num_of_words;
{
    register int len, x, y, i;

    werase(window);
    for (i = 0; i < num_of_words; i++) {
	len = strlen(words[i]);
	getyx(window, y, x);
	if ((x + len) > window->_maxx) {
	    mvwprintw(window, y + 1, 0, "%s ", words[i]);
	}
	else if ((x + len + 1) > window->_maxx) {
	    waddstr(window, words[i]);
	    wmove(window, y + 1, 0);
	}
	else {
	    wprintw(window, "%s ", words[i]);
	}
    }
#ifndef AUTO_TOUCH
    wrefresh(window);
#endif
}




int word_used_already(word, words, num_of_words)
char *word;
char words[MAX_NUM_OF_WORDS][MAX_WORD_LENGTH];
int num_of_words;
{
    register int i;

    for (i = 0; i < num_of_words; i++) {
	if (strcmp(word, words[i]) == 0) return(1);
    }
    return(0);
}

/* Tray manipulation routines. */


get_tray(game, tray)
int game;
char tray[5][5];
{
    if (safe_read(game, (char *)tray, 25 * sizeof(char)) < 0) {
	fprintf(stderr, "boggle: bogglemaster died in get_tray\n");
	resetty();
	exit(1);
    }
}    


print_tray(tray)
char tray[5][5];
{
    extern WINDOW *tray_window;

    register int x, y;
    register char chr;

    wclear(tray_window);
    for (y = 0; y < 5; y++) {
	wmove(tray_window, y, 0);
	for (x = 0; x < 5; x++) {
	    if ((chr = tray[x][y]) == 'q')
		waddstr(tray_window, " qu");
	    else
	        wprintw(tray_window, " %c ", chr);
	}
    }
#ifndef AUTO_TOUCH
    wrefresh(tray_window);
#endif
}


flip_tray(tray)
char tray[5][5];
{
    register int y;
    register char temp;

    for (y = 0; y < 5; y++) {
	temp = tray[0][y];
	tray[0][y] = tray[4][y];
	tray[4][y] = temp;
	temp = tray[1][y];
	tray[1][y] = tray[3][y];
	tray[3][y] = temp;
    }
}


rotate_tray(tray)
char tray[5][5];
{
    rotate_four(tray, 0, 0);
    rotate_four(tray, 1, 0);
    rotate_four(tray, 2, 0);
    rotate_four(tray, 3, 0);
    rotate_four(tray, 1, 1);
    rotate_four(tray, 2, 1);
}


rotate_four(tray, x, y)
char tray[5][5];
register int x, y;
{
    register char temp;

    temp = tray[y][4-x];
    tray[y][4-x] = tray[4-x][4-y];
    tray[4-x][4-y] = tray[4-y][x];
    tray[4-y][x] = tray[x][y];
    tray[x][y] = temp;
}


int word_is_legal(word, tray)
char *word;
char tray[5][5];
{
    register int x, y;
    char std_word[MAX_WORD_LENGTH];
    struct pair used[MAX_WORD_LENGTH];

    remove_u_after_q(word, std_word);
    for (x = 0; x < 5; x++) {
	for (y = 0; y < 5; y++) {
	    if (subword_is_legal(std_word, tray, x, y, used, 0)) return(1);
	}
    }
    return(0);
}


int subword_is_legal(subword, tray, x, y, used, num_used)
char *subword;
char tray[5][5];
register int x, y;
struct pair used[];
int num_used;
{
    register int i;

    if (*subword == '\0') return(1);
    if ((x < 0) || (x > 4) || (y < 0) || (y > 4)) return(0);
    for (i = 0; i < num_used; i++) {
	if ((used[i].x == x) && (used[i].y == y)) return(0);
    }
    if (*subword != tray[x][y]) return(0);
    subword++;
    used[num_used].x = x;
    used[num_used].y = y;
    num_used += 1;
    if (subword_is_legal(subword, tray, x - 1, y - 1, used, num_used) ||
        subword_is_legal(subword, tray, x - 1, y    , used, num_used) ||
	subword_is_legal(subword, tray, x - 1, y + 1, used, num_used) ||
	subword_is_legal(subword, tray, x    , y - 1, used, num_used) ||
	subword_is_legal(subword, tray, x    , y + 1, used, num_used) ||
	subword_is_legal(subword, tray, x + 1, y - 1, used, num_used) ||
	subword_is_legal(subword, tray, x + 1, y    , used, num_used) ||
	subword_is_legal(subword, tray, x + 1, y + 1, used, num_used))
	return(1);
    return(0);
}


highlight_word(word, tray)
char *word;
char tray[5][5];
{
    register int x, y;
    char std_word[MAX_WORD_LENGTH];
    struct pair used[MAX_WORD_LENGTH];

    remove_u_after_q(word, std_word);
    for (x = 0; x < 5; x++) {
	for (y = 0; y < 5; y++) {
	    if (subword_is_legal(std_word, tray, x, y, used, 0)) {
		print_tray_with_word(tray, used, strlen(std_word));
		return;
	    }
	}
    }
}


print_tray_with_word(tray, used, len)
char tray[5][5];
struct pair used[];
int len;
{
    extern WINDOW *tray_window;

    register int x, y;
    register char chr;

    wclear(tray_window);
    for (y = 0; y < 5; y++) {
	wmove(tray_window, y, 0);
	for (x = 0; x < 5; x++) {
	    if (marked(used, x, y, len)) wstandout(tray_window);
	    if ((chr = tray[x][y]) == 'q')
		waddstr(tray_window, " qu");
	    else
	        wprintw(tray_window, " %c ", chr);
	    wstandend(tray_window);
	}
    }
#ifndef AUTO_TOUCH
    wrefresh(tray_window);
#endif
}


int marked(used, x, y, len)
struct pair used[];
int x, y, len;
{
    register int i;

    for (i = 0; i < len; i++) {
	if ((used[i].x == x) && (used[i].y == y)) return(1);
    }
    return(0);
}
	    
/* Trivial utilities */


my_refresh()
{
    extern WINDOW *input_window;

    /* The second refresh moves the cursor to where it should be. */
#ifdef AUTO_TOUCH
    refresh();
#endif
    wrefresh(input_window);
}


remove_u_after_q(inbuf, outbuf)
char *inbuf, *outbuf;
{
    register char chr;

    do {
	if (((chr = *(inbuf++)) == 'q') && (*inbuf == 'u'))
	    inbuf++;
	*(outbuf++) = chr;
    } while (chr != '\0');
}


flush_typeahead()
{
    static struct timeval zero_timeout = {0, 0};
    fd_set readfds;
    char chr;

    FD_ZERO(&readfds);
    FD_SET(0, &readfds);
    while (select(1, &readfds, NULL, NULL, &zero_timeout) > 0) {
	if (read(0, &chr, 1) <= 0) return;
	FD_SET(0, &readfds);
    }
}


downcase_word(word)
char *word;
{
    register char chr;

    while (chr = *word) {
	if ((chr >= 'A') && (chr <= 'Z')) {
	    *word = chr - 'A' + 'a';
	}
	word++;
    }
}


void get_scored_word(game, scored, words, num_words)
int game;
WordWindow *scored;
char (*words)[MAX_WORD_LENGTH];
int num_words;
{
     char buf[MAX_WORD_LENGTH];

     if (safe_read(game, buf, MAX_WORD_LENGTH) < 0) {
	  fprintf(stderr, "boggle: bogglemaster died 14\n");
	  resetty();
	  exit(1);
     }

     add_word_to_ww_hl_p(scored, buf, words, num_words);
     
     return;
}
