#include <stdio.h> #include <ctype.h> #include "teeny.h" #include "match.h" /* Copyright(C) 1990, Andrew Molitor, All Rights Reserved. This software may be freely used, modified, and redistributed, as long as this copyright message is left intact, and this software is not used to develop any commercial product, or used in any product that is provided on a pay-for-use basis. No warranties whatsoever. This is not guaranteed to compile, nor, in the event that it does compile to binaries, are these binaries guaranteed to perform any function whatsoever. */ /* 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(); long random(); /* 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. */ 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); 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 != 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,*p,ch; 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; } /* If it's a player, terminate after the name, before pwd */ if(type == TYP_PLAYER){ p = name; while(!isspace(*p) && *p) p++; ch = *p; *p = '\0'; } else { ch = '\0'; } count = match_thing_name(str,name); if(ch) /* If required, repair the name */ *p = ch; 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; } } 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) char *str; int list; int *count; { int total_matches; struct match *current_match,*exits_matched; char *name; /* 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; } /* 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); } the_match = (int) (random() % (long) total); current_match = matches; while(the_match > 0){ current_match = current_match->fwd; the_match--; } 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 { name++; /* Skip the semi-colon */ } 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; }