1 /* NetHack 3.7 hacklib.c $NHDT-Date: 1706213796 2024/01/25 20:16:36 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.116 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Michael Allison, 2007. */
4 /* Copyright (c) Robert Patrick Rankin, 1991 */
5 /* NetHack may be freely redistributed. See license for details. */
7 #include "hack.h" /* for config.h+extern.h */
10 Assorted 'small' utility routines. They're virtually independent of
13 return type routine name argument type(s)
20 char * upstart (char *)
21 char * upwords (char *)
22 char * mungspaces (char *)
23 char * trimspaces (char *)
24 char * strip_newline (char *)
25 char * stripchars (char *, const char *, const char *)
26 char * stripdigits (char *)
28 const char * c_eos (const char *)
29 boolean str_start_is (const char *, const char *, boolean)
30 boolean str_end_is (const char *, const char *)
31 int str_lines_maxlen (const char *)
32 char * strkitten (char *,char)
33 void copynchars (char *,const char *,int)
34 char chrcasecpy (int,int)
35 char * strcasecpy (char *,const char *)
36 char * s_suffix (const char *)
37 char * ing_suffix (const char *)
38 char * xcrypt (const char *, char *)
39 boolean onlyspace (const char *)
40 char * tabexpand (char *)
42 char * strsubst (char *, const char *, const char *)
43 int strNsubst (char *,const char *,const char *,int)
44 const char * findword (const char *,const char *,int,boolean)
45 const char * ordin (int)
48 int distmin (int, int, int, int)
49 int dist2 (int, int, int, int)
50 boolean online2 (int, int)
51 int strncmpi (const char *, const char *, int)
52 char * strstri (const char *, const char *)
53 boolean fuzzymatch (const char *, const char *,
54 const char *, boolean)
55 int swapbits (int, int, int)
56 void nh_snprintf (const char *, int, char *, size_t,
64 return (boolean
) ('0' <= c
&& c
<= '9');
67 /* is 'c' a letter? note: '@' classed as letter */
71 return (boolean
) ('@' <= c
&& c
<= 'Z') || ('a' <= c
&& c
<= 'z');
74 /* force 'c' into uppercase */
78 return (char) (('a' <= c
&& c
<= 'z') ? (c
& ~040) : c
);
81 /* force 'c' into lowercase */
85 return (char) (('A' <= c
&& c
<= 'Z') ? (c
| 040) : c
);
88 /* convert a string into all lowercase */
95 if ('A' <= *p
&& *p
<= 'Z')
100 /* convert a string into all uppercase */
107 if ('a' <= *p
&& *p
<= 'z')
112 /* convert first character of a string to uppercase */
121 /* capitalize first letter of every word in a string (in place) */
126 boolean space
= TRUE
;
131 } else if (space
&& letter(*p
)) {
140 /* remove excess whitespace from a string buffer (in place) */
145 boolean was_space
= TRUE
;
147 for (p
= p2
= bp
; (c
= *p
) != '\0'; p
++) {
149 break; /* treat newline the same as end-of-string */
152 if (c
!= ' ' || !was_space
)
154 was_space
= (c
== ' ');
156 if (was_space
&& p2
> bp
)
162 /* skip leading whitespace; remove trailing whitespace, in place */
164 trimspaces(char *txt
)
168 /* leading whitespace will remain in the buffer */
169 while (*txt
== ' ' || *txt
== '\t')
172 while (--end
>= txt
&& (*end
== ' ' || *end
== '\t'))
178 /* remove \n from end of line; remove \r too if one is there */
180 strip_newline(char *str
)
182 char *p
= strrchr(str
, '\n');
185 if (p
> str
&& *(p
- 1) == '\r')
192 /* return the end of a string (pointing at '\0') */
197 s
++; /* s += strlen(s); */
201 /* version of eos() which takes a const* arg and returns that result */
206 s
++; /* s += strlen(s); */
210 /* determine whether 'str' starts with 'chkstr', possibly ignoring case;
211 * panics on huge strings */
223 return (*chkstr
== 0); /* chkstr >= str */
225 return TRUE
; /* chkstr < str */
226 t1
= caseblind
? lowc(*str
) : *str
;
227 t2
= caseblind
? lowc(*chkstr
) : *chkstr
;
234 panic("string too long");
239 /* determine whether 'str' ends in 'chkstr' */
241 str_end_is(const char *str
, const char *chkstr
)
243 int clen
= (int) strlen(chkstr
);
245 if ((int) strlen(str
) >= clen
)
246 return (boolean
) (!strncmp(eos((char *) str
) - clen
, chkstr
, clen
));
250 /* return max line length from buffer comprising newline-separated strings */
252 str_lines_maxlen(const char *str
)
255 int len
, max_len
= 0;
259 s2
= strchr(s1
, '\n');
261 len
= (int) (s2
- s1
);
264 len
= (int) strlen(s1
);
274 /* append a character to a string (in place): strcat(s, {c,'\0'}); */
276 strkitten(char *s
, char c
)
285 /* truncating string copy */
287 copynchars(char *dst
, const char *src
, int n
)
289 /* copies at most n characters, stopping sooner if terminator reached;
290 treats newline as input terminator; unlike strncpy, always supplies
291 '\0' terminator so dst must be able to hold at least n+1 characters */
292 while (n
> 0 && *src
!= '\0' && *src
!= '\n') {
299 /* convert char nc into oc's case; mostly used by strcasecpy */
301 chrcasecpy(int oc
, int nc
)
303 #if 0 /* this will be necessary if we switch to <ctype.h> */
304 oc
= (int) (unsigned char) oc
;
305 nc
= (int) (unsigned char) nc
;
307 if ('a' <= oc
&& oc
<= 'z') {
308 /* old char is lower case; if new char is upper case, downcase it */
309 if ('A' <= nc
&& nc
<= 'Z')
310 nc
+= 'a' - 'A'; /* lowc(nc) */
311 } else if ('A' <= oc
&& oc
<= 'Z') {
312 /* old char is upper case; if new char is lower case, upcase it */
313 if ('a' <= nc
&& nc
<= 'z')
314 nc
+= 'A' - 'a'; /* highc(nc) */
319 /* overwrite string, preserving old chars' case;
320 for case-insensitive editions of makeplural() and makesingular();
321 src might be shorter, same length, or longer than dst */
323 strcasecpy(char *dst
, const char *src
)
326 int ic
, oc
, dst_exhausted
= 0;
328 /* while dst has characters, replace each one with corresponding
329 character from src, converting case in the process if they differ;
330 once dst runs out, propagate the case of its last character to any
331 remaining src; if dst starts empty, it must be a pointer to the
332 tail of some other string because we examine the char at dst[-1] */
333 while ((ic
= (int) *src
++) != '\0') {
334 if (!dst_exhausted
&& !*dst
)
336 oc
= (int) *(dst
- dst_exhausted
);
337 *dst
++ = chrcasecpy(oc
, ic
);
343 /* return a name converted to possessive */
345 s_suffix(const char *s
)
347 static char buf
[BUFSZ
];
350 if (!strcmpi(buf
, "it")) /* it -> its */
352 else if (!strcmpi(buf
, "you")) /* you -> your */
354 else if (*(eos(buf
) - 1) == 's') /* Xs -> Xs' */
361 /* construct a gerund (a verb formed by appending "ing" to a noun) */
363 ing_suffix(const char *s
)
365 static const char vowel
[] = "aeiouwy";
366 static char buf
[BUFSZ
];
372 onoff
[0] = *p
= *(p
+ 1) = '\0';
373 if ((p
>= &buf
[3] && !strcmpi(p
- 3, " on"))
374 || (p
>= &buf
[4] && !strcmpi(p
- 4, " off"))
375 || (p
>= &buf
[5] && !strcmpi(p
- 5, " with"))) {
376 p
= strrchr(buf
, ' ');
380 if (p
>= &buf
[2] && !strcmpi(p
- 2, "er")) { /* slither + ing */
382 } else if (p
>= &buf
[3] && !strchr(vowel
, *(p
- 1))
383 && strchr(vowel
, *(p
- 2)) && !strchr(vowel
, *(p
- 3))) {
384 /* tip -> tipp + ing */
387 } else if (p
>= &buf
[2] && !strcmpi(p
- 2, "ie")) { /* vie -> vy + ing */
390 } else if (p
>= &buf
[1] && *(p
- 1) == 'e') /* grease -> greas + ing */
398 /* trivial text encryption routine (see makedefs) */
400 xcrypt(const char *str
, char *buf
)
406 for (bitmask
= 1, p
= str
, q
= buf
; *p
; q
++) {
410 if ((bitmask
<<= 1) >= 32)
417 /* is a string entirely whitespace? */
419 onlyspace(const char *s
)
422 if (*s
!= ' ' && *s
!= '\t')
427 /* expand tabs into proper number of spaces (in place) */
430 char *sbuf
) /* assumed to be [BUFSZ] but can be smaller provided that
431 * expanded string fits; expansion bigger than BUFSZ-1
432 * will be truncated */
434 char buf
[BUFSZ
+ 10];
440 for (bp
= buf
, idx
= 0; *s
; s
++) {
443 * clang-8's optimizer at -Os has been observed to mis-compile
444 * this code. Symptom is nethack getting stuck in an apparent
445 * infinite loop (or perhaps just an extremely long one) when
446 * examining data.base entries.
447 * clang-9 doesn't exhibit this problem. [Was the incorrect
448 * optimization fixed or just disabled?]
458 bp
= &buf
[BUFSZ
- 1];
463 return strcpy(sbuf
, buf
);
466 #define VISCTRL_NBUF 5
467 /* make a displayable string from a character */
471 static char visctrl_bufs
[VISCTRL_NBUF
][5];
474 char *ccc
= visctrl_bufs
[nbuf
];
475 nbuf
= (nbuf
+ 1) % VISCTRL_NBUF
;
477 if ((uchar
) c
& 0200) {
484 ccc
[i
++] = c
| 0100; /* letter */
485 } else if (c
== 0177) {
487 ccc
[i
++] = c
& ~0100; /* '?' */
489 ccc
[i
++] = c
; /* printable character */
495 /* strip all the chars in stuff_to_strip from orig */
496 /* caller is responsible for ensuring that bp is a
497 valid pointer to a BUFSZ buffer */
501 const char *stuff_to_strip
,
507 while (*orig
&& i
< (BUFSZ
- 1)) {
508 if (!strchr(stuff_to_strip
, *orig
)) {
519 /* remove digits from string */
525 for (s1
= s2
= s
; *s1
; s1
++)
526 if (*s1
< '0' || *s1
> '9')
533 /* substitute a word or phrase in a string (in place);
534 caller is responsible for ensuring that bp points to big enough buffer */
539 const char *replacement
)
541 char *found
, buf
[BUFSZ
];
542 /* [this could be replaced by strNsubst(bp, orig, replacement, 1)] */
544 found
= strstr(bp
, orig
);
546 Strcpy(buf
, found
+ strlen(orig
));
547 Strcpy(found
, replacement
);
553 /* substitute the Nth occurrence of a substring within a string (in place);
554 if N is 0, substitute all occurrences; returns the number of substitutions;
555 maximum output length is BUFSZ (BUFSZ-1 chars + terminating '\0') */
558 char *inoutbuf
, /* current string, and result buffer */
559 const char *orig
, /* old substring; if "", insert in front of Nth char */
560 const char *replacement
, /* new substring; if "", delete old substring */
561 int n
) /* which occurrence to replace; 0 => all */
563 char *bp
, *op
, workbuf
[BUFSZ
];
565 unsigned len
= (unsigned) strlen(orig
);
566 int ocount
= 0, /* number of times 'orig' has been matched */
567 rcount
= 0; /* number of substitutions made */
569 for (bp
= inoutbuf
, op
= workbuf
; *bp
&& op
< &workbuf
[BUFSZ
- 1]; ) {
570 if ((!len
|| !strncmp(bp
, orig
, len
)) && (++ocount
== n
|| n
== 0)) {
571 /* Nth match found */
572 for (rp
= replacement
; *rp
&& op
< &workbuf
[BUFSZ
- 1]; )
576 bp
+= len
; /* skip 'orig' */
580 /* no match (or len==0) so retain current character */
583 if (!len
&& n
== ocount
+ 1) {
584 /* special case: orig=="" (!len) and n==strlen(inoutbuf)+1,
585 insert in front of terminator (in other words, append);
586 [when orig=="", ocount will have been incremented once for
588 for (rp
= replacement
; *rp
&& op
< &workbuf
[BUFSZ
- 1]; )
594 Strcpy(inoutbuf
, workbuf
);
599 /* search for a word in a space-separated list; returns non-Null if found */
602 const char *list
, /* string of space-separated words */
603 const char *word
, /* word to try to find */
604 int wordlen
, /* so that it isn't required to be \0 terminated */
605 boolean ignorecase
) /* T: case-blind, F: case-sensitive */
607 const char *p
= list
;
614 if ((ignorecase
? !strncmpi(p
, word
, wordlen
)
615 : !strncmp(p
, word
, wordlen
))
616 && (p
[wordlen
] == '\0' || p
[wordlen
] == ' '))
618 p
= strchr(p
+ 1, ' ');
620 return (const char *) 0;
623 /* return the ordinal suffix of a number */
625 ordin(int n
) /* note: should be non-negative */
629 return (dd
== 0 || dd
> 3 || (n
% 100) / 10 == 1) ? "th"
630 : (dd
== 1) ? "st" : (dd
== 2) ? "nd" : "rd";
633 DISABLE_WARNING_FORMAT_NONLITERAL
/* one compiler complains about
634 result of ?: for format string */
636 /* make a signed digit string from a number */
642 Sprintf(buf
, (n
< 0) ? "%d" : "+%d", n
);
646 RESTORE_WARNING_FORMAT_NONLITERAL
648 /* return the sign of a number: -1, 0, or 1 */
652 return (n
< 0) ? -1 : (n
!= 0);
655 /* distance between two points, in moves */
657 distmin(coordxy x0
, coordxy y0
, coordxy x1
, coordxy y1
)
659 coordxy dx
= x0
- x1
, dy
= y0
- y1
;
665 /* The minimum number of moves to get from (x0,y0) to (x1,y1) is the
666 * larger of the [absolute value of the] two deltas.
668 return (dx
< dy
) ? dy
: dx
;
671 /* square of Euclidean distance between pair of pts */
673 dist2(coordxy x0
, coordxy y0
, coordxy x1
, coordxy y1
)
675 coordxy dx
= x0
- x1
, dy
= y0
- y1
;
677 return dx
* dx
+ dy
* dy
;
680 /* integer square root function without using floating point */
687 * This could be replaced by a faster algorithm, but has not been because:
688 * + the simple algorithm is easy to read;
689 * + this algorithm does not require 64-bit support;
690 * + in current usage, the values passed to isqrt() are not really that
691 * large, so the performance difference is negligible;
692 * + isqrt() is used in only few places, which are not bottle-necks.
702 /* are two points lined up (on a straight line)? */
704 online2(coordxy x0
, coordxy y0
, coordxy x1
, coordxy y1
)
706 int dx
= x0
- x1
, dy
= y0
- y1
;
707 /* If either delta is zero then they're on an orthogonal line,
708 * else if the deltas are equal (signs ignored) they're on a diagonal.
710 return (boolean
) (!dy
|| !dx
|| dy
== dx
|| dy
== -dx
);
714 /* case insensitive counted string comparison */
715 /*{ aka strncasecmp }*/
718 const char *s1
, const char *s2
,
719 int n
) /*(should probably be size_t, which is unsigned)*/
725 return (*s1
!= 0); /* s1 >= s2 */
727 return -1; /* s1 < s2 */
731 return (t1
> t2
) ? 1 : -1;
733 return 0; /* s1 == s2 */
735 #endif /* STRNCMPI */
738 /* case insensitive substring search */
740 strstri(const char *str
, const char *sub
)
744 #define TABSIZ 0x20 /* 0x40 would be case-sensitive */
745 char tstr
[TABSIZ
], tsub
[TABSIZ
]; /* nibble count tables */
747 assert( (TABSIZ
& ~(TABSIZ
-1)) == TABSIZ
); /* must be exact power of 2 */
748 assert( &lowc
!= 0 ); /* can't be unsafe macro */
751 /* special case: empty substring */
755 /* do some useful work while determining relative lengths */
756 for (i
= 0; i
< TABSIZ
; i
++)
757 tstr
[i
] = tsub
[i
] = 0; /* init */
758 for (k
= 0, s1
= str
; *s1
; k
++)
759 tstr
[*s1
++ & (TABSIZ
- 1)]++;
760 for (s2
= sub
; *s2
; --k
)
761 tsub
[*s2
++ & (TABSIZ
- 1)]++;
763 /* evaluate the info we've collected */
765 return (char *) 0; /* sub longer than str, so can't match */
766 for (i
= 0; i
< TABSIZ
; i
++) /* does sub have more 'x's than str? */
767 if (tsub
[i
] > tstr
[i
])
768 return (char *) 0; /* match not possible */
770 /* now actually compare the substring repeatedly to parts of the string */
771 for (i
= 0; i
<= k
; i
++) {
774 while (lowc(*s1
++) == lowc(*s2
++))
776 return (char *) &str
[i
]; /* full match */
778 return (char *) 0; /* not found */
782 /* compare two strings for equality, ignoring the presence of specified
783 characters (typically whitespace) and possibly ignoring case */
786 const char *s1
, const char *s2
,
787 const char *ignore_chars
,
793 while ((c1
= *s1
++) != '\0' && strchr(ignore_chars
, c1
) != 0)
795 while ((c2
= *s2
++) != '\0' && strchr(ignore_chars
, c2
) != 0)
798 break; /* stop when end of either string is reached */
806 /* match occurs only when the end of both strings has been reached */
807 return (boolean
) (!c1
&& !c2
);
813 * The time is used for:
815 * - year on tombstone and yyyymmdd in record file
816 * - phase of the moon (various monsters react to NEW_MOON or FULL_MOON)
817 * - night and midnight (the undead are dangerous at midnight)
818 * - determination of what files are "very old"
821 /* TIME_type: type of the argument to time(); we actually use &(time_t);
822 you might need to define either or both of these to 'long *' in *conf.h */
824 #define TIME_type time_t *
826 #ifndef LOCALTIME_type
827 #define LOCALTIME_type time_t *
830 /* swapbits(val, bita, bitb) swaps bit a with bit b in val */
832 swapbits(int val
, int bita
, int bitb
)
834 int tmp
= ((val
>> bita
) & 1) ^ ((val
>> bitb
) & 1);
836 return (val
^ ((tmp
<< bita
) | (tmp
<< bitb
)));
839 DISABLE_WARNING_FORMAT_NONLITERAL
842 * Wrap snprintf for use in the main code.
845 * 1. If there are any platform issues, we have one spot to fix them -
846 * snprintf is a routine with a troubling history of bad implementations.
847 * 2. Add cumbersome error checking in one spot. Problems with text
848 * wrangling do not have to be fatal.
849 * 3. Gcc 9+ will issue a warning unless the return value is used.
850 * Annoyingly, explicitly casting to void does not remove the error.
851 * So, use the result - see reason #2.
855 const char *func UNUSED
, int line UNUSED
,
856 char *str
, size_t size
,
857 const char *fmt
, ...)
863 n
= vsnprintf(str
, size
, fmt
, ap
);
865 if (n
< 0 || (size_t) n
>= size
) { /* is there a problem? */
867 TODO
: add
set_impossible(), impossible
-> func pointer
,
868 test funcpointer before call
869 impossible("snprintf %s: func %s, file line %d",
870 (n
< 0) ? "format error" : "overflow",
873 str
[size
- 1] = '\0'; /* make sure it is nul terminated */
877 RESTORE_WARNING_FORMAT_NONLITERAL
879 /* Unicode routines */
882 unicodeval_to_utf8str(int uval
, uint8
*buffer
, size_t bufsz
)
884 // static uint8 buffer[7];
890 * Binary Hex Comments
891 * 0xxxxxxx 0x00..0x7F Only byte of a 1-byte character encoding
892 * 10xxxxxx 0x80..0xBF Continuation byte : one of 1-3 bytes following
893 * first 110xxxxx 0xC0..0xDF First byte of a 2-byte character encoding
894 * 1110xxxx 0xE0..0xEF First byte of a 3-byte character encoding
895 * 11110xxx 0xF0..0xF7 First byte of a 4-byte character encoding
900 } else if (uval
< 0x800) {
901 *b
++ = 192 + uval
/ 64;
902 *b
++ = 128 + uval
% 64;
903 } else if (uval
- 0xd800u
< 0x800) {
905 } else if (uval
< 0x10000) {
906 *b
++ = 224 + uval
/ 4096;
907 *b
++ = 128 + uval
/ 64 % 64;
908 *b
++ = 128 + uval
% 64;
909 } else if (uval
< 0x110000) {
910 *b
++ = 240 + uval
/ 262144;
911 *b
++ = 128 + uval
/ 4096 % 64;
912 *b
++ = 128 + uval
/ 64 % 64;
913 *b
++ = 128 + uval
% 64;
917 *b
= '\0'; /* NUL terminate */
922 case_insensitive_comp(const char *s1
, const char *s2
)
926 for (;; s1
++, s2
++) {
929 u1
= (uchar
) tolower(u1
);
932 u2
= (uchar
) tolower(u2
);
933 if (u1
== '\0' || u1
!= u2
)
940 copy_bytes(int ifd
, int ofd
)
947 nfrom
= read(ifd
, buf
, BUFSIZ
);
948 /* read can return -1 */
949 if (nfrom
>= 0 && nfrom
<= BUFSIZ
)
950 nto
= write(ofd
, buf
, nfrom
);
951 if (nto
!= nfrom
|| nfrom
< 0)
953 } while (nfrom
== BUFSIZ
);