dbm/
misc/
old-docs/
/* match.c */

#include "copyright.h"
#include "config.h"

#include <stdio.h>
#include <ctype.h>

#include "teeny.h"
#include "match.h"
#include "case.h"

/*
 * Matching routines for TeenyMUD. Matching against contents lists is
 * different* from matching against exit lists, so be careful.
 * 
 * Some matching rouines require dinking with private data elsewhere, so
 * match_player() (which hacks through the DB internals) lives with the db
 * stuff in db.c, and match_who() (which hacks around with the WHO list
 * maintained in misc.c) is in misc.c.
 * 
 */


struct match   *matches = NULL;
struct match   *free_matches = NULL;
int             current_best;

int             match_thing_name();
int             match_an_exit();
int             match_tokens();
struct match   *get_match();

#ifdef OLD_RAND
long            rand();
#else
long            random();
#endif				/* OLD_RAND */


int             best_match();
int             score_match();
int             pick2();
int             pick3();

/*
 * Searches the contents list, matching contents-wise (i.e. inexact matches
 * OK, pick the best), and if that comes up empty, then the exits list
 * matching exit-wise (i.e. must match an alias exactly). It returns one of
 * the best it finds, chosen at random. Note that any match at all in the
 * contents list will take precedence.
 * 
 * Now it will return -2 if a tie occurs that should be notified as "I can tell
 * which one you mean."
 * 
 */

int             match_here(player, thing, str, code)
  int             player;
  int             thing;
  char           *str;
  int             code;
{
  int             list, total, the_match;
  int             loc, flags;

  /* Catch "me" if we are, in fact, searching the player's location */

  if ((code & MAT_PLAYERS) && strcmp(str, "me") == 0) {
    if (get_int_elt(player, LOC, &loc) == -1) {
      warning("match_here", "could not get LOC on player");
      return (-1);
    }
    if (loc == thing) {
      return (player);
    }
  }
  /* Catch object number references to, verify locations and types */
  /* Fall through if anything is wrong, so *names* like #22 are OK */

  if (*str == '#' && isdigit(str[1])) {
    the_match = atoi(str + 1);
    if (exists_object(the_match)) {
      if (get_int_elt(the_match, LOC, &loc) == -1) {
	warning("match_here", "could not get LOC");
	return (-1);
      }
      if (loc == thing) {
	if (get_int_elt(the_match, FLAGS, &flags) == -1) {
	  warning("match_here", "bad flags ref");
	  return (-1);
	}
	switch (flags & TYPE_MASK) {
	case TYP_THING:
	  if (code & MAT_THINGS)
	    return (the_match);
	  break;
	case TYP_PLAYER:
	  if (code & MAT_PLAYERS)
	    return (the_match);
	  break;
	case TYP_EXIT:
	  if (code & MAT_EXITS)
	    return (the_match);
	  break;
	}
      }
    }
  }
  /* Do exits first. Only exact matches count here, so we  */
  /* avoid troubles with 's' matching a thing called 'Sam' */
  /* rather than the exit 's;so;sou;sout;south'            */

  if (code & MAT_EXITS) {

    if (get_int_elt(thing, EXITS, &list) == -1) {
      warning("match_here", "bad exits list on thing.");
      return (-1);
    }
    matches = match_exits(str, list, &total, MAT_INTERNAL | MAT_EXTERNAL);

    if (matches != NULL) {
      /* OK, we have some exits on this list. Pick one. */

      the_match = choose_match(total);
      free_match_list(matches);
      matches = NULL;
      return (the_match);
    }
  }
  /* no dice on exits. Try other stuff */

  if (code & (MAT_THINGS | MAT_PLAYERS)) {

    /* Go get the list of things on the thing. */

    if (get_int_elt(thing, CONTENTS, &list) == -1) {
      warning("match_here", "bad contents list on thing.");
      return (-1);
    }
    current_best = 0;
    total = match_contents(str, list, code);
    if (total == -2)
      return (-2);
    if (total != 0) {
      the_match = choose_match(total);
      free_match_list(matches);
      matches = NULL;
      return (the_match);
    }
  }
  return (-1);
}


/*
 * Match against a contents list. Makes a list of all the stuff in the list
 * that matched as well as possible. Returns total number of matches. May be
 * called multiple times without spamming the matches list.
 * 
 * Caller MUST zero current_best.
 */

