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