/* 
   This file is part of Practical Distributed Processing
   Copyright (C) 2006-2007 Phillip J. Brooke and Richard F. Paige
*/

#include "netstr.h"
#include "ngclient1.h"
#include "ngcommon.h"
#include <arpa/inet.h>
#include <math.h>
#include <ncurses.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

/* Our TCP and UDP sockets and address info. */
int st;
int su;
struct sockaddr_in at;
struct sockaddr_in au;

/* An address for the admin server. */
struct sockaddr_in admin_a;

/* Current map server. */
struct sockaddr_in map_a;

/* Our position is given in the player list (see ngcommon.h) starting
   at phead.  Similarly, shots are listed at shead. */

/* Threads while running. */
pthread_t  t_messages; 
pthread_t  t_control;
pthread_t  t_display;

/* Ncurses windows. */
WINDOW *w_info;
WINDOW *w_err;
pthread_mutex_t wmutex; /* Ncurses is not thread safe! */
int infocol;  /* Where we'll write a second column of info. */
int indcol;   /* Column to place the indicators. */

/* Player up?  assume dead otherwise. */
int player_up = 0;

/* Timer/indicator structure. */
typedef struct indicator_struct {
  int                state;
  int                row;
  char               symbol;
  struct timeval     last_change;
} indicator;

/* Subprogram prototypes. */
void open_ports(void);
void fill_local_address(void);
void get_admin_address(void);
int do_login(void);
void *running(void *param);
void *player_control(void *param);
void *bot_control(void *param);
void *player_display(void *param);
indicator new_indicator (int r, char s);
void tick(indicator *i);

int main (int argc, char *argv[]) {
  int                row, col;
  pthread_attr_t      attr;
  pthread_mutexattr_t mattr;

  printf("Dumb client starting, protocol version %d...\n", PROTOCOL_VERSION);
  read_command_line(argc, argv);
  if (!admin_host) {
    printf("Need admin server name supplying with `-a'!\n");
    exit(EXIT_FAILURE);
  }
  if (!name) {
    printf("Need player name supplying with `-n'!\n");
    exit(EXIT_FAILURE);
  }
  get_admin_address();
  open_ports();
  if (!do_login()) {
    /* Set up ncurses. */
    initscr();
    keypad(stdscr, TRUE);
    nonl();
    nocbreak();
    noecho();
    /* Set up two windows, one for info, one for stderr messages. */
    getmaxyx(stdscr, row, col);
    infocol = col / 2;
    indcol = col - 1;
    w_info = newwin(row-6, 0, 0, 0);
    w_err = newwin(4, 0, row-5, 0);
    werase(w_info);
    werase(w_err);
    idlok(w_err, 1);
    scrollok(w_err, 1);
    curs_set(0);
    /* Create mutexes. */
    pthread_mutexattr_init(&mattr);
    pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE); 
    if (pthread_mutex_init(&pmutex, &mattr) < 0) 
    {
        perror("pthread_mutex_init failed (pmutex)");
        exit(EXIT_FAILURE);
    }
    if (pthread_mutex_init(&wmutex, &mattr) < 0) 
    {
        perror("pthread_mutex_init failed (wmutex)");
        exit(EXIT_FAILURE);
    }
    /* Set attributes for threads. */
    pthread_attr_init(&attr);
    /* Deal with server messages. */
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_create(&t_messages, &attr, running, NULL);
    /* Deal with displaying info. */
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_create(&t_display, &attr, player_display, NULL);
    /* Deal with player input. */
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    if (bot) {
      pthread_create(&t_control, &attr, bot_control, NULL);
    } else {
      pthread_create(&t_control, &attr, player_control, NULL);
    }
    /* Wait for the threads to end. */
    pthread_join(t_display, NULL);
    pthread_join(t_messages, NULL);
    pthread_join(t_control, NULL);
    /* Clean up ncurses. */
    endwin();
  }
  printf("Dumb client shutting down.\n");
  exit(EXIT_SUCCESS);
}