match_contents(str, list, code)
  char           *str;
  int             list;		/* First object in the contents list */
  int             code;

{
  int             count, total_matches;
  char           *name;
  struct match   *current_match;
  int             flags, type;

  /* Loop across the entire list */

  total_matches = 0;
  while (list != -1) {		/* While not end of list */

    if (get_str_elt(list, NAME, &name) == -1) {
      warning("match_contents", "nameless object found!");
      break;
    }
    if (get_int_elt(list, FLAGS, &flags) == -1) {
      warning("match_contents", "bad flags reference");
      break;
    }
    type = TYPE_MASK & flags;

    /* Check to see if we are looking for this sort of thing */

    if ((!(code & MAT_PLAYERS) && type == TYP_PLAYER)
	|| (!(code & MAT_THINGS) && type == TYP_THING)) {
      if (get_int_elt(list, NEXT, &list) == -1) {
	warning("match_contents", "bad contents list");
	break;
      }
      continue;
    }
    count = match_thing_name(str, name);

    if (count != 0) {
      if (current_best == count) {

	/* This match was just as good */

	current_match = get_match();
	current_match->obj = list;
	insert_match(current_match, &matches);
	total_matches++;

      } else if ((count > current_best && current_best >= 0)
		 || count == -1) {

	/* This latest was a better match */

	free_match_list(matches);
	matches = NULL;
	current_match = get_match();
	current_match->obj = list;
	insert_match(current_match, &matches);
	total_matches = 1;
	current_best = count;
      }
    }
    if (get_int_elt(list, NEXT, &list) == -1) {
      warning("match_contents", "bad contents list");
      break;
    }
  }
  /*
   * if we have a tie and the string isn't specific enough, then return -2
   * and let the player know we can't tell what he's referring to.  Otherwise
   * it's okay to choose randomly from the choices, or if there's only one,
   * well yay.
   */
  if (current_best != -1 && total_matches > 1)
    return (-2);
  else
    return (total_matches);
}

/*
 * Matches a string against the list of exits. Only exact matches are
 * allowed, we handle semi-colon seperated aliases on exits. Matching is not
 * case sensitive. Things better not have whitespace leading or following.
 * 
 */

struct match   *
                match_exits(str, list, count, flags)
  char           *str;
  int             list;
  int            *count;
  int             flags;
{
  int             total_matches, exflags;
  struct match   *current_match, *exits_matched;
  char           *name;

  while (*str && isspace(*str))
    str++;

  /* Loop across the list. 'list' should be the first element */

  total_matches = 0;
  exits_matched = NULL;
  while (list != -1) {
    if (get_str_elt(list, NAME, &name) == -1) {
      warning("match_exits", "bad exits list");
      break;
    }
    if (get_int_elt(list, FLAGS, &exflags) == -1) {
      warning("match_exits", "bad flags on exit");
      break;
    }
    while (*name && isspace(*name))
      name++;

    if (((flags & MAT_EXTERNAL) && (exflags & EXTERNAL)) ||
	((flags & MAT_INTERNAL) && !(exflags & EXTERNAL))) {
      /* See if we have a match on this name */

      if (match_an_exit(str, name)) {

	/* Get a new match struct and enlist it */

	current_match = get_match();
	current_match->obj = list;
	insert_match(current_match, &exits_matched);
	total_matches++;
      }
    }
    /* Get the next list element */

    if (get_int_elt(list, NEXT, &list) == -1) {
      warning("match_exits", "bad exits list");
      break;
    }
  }
  *count = total_matches;


  if (total_matches == 0) {
    return (NULL);
  }
  return (exits_matched);
}


/*
 * Chooses a match at random from the 'matches' list, using the number passed
 * as a count of how many things are on the list.
 * 
 */

int             choose_match(total)
  int             total;
{
  int             the_match;
  struct match   *current_match;

  if (total == 0) {
    return (-1);
  }
#ifdef OLD_RAND
  the_match = (int) (rand() % (long) total);
#else
  the_match = (int) (random() % (long) total);
#endif				/* OLD_RAND */

  current_match = matches;

  while (the_match > 0) {
    current_match = current_match->fwd;
    the_match--;
  }

  /*
   * Old way.  Made match confused. if (total > 1) return (-2);
   */

  the_match = current_match->obj;	/* This is bad. Recycling vbls! */

  return (the_match);
}

