/**************************************************************** * explore.c: Common robot code for TinyMUD automata * * HISTORY * 22-Dec-90 Michael Mauldin (mlm) at Carnegie-Mellon University * Eleventh remedial release. * Mods for DragonMud, especially visible exits, and * TinyMUCK (Brigadoon). * * 27-Sep-90 Michael Mauldin (mlm) at Carnegie-Mellon University * Tenth ceremonial relase. * Mods for TinyHELL. * * 05-May-90 Michael Mauldin (mlm) at Carnegie-Mellon University * Seventh sequential release. * Mods for TinyHELL. * * 26-Feb-90 Michael Mauldin (mlm) at Carnegie-Mellon University * Sixth experimental release. Changed file formats, added lots * of info to room memory. Found a memory allocation bug. * * 10-Feb-90 Michael Mauldin (mlm) at Carnegie-Mellon University * Fifth special release. * * 04-Feb-90 Michael Mauldin (mlm) at Carnegie-Mellon University * Fourth general release * * 25-Jan-90 Michael Mauldin (mlm) at Carnegie-Mellon University * Third interim release (allow numeric IP addresses) * * 05-Jan-90 Michael Mauldin (mlm) at Carnegie-Mellon University * Second General Release. * * 31-Dec-89 Michael Mauldin (mlm) at Carnegie-Mellon University * Created. ****************************************************************/ # include # include # include # include # include # include # include # include # include # include #include #include #include # include "robot.h" # include "vars.h" #include "zog.h" #include "rand.h" #include "words.h" #include "reply1.h" #include "players.h" #include "explore.h" static int new_goal = 1; static void realloc_rooms(long int n); static const char *exitstring(const char *); static int same_room(long int, long int, const char *, const char *); static int islandmark(const char *); static const char *unexp_exit(long int, int); static void check_id(const char *); static int not_exit(const char *); static void clean_bad_exits(int); /**************************************************************** * read_map: Read in a TinyMud map file ****************************************************************/ int read_map(const char *fn) { char buf[BIGBUF], name[MSGSIZ], dir[MSGSIZ], wname[MSGSIZ]; FILE *mfile; long nrooms = 0, cur = 0, xloc = -1; /* Open the map file */ if ((mfile = fopen (fn, "r")) == NULL) { if (access (fn, 0) < 0) { fprintf (stderr, "Util: starting with blank map\n"); realloc_rooms (200); return (1); } else { fatal ("Map: can't read %s.\n", fn); } } /* Read map file header */ if (!fgets (buf, BIGBUF, mfile)) { fclose (mfile); fprintf (stderr, "Util: null map file, %s, will be over-written\n", fn); realloc_rooms (200); return (1); } /* Check for old style map file */ if (stlmatch (buf, "Gloria Map File")) { fclose (mfile); fatal ("Old style map file, use mn6cvt to convert it\n"); } /* Check for new style header */ if (!stlmatch (buf, "Maas-Neotek Map file")) { fclose (mfile); fatal ("Fatal, '%s' %s\nBad line: %s\n", fn, "does not have a valid Maas-Neotek map file header", buf); } /* Now read room list */ while (fgets (buf, BUFSIZ, mfile)) { buf[strlen (buf) - 1] = '\0'; if (*buf != '\0' && buf[1] != ':') { fprintf (stderr, "Warn: bad map line: %s\n", buf); continue; } /* Handle each line differently */ switch (*buf) { case 'K': nrooms = atol (buf+2); if (nrooms <= 0) { fclose (mfile); fatal ("Fatal error in %s, %s\n: %s\n", fn, "number of rooms must be positive", buf); } if (nrooms < 200) nrooms = 200; else nrooms += 100; realloc_rooms (nrooms); break; case 'T': case 'H': case 'P': case 'I': /* Ignore extra information for now */ case 'W': if (sscanf (buf, "W:%[^\n]", wname) == 1) { if (!streq (world, wname)) { fprintf (stderr, "Wrld: new is %s, old was %s\n", world, wname); } } break; case 'R': if (sscanf (buf, "R:%ld %[^\n]", &cur, name) != 2) { cur = -1; } else if (cur < 0 || cur >= maxrooms) { fatal ("Bogus room number %ld (max %ld) in read_map: %s\n", cur, maxrooms, buf); } else { room[cur].name = makestring (name); } break; case 'F': if (cur >= 0) { if (sscanf (buf, "F:%ld %ld %ld %ld %ld %ld %ld %ld %d", &room[cur].number, &room[cur].firstin, &room[cur].lastin, &room[cur].cntin, &room[cur].totalin, &room[cur].awakesum, &room[cur].sleepsum, &room[cur].msgsum, &room[cur].flags) == 9) { /* good, new room with flags */ } /* Old style rooms didnt save flags in map file */ else if (sscanf (buf, "F:%ld %ld %ld %ld %ld %ld %ld %ld", &room[cur].number, &room[cur].firstin, &room[cur].lastin, &room[cur].cntin, &room[cur].totalin, &room[cur].awakesum, &room[cur].sleepsum, &room[cur].msgsum) == 8) { room[cur].flags = 0; } else { fprintf (stderr, "Bogus F line: %s\n", buf); } } if (rooms <= cur) rooms = cur+1; break; case 'D': if (cur >= 0 && buf[2]) { room[cur].desc = makestring (buf + 2); } else { fprintf (stderr, "Warn: cur %ld, ignoring '%s'\n", cur,buf);} break; case 'C': if (cur >= 0 && buf[2]) { room[cur].contents = makestring (buf + 2); } else { fprintf (stderr, "Warn: cur %ld, ignoring '%s'\n", cur,buf);} break; case 'X': if (cur >= 0 && buf[2]) { room[cur].exlist = makestring (buf + 2); } else { fprintf (stderr, "Warn: cur %ld, ignoring '%s'\n", cur,buf);} break; case 'B': if (cur >= 0 && buf[2]) { add_exit (cur, exitstring (buf+2), cur); } else { fprintf (stderr, "Warn: cur %ld, ignoring '%s'\n", cur,buf);} break; case 'E': if (cur >= 0) { if (sscanf (buf+2, "%ld %[^\n]", &xloc, dir) == 2 && *dir) { add_exit (cur, exitstring (dir), xloc); } else { fprintf (stderr, "Warn: bogus exit line: %s\n", buf); } } else { fprintf (stderr, "Warn: ignoring exit, cur %ld: %s\n", cur, buf); } break; case '#': case '\0': break; default: fprintf (stderr, "Warn: bad map file line: %s\n", buf); } } fclose (mfile); reach_added++; newexits = newrooms = 0; lastcheck = now = time (0); fprintf (stderr, "Util: read %ld rooms from %s\n", rooms, fn); return (1); } /**************************************************************** * realloc_rooms: ****************************************************************/ void realloc_rooms(long int n) { O_ROOM *oldroom = room; long oldmax = maxrooms; register long i; if (path != NULL) free (path); maxrooms = n; room_sp = maxrooms * sizeof (O_ROOM); room = (O_ROOM *) ralloc (room_sp); path_sp = maxrooms * sizeof (PATH); path = (PATH *) ralloc (path_sp); fprintf (stderr, "Util: allocating room array of %ld entries\n", maxrooms); if (room == NULL) { crash_robot ("malloc returns NULL in realloc_rooms"); } /* If expanding, copy over old information */ i = 0; if (oldroom) { for (; inext) { if ( !reach_only || ((reach_only == 1) && can_reach(xp->room)) || ((reach_only == 2) && can_reach(xp->room) && (xp->room != i)) ) { if (xp->room == i) { fprintf (outfil, "B:%s\n", xp->dir); } else { fprintf (outfil, "E:%ld\t%s\n", xp->room, xp->dir); } } } } } fclose (outfil); if (swap) swap_files (tmpfile, outname); } #if 0 // not used anywhere! /**************************************************************** * dump_map: Dump current state of map ****************************************************************/ dump_map () { register long i; register O_EXIT *xp; fprintf (stderr, "\n\nMaas-Neotek Map Dump: %ld rooms\n\n", rooms); for (i=0; inext) { if (xp->room == i) fprintf (stderr, " %s", xp->dir); } fprintf (stderr, "\n"); } for (xp = room[i].exits; xp; xp = xp->next) { if (xp->room != i) { fprintf (stderr, "%15.15s ", xp->dir); if (xp->room < maxrooms) { print_room (&room[xp->room], xp->room); } else { fprintf (stderr, "(undefined)"); } fprintf (stderr, "\n"); } } fprintf (stderr, "\n"); } } } #endif #if 0 // used only within routines that themselves aren't used static void print_room(O_ROOM *rm, long int num) { if (rm == NULL) return; fprintf (stderr, "\"%s\"", rm->name); if (rm->number >= 0) { fprintf (stderr, " (#%ld)", rm->number); } # ifdef DEBUG if (num >= 0) { fprintf (stderr, " [%ld]", num); } #else (void)num; # endif } #endif /**************************************************************** * exitstring: Return standard pointers for common exits, and * use makestring for uncommon exits. ****************************************************************/ static const char *exitstring(const char *str) { const char **ep; for (ep=stexits;*ep;ep++) { if (strcmp(*ep,str) == 0) { return(*ep); } } return(makestring(str)); } /**************************************************************** * find_room: Find a room number given the name * modification: find last room in list, not first ****************************************************************/ long int find_room(const char *roomnam, const char *desc) { register long i; long best = -1, bestscore = 0, matches = 0, score; long roomnum; if (desc && !*desc) desc = NULL; if (smatch (roomnam, "* (#*)", roomstr) && isdigit (*room2)) { roomnum = atoi (room2); } else if (smatch (roomnam, "*(#*)", roomstr) && isdigit (*room2)) { roomnum = atoi (room2); } else { strcpy (room1, roomnam); roomnum = -1; } for (i=0; i bestscore) || ( (score == bestscore) && ( (can_reach(i) && !can_reach(best)) || (strlen(room[i].name) < strlen(room[best].name)) ) ) ) { if (best >= 0 && can_reach (best) && !can_reach (i)) continue; best = i; bestscore = score; } if (debug && desc && room[i].desc) { fprintf (stderr, "Find: matching %s[%16.16s] to %s[%16.16s](%ld)\n", roomnam, desc, room[i].name, room[i].desc, i); } matches++; } else if (debug && streq (room[i].name, room1)) { fprintf (stderr, "Find: failing to match %s, desc '%s', number %ld\n", room1, desc ? desc : "(no desc)", roomnum); fprintf (stderr, "Find: room[%ld].desc '%s', room[%ld].number %ld\n", i, room[i].desc ? room[i].desc : "(no desc)", i, room[i].number); } } } /* Print result of find_room */ if (!debug) { /* No message */ } else if (matches > 1) { if (desc) { fprintf (stderr, "Find: find_room, multiple matches for %s[%16.16s], first %s(%ld)\n", roomnam, desc, room[best].name, best); } else { fprintf (stderr, "Find: find_room, multiple matches for %s, first %s(%ld)\n", roomnam, room[best].name, best); } } else if (matches == 1) { if (desc) { fprintf (stderr, "Find: find_room, match for %s[%16.16s] => %s(%ld)\n", roomnam, desc, room[best].name, best); } else { fprintf (stderr, "Find: find_room, match for %s => %s(%ld)\n", roomnam, room[best].name, best); } } else if (matches == 0) { if (desc) { fprintf (stderr, "Find: find_room, no matches for %s[%16.16s]\n", roomnam, desc); } else { fprintf (stderr, "Find: find_room, no matches for %s\n", roomnam); } } /* If room name/description changed, make the change */ if (best >= 0) { if (!streq (room[best].name, room1)) { if (!ismuck || ((*room1 != '@') && (room[best].name[0] != '@'))) { fprintf (stderr, "Warn: Changing room name '%s' to '%s' (%ld)\n", room[best].name, room1, best); } else { fprintf (stderr, "Warn: Changing room name '%20.20s' to '%20.20s' (%ld)\n", room[best].name, room1, best); } freestring (room[best].name); room[best].name = makestring (room1); } if (desc && (!room[best].desc || !streq (room[best].desc, desc))) { if (debug) { fprintf (stderr, "Warn: Changing room desc for %s from '%s' to '%s' (%ld)\n", room[best].name, room[best].desc, desc, best); } freestring (room[best].desc); room[best].desc = makestring (desc); } if (room[best].number < 0 && roomnum >= 0) room[best].number = roomnum; } return (best); } /**************************************************************** * same_room: Determine if a room is the "same", but allow for the room * name/description to change is there is a room number. ****************************************************************/ static int same_room(long int rm, long int number, const char *name, const char *desc) { if (rm < 0) return (0); /* Perfect match, number, name, and description */ if (number >= 0 && room[rm].number == number && streq (room[rm].name, name) && (!desc || !*desc || !room[rm].desc || streq (room[rm].desc, desc))) { return (3); } /* If numbers dont match, rooms cant be the same */ if ((number >= 0 && room[rm].number >= 0) && room[rm].number != number) { return (0); } /* If name && description match, assume same */ if (streq (room[rm].name, name) && (!desc || !*desc || !room[rm].desc || streq (desc, room[rm].desc))) { return (2); } /* If no numbers, assume not the same */ if (number < 0 || room[rm].number < 0) { return (0); } /* * If numbers present and match, and either name or description match, * then assume same room. This is to handle the Rec Room, which is * link_ok, and has a stable description, but whose name changes * frequently. */ if (streq (room[rm].name, name) || (desc && *desc && room[rm].desc && streq (room[rm].desc, desc))) { return (1); } /* All cases fail, assume different */ return (0); } /**************************************************************** * close_room: find a room number given an approximate name ****************************************************************/ # define POTSDESC \ "This would appear to be the waiting room for POTSMASTER's office. His office is one door further in. It appears to have a cardkey-style lock." long int close_room(const char *roomnam) { register long i, anchor, result = -1; char pat[TOKSIZ]; int ptype = 0, tmpdebug = 0; if (debug) { fprintf (stderr, "Find: close room(%s) called\n", roomnam); debug--; tmpdebug++; } /* Special case for here */ if (result < 0 && (streq (roomnam, "here") || streq (roomnam, "there") || streq (roomnam, "aqui") || streq (roomnam, "you") || streq (roomnam, "este cuarto") || streq (roomnam, "este sala") || streq (roomnam, "este sitio") || streq (roomnam, "where you are") || streq (roomnam, "this place") || streq (roomnam, "this spot") || streq (roomnam, "this room"))) { result = hererm; ptype=1; } /*----Special for 'First and Main' in brigadoon----*/ if (result < 0 && streq (world, "Brigadoon") && (streq (roomnam, "town") || streq (roomnam, "town square") || ((sindex (roomnam, "first") || sindex (roomnam, "1st")) && (sindex (roomnam, "and") || sindex (roomnam, "&")) && sindex (roomnam, "main"))) && (i = find_room ("Intersection of Main and 1st", NULL)) >= 0) { result = i; ptype=2; } /*----Special for 'Tanstaafl Center' in brigadoon----*/ if (result < 0 && streq (world, "HoloMuck") && (streq (roomnam, "town") || streq (roomnam, "center") || streq (roomnam, "town center") || streq (roomnam, "town square")) && (i = find_room ("Tanstaafl Center", NULL)) >= 0) { result = i; ptype=2; } /*----Special for 'town' (assumes 'town' from robots home goes to town)----*/ if (result < 0 && (streq (roomnam, "town") || streq (roomnam, "town square") || streq (roomnam, "the town square")) && (((anchor = homerm) >= 0 && (i = leads_to (anchor, "town")) >= 0 && stlmatch (room[i].name, "Town")) || (streq (world, "Islandia") && (i = find_room ("Town Square", NULL)) >= 0))) { result = i; ptype=2; } /*----Special for 'town'----*/ if (result < 0 && (streq (roomnam, "town") || streq (roomnam, "town square") || streq (roomnam, "the town square")) && ((streq (world, "Islandia") && (i = find_room ("Town Square", NULL)) >= 0) || (i = find_room ("The Town Square", NULL)) >= 0)) { result = i; ptype=3; } /*---- Special for 'library' ----*/ if (result < 0 && streq (roomnam, "library") && ((i = find_room ("Library Vestibule", NULL)) >= 0 || (i = find_room ("Library lobby", NULL)) >= 0 || (i = find_room ("Reference Room", NULL)) >= 0)) { result = i; ptype=4; } /*---- Special for 'rec room' ----*/ if (result < 0 && recrm >= 0 && (streq (roomnam, "the rec room") || streq (roomnam, "the recroom") || streq (roomnam, "rec room") || streq (roomnam, "recroom"))) { result = recrm; ptype=5; } /*---- Special for 'time traveller nexus' ----*/ if (result < 0 && (streq (roomnam, "the main nexus") || streq (roomnam, "the nexus") || streq (roomnam, "main nexus") || streq (roomnam, "nexus")) && (i = find_room ("The Inter Nexus", NULL)) >= 0) { result = i; ptype=6; } /*---- Try the exact match next ----*/ if (result < 0 && (i = find_room (roomnam, NULL)) >= 0) result = i; /* Strip off 'the' */ if (result < 0 && stlmatch (roomnam, "the ")) { roomnam += 4; if ((i = find_room (roomnam, NULL)) >= 0) { result = i; ptype=7; } } /*---- Special case room names ----*/ /* Special case for robots home */ if (result < 0 && ((sindex (roomnam, lcstr (myname)) || sindex (roomnam, "your ") || sindex (roomnam, "to ") || sindex (roomnam, "su ")) && (sindex (roomnam, " office") || sindex (roomnam, " place") || sindex (roomnam, " pad") || sindex (roomnam, " desk") || sindex (roomnam, " home") || sindex (roomnam, " oficina") || sindex (roomnam, " casa"))) && (i = find_room (home, NULL)) >= 0) { result = i; ptype=8; } /* Special case for robots manual */ if (result < 0 && ((sindex (roomnam, lcstr (myname)) || sindex (roomnam, "robot") || sindex (roomnam, "automata") || sindex (roomnam, "your") || sindex (roomnam, "su")) && (sindex (roomnam, "manual") || sindex (roomnam, "guide") || sindex (roomnam, "instruction") || sindex (roomnam, "program") || sindex (roomnam, "source") || sindex (roomnam, "guia") || sindex (roomnam, "instrucciones") || sindex (roomnam, "programa"))) && (i = find_room ("Maas-Neotek Robot User's Guide", NULL)) >= 0) { result = i; ptype=9; } /* Special for Brigadoon chat room */ if (result < 0 && sindex (roomnam, "inter nexus") && sindex (roomnam, "chat") && sindex (roomnam, "room") && !sindex (roomnam, "line") && (i = find_room ("The Chat Line [1028]", NULL)) >= 0) { result = i; ptype = 10; } /*---- Lower case the input name, and look for substrings ----*/ if (result < 0) { strcpy (pat, lcstr (roomnam)); for (i=0; i= 0 && debug) { fprintf (stderr, "Map: close_room maps '%s' to '%s', match type %d\n", roomnam, room[result].name, ptype); } else if (debug) { fprintf (stderr, "Find: closeroom(%s) fails\n", roomnam); } return (result); } /**************************************************************** * add_room: Add a room ****************************************************************/ long int add_room(const char *roomnam, const char *roomdsc) { long rm; long roomnum; if (! *roomnam) return (-1); if (roomdsc && !*roomdsc) roomdsc = NULL; if (room == NULL) { crash_robot ("room is NULL in add_room"); } if ((rm = find_room (roomnam, roomdsc)) >= 0) { if (roomdsc && room[rm].desc == NULL) { room[rm].desc = makestring (roomdsc); } else if (roomdsc && !streq (roomdsc, room[rm].desc)) { fprintf (stderr, "Warn: changing description of %s(%ld) from '%s' to '%s'\n", room[rm].name, rm, room[rm].desc, roomdsc); freestring (room[rm].desc); room[rm].desc = makestring (roomdsc); } return (rm); } if (roomdsc) { fprintf (stderr, "Map: add_room (%s, '%16.16s') ==> %ld\n", roomnam, roomdsc, rooms); } else { fprintf (stderr, "Map: add_room (%s, NULL) ==> %ld\n", roomnam, rooms); } newrooms++; /* Grow array if necessary */ if (rooms >= maxrooms) realloc_rooms (maxrooms * 6 / 5 + 50); if (smatch (roomnam, "* (#*)", roomstr) && isdigit (*room2)) { roomnum = atoi (room2); } else if (smatch (roomnam, "*(#*)", roomstr) && isdigit (*room2)) { roomnum = atoi (room2); } else { strcpy (room1, roomnam); roomnum = -1; } room[rooms].name = makestring (room1); if (desc) { room[rooms].desc = makestring (desc); } else { room[rooms].desc = NULL; } room[rooms].contents = NULL; room[rooms].exlist = NULL; room[rooms].number = roomnum; room[rooms].firstin = now; room[rooms].exits = NULL; room[rooms].flags = 0; return (rooms++); } /**************************************************************** * add_exit: Add an exit from a room into another room * * Note: future plan is to keep list sorted by destination room number, * this will allow us to print the answer to "what exits are here" * a lot prettier. ****************************************************************/ void add_exit(long int from, const char *dir, long int to) { register O_EXIT *xp; if (from < 0 || to < 0) return; if (streq (dir, "home")) return; if (!isprint (*dir)) { crash_robot ("non-printing direction \\%03o in add_exit\n", *dir); } /* * Dont learn paths to our home unless the direction is NOT home * and there is a direct exit from the home room to here. Instead * mark the exit as a NOP. */ if (to == homerm && homerm >= 0) { if (!exit_to (to, from)) to = from; } /*-------- First see if this is an old exit --------*/ for (xp = room[from].exits; xp; xp = xp->next) { if (strcmp (dir, xp->dir) == 0) { if (xp->room != to && xp->room >= 0) { fprintf (stderr, "Exit: dir '%s' from %s(%ld) now goes to %s(%ld), was %s(%ld)\n", dir, room[from].name, from, room[to].name, to, room[xp->room].name, xp->room); xp->room = to; newexits++; reach_changed++; } else if (UNK_EXIT_P (xp)) { fprintf (stderr, "Exit: dir '%s' from %s(%ld) now goes to %s(%ld), was unknown\n", dir, room[from].name, from, room[to].name, to); xp->room = to; newexits++; reach_added++; } return; } } if (to == from && awake && debug) { fprintf (stderr, "Exit: marking '%s' from %s(%ld) as a NOP\n", dir, room_name(from), from); } newexits++; exits++; reach_added++; /*-------- Okay, allocate a new exit, and link it into the list --------*/ exit_sp += sizeof (O_EXIT); exit_ct ++; xp = (O_EXIT *) ralloc (sizeof (O_EXIT)); if (xp == NULL) { perror ("add_exit:malloc #1"); crash_robot ("malloc returns NULL in add_exit"); } xp->dir = exitstring (dir); xp->room = to; xp->next = room[from].exits; room[from].exits = xp; if (awake && !terse && from != to) { fprintf (stderr, "Exit: dir '%s' from %s(%ld) now goes to %s(%ld)\n", dir, room_name (from), from, room_name (to), to); } return; } /**************************************************************** * find_path: Find a path from one room to another ****************************************************************/ const char *find_path (long int from, long int to, long int desc) { register long i; long extended = 0, found = 0, depth = 0; const char *result = 0; static char buf[BIGBUF]; static long maxcnt = 0; register O_EXIT *xp; O_EXIT *lastxp; if (from < 0 || to < 0) return (NULL); if (debug) { fprintf (stderr, "Path: from: %s(%ld), to: %s(%ld), %s\n", room[from].name, from, room[to].name, to, desc ? "whole" : "next move"); } if (from == to) { return (NULL); } for (i=0; inext) { if (xp->room != i && xp->room >= 0 && xp->room < rooms && path[xp->room].depth == depth) { extended++; path[i].dir = xp->dir; path[i].loc = xp->room; path[i].depth = path[xp->room].depth + 1; if (i == from) found++; # ifdef DEBUGBFS if (debug) { fprintf (stderr, "Adding %s(%ld), depth %ld, dir %s, to %s(%ld)\n", room[i].name, i, path[i].depth, path[i].dir, room[path[i].loc].name, path[i].loc); } # endif } } } } if (!found && (desc == NEXTMOVEHOME || desc == SHORTPATHHOME) && homerm >= 0 && path[homerm].depth >= 0) { extended++; path[from].dir = "home"; path[from].loc = homerm; path[from].depth = path[homerm].depth + 1; found++; if (debug) { fprintf (stderr, "Path: using home, depth %d\n", path[from].depth); } } } if (depth > maxcnt && !terse) { fprintf (stderr, "Find: find_path (%s) %s, max depth %ld (was %ld)\n", room[to].name, found ? "wins" : "loses", depth, maxcnt); maxcnt = depth; } else if (debug) { fprintf (stderr, "Path: find_path search %s\n", found ? "wins" : "loses"); } if (found && desc != NEXTMOVE && desc != NEXTMOVEHOME) { *buf = '\0'; if (desc == LONGPATH) { sprintf (buf, "Path from %s to %s, type", room[from].name, room[to].name); i = from; while (i != to && path[i].dir && i< rooms) { sprintf (buf, "%s 'go %s' to get to %s%s", buf, path[i].dir, room[path[i].loc].name, (path[i].loc) == to ? "." : ","); i = path[i].loc; } } if (desc == MEDIUMPATH || strlen (buf) > 250) { sprintf (buf, "Path from %s to %s, type", room[from].name, room[to].name); i = from; while (i != to && path[i].dir && i< rooms) { if (islandmark (room[path[i].loc].name) || path[i].loc == to) { sprintf (buf, "%s %s (to %s)%s", buf, path[i].dir, room[path[i].loc].name, (path[i].loc) == to ? "." : ","); } else { sprintf (buf, "%s %s,", buf, path[i].dir); } i = path[i].loc; } } if (! *buf || strlen (buf) > 250) { *buf = '\0'; i = from; while (i != to && path[i].dir && i< rooms) { sprintf (buf, "%s %s%s", buf, path[i].dir, (path[i].loc) == to ? "" : ","); i = path[i].loc; } } result = buf; } else if (found) { result = path[from].dir; } if (debug) { fprintf (stderr, "Path: find_path returns %s\n", result ? result : "(nil)"); } return (result); } /**************************************************************** * explore_exit: Find an exit that is either untried or that * goes to a different room. There is a 1% chance of trying an exit * even if it has been tried before. This gives a small hope that * we will discover new exits created after a room has been explored. ****************************************************************/ static const char *explore_exit(void) { const char *dir = 0; register O_EXIT *xp; register long cnt=0; if (hererm < 0) return (NULL); if (debug) { fprintf (stderr, "Expl: in explore_exit, %s(%ld)\n", here, hererm); } if (hererm < 0) { dir = stexits[randint (numexits)]; } else if ((dir = unexp_exit (hererm, RANDOM))) { if (debug) { fprintf (stderr, "Expl: unexp_exit '%s'(%ld) => <%s>\n", room_name (hererm), hererm, dir); } } else { for (xp = room[hererm].exits; xp; xp = xp->next) { if (xp->room != hererm) { if (randint (++cnt) == 0) dir = xp->dir; } } if (cnt && debug) { fprintf (stderr, "Expl: old exit '%s'(%ld) => <%s>\n", room_name (hererm), hererm, dir); } } if (debug) { fprintf (stderr, "Expl: explore exit from %s returns <%s>\n", here, dir ? dir : "(nil)"); } # ifndef CRAZY if (dir && stlmatch (dir, "OUTPUT")) crash_robot ("bad exit in\n"); # endif return (dir); } /**************************************************************** * unexplored_room: Return the name of an unexplored room ****************************************************************/ static long int unexplored_room(void) { register long rm; static long lastreset = 0; const char *dir; static long last_cleared = 0; if (debug) { fprintf (stderr, "Expl: in unexplored_room, here %s, lastrm %ld\n", here, lastrm); } if (now > (lastreset + 36 * HOURS)) { time_t tt; lastreset = now; lastrm = 0; tt = now; fprintf (stderr, "Expl: resetting lastrm to 0 at %15.15s\n", ctime (&tt)+4); new_goal = 1; } /* Main loop, check rooms in order until we find an unexplored one */ for (rm = lastrm; rm < rooms; rm++) { if (room[rm].name && (rm == hererm || can_reach (rm))) { dir = unexp_exit (rm, FIRST); if (dir) { if (!terse) { fprintf (stderr, "Expl: unexplored_room returns %s(%ld), <%s>.\n", room_name (rm), rm, dir); } if (lastrm != rm) { new_goal = 1; lastrm = rm; } return (rm); } } /* Check for pending IO, bail out if necessary */ if (rm > lastrm && charsavail (fmud)) { fprintf (stderr, "Expl: %s out at %ld (of %ld) to service IO\n", "unexplored_room bails", rm, rooms); lastrm = rm; return (-1); } } if (!terse) { fprintf (stderr, "Expl: unexplored_room loses\n"); } /* Occasionally reset the explored bits */ if (last_cleared + 8 * HOURS < now) { for (rm = 0; rm < rooms; rm++) { ROOM_CLR (rm, RM_EXPLORED); } lastrm = 0; last_cleared = now; fprintf (stderr, "Expl: clearing explored markers from all %ld rooms\n", rooms); /* Recurse: time in last_cleared prevents infinite recursion */ return (unexplored_room ()); } return (-1); } /**************************************************************** * unexp_exit: Return the name of an unexplored exit. If pickrandom * is true, chose randomly from all unexplored exits in the room. ****************************************************************/ static const char *unexp_exit(long int rm, int pickrandom) { register long i, cnt=0; const char *dir = 0; const char *ep; const char *w; register O_EXIT *xp; long type = 0; char buf[BIGBUF+BIGBUF]; if (rm < 0) return (NULL); if (ROOM_GET (rm, RM_EXPLORED)) return (NULL); /* Check all standard exits exit for one that we have not tried */ for (i=0; inext) { if (UNK_EXIT_P (xp)) { if (pickrandom) { if (randint (++cnt) == 0) { dir = xp->dir; type=3; } } else { return (xp->dir); } } } /* Ah, this room has been explored...reclaim its bad exits */ if (dir == NULL && !ROOM_GET (rm, RM_EXPLORED)) clean_bad_exits (rm); if (debug) { fprintf (stderr, "Expl: unexp_exit type %ld '%s'(%ld) => <%s>\n", type, room_name (rm), rm, dir ? dir : "NULL"); } if (dir) check_id (dir); return (dir); } /**************************************************************** * clean_bad_exits: This room has been completely explored. * Remove all exits from the exit list that do not go * to other rooms. ****************************************************************/ static void clean_bad_exits(int rm) { register O_EXIT *ep, *tp, *lastep = NULL; register int total=0, cnt=0; if (rm < 0) return; /* Loop through each exit, removing bad ones */ for (ep = room[rm].exits; ep;) { if (ep->room == rm) { cnt++; /* First remove it from the linked list */ tp = ep->next; if (lastep == NULL) { room[rm].exits = tp; } else { lastep->next = tp; } /* Then reclaim the space */ ep->next = NULL; /* Just in case anybody is still pointing here */ free (ep); exit_sp -= sizeof (O_EXIT); exit_ct --; /* And loop to the next exit in the list */ ep = tp; } else { lastep = ep; ep = ep->next; } total++; } if (terse < 2) { fprintf (stderr, "Expl: Marking %s(%d) explored, reclaimed %d of %d exits\n", room_name (rm), rm, cnt, total); } ROOM_SET (rm, RM_EXPLORED); newexits++; } /**************************************************************** * leads_to: Where would 'dir' takes us from from? ****************************************************************/ long int leads_to(long int from, const char *dir) { register O_EXIT *xp; if (from < 0) return (-1); for (xp = room[from].exits; xp; xp = xp->next) { /* Check for bogus map */ if (xp == xp->next || !xp->dir || !xp->dir[0] || !isprint (xp->dir[0]) || (xp->dir[1] && !isprint (xp->dir[1]))) { fprintf (stderr, "Dir[0] = '%03o', Dir[1] = '%03o'\n", xp->dir[0] & 255, xp->dir[1] & 255); write_players (plyfile); crash_robot ("bogus direction entry in leads_to (%ld, %s)", from, dir); } if (streq (dir, xp->dir)) { if (debug > 1) { fprintf (stderr, "Lead: from %s(%ld), '%s' --> %s(%ld)\n", room[from].name, from, dir, room[xp->room].name, xp->room); } return (xp->room); } } return (-1); } /**************************************************************** * exit_to: Is there an exit from one room into the other? ****************************************************************/ const char *exit_to (long int from, long int to) { register O_EXIT *xp; if (from < 0 || to < 0) return (NULL); for (xp = room[from].exits; xp; xp = xp->next) { if (xp->room == to) { if (debug) { fprintf (stderr, "Exit: from %s(%ld), '%s' --> %s(%ld)\n", room[from].name, from, xp->dir, room[to].name, to); } return (xp->dir); } } return (NULL); } #if 0 // Not used anywhere /**************************************************************** * hashf: Compress a string down to an 6 character identifier ****************************************************************/ const char fivebit[] = "abcdefghijklmnopqrstuvwxyz012345"; long modulus = 1073741789; long mult = 269; char *hashf(const char *str) { unsigned long result = 0; static char buf[8]; char *t = buf; while (*str) { result = ((result * mult) + *str++) % modulus; } do { *t++ = fivebit[result & 037]; result >>= 5; } while (result > 0); *t = '\0'; return (buf); } #endif /**************************************************************** * room_match: returns T if two roomnames are the same room ****************************************************************/ int room_match(const char *rm1, const char *rm2) { long r1, r2; if (strcmp (rm1, rm2) == 0) { return (1); } if ((r1 = find_room (rm1, NULL)) >= 0 && (r2 = find_room (rm2, NULL)) >= 0 && r1 == r2) { return (1); } return (0); } /**************************************************************** * islandmark: A list of landmark rooms ****************************************************************/ const char * const landmarks[] = { "Buried Alive!", "Crystal Palace Entry", "Dictionary Room", "Encyclopedia Room", "HELL Chamber of Commerce", "Mail Directory", "McDonalds", "Misty Forest", "Pandemonium Newsstand", "Pandemonium Town Square", "Styx Casino Lobby", "The Nexus Club", "Tourist Station", NULL }; static int islandmark(const char *loc) { const char * const *rp; for (rp=landmarks; *rp; rp++) { if (streq (loc, *rp)) return (1); } return (0); } /**************************************************************** * do_explore: Explore the universe ****************************************************************/ int do_explore(void) { const char *dir; long eroom; /* Explore */ if (exploring && !nextwait && ! *pagedby && ! *typecmd && !dead && !takingnotes && !playinghearts) { /* Always try the 'out' exit first */ if (hererm >= 0 && leads_to (hererm, "out") < 0) { movemud ("out"); hangaround (1); return (1); } /* If there is an unexplored exit here, just try it */ dir = unexp_exit (hererm, RANDOM); if (dir) { if (debug) { fprintf (stderr, "Expl: unexp_exit '%s'(%ld) => <%s>\n", room_name (hererm), hererm, dir); } movemud (dir); hangaround (speed); return (1); } /* If not at home, go there with some probability */ if (hererm != homerm && (now - room[homerm].lastin) > (randint (20) + 20) * MINUTES) { if (!terse) { fprintf (stderr, "Expl: getting homesick after %ld seconds\n", now - room[homerm].lastin); } reset_robot ("homesick"); return (1); } /* 66% chance we will pick earliest unexplored room and go explore it */ if (randint (100) < 66) { /* If no unexplored room, fail, and we fall through to do_twiddle */ if ((eroom = unexplored_room ()) < 0) { return (0); } /* Give a chance to process messages */ hangaround (speed); if (*pagedby || nextwait || *typecmd) return (1); /* Now make a goal to get to the next room to explore */ if (eroom != hererm && (find_path (hererm, eroom, NEXTMOVEHOME) || homerm == eroom || find_path (homerm, eroom, NEXTMOVE))) { strcpy (pagedby, ""); pagedfrom = hererm; pagedto = eroom; pagedat = now; disengage (room[pagedto].name); if (!terse && new_goal) { time_t tt; tt = pagedat; fprintf (stderr, "\n---- %15.15s ---- Heading off to %s(%ld) ----\n\n", ctime (&tt) + 4, room[pagedto].name, pagedto); new_goal = 0; } else if ((new_goal || !terse) && terse < 2) { time_t tt; tt = pagedat; fprintf (stderr, "Expl: %15.15s, heading to %s(%ld)\n", ctime (&tt) + 4, room[pagedto].name, pagedto); new_goal = 0; } return (1); } } /* Else just random walk from here */ dir = explore_exit (); if (dir) { if (!terse) { fprintf (stderr, "Expl: unexp_exit '%s'(%ld) => <%s>\n", room_name (hererm), hererm, dir); } movemud (dir); hangaround (speed); return (1); } /* Stuck, go home */ if (hererm != homerm) { fprintf (stderr, "Expl: stuck in '%s'(%ld), going home\n", room_name (hererm), hererm); movemud ("home"); hangaround (speed); return (1); } /* Stuck in home??? Fail */ fprintf (stderr, "Warn: stuck in home: '%s'(%ld)\n", here, hererm); return (0); } return (0); } /**************************************************************** * do_findold: Check on old rooms ****************************************************************/ int do_findold(long int age) { long oldest = 0, lastin = now, rm; static long oldest_age = 0; if (!visitold) return (0); /* Can we shortcut the work? */ if (now - oldest_age <= age) return (0); /* Check more important goals first */ if (!nextwait && ! *pagedby && ! *typecmd && !dead && !takingnotes && !playinghearts) { /* Find oldest reachable room */ for (rm=0; rm age) { strcpy (pagedby, ""); pagedfrom = hererm; pagedto = oldest; pagedat = now; disengage (room[pagedto].name); if (!terse) { time_t tt; tt = pagedat; fprintf (stderr, "\n---- %15.15s ---- Old room (%s) %s(%ld) ----\n\n", ctime (&tt) + 4, lastin ? exact_dur (now-lastin) : "forever", room[pagedto].name, pagedto); } else if (terse < 2) { time_t tt; tt = pagedat; fprintf (stderr, "Expl: %15.15s, find old room (%s) %s(%ld)\n", ctime (&tt) + 4, lastin ? exact_dur (now-lastin) : "forever", room[pagedto].name, pagedto); } return (1); } } return (0); } /**************************************************************** * disengage: We are leaving, break off any converstation ****************************************************************/ void disengage(const char *dest) { long pl; int in_conv = 0; /* Count present */ alone = 0; player[me].active = player[me].present = 0; for (pl = 0; pl= 0 && player[pl].active && player[pl].present) { in_conv = 1; } /* Must disengage from a conversation */ if (dest && in_conv && msgtype <= M_WHISPER) { switch (nrrint (184, 4)) { case 0: command ("\"Excuse me, %s, I feel like exploring %s.", speaker, dest); break; case 1: command ("\"Pardon me, %s, I must be going to %s", speaker, dest); break; case 2: command ("\"Goodbye, %s, I'm heading to %s", speaker, dest); break; case 3: command ("\"Goodbye, %s, I feel like taking a walk to %s", speaker, dest); break; } } /* Might say good bye just to be sociable */ else if (dest && ((hererm == homerm) || (!quiet && dest && (randint(100) < 50)))) { switch (nrrint (185, 4)) { case 0: command ("\"I feel like exploring %s.", dest); break; case 1: command ("\"Pardon me, I must be going to %s.", dest); break; case 2: command ("\"Goodbye, I'm heading off to %s.", dest); break; case 3: command ("\"I feel like taking a walk to %s", dest); break; } } /* Must disengage from a conversation */ else if (in_conv && msgtype <= M_WHISPER) { switch (nrrint (186, 4)) { case 0: command ("\"Excuse me, %s, %s.", speaker, "I feel like doing some exploring"); break; case 1: command ("\"Pardon me, %s, I must be going.", speaker); break; case 2: command ("\"Bye, %s.", speaker); break; case 3: command ("\"Bye, %s, I feel like taking a walk.", speaker); break; } } /* Might say good bye just to be sociable */ else if (!quiet && randint (100) < 50) { switch (nrrint (187, 4)) { case 0: command ("\"I feel like doing some exploring."); break; case 1: command ("\"Pardon me, I must be going."); break; case 2: command ("\"Goodbye."); break; case 3: command ("\"I feel like taking a walk."); break; } } } /**************************************************************** * ispublic: A list of 'public' places ****************************************************************/ const char * const pubrooms[] = { "Town Square", "The Town Square", "Library Vestibule", "Encyclopedia Alcove", "Islandia Historical Room", "Map Room", "Government Street", "King Street", "Queen Street", "Library Reception Desk", "Library lobby", "The Temple of Arkteks-t'leep", "Reference Room", "Circulating Room", "Oxford English Dictionary Room", "Encyclopedia Collection", "Landing on Stairway", "Wizard's Room", "The Padded Cell", "Lost and Found Desk", "Main Hall", "South Hall", "North Hall", "Front Lawn", "West End Institute for the Reality Disabled", "Aspnes Park", "Mail Room", "Play Room", NULL }; int ispublic(const char *loc) { const char * const *rp; for (rp=pubrooms; *rp; rp++) { if (stlmatch (loc, *rp)) return (1); } return (0); } #if 0 // not used by anything /**************************************************************** * is_noisy: A list of 'noisy' places ****************************************************************/ const char * const noiserooms[] = { "Pandemonium Town Square", "Purgatory", "The Great Abyss", "The Nexus Club", "The Town Square", "Town Square", NULL }; int is_noisy (loc) char *loc; { const char * const *rp; for (rp=noiserooms; *rp; rp++) { if (streq (loc, *rp)) return (1); } return (0); } #endif /**************************************************************** * not_exit: A stop list of exit words from descriptions ****************************************************************/ const char * const exit_stop[] = { "a", "about", "against", "all", "an", "and", "are", "as", "at", "be", "but", "by", "can", "continues", "each", "exits", "few", "filled", "for", "from", "go", "goes", "has", "have", "here", "if", "in", "include", "into", "is", "it", "it's", "just", "large", "lead", "leading", "leads", "like", "looking", "looks", "lots", "many", "now", "of", "on", "one", "other", "runs", "see", "seems", "several", "small", "so", "some", "standing", "surrounded", "that", "the", "them", "there", "there's", "this", "to", "was", "where", "with", "you", "your", "home", "Exits", 0 }; static int not_exit(const char *loc) { const char * const *ep; for (ep=exit_stop; *ep; ep++) { if (streq (loc, *ep)) return (1); } return (0); } /**************************************************************** * exit_to_query: Print rooms and exits that lead directly to room ****************************************************************/ void exit_to_query(const char *roomnam, const char *name) { register long rm, i; long printed = 0; register O_EXIT *xp; if (alone > 2 && msgtype < M_WHISPER) msgtype = M_WHISPER; fprintf (stderr, "Exit: query_to (%s), for %s\n", roomnam, name); if ((rm = close_room (roomnam)) < 0) { reply ("\"I have never heard of %s{, n}.", roomnam); return; } for (i=0; inext) { if (xp->room == rm && i != rm) { if (!printed++) { reply ("\"Here are the exits to %s, %s:", room_name (rm), name); } if (((printed > 15) && (alone > 2)) || ((printed > 30) && !isowner(name)) ) { reply ("\"There are more exits, but %s, %s.", " it's too crowded here to go on", name); return; } strcpy (speaker, name); reply ("| <%s> from %s.", xp->dir, room_name (i)); break; } } } } if (!printed) { reply ("\"I don't know any exits to %s{, n}.", room_name (rm)); } else { reply ("|done."); } } /**************************************************************** * exit_from_query: Print exits and their rooms for a given room ****************************************************************/ void exit_from_query(const char *roomnam, const char *name) { register long rm; long printed = 0, tried=0, total=0, unknown=0; register O_EXIT *xp; if (alone > 2 && msgtype < M_WHISPER) msgtype = M_WHISPER; if ((rm = close_room (roomnam)) < 0) { reply ("\"I have never heard of %s{, n}.", roomnam); return; } /* Count exits */ for (xp = room[rm].exits; xp; xp = xp->next) { if (xp->room == rm) { tried++; } else if (UNK_EXIT_P (xp)) { unknown++; } else { total++; } } /* If too many exits, just give count */ if ((total + unknown) > 50 || ((total + unknown) > 15 && alone > 2)) { if (unknown > 0) { reply("\"There are %ld exits from %s, plus %ld more I've heard about, %s.", total, room_name (rm), unknown, name); } else { reply ("\"There are %ld exits from %s, %s.", total, room_name (rm), name); } return; } /* List them all (should really sort by destination) */ for (xp = room[rm].exits; xp; xp = xp->next) { if (xp->room != rm) { if (!printed++) { reply ("\"Here are the %ld exits for %s, %s:", total+unknown, room_name (rm), name); } if (((printed > 15) && (alone > 2)) || ((printed > 30) && !isowner(name)) ) { reply ("\"There are more exits, but %s, %s.", " it's too crowded here to go on", name); break; } strcpy (speaker, name); if (xp->room >= 0 && xp->room < rooms) { reply ("| <%s> goes to %s.", xp->dir, room_name (xp->room)); } else { reply ("| <%s> is an exit I've heard about.", xp->dir); } } } if (!printed) { reply ("\"I don't know any exits from %s{, n}.", room_name (rm)); } else { reply ("|done."); } } /**************************************************************** * room_query: Print all rooms matching some pattern ****************************************************************/ void room_query(const char *pat, const char *name, int full) { register long rm; long printed = 0; if (alone > 2 && msgtype < M_WHISPER) msgtype = M_WHISPER; /* First do rooms */ if (!full) { for (rm = 0; rm < rooms; rm++) { if (room[rm].name && sindex (lcstr (room[rm].name), pat)) { if (!printed++) { reply ("\"Here are the rooms that match '%s'{, n}:", pat); } if (((printed > 15) && (alone > 2)) || ((printed > 30) && !isowner(name)) ) { reply ("\"There are more rooms, but %s, %s.", " it's too crowded here to go on", name); break; } strcpy (speaker, name); if (can_reach (rm)) { reply ("| %s", room_name (rm)); } else { reply ("| %s (unreachable)", room_name (rm)); } } } } /* If no rooms, check descriptions */ if (!printed || full) { for (rm = 0; rm < rooms; rm++) { if ((room[rm].name && sindex (lcstr (room[rm].name), pat)) || (room[rm].desc && sindex (lcstr (room[rm].desc), pat)) || (room[rm].contents && sindex (lcstr (room[rm].contents), pat)) ) { if (!printed++) { reply ("\"Here are the rooms whose descriptions match '%s', %s:", pat, name); } if (((printed > 10) && (alone > 2)) || ((printed > 20) && !isowner(name)) ) { reply ("\"There are more rooms, but %s, %s.", " it's too crowded here to go on", name); break; } strcpy (speaker, name); reply ("|"); strcpy (speaker, name); reply ("| %s", room_name (rm)); if (room[rm].desc && room[rm].desc[0]) { strcpy (speaker, name); reply ("| description: %s", room[rm].desc); } if (room[rm].contents && room[rm].contents[0]) { strcpy (speaker, name); reply ("| contents: %s", room[rm].contents); } } } } if (!printed) { reply ("\"I don't know any rooms that match '%s', %s.", pat, name); } else { reply ("|done."); } } /**************************************************************** * most_rm_query: Which room(s) have the most of something ****************************************************************/ # define QCNT 10 void most_rm_query(const char *name, int type) { long rm, b, i, j, best[QCNT], bcnt = 0; double val, bestval[QCNT]; /* Search through every room */ for (rm=0; rm 1 * DAYS) continue; if (!can_reach (rm)) continue; switch (type) { case NOISE: val = (double) room[rm].msgsum / room[rm].totalin; break; case PEOPLE: val = (double) room[rm].awakesum / room[rm].cntin; break; case SLEEPERS: val = (double) room[rm].sleepsum / room[rm].cntin; break; case FRIENDLY: val = (double) room[rm].friendly; break; } /* Now find place to insert in sorted list */ for (i=0; ii; j--) { best[j] = best[j-1]; bestval[j] = bestval[j-1]; } /* Now insert this room */ best[i] = rm; bestval[i] = val; if (bcnt < QCNT) bcnt++; break; } } /* If we ran off the list, but it is short, add to the end */ if (i == bcnt && bcnt < QCNT) { best[bcnt] = rm; bestval[bcnt] = val; bcnt++; } } /* best[0..bcnt-1] and bestval[0..bcnt-1] now hold sorted list */ switch (type) { case NOISE: reply ("\"The %d noisiest rooms are:", QCNT); for (b=0; bnext) { if (xp->room != i && !ROOM_GET (xp->room, RM_REACH)) { extended++; ROOM_SET (xp->room, RM_REACH); if (xp->room == rm) found++; # ifndef REAL if (debug > 1) { fprintf (stderr, "Adding %s(%ld), depth %ld\n", room[xp->room].name, xp->room, depth); } # endif } } } } } /* If we searched exhaustively, remember that */ if (!extended) { reach_added = 0; if (debug) { fprintf (stderr, "Reac: search exhausted for %s(%ld)\n", room_name (rm), rm); } } else { reach_added = 1; } /* The RM_REACH bit for room 'rm' is now valid */ return (ROOM_GET (rm, RM_REACH)); } /**************************************************************** * ****************************************************************/ void cant_reach(long int rm) { ROOM_CLR (rm, RM_REACH); reach_changed++; } /**************************************************************** * check_id: If not all characters in string are printing, crash ****************************************************************/ static void check_id(const char *str) { if (!str) { crash_robot ("null string in check_id\n"); } while (*str) { if (!isprint (*str)) crash_robot ("bad char '\\%03o' in str\n", *str); str++; } }