void open_ports(void) {
  int                on              = 1;

  /* Create a UDP socket. */
  if ((su = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
    perror("Could not create UDP socket");
    exit(EXIT_FAILURE);
  }

  if (setsockopt(su, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    {
      perror("Problem setting UDP socket option");
      exit(EXIT_FAILURE);
    }

  au.sin_family = AF_INET;
  au.sin_addr.s_addr = INADDR_ANY;
  au.sin_port = 0;

  if (bind(su, (struct sockaddr *) &au, sizeof(au)) != 0)
    {
      perror("Could not bind UDP socket");
      exit(EXIT_FAILURE);
    }

  /* Create a TCP socket for the admin server. */
  if ((st = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
      perror("Could not create TCP socket");
      exit(EXIT_FAILURE);
    }

  if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    {
      perror("Problem setting TCP socket option");
      exit(EXIT_FAILURE);
    }

  at.sin_family = AF_INET;
  at.sin_addr.s_addr = admin_a.sin_addr.s_addr;
  at.sin_port = htons(ADMIN_PORT);

  if (connect(st, (struct sockaddr *) &at, sizeof(at)) != 0)
    {
      perror("Could not connect to admin server");
      exit(EXIT_FAILURE);
    }
  
  fill_local_address();
  /* The dumb client can be chatty.  What ports did we get? */
  printf("Opened address %s, TCP port %d and UDP port %d.\n",
	 inet_ntoa(at.sin_addr),
	 ntohs(at.sin_port), 
	 ntohs(au.sin_port));
}

void fill_local_address(void) {
  socklen_t          sockaddr_in_len;

  /* What have we actually ended up with? */
  sockaddr_in_len = sizeof(struct sockaddr_in);
  getsockname(st, (struct sockaddr *) &at, &sockaddr_in_len);
  getsockname(su, (struct sockaddr *) &au, &sockaddr_in_len);
}

void get_admin_address(void) {
  struct hostent *   admin_h;

  /* Sort out the admin server address. */
  if ((admin_h = gethostbyname(admin_host)) == NULL)
    { 
      perror("Could not get host details.");
      exit(EXIT_FAILURE);
    }   

  admin_a.sin_family = AF_INET;
  memcpy(&admin_a.sin_addr.s_addr, admin_h->h_addr, admin_h->h_length);
  admin_a.sin_port = htons(ADMIN_PORT);

  printf("Got admin server IP address %s, port %d.\n",
	 inet_ntoa(admin_a.sin_addr), ntohs(admin_a.sin_port));
}

int do_login(void) {
  char              *buffer      = malloc(BUFFER_SIZE);
  char              *notok       = malloc(BUFFER_SIZE);
  char              *ok          = malloc(BUFFER_SIZE);
  char              *reply       = malloc(BUFFER_SIZE);
  int                drop        = 0;
  socklen_t          sockaddr_in_len;

  sockaddr_in_len = sizeof(struct sockaddr_in);

  snprintf(ok, BUFFER_SIZE, "OK");
  snprintf(notok, BUFFER_SIZE, "NOT-OK");
  /* Wait for a message.  We know what it should be.... */
  snprintf(reply, BUFFER_SIZE, PROTOCOL_LN " ADMIN");
  if (NSrecv(st, buffer, BUFFER_SIZE, 0) <= 0) {
    perror("Problem with recv, dropping connection");
    drop = 1;
  }
  if (!drop) {
    if (strncmp(reply, buffer, strlen(reply))) {
      printf("Bogus string, dropping connection.\n");
      drop = 1;
    }
  }
  if (!drop) {
    snprintf(buffer, BUFFER_SIZE, PROTOCOL_LN " CLIENT");
    if (send(st, buffer, strlen(buffer), 0) < 0) {
      perror("Problem with send of initial message");
      exit(EXIT_FAILURE);
    }
  }
  if (!drop) {
    /* Wait for a message.  We know what it should be.... */
    if (NSrecv(st, buffer, BUFFER_SIZE, 0) <= 0) {
      perror("Problem with recv, dropping connection");
      drop = 1;
    }
  }
  if (!drop) {
    if (strncmp(ok, buffer, strlen(reply))) {
      printf("Bogus string, dropping connection.\n");
      drop = 1;
    }
  }
  /* Make one attempt to login. */
  if (!drop) {
    snprintf(buffer, BUFFER_SIZE, "LOGIN %s", name);
    if (send(st, buffer, strlen(buffer), 0) < 0) {
      perror("Problem with LOGIN message");
      exit(EXIT_FAILURE);
    }
  }
  if (!drop) {
    if (NSrecv(st, buffer, BUFFER_SIZE, 0) <= 0) {
      perror("Problem with recv, dropping connection");
      drop = 1;
    }
  }
  if (!drop) {
    if (!strncmp(ok, buffer, strlen(reply))) {
      printf("Login accepted!\n");
    } else if (!strncmp(notok, buffer, strlen(reply))) {
      printf("Login rejected!\n");
      drop = 1;
    } else {
      printf("Bogus message in response to login.\n");
      drop = 1;
    }
  } 
  if (!drop) {
    /* If we're here, then our login has been accepted.  Send the UDP
       port. */
    snprintf(buffer, BUFFER_SIZE, "UDP-PORT %d", ntohs(au.sin_port));
    if (send(st, buffer, strlen(buffer), 0) < 0) {
      perror("Problem with LOGIN message");
      exit(EXIT_FAILURE);
    }
  }
  /* And then wait for a complicated packet telling us where to start. */
  /* This packet will arrive on our UDP socket, not the TCP socket,
     which is no longer needed. */
  close(st);
  /* Free stuff. */
  free(buffer);
  free(notok);
  free(ok);
  free(reply);
  return drop;
}

void *running(void *param) {
  char              *buffer          = malloc(BUFFER_SIZE);
  char              *ms_name         = malloc(BUFFER_SIZE);
  char              *username        = malloc(BUFFER_SIZE);
  float              xi, yi;
  int                exit_flag       = 0;
  int                udp_port;
  int                xt, yt;
  int                xtp, ytp;
  pnode             *pcurr;
  pnode             *pcurr2;
  pnode             *pnext;
  snode             *scurr;
  snode             *snext;
  socklen_t          sockaddr_in_len;
  struct hostent    *h;
  struct sockaddr_in a;
  indicator          ind;

  sockaddr_in_len = sizeof(struct sockaddr_in);
  ind = new_indicator(1, 'P');
  while (!exit_flag) {
    tick(&ind);
    /* Listen only on our UDP socket forever. */
    if (NSrecvfrom(su, buffer, BUFFER_SIZE, 0,
		   (struct sockaddr *) &a, &sockaddr_in_len) <= 0) {
      /* Ignore it. */
      pthread_mutex_lock(&wmutex);
      wprintw(w_err, "Ignoring broken message from %s:%d.\n",
	     inet_ntoa(a.sin_addr), ntohs(a.sin_port));
      pthread_mutex_unlock(&wmutex);
    } else if (!( ((a.sin_addr.s_addr==admin_a.sin_addr.s_addr)
		   && (ntohs(a.sin_port)==ntohs(admin_a.sin_port)))
		  || ((a.sin_addr.s_addr==map_a.sin_addr.s_addr)
		      && (ntohs(a.sin_port)==ntohs(map_a.sin_port)))  )) {
      /* Not from admin or map... */
      pthread_mutex_lock(&wmutex);
      wprintw(w_err, "Ignoring non-server message from %s:%d.\n",
	     inet_ntoa(a.sin_addr), ntohs(a.sin_port));
      pthread_mutex_unlock(&wmutex);
    } else {
      /* Handle a UDP message. */
      /* So what messages can the client's UDP port accept? */
      /* From the admin server. */
      /* PLAYER UP */
      if (sscanf(buffer, PROTOCOL_LN "\nPLAYER UP %d %d %f %f %" Xstr(SHORT_BUFFER) "s %d", 
			&xt, &yt, &xi, &yi, ms_name, &udp_port) == 6) {
	if (check_tile(&xt, &yt) && check_inner(&xi, &yi)) {
	  map_a.sin_family = AF_INET;
	  if ((h = gethostbyname(ms_name)) == NULL)
	    { 
	      perror("Could not resolve map server");
	      exit(EXIT_FAILURE);
	    }
	  memcpy(&map_a.sin_addr.s_addr, h->h_addr, h->h_length);
	  map_a.sin_port = htons(udp_port);
	  pthread_mutex_lock(&pmutex);
	  player_up = 1;
	  if ((pcurr = match_player(name))) {
	    /* Update our position. */
	    pcurr->xt = xt;
	    pcurr->yt = yt;
	    pcurr->xi = xi;
	    pcurr->yi = yi;
	  } else {
	    pcurr = add_player(name, xt, yt, xi, yi, 
			       0, 0, 0, 0, NULL, 0);
	  }
	  pcurr->xr = to_real(xt, xi);
	  pcurr->yr = to_real(yt, yi);
	  /* Remove all other players and shots from our list. */
	  pcurr = phead;
	  /* Now that we have pcurr as phead, set phead to be just us. */
	  phead = match_player(name);
	  /* Then walk through freeing all that aren't us. */
	  while (pcurr) {
	    pnext = pcurr->next;
	    if (pcurr != phead) {
	      /* Not us, delete him. */
	      free(pcurr->name);
	      free(pcurr);
	    } 
	    pcurr = pnext;
	  } /*while (pcurr)*/
	  /* Also clear all shots. */
	  scurr = shead;
	  while (scurr) {
	    snext = scurr->next;
	    free(scurr->name);
	    free(scurr);
	    scurr = snext;
	  }
	  shead = NULL;
	  /* Done with clearing lists. */
	  pthread_mutex_unlock(&pmutex);
	} else {
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, "Rejected coordinates in PLAYER UP.\n");
	  pthread_mutex_unlock(&wmutex);
	}
      }
      /* From map servers... */
      /* PLAYER MOVE */
      else if (sscanf(buffer, PROTOCOL_LN "\nPLAYER MOVE %" Xstr(SHORT_BUFFER) "s %d %d %d %d %f %f",
		      username, &xtp, &ytp, &xt, &yt, &xi, &yi) == 7) {
	if (check_tile(&xtp, &ytp) && check_tile(&xt, &yt) && check_inner(&xi, &yi)) {
	  pthread_mutex_lock(&pmutex);
	  if ((pcurr = match_player(username))) {
	    /* Update position. */
	    pcurr->xt = xt;
	    pcurr->yt = yt;
	    pcurr->xi = xi;
	    pcurr->yi = yi;
	  } else {
	    pcurr = add_player(username, xt, yt, xi, yi, 
			       0, 0, 0, 0, NULL, 0);
	  }
	  pcurr->xr = to_real(xt, xi);
	  pcurr->yr = to_real(yt, yi);
	  /* Views. */
	  if (strcmp(name, username)) {
	    /* Different player moving. */
	    pcurr2 = match_player(name);
	    if (pcurr2 
		&& !local_tile(pcurr2->xt, pcurr2->yt, pcurr->xt, pcurr->yt)) {
	      /* Remove it. */
	      delete_player(pcurr);
	    }
	  } else {
	    /* We're moving.  Check if we've moved out of sight of any
	       existing players or shots. */
	    pcurr2 = phead;
	    while (pcurr2) {
	      pnext = pcurr2->next;
	      if (!local_tile(pcurr2->xt, pcurr2->yt, pcurr->xt, pcurr->yt)) {
		delete_player(pcurr2);
	      }
	      pcurr2 = pnext;
	    }
	    scurr = shead;
	    while (scurr) {
	      snext = scurr->next;
	      if (!local_tile(scurr->xt, scurr->yt, pcurr->xt, pcurr->yt)) {
		delete_shot(scurr);
	      }
	      scurr = snext;
	    }
	  }
	  pthread_mutex_unlock(&pmutex);
	} else {
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, "Rejected coordinates in PLAYER MOVE.\n");
	  pthread_mutex_unlock(&wmutex);
	}
      } 
      /* CHANGE SERVER */
      else if (sscanf(buffer, PROTOCOL_LN "\nCHANGE SERVER %" Xstr(SHORT_BUFFER) "s %d",
		      ms_name, &udp_port) == 2) {
	map_a.sin_family = AF_INET;
	if ((h = gethostbyname(ms_name)) == NULL)
	  { 
	    perror("Could not resolve map server");
	    exit(EXIT_FAILURE);
	  }
	memcpy(&map_a.sin_addr.s_addr, h->h_addr, h->h_length);
	map_a.sin_port = htons(udp_port);
      } 
      /* SHOT */
      else if (sscanf(buffer, PROTOCOL_LN "\nSHOT %" Xstr(SHORT_BUFFER) "s %d %d %d %d %f %f",
		      username, &xtp, &ytp, &xt, &yt, &xi, &yi) == 7) {
	if (check_tile(&xtp, &ytp) && check_tile(&xt, &yt) && check_inner(&xi, &yi)) {
	  pthread_mutex_lock(&pmutex);
	  if ((scurr = match_shot(username))) {
	    scurr->xt = xt;
	    scurr->yt = yt;
	    scurr->xi = xi;
	    scurr->yi = yi;
	  } else {
	    scurr = add_shot(username,
			     xt, yt, xi, yi,
			     0, 0, 0, 0);
	  }
	  scurr->xr = to_real(xt, xi);
	  scurr->yr = to_real(yt, yi);
	  /* Views. */
	  pcurr = match_player(name);
	  if (pcurr  
	      && !local_tile(pcurr->xt, pcurr->yt, scurr->xt, scurr->yt)) {
	    /* Remove it. */
	    delete_shot(scurr);
	  }
	  pthread_mutex_unlock(&pmutex);
	} else {
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, "Rejected coordinates in SHOT.\n");
	  pthread_mutex_unlock(&wmutex);
	}
      } 
      /* REMOVE PLAYER */
      else if (sscanf(buffer, PROTOCOL_LN "\nREMOVE PLAYER %" Xstr(SHORT_BUFFER) "s %d %d",
		      username, &xt, &yt) == 3) {
	if (check_tile(&xt, &yt)) {
	  pthread_mutex_lock(&pmutex);
	  if ((pcurr = match_player(username))) {
	    delete_player(pcurr);
	  }
	  /* If it's us, we should quit. */
	  if (!strcmp(name, username)) {
	    exit_flag = 1;
	  }
	  pthread_mutex_unlock(&pmutex);
	} else {
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, "Rejected coordinates in REMOVE PLAYER.\n");
	  pthread_mutex_unlock(&wmutex);
	}
      } 
      /* REMOVE SHOT */
      else if (sscanf(buffer, PROTOCOL_LN "\nREMOVE SHOT %" Xstr(SHORT_BUFFER) "s %d %d",
		      username, &xt, &yt) == 3) {
	if (check_tile(&xt, &yt)) {
	  pthread_mutex_lock(&pmutex);
	  if ((scurr = match_shot(username))) {
	    delete_shot(scurr);
	  }
	  pthread_mutex_unlock(&pmutex);
	} else {	
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, "Rejected coordinates in REMOVE SHOT.\n");
	  pthread_mutex_unlock(&wmutex);
	}
      } 
      /* PLAYER DIED */
      else if (sscanf(buffer, PROTOCOL_LN "\nPLAYER DIED %" Xstr(SHORT_BUFFER) "s %d %d",
		      username, &xt, &yt) == 3) {
	if (check_tile(&xt, &yt)) {
	  pthread_mutex_lock(&pmutex);
	  if (strcmp(username, name)) {
	    /* If the player who died is someone else, we remove him. */
	    if ((pcurr = match_player(username))) {
	      delete_player(pcurr);
	    }
	  } else {
	    /* If the player who died is us, we'll remove all the
	       others from our list when we're back up. */
	    player_up = 0;
	    pthread_mutex_lock(&wmutex);
	    wprintw(w_err, "** Dead! **\n");
	    pthread_mutex_unlock(&wmutex);
	  }
	  pthread_mutex_unlock(&pmutex);
	} else {
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, "Rejected coordinates in PLAYER DIED.\n");
	  pthread_mutex_unlock(&wmutex);
	}
      } 
      /* SHUTDOWN */
      else if (!strcmp(buffer, PROTOCOL_LN "\nSHUTDOWN")) {
	exit_flag = 1;
      } 
      /* No match? */
      else {
	pthread_mutex_lock(&wmutex);
	wprintw(w_err, "Ignoring unmatched message from %s:%d.\n",
	       inet_ntoa(a.sin_addr), ntohs(a.sin_port));
	wprintw(w_err, "  Message: `%s'\n", buffer);
	pthread_mutex_unlock(&wmutex);
      }
    }
  } /* while */
  /* Done. */
  pthread_cancel(t_control);
  pthread_cancel(t_display);
  pthread_exit(NULL);
  return 0; /* Never reach this line. */
}
  