/*
 * Matches a string against an exit name. We require exact (though not case
 * sensitive) matches. Cope with semi-colon seperated aliases in the exit
 * name.
 * 
 * Return 1 if a match, 0 if not.
 * 
 */

int             match_an_exit(str, name)
  char           *str, *name;

{
  char           *p;

  p = str;
  while (1) {

    while (DOWNCASE(*p) == DOWNCASE(*name) && *p
	   && *name != ';' && *name) {
      p++;
      name++;
    }

    /* Post mortem */

    if ((*name == ';' || *name == '\0') && *p == '\0') {
      return (1);
    }
    while (*name != ';' && *name)
      name++;
    if (*name == '\0') {
      break;
    } else {
      /* Skip the semi-colon and any whitespace after it */
      while (*name && ((*name == ';') || isspace(*name)))
	name++;
    }

    p = str;
  }
  return (0);
}

/*
 * Match a thing name. The p is, roughly, what the monkey at the keyboard
 * typed, q is the name we're matching against. Returns number of characters
 * matched, or -1 to indicate an exact match.
 * 
 * Case insensitive.
 */

int             match_thing_name(p, q)
  char           *p, *q;
{
  int             matched = 0;
  int             old_matched = 0;
  int             exact;
  int             initial_token;

  while (isspace(*p) && *p)
    p++;
  if (*p == '\0') {
    return (0);
  }
  /* We slide along q, trying to match tokens in p against initial */
  /* segments of sequential tokens of q. We keep track of stuff.   */

  initial_token = 1;
  while (1) {			/* Break out when we run out of space in q */

    while (isspace(*q) && *q)
      q++;
    if (*q == '\0')
      break;

    matched = match_tokens(p, q, &exact);

    if (exact && initial_token) {
      return (-1);		/* Exact match */
    } else if (matched > old_matched) {
      old_matched = matched;
    }
    /* Skip q ahead to the end of this token */

    while (!isspace(*q) && *q)
      q++;
    if (*q == '\0')
      break;
    initial_token = 0;
  }
  return (old_matched);		/* This will be the best count we found */
}

/*
 * Match p against q, token by token. p's tokens must be initial segments of
 * q's tokens, in sequence, otherwise no match at all. Returns number of
 * characters matched. Sets the value of exact to 1 or 0 to indicate whether
 * or not all tokens matched exactly.
 * 
 * Not case sensitive. Pretty much assumes no leading or trailing whitespace,
 * this should not be a big problem. It's a *game*.
 * 
 */

int             match_tokens(p, q, exact)
  char           *p, *q;
  int            *exact;

{
  int             count = 0;

  *exact = 1;
  while (1) {

    /* Loop across characters of this token */

    while (DOWNCASE(*p) == DOWNCASE(*q) && !isspace(p[1])
	   && !isspace(q[1]) && *p && *q) {

      p++;
      q++;
      count++;
    }

    /* Post mortem. */

    if (!(*q) && *p) {		/* q ran out early. */
      count = *exact = 0;
      break;
    }
    if (*p == '\0') {
      if (*q)
	*exact = 0;
      break;
    }
    /* Neither *p nor *q are \0 */

    if (DOWNCASE(*p) != DOWNCASE(*q)) {	/* No Match */
      count = *exact = 0;
      break;
    }
    /* OK. one of p[1] or q[1] was a space */

    if (isspace(p[1]) || p[1] == '\0') {
      count++;			/* Count misses a char. */
      p++;
      while (isspace(*p) && *p)
	p++;
    } else {
      count = *exact = 0;
      break;			/* No match */
    }
    if (!isspace(q[1]) && q[1]) {
      *exact = 0;
    }
    while (!isspace(*q) && *q)
      q++;
    while (isspace(*q) && *q)
      q++;
  }
  return (count);
}

/*
 * Insert a match struct into a match list.
 */

insert_match(mt, mtlist)
  struct match   *mt, **mtlist;

