1 /* $OpenBSD: bog.c,v 1.33 2016/09/12 20:11:10 tb Exp $ */
2 /* $NetBSD: bog.c,v 1.5 1995/04/24 12:22:32 cgd Exp $ */
6 * The Regents of the University of California. All rights reserved.
8 * This code is derived from software contributed to Berkeley by
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
49 static void init(void);
50 static void init_adjacencies(void);
51 static int compar(const void *, const void *);
53 struct dictindex dictindex
[26];
55 static int **adjacency
, **letter_map
;
58 int wordpath
[MAXWORDLEN
+ 1];
59 int wordlen
; /* Length of last word returned by nextword() */
64 char **pword
, *pwords
, *pwordsp
;
65 int npwords
, maxpwords
= MAXPWORDS
, maxpspace
= MAXPSPACE
;
67 char **mword
, *mwords
, *mwordsp
;
68 int nmwords
, maxmwords
= MAXMWORDS
, maxmspace
= MAXMSPACE
;
71 int tnmwords
= 0, tnpwords
= 0;
88 main(int argc
, char *argv
[])
93 batch
= debug
= reuse
= selfuse
;
96 tlimit
= 180; /* 3 minutes is standard */
98 while ((ch
= getopt(argc
, argv
, "Bbcdht:w:")) != -1)
113 if ((tlimit
= atoi(optarg
)) < 1)
114 errx(1, "bad time limit");
117 if ((minlength
= atoi(optarg
)) < 3)
118 errx(1, "min word length must be > 2");
127 ncubes
= grid
* grid
;
129 /* process final arguments */
131 if (strcmp(argv
[0], "+") == 0)
133 else if (strcmp(argv
[0], "++") == 0)
137 if (reuse
|| selfuse
) {
143 if (strlen(argv
[0]) != ncubes
)
145 for (p
= argv
[0]; *p
!= '\0'; p
++)
146 if (!islower((unsigned char)*p
))
147 errx(1, "only lower case letters are allowed "
150 } else if (argc
!= 0)
153 if (batch
&& bspec
== NULL
)
154 errx(1, "must give both -b and a board setup");
159 while ((p
= batchword(stdin
)) != NULL
)
164 prompt("Loading the dictionary...");
165 if ((dictfp
= opendict(DICT
)) == NULL
) {
171 if (loaddict(dictfp
) < 0) {
172 warnx("can't load %s", DICT
);
179 if (loadindex(DICTINDEX
) < 0) {
180 warnx("can't load %s", DICTINDEX
);
185 prompt("Type <space> to begin...");
186 while (inputch() != ' ');
188 for (done
= 0; !done
;) {
190 bspec
= NULL
; /* reset for subsequent games */
192 prompt("Type <space> to continue, any cap to quit...");
193 delay(10); /* wait for user to quit typing */
199 else if (ch
== '\014' || ch
== '\022') /* ^l or ^r */
216 * Read a line from the given stream and check if it is legal
217 * Return a pointer to a legal word or a null pointer when EOF is reached
225 q
= &wordpath
[MAXWORDLEN
+ 1];
229 while ((w
= nextword(fp
)) != NULL
) {
230 if (wordlen
< minlength
)
233 while (p
< q
&& *p
!= -1)
236 if (checkword(w
, -1, wordpath
) != -1)
244 * Reset the word lists from last game
245 * Keep track of the running stats
252 char buf
[MAXWORDLEN
+ 1];
262 q
= &wordpath
[MAXWORDLEN
+ 1];
274 if (get_line(buf
) == NULL
) {
280 if (t
- start_t
>= tlimit
) {
284 if (buf
[0] == '\0') {
287 remaining
= tlimit
- (int) (t
- start_t
);
288 snprintf(buf
, sizeof(buf
),
289 "%d:%02d", remaining
/ 60, remaining
% 60);
293 if (strlen(buf
) < (size_t)minlength
) {
299 while (p
< q
&& *p
!= -1)
303 if (checkword(buf
, -1, wordpath
) < 0)
308 for (i
= 0; wordpath
[i
] != -1; i
++)
309 printf(" %d", wordpath
[i
]);
312 for (i
= 0; i
< npwords
; i
++) {
313 if (strcmp(pword
[i
], buf
) == 0)
316 if (i
!= npwords
) { /* already used the word */
320 else if (!validword(buf
))
325 if (npwords
== maxpwords
- 1) {
326 maxpwords
+= MAXPWORDS
;
327 pword
= realloc(pword
,
328 maxpwords
* sizeof(char *));
331 errx(1, "%s", strerror(ENOMEM
));
334 len
= strlen(buf
) + 1;
335 if (pwordsp
+ len
>= &pwords
[maxpspace
]) {
336 maxpspace
+= MAXPSPACE
;
337 pwords
= realloc(pwords
, maxpspace
);
338 if (pwords
== NULL
) {
340 errx(1, "%s", strerror(ENOMEM
));
343 pword
[npwords
++] = pwordsp
;
344 memcpy(pwordsp
, buf
, len
);
354 * Sort the player's words and terminate the list with a null
355 * entry to help out checkdict()
357 qsort(pword
, npwords
, sizeof(pword
[0]), compar
);
358 pword
[npwords
] = NULL
;
361 * These words don't need to be sorted since the dictionary is sorted
372 * Check if the given word is present on the board, with the constraint
373 * that the first letter of the word is adjacent to square 'prev'
374 * Keep track of the current path of squares for the word
375 * A 'q' must be followed by a 'u'
376 * Words must end with a null
377 * Return 1 on success, -1 on failure
380 checkword(char *word
, int prev
, int *path
)
386 printf("checkword(%s, %d, [", word
, prev
);
387 for (i
= 0; wordpath
[i
] != -1; i
++)
388 printf(" %d", wordpath
[i
]);
395 lm
= letter_map
[*word
- 'a'];
398 char subword
[MAXWORDLEN
+ 1];
401 * Check for letters not appearing in the cube to eliminate some
408 if (*letter_map
[*p
- 'a'] == -1)
419 usedbits
|= (1 << *lm
);
420 if (checkword(subword
+ 1, *lm
, path
+ 1) > 0)
422 usedbits
&= ~(1 << *lm
);
429 * A cube is only adjacent to itself in the adjacency matrix if selfuse
430 * was set, so a cube can't be used twice in succession if only the
433 for (i
= 0; lm
[i
] != -1; i
++) {
434 if (adjacency
[prev
][lm
[i
]]) {
439 * If necessary, check if the square has already
442 if (!reuse
&& !selfuse
&& (usedbits
& used
))
446 if (checkword(word
+ 1, lm
[i
], path
+ 1) > 0)
451 *path
= -1; /* in case of a backtrack */
456 * A word is invalid if it is not in the dictionary
457 * At this point it is already known that the word can be formed from
461 validword(char *word
)
467 if (dictseek(dictfp
, dictindex
[j
].start
, SEEK_SET
) < 0) {
469 errx(1, "seek error in validword()");
472 while ((w
= nextword(dictfp
)) != NULL
) {
475 if (*w
!= word
[0]) /* end of words starting with word[0] */
478 while ((ch
= *w
++) == *q
++ && ch
!= '\0')
480 if (*(w
- 1) == '\0' && *(q
- 1) == '\0')
483 if (dictfp
!= NULL
&& feof(dictfp
)) /* Special case for z's */
489 * Check each word in the dictionary against the board
490 * Delete words from the machine list that the player has found
491 * Assume both the dictionary and the player's words are already sorted
498 int prevch
, previndex
, *pi
, *qi
, st
;
504 qi
= &wordpath
[MAXWORDLEN
+ 1];
506 dictseek(dictfp
, 0L, SEEK_SET
);
507 while ((w
= nextword(dictfp
)) != NULL
) {
508 if (wordlen
< minlength
)
512 * If we've moved on to a word with a different first
513 * letter then we can speed things up by skipping all
514 * words starting with a letter that doesn't appear in
517 i
= (int) (*w
- 'a');
518 while (i
< 26 && letter_map
[i
][0] == -1)
522 previndex
= prevch
- 'a';
525 * Fall through if the word's first letter appears in
526 * the cube (i.e., if we can't skip ahead), otherwise
527 * seek to the beginning of words in the dictionary
528 * starting with the next letter (alphabetically)
529 * appearing in the cube and then read the first word.
531 if (i
!= previndex
+ 1) {
533 dictindex
[i
].start
, SEEK_SET
) < 0) {
535 errx(1, "seek error in checkdict()");
542 while (pi
< qi
&& *pi
!= -1)
545 if (checkword(w
, -1, wordpath
) == -1)
549 while (*pw
!= NULL
&& (st
= strcmp(*pw
, w
)) < 0)
551 if (st
== 0) /* found it */
553 if (nmwords
== maxmwords
- 1) {
554 maxmwords
+= MAXMWORDS
;
555 mword
= realloc(mword
, maxmwords
* sizeof(char *));
558 errx(1, "%s", strerror(ENOMEM
));
561 if (mwordsp
+ wordlen
+ 1 >= &mwords
[maxmspace
]) {
562 maxmspace
+= MAXMSPACE
;
563 mwords
= realloc(mwords
, maxmspace
);
564 if (mwords
== NULL
) {
566 errx(1, "%s", strerror(ENOMEM
));
569 mword
[nmwords
++] = mwordsp
;
570 memcpy(mwordsp
, w
, wordlen
+ 1);
571 mwordsp
+= wordlen
+ 1;
576 * Crank up a new game
577 * If the argument is non-null then it is assumed to be a legal board spec
578 * in ascending cube order, oth. make a random board
585 const char *tmp
, **cubes
;
587 const char chal_cube
[] = "iklmqu"; /* challenge cube */
588 static const char *cubes4
[] = {
589 "ednosw", "aaciot", "acelrs", "ehinps",
590 "eefhiy", "elpstu", "acdemp", "gilruw",
591 "egkluy", "ahmors", "abilty", "adenvz",
592 "bfiorx", "dknotu", "abjmoq", "egintv"
594 static const char *cubes5
[] = {
595 "aaafrs", "aaeeee", "aafirs", "adennn", "aeeeem",
596 "aeegmu", "aegmnn", "afirsy", "bjkqxz", "ccnstw",
597 "ceiilt", "ceilpt", "ceipst", "ddlnor", "dhhlor",
598 "dhhnot", "dhlnor", "eiiitt", "emottt", "ensssu",
599 "fiprsy", "gorrvw", "hiprry", "nootuw", "ooottu"
602 cubes
= grid
== 4 ? cubes4
: cubes5
;
604 /* Shuffle the cubes using Fisher-Yates (aka Knuth P). */
607 q
= (int)arc4random_uniform(p
+ 1);
613 /* Build the board by rolling each cube. */
614 for (i
= 0; i
< ncubes
; i
++)
615 board
[i
] = cubes
[i
][arc4random_uniform(6)];
618 * For challenge mode, roll chal_cube and replace a random
619 * cube with its value. Set the high bit to distinguish it.
622 i
= arc4random_uniform(ncubes
);
623 board
[i
] = SETHI(chal_cube
[arc4random_uniform(6)]);
626 for (i
= 0; i
< ncubes
; i
++)
629 board
[ncubes
] = '\0';
632 * Set up the map from letter to location(s)
633 * Each list is terminated by a -1 entry
635 for (i
= 0; i
< 26; i
++) {
636 lm
[i
] = letter_map
[i
];
640 for (i
= 0; i
< ncubes
; i
++) {
643 j
= (int) (SEVENBIT(board
[i
]) - 'a');
649 for (i
= 0; i
< 26; i
++) {
652 printf("%c:", 'a' + i
);
653 for (j
= 0; (ch
= letter_map
[i
][j
]) != -1; j
++)
662 compar(const void *p
, const void *q
)
664 return (strcmp(*(const char *const*)p
, *(const char *const*)q
));
668 * Allocate and initialize data structures.
676 minlength
= grid
- 1;
678 board
= malloc(ncubes
+ 1);
681 letter_map
= calloc(26, sizeof(int *));
682 if (letter_map
== NULL
)
684 for (i
= 0; i
< 26; i
++) {
685 letter_map
[i
] = calloc(ncubes
, sizeof(int));
686 if (letter_map
[i
] == NULL
)
689 pword
= calloc(maxpwords
, sizeof(char *));
692 pwords
= malloc(maxpspace
);
695 mword
= calloc(maxmwords
, sizeof(char *));
698 mwords
= malloc(maxmspace
);
703 #define SET_ADJ(r) do { \
707 if (col + 1 < grid) \
712 * Compute adjacency matrix for the grid
715 init_adjacencies(void)
720 adjacency
= calloc(ncubes
, sizeof(int *));
721 if (adjacency
== NULL
)
725 * Fill in adjacencies. This is an ncubes x ncubes matrix where
726 * the position X,Y is set to 1 if cubes X and Y are adjacent.
728 for (cube
= 0; cube
< ncubes
; cube
++) {
729 adj
= adjacency
[cube
] = calloc(ncubes
, sizeof(int));
743 SET_ADJ(cube
- grid
);
747 SET_ADJ(cube
+ grid
);
754 fprintf(stderr
, "usage: "
755 "%s [-Bbcd] [-t time] [-w length] [+[+]] [boardspec]\n",