void *player_control(void *param) {
  char           *buffer     = malloc(BUFFER_SIZE);
  float           xi, yi;
  float           xr, yr;
  int             exit_flag = 0;
  int             xt, yt;
  pnode          *pcurr;
  indicator          ind;

  ind = new_indicator(2, 'C');
  while (!exit_flag) {
    tick(&ind);
    if (getnstr(buffer, SHORT_BUFFER) == ERR) {
      /* End of file.  Our stdin has gone!  Die. */
      pthread_mutex_lock(&wmutex);
      wprintw(w_err, "Error in string read!  Will quit.\n");
      pthread_mutex_unlock(&wmutex);
      exit_flag = 1;
    } else {
      if (!strncmp(buffer, "quit", 4)) {
	exit_flag = 1;
      } else if (player_up && !strncmp(buffer, "s", 1)) {
	/* Stop! (our movement) So send a MOVE message with our
	   destination set as our current position. */
	pthread_mutex_lock(&pmutex);
	pcurr = match_player(name);
	snprintf(buffer, BUFFER_SIZE, 
		 PROTOCOL_LN "\nMOVE %s %d %d %f %f",
		 name, pcurr->xt, pcurr->yt, pcurr->xi, pcurr->yi);
	pthread_mutex_unlock(&pmutex);
	if (sendto(su, buffer, strlen(buffer), 0, 
		   (struct sockaddr *) &(map_a), 
		   sizeof(map_a)) < 0) {
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, "Problem with sendto (move/stop).\n");
	  pthread_mutex_unlock(&wmutex);
	}
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, ">> Stopping\n");
	  pthread_mutex_unlock(&wmutex);
      }  else if (player_up && sscanf(buffer, "m %f %f", &xr, &yr) == 2) {
	/* Move to given location.  So send a MOVE message. */
	/* Convert the coordinates. */
	xt = to_tile(xr);
	yt = to_tile(yr);
	xi = to_inner(xr);
	yi = to_inner(yr);
	if (check_tile(&xt, &yt) && check_inner(&xi, &yi)) {
	  /* Send! */
	  snprintf(buffer, BUFFER_SIZE, 
		   PROTOCOL_LN "\nMOVE %s %d %d %f %f",
		   name, xt, yt, xi, yi);
	  if (sendto(su, buffer, strlen(buffer), 0, 
		     (struct sockaddr *) &(map_a), 
		     sizeof(map_a)) < 0) {
	    pthread_mutex_lock(&wmutex);
	    wprintw(w_err, "Problem with sendto (move).\n");
	    pthread_mutex_unlock(&wmutex);
	  }
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, ">> Moving to (%7.2f,%7.2f)\n", xr, yr);
	  pthread_mutex_unlock(&wmutex);
	} else {
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, "!> Coordinates invalid -- not moved\n");
	  pthread_mutex_unlock(&wmutex);
	}
	
      }   else if (player_up && sscanf(buffer, "f %f %f", &xr, &yr) == 2) {
	/* Fire a shot at a given location.  So send a FIRE message. */
	/* Convert the coordinates. */
	xt = to_tile(xr);
	yt = to_tile(yr);
	xi = to_inner(xr);
	yi = to_inner(yr);
	if (check_tile(&xt, &yt) && check_inner(&xi, &yi)) {
	  /* Send! */
	  snprintf(buffer, BUFFER_SIZE, 
		   PROTOCOL_LN "\nFIRE %s %d %d %f %f",
		   name, xt, yt, xi, yi);
	  if (sendto(su, buffer, strlen(buffer), 0, 
		     (struct sockaddr *) &(map_a), 
		     sizeof(map_a)) < 0) {
	    pthread_mutex_lock(&wmutex);
	    wprintw(w_err, "Problem with sendto (fire).\n");
	    pthread_mutex_unlock(&wmutex);
	  }
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, ">> Firing at (%7.2f,%7.2f)\n", 
		  xr, yr);
	  pthread_mutex_unlock(&wmutex);
	} else {
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, "!> Coordinates invalid -- not moved\n");
	  pthread_mutex_unlock(&wmutex);
	}
      } else {
	pthread_mutex_lock(&wmutex);
	wprintw(w_err, "stop `s', move `m <x> <y>', fire `f <x> <y>', `quit'\n");
	pthread_mutex_unlock(&wmutex);
      }
    }
  }
  /* Send a quit message. */
  snprintf(buffer, BUFFER_SIZE, 
	   PROTOCOL_LN "\nQUIT %s",
	   name);
  if (sendto(su, buffer, strlen(buffer), 0, 
	     (struct sockaddr *) &(map_a), 
	     sizeof(map_a)) < 0) {
    pthread_mutex_lock(&wmutex);
    wprintw(w_err, "Problem with sendto (quit).\n");
    pthread_mutex_unlock(&wmutex);
  }
  /* Done. */
  pthread_cancel(t_messages);
  pthread_cancel(t_display);
  pthread_exit(NULL);
  return 0; /* Never reach this line. */
}