{
  if (*mtlist == NULL) {
    mt->back = mt->fwd = *mtlist = mt;
    return;
  }
  mt->fwd = (*mtlist)->fwd;
  mt->back = (*mtlist);
  ((*mtlist)->fwd)->back = mt;
  (*mtlist)->fwd = mt;
}

/*
 * Management code for match structs. We don't wanna malloc() and free all
 * over the place, so we do it more efficiently ourselves. I know, this is
 * anal retentive and probably no faster. Hah!
 * 
 */

struct match   *
                get_match()
{
  int             i;
  struct match   *ret;

  /* If we have no free match structs around to give away, go get some */

  if (free_matches == NULL) {
    free_matches = (struct match *) ty_malloc(
					      32 * sizeof(struct match),
					      "get_match");

    /* Link them up into a free list */

    for (i = 0; i < 31; i++) {
      free_matches[i].fwd = &(free_matches[i + 1]);
      free_matches[i + 1].back = &(free_matches[i]);
    }
    free_matches[0].back = &(free_matches[31]);
    free_matches[31].fwd = &(free_matches[0]);
  }
  /* Grab a match struct off the free list */

  if (free_matches->back == free_matches) {
    ret = free_matches;
    free_matches = NULL;
  } else {
    ret = free_matches->fwd;
    (ret->fwd)->back = free_matches;
    free_matches->fwd = ret->fwd;
  }

  return (ret);
}

/*
 * Places a list of matches on the free list. Copes with an empty list.
 */

free_match_list(mt)
  struct match   *mt;

{
  if (mt == NULL) {
    return;
  }
  if (free_matches == NULL) {
    free_matches = mt;
    return;
  }
  (mt->back)->fwd = free_matches->fwd;
  (free_matches->fwd)->back = mt->back;
  mt->back = free_matches;
  free_matches->fwd = mt;

}

/*
 * Takes three objects, and returns the best match with a given string.
 */
int             best_match(str, match1, match2, match3)
  char           *str;
  int             match1, match2, match3;
{
  int             m1score, m2score, m3score;

  if (match1 == -1)
    m1score = -2;
  else
    m1score = score_match(str, match1);

  if (match2 == -1)
    m2score = -2;
  else
    m2score = score_match(str, match2);

  if (match3 == -1)
    m3score = -2;
  else
    m3score = score_match(str, match3);

  if (m3score == -2 && m2score == -2 && m1score == -2)
    return (-1);

  if (m1score == -1)		/* this is fun! */
    if (m2score == -1)
      if (m3score == -1)
	return (pick3(match1, match2, match3));
      else
	return (pick2(match1, match2));
    else if (m3score == -1)
      return (pick2(match1, match3));
    else
      return (match1);
  else if (m2score == -1)
    if (m3score == -1)
      return (pick2(match2, match3));
    else
      return (match2);
  else if (m3score == -1)
    return (match3);

  /*
   * Okay, no exact matches.  Next, check for ties... (ties return -2)
   * Three-way tie handled in first case.
   */

  if ((m1score >= m2score && m1score == m3score) ||
      (m1score > m3score && m1score == m2score) ||
      (m2score > m1score && m2score == m3score))
    return (-2);

  /* No tie: therefore one is definitely bigger. */

  if (m1score > m2score && m1score > m3score)
    return (match1);
  else if (m2score > m3score)
    return (match2);
  else
    return (match3);

}

int             score_match(str, object)
  char           *str;
  int             object;
{
  int             score;
  char           *name;
  if (get_str_elt(object, NAME, &name) == -1) {
    warning("best_match", "bad object name or flags ref");
    /* treat as not a match. */
    return (-2);
  }
  score = match_thing_name(str, name);
  return (score);
}

int             pick2(a, b)
  int             a, b;
{
  /* randomly choose between a and b */
#ifdef OLD_RAND
  if (rand() % 2 == 0)
#else
  if (random() % 2 == 0)
#endif				/* OLD_RAND */
    return (a);
  else
    return (b);
}

int             pick3(a, b, c)
  int             a, b, c;
{
  /* randomly choose between a, b, and c */
  int             rv;

#ifdef OLD_RAND
  if ((rv = rand() % 3) == 0)
#else
  if ((rv = random() % 3) == 0)
#endif				/* OLD_RAND */
    return (a);
  else if (rv == 1)
    return (b);
  else
    return (c);
}