void *bot_control(void *param) {
  char           *buffer     = malloc(BUFFER_SIZE);
  float           xi, yi;
  float           xr, yr;
  int             only_me;
  int             r_val;
  int             xt, yt;
  pnode          *pcurr;
  snode          *scurr;
  struct timespec ts;
  indicator          ind;
  
  ind = new_indicator(2, 'B');
  /* Time-slice set up. */
  ts.tv_sec = 0;
  ts.tv_nsec = BOTPERIOD;
  while (1) {
    tick(&ind);
    /* Tactics are dumb.  Roll a die: pick a random destination and
       move to it. */
    if (player_up && (random()%100) < BOTMOVEPC) {
      xr = fmodf( (float) random(), (N_TILES * TILE_LENGTH));
      yr = fmodf( (float) random(), (N_TILES * TILE_LENGTH));
      pthread_mutex_lock(&wmutex);
      wprintw(w_err, ">> Moving to (%7.2f,%7.2f)\n", xr, yr);
      pthread_mutex_unlock(&wmutex);
      xt = to_tile(xr);
      yt = to_tile(yr);
      xi = to_inner(xr);
      yi = to_inner(yr);
      snprintf(buffer, BUFFER_SIZE, 
	       PROTOCOL_LN "\nMOVE %s %d %d %f %f",
	       name, xt, yt, xi, yi);
      if (sendto(su, buffer, strlen(buffer), 0, 
		 (struct sockaddr *) &(map_a), 
		 sizeof(map_a)) < 0) {
	pthread_mutex_lock(&wmutex);
	wprintw(w_err, "Problem with sendto (move).\n");
	pthread_mutex_unlock(&wmutex);
      }
    }
    /* If we don't have a shot in the air, pick someone and fire. */ 
    pthread_mutex_lock(&pmutex);
    if (player_up && !(scurr = match_shot(name))) {
      /* Can we find a target -- who isn't us, of course. */
      only_me = 1;
      pcurr = phead;
      while (pcurr) {
	if (strcmp(pcurr->name, name)) {
	  only_me = 0;
	}
	pcurr = pcurr->next;
      }
      if (!only_me) {
	r_val = random()%20;
	pcurr = phead;
	while (r_val > 0) {
	  pcurr = pcurr->next;
	  if (!pcurr) { pcurr = phead; }
	  if (!strcmp(pcurr->name, name)) {
	    pcurr = pcurr->next;
	    if (!pcurr) { pcurr = phead; }
	  }
	  r_val--;
	}
	/* Targeting pcurr->name. */
	pthread_mutex_lock(&wmutex);
	wprintw(w_err, ">> Firing at %s (%7.2f,%7.2f)\n", 
		pcurr->name, pcurr->xr, pcurr->yr);
	pthread_mutex_unlock(&wmutex);
	xt = to_tile(pcurr->xr);
	yt = to_tile(pcurr->yr);
	xi = to_inner(pcurr->xr);
	yi = to_inner(pcurr->yr);
	snprintf(buffer, BUFFER_SIZE, 
		 PROTOCOL_LN "\nFIRE %s %d %d %f %f",
		 name, xt, yt, xi, yi);
	if (sendto(su, buffer, strlen(buffer), 0, 
		   (struct sockaddr *) &(map_a), 
		   sizeof(map_a)) < 0) {
	  pthread_mutex_lock(&wmutex);
	  wprintw(w_err, "Problem with sendto (fire).\n");
	  pthread_mutex_unlock(&wmutex);
	}
      }
    }
    pthread_mutex_unlock(&pmutex);
    /* Sleep. */
    nanosleep(&ts, NULL);
  }
  /* Never quits! */
  pthread_exit(NULL);
}

void *player_display(void *param) {
  pnode             *pcurr;
  snode             *scurr;
  struct timespec    ts;
  struct sockaddr_in au2;
  int                udp_port;
  socklen_t          sockaddr_in_len;
  indicator          ind;

  ind = new_indicator(0, 'D');
  /* What port did we end up with? */
  sockaddr_in_len = sizeof(struct sockaddr_in);
  getsockname(su, (struct sockaddr *) &au2, &sockaddr_in_len);
  udp_port = ntohs(au2.sin_port);
  /* Time-slice set up. */
  ts.tv_sec = 0;
  ts.tv_nsec = DISPLAYPERIOD;
  /* Loop forever. */
  while (1) {
    pthread_mutex_lock(&pmutex);
    pthread_mutex_lock(&wmutex);
    werase(w_info);
    wattron(w_info, A_BOLD);
    wprintw(w_info, "Admin server %s:%d\nMap server   %s:%d\nWorld size   0.00-%.2f\n",
	   inet_ntoa(admin_a.sin_addr), ntohs(admin_a.sin_port),
	   inet_ntoa(map_a.sin_addr), ntohs(map_a.sin_port),
	   (float) N_TILES*TILE_LENGTH);
    mvwprintw(w_info, 0, infocol, "Player %s", name);
    mvwprintw(w_info, 1, infocol, "Port %d", udp_port);
    mvwprintw(w_info, 2, infocol, "%s\n\n", 
	      player_up ? "Player up" : "* DEAD *");
    wattroff(w_info, A_BOLD);
    
    pcurr = phead;
    while (pcurr) {
      if (!strcmp(pcurr->name, name)) { wattron(w_info, A_BOLD); }
      wprintw(w_info, "(%7.2f,%7.2f) player %s\n",
	     pcurr->xr, pcurr->yr, pcurr->name);
      if (!strcmp(pcurr->name, name)) { wattroff(w_info, A_BOLD); }
      pcurr = pcurr->next;
    }
    scurr = shead;
    while (scurr) {
      if (!strcmp(scurr->name, name)) { wattron(w_info, A_BOLD); }
      wprintw(w_info, "(%7.2f,%7.2f) shot from player %s\n",
	     scurr->xr, scurr->yr, scurr->name);
      if (!strcmp(scurr->name, name)) { wattroff(w_info, A_BOLD); }
      scurr = scurr->next;
    }
    tick(&ind);
    wrefresh(w_info);
    wrefresh(w_err);
    pthread_mutex_unlock(&wmutex);
    pthread_mutex_unlock(&pmutex);
    /* Sleep. */
    nanosleep(&ts, NULL);
  }
  pthread_exit(NULL);
}

indicator new_indicator (int r, char s) {
  indicator i;

  i.state = 0;
  i.row = r;
  i.symbol = s;
  gettimeofday(&i.last_change, NULL);
  return i;
}

void tick(indicator *i) {
  struct timeval tv;
  long int diff;

  gettimeofday(&tv, NULL);
  diff = 1000000 * (tv.tv_sec - i->last_change.tv_sec);
  diff = diff + (tv.tv_usec - i->last_change.tv_usec);
  if (diff > INDICATORPERIOD) {
    i->state = 1 - i->state;
    i->last_change = tv;
  }
  pthread_mutex_lock(&wmutex);
  wattron(w_info, A_BOLD);
  mvwprintw(w_info, i->row, indcol, "%c", i->state ? i->symbol : ' ');
  wattroff(w_info, A_BOLD);
  wrefresh(w_info);
  pthread_mutex_unlock(&wmutex);
}
