Bring hunt in from OpenBSD. The best multi-player terminal game ever!
[dragonfly.git] / games / hunt / hunt / otto.c
1 /*
2  * Copyright (c) 1983-2003, Regents of the University of California.
3  * All rights reserved.
4  * 
5  * Redistribution and use in source and binary forms, with or without 
6  * modification, are permitted provided that the following conditions are 
7  * met:
8  * 
9  * + Redistributions of source code must retain the above copyright 
10  *   notice, this list of conditions and the following disclaimer.
11  * + Redistributions in binary form must reproduce the above copyright 
12  *   notice, this list of conditions and the following disclaimer in the 
13  *   documentation and/or other materials provided with the distribution.
14  * + Neither the name of the University of California, San Francisco nor 
15  *   the names of its contributors may be used to endorse or promote 
16  *   products derived from this software without specific prior written 
17  *   permission.
18  * 
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
22  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  *
31  * $OpenBSD: otto.c,v 1.9 2006/03/27 00:10:15 tedu Exp $
32  * $NetBSD: otto.c,v 1.2 1997/10/10 16:32:39 lukem Exp $
33  * $DragonFly: src/games/hunt/hunt/otto.c,v 1.1 2008/09/02 21:50:20 dillon Exp $
34  */
35
36 /*
37  *      otto    - a hunt otto-matic player
38  *
39  *              This guy is buggy, unfair, stupid, and not extensible.
40  *      Future versions of hunt will have a subroutine library for
41  *      automatic players to link to.  If you write your own "otto"
42  *      please let us know what subroutines you would expect in the
43  *      subroutine library.
44  */
45
46 #include <sys/time.h>
47 #include <ctype.h>
48 #include <signal.h>
49 #include <stdlib.h>
50 #include <unistd.h>
51 #include <string.h>
52 #include "hunt.h"
53 #include "client.h"
54 #include "display.h"
55
56 #include <stdio.h>
57 #define panic(m)        _panic(__FILE__,__LINE__,m)
58
59 useconds_t      Otto_pause      = 55000;
60
61 int     Otto_mode;
62
63 # undef         WALL
64 # undef         NORTH
65 # undef         SOUTH
66 # undef         WEST
67 # undef         EAST
68 # undef         FRONT
69 # undef         LEFT
70 # undef         BACK
71 # undef         RIGHT
72
73 # define        SCREEN(y, x)    display_atyx(y, x)
74
75 # define        OPPONENT        "{}i!"
76 # define        PROPONENT       "^v<>"
77 # define        WALL            "+\\/#*-|"
78 # define        PUSHOVER        " bg;*#&"
79 # define        SHOTS           "$@Oo:"
80
81 /* number of "directions" */
82 # define        NUMDIRECTIONS   4
83 # define        direction(abs,rel)      (((abs) + (rel)) % NUMDIRECTIONS)
84
85 /* absolute directions (facings) - counterclockwise */
86 # define        NORTH           0
87 # define        WEST            1
88 # define        SOUTH           2
89 # define        EAST            3
90 # define        ALLDIRS         0xf
91
92 /* relative directions - counterclockwise */
93 # define        FRONT           0
94 # define        LEFT            1
95 # define        BACK            2
96 # define        RIGHT           3
97
98 # define        ABSCHARS        "NWSE"
99 # define        RELCHARS        "FLBR"
100 # define        DIRKEYS         "khjl"
101
102 static  char    command[1024];  /* XXX */
103 static  int     comlen;
104
105 # define        DEADEND         0x1
106 # define        ON_LEFT         0x2
107 # define        ON_RIGHT        0x4
108 # define        ON_SIDE         (ON_LEFT|ON_RIGHT)
109 # define        BEEN            0x8
110 # define        BEEN_SAME       0x10
111
112 struct  item    {
113         char    what;
114         int     distance;
115         int     flags;
116 };
117
118 static  struct  item    flbr[NUMDIRECTIONS];
119
120 # define        fitem   flbr[FRONT]
121 # define        litem   flbr[LEFT]
122 # define        bitem   flbr[BACK]
123 # define        ritem   flbr[RIGHT]
124
125 static  int             facing;
126 static  int             row, col;
127 static  int             num_turns;              /* for wandering */
128 static  char            been_there[HEIGHT][WIDTH2];
129
130 static  void            attack(int, struct item *);
131 static  void            duck(int);
132 static  void            face_and_move_direction(int, int);
133 static  int             go_for_ammo(char);
134 static  void            ottolook(int, struct item *);
135 static  void            look_around(void);
136 static  int             stop_look(struct item *, char, int, int);
137 static  void            wander(void);
138 static  void            _panic(const char *, int, const char *);
139
140 int
141 otto(y, x, face, buf, buflen)
142         int     y, x;
143         char    face;
144         char    *buf;
145         size_t  buflen;
146 {
147         int             i;
148
149         if (usleep(Otto_pause) < 0)
150                 panic("usleep");
151
152         /* save away parameters so other functions may use/update info */
153         switch (face) {
154         case '^':       facing = NORTH; break;
155         case '<':       facing = WEST; break;
156         case 'v':       facing = SOUTH; break;
157         case '>':       facing = EAST; break;
158         default:        panic("unknown face");
159         }
160         row = y; col = x;
161         been_there[row][col] |= 1 << facing;
162
163         /* initially no commands to be sent */
164         comlen = 0;
165
166         /* find something to do */
167         look_around();
168         for (i = 0; i < NUMDIRECTIONS; i++) {
169                 if (strchr(OPPONENT, flbr[i].what) != NULL) {
170                         attack(i, &flbr[i]);
171                         memset(been_there, 0, sizeof been_there);
172                         goto done;
173                 }
174         }
175
176         if (strchr(SHOTS, bitem.what) != NULL && !(bitem.what & ON_SIDE)) {
177                 duck(BACK);
178                 memset(been_there, 0, sizeof been_there);
179         } else if (go_for_ammo(BOOT_PAIR)) {
180                 memset(been_there, 0, sizeof been_there);
181         } else if (go_for_ammo(BOOT)) {
182                 memset(been_there, 0, sizeof been_there);
183         } else if (go_for_ammo(GMINE))
184                 memset(been_there, 0, sizeof been_there);
185         else if (go_for_ammo(MINE))
186                 memset(been_there, 0, sizeof been_there);
187         else
188                 wander();
189
190 done:
191         if (comlen) {
192                 if (comlen > (int)buflen)
193                         panic("not enough buffer space");
194                 memcpy(buf, command, comlen);
195         }
196         return comlen;
197 }
198
199 static int
200 stop_look(itemp, c, dist, side)
201         struct  item    *itemp;
202         char    c;
203         int     dist;
204         int     side;
205 {
206         switch (c) {
207
208         case SPACE:
209                 if (side)
210                         itemp->flags &= ~DEADEND;
211                 return 0;
212
213         case MINE:
214         case GMINE:
215         case BOOT:
216         case BOOT_PAIR:
217                 if (itemp->distance == -1) {
218                         itemp->distance = dist;
219                         itemp->what = c;
220                         if (side < 0)
221                                 itemp->flags |= ON_LEFT;
222                         else if (side > 0)
223                                 itemp->flags |= ON_RIGHT;
224                 }
225                 return 0;
226
227         case SHOT:
228         case GRENADE:
229         case SATCHEL:
230         case BOMB:
231         case SLIME:
232                 if (itemp->distance == -1 || (!side
233                     && (itemp->flags & ON_SIDE
234                     || itemp->what == GMINE || itemp->what == MINE))) {
235                         itemp->distance = dist;
236                         itemp->what = c;
237                         itemp->flags &= ~ON_SIDE;
238                         if (side < 0)
239                                 itemp->flags |= ON_LEFT;
240                         else if (side > 0)
241                                 itemp->flags |= ON_RIGHT;
242                 }
243                 return 0;
244
245         case '{':
246         case '}':
247         case 'i':
248         case '!':
249                 itemp->distance = dist;
250                 itemp->what = c;
251                 itemp->flags &= ~(ON_SIDE|DEADEND);
252                 if (side < 0)
253                         itemp->flags |= ON_LEFT;
254                 else if (side > 0)
255                         itemp->flags |= ON_RIGHT;
256                 return 1;
257
258         default:
259                 /* a wall or unknown object */
260                 if (side)
261                         return 0;
262                 if (itemp->distance == -1) {
263                         itemp->distance = dist;
264                         itemp->what = c;
265                 }
266                 return 1;
267         }
268 }
269
270 static void
271 ottolook(rel_dir, itemp)
272         int             rel_dir;
273         struct  item    *itemp;
274 {
275         int             r, c;
276         char            ch;
277
278         r = 0;
279         itemp->what = 0;
280         itemp->distance = -1;
281         itemp->flags = DEADEND|BEEN;            /* true until proven false */
282
283         switch (direction(facing, rel_dir)) {
284
285         case NORTH:
286                 if (been_there[row - 1][col] & NORTH)
287                         itemp->flags |= BEEN_SAME;
288                 for (r = row - 1; r >= 0; r--)
289                         for (c = col - 1; c < col + 2; c++) {
290                                 ch = SCREEN(r, c);
291                                 if (stop_look(itemp, ch, row - r, c - col))
292                                         goto cont_north;
293                                 if (c == col && !been_there[r][c])
294                                         itemp->flags &= ~BEEN;
295                         }
296         cont_north:
297                 if (itemp->flags & DEADEND) {
298                         itemp->flags |= BEEN;
299                         if (r >= 0)
300                                 been_there[r][col] |= NORTH;
301                         for (r = row - 1; r > row - itemp->distance; r--)
302                                 been_there[r][col] = ALLDIRS;
303                 }
304                 break;
305
306         case SOUTH:
307                 if (been_there[row + 1][col] & SOUTH)
308                         itemp->flags |= BEEN_SAME;
309                 for (r = row + 1; r < HEIGHT; r++)
310                         for (c = col - 1; c < col + 2; c++) {
311                                 ch = SCREEN(r, c);
312                                 if (stop_look(itemp, ch, r - row, col - c))
313                                         goto cont_south;
314                                 if (c == col && !been_there[r][c])
315                                         itemp->flags &= ~BEEN;
316                         }
317         cont_south:
318                 if (itemp->flags & DEADEND) {
319                         itemp->flags |= BEEN;
320                         if (r < HEIGHT)
321                                 been_there[r][col] |= SOUTH;
322                         for (r = row + 1; r < row + itemp->distance; r++)
323                                 been_there[r][col] = ALLDIRS;
324                 }
325                 break;
326
327         case WEST:
328                 if (been_there[row][col - 1] & WEST)
329                         itemp->flags |= BEEN_SAME;
330                 for (c = col - 1; c >= 0; c--)
331                         for (r = row - 1; r < row + 2; r++) {
332                                 ch = SCREEN(r, c);
333                                 if (stop_look(itemp, ch, col - c, row - r))
334                                         goto cont_west;
335                                 if (r == row && !been_there[r][c])
336                                         itemp->flags &= ~BEEN;
337                         }
338         cont_west:
339                 if (itemp->flags & DEADEND) {
340                         itemp->flags |= BEEN;
341                         been_there[r][col] |= WEST;
342                         for (c = col - 1; c > col - itemp->distance; c--)
343                                 been_there[row][c] = ALLDIRS;
344                 }
345                 break;
346
347         case EAST:
348                 if (been_there[row][col + 1] & EAST)
349                         itemp->flags |= BEEN_SAME;
350                 for (c = col + 1; c < WIDTH; c++)
351                         for (r = row - 1; r < row + 2; r++) {
352                                 ch = SCREEN(r, c);
353                                 if (stop_look(itemp, ch, c - col, r - row))
354                                         goto cont_east;
355                                 if (r == row && !been_there[r][c])
356                                         itemp->flags &= ~BEEN;
357                         }
358         cont_east:
359                 if (itemp->flags & DEADEND) {
360                         itemp->flags |= BEEN;
361                         been_there[r][col] |= EAST;
362                         for (c = col + 1; c < col + itemp->distance; c++)
363                                 been_there[row][c] = ALLDIRS;
364                 }
365                 break;
366
367         default:
368                 panic("unknown look");
369         }
370 }
371
372 static void
373 look_around()
374 {
375         int     i;
376
377         for (i = 0; i < NUMDIRECTIONS; i++) {
378                 ottolook(i, &flbr[i]);
379         }
380 }
381
382 /*
383  *      as a side effect modifies facing and location (row, col)
384  */
385
386 static void
387 face_and_move_direction(rel_dir, distance)
388         int     rel_dir, distance;
389 {
390         int     old_facing;
391         char    cmd;
392
393         old_facing = facing;
394         cmd = DIRKEYS[facing = direction(facing, rel_dir)];
395
396         if (rel_dir != FRONT) {
397                 int     i;
398                 struct  item    items[NUMDIRECTIONS];
399
400                 command[comlen++] = toupper(cmd);
401                 if (distance == 0) {
402                         /* rotate ottolook's to be in right position */
403                         for (i = 0; i < NUMDIRECTIONS; i++)
404                                 items[i] =
405                                         flbr[(i + old_facing) % NUMDIRECTIONS];
406                         memcpy(flbr, items, sizeof flbr);
407                 }
408         }
409         while (distance--) {
410                 command[comlen++] = cmd;
411                 switch (facing) {
412
413                 case NORTH:     row--; break;
414                 case WEST:      col--; break;
415                 case SOUTH:     row++; break;
416                 case EAST:      col++; break;
417                 }
418                 if (distance == 0)
419                         look_around();
420         }
421 }
422
423 static void
424 attack(rel_dir, itemp)
425         int             rel_dir;
426         struct  item    *itemp;
427 {
428         if (!(itemp->flags & ON_SIDE)) {
429                 face_and_move_direction(rel_dir, 0);
430                 command[comlen++] = 'o';
431                 command[comlen++] = 'o';
432                 duck(FRONT);
433                 command[comlen++] = ' ';
434         } else if (itemp->distance > 1) {
435                 face_and_move_direction(rel_dir, 2);
436                 duck(FRONT);
437         } else {
438                 face_and_move_direction(rel_dir, 1);
439                 if (itemp->flags & ON_LEFT)
440                         rel_dir = LEFT;
441                 else
442                         rel_dir = RIGHT;
443                 (void) face_and_move_direction(rel_dir, 0);
444                 command[comlen++] = 'f';
445                 command[comlen++] = 'f';
446                 duck(FRONT);
447                 command[comlen++] = ' ';
448         }
449 }
450
451 static void
452 duck(rel_dir)
453         int     rel_dir;
454 {
455         int     dir;
456
457         switch (dir = direction(facing, rel_dir)) {
458
459         case NORTH:
460         case SOUTH:
461                 if (strchr(PUSHOVER, SCREEN(row, col - 1)) != NULL)
462                         command[comlen++] = 'h';
463                 else if (strchr(PUSHOVER, SCREEN(row, col + 1)) != NULL)
464                         command[comlen++] = 'l';
465                 else if (dir == NORTH
466                         && strchr(PUSHOVER, SCREEN(row + 1, col)) != NULL)
467                                 command[comlen++] = 'j';
468                 else if (dir == SOUTH
469                         && strchr(PUSHOVER, SCREEN(row - 1, col)) != NULL)
470                                 command[comlen++] = 'k';
471                 else if (dir == NORTH)
472                         command[comlen++] = 'k';
473                 else
474                         command[comlen++] = 'j';
475                 break;
476
477         case WEST:
478         case EAST:
479                 if (strchr(PUSHOVER, SCREEN(row - 1, col)) != NULL)
480                         command[comlen++] = 'k';
481                 else if (strchr(PUSHOVER, SCREEN(row + 1, col)) != NULL)
482                         command[comlen++] = 'j';
483                 else if (dir == WEST
484                         && strchr(PUSHOVER, SCREEN(row, col + 1)) != NULL)
485                                 command[comlen++] = 'l';
486                 else if (dir == EAST
487                         && strchr(PUSHOVER, SCREEN(row, col - 1)) != NULL)
488                                 command[comlen++] = 'h';
489                 else if (dir == WEST)
490                         command[comlen++] = 'h';
491                 else
492                         command[comlen++] = 'l';
493                 break;
494         }
495 }
496
497 /*
498  *      go for the closest mine if possible
499  */
500
501 static int
502 go_for_ammo(mine)
503         char    mine;
504 {
505         int     i, rel_dir, dist;
506
507         rel_dir = -1;
508         dist = WIDTH;
509         for (i = 0; i < NUMDIRECTIONS; i++) {
510                 if (flbr[i].what == mine && flbr[i].distance < dist) {
511                         rel_dir = i;
512                         dist = flbr[i].distance;
513                 }
514         }
515         if (rel_dir == -1)
516                 return FALSE;
517
518         if (!(flbr[rel_dir].flags & ON_SIDE)
519         || flbr[rel_dir].distance > 1) {
520                 if (dist > 4)
521                         dist = 4;
522                 face_and_move_direction(rel_dir, dist);
523         } else
524                 return FALSE;           /* until it's done right */
525         return TRUE;
526 }
527
528 static void
529 wander()
530 {
531         int     i, j, rel_dir, dir_mask, dir_count;
532
533         for (i = 0; i < NUMDIRECTIONS; i++)
534                 if (!(flbr[i].flags & BEEN) || flbr[i].distance <= 1)
535                         break;
536         if (i == NUMDIRECTIONS)
537                 memset(been_there, 0, sizeof been_there);
538         dir_mask = dir_count = 0;
539         for (i = 0; i < NUMDIRECTIONS; i++) {
540                 j = (RIGHT + i) % NUMDIRECTIONS;
541                 if (flbr[j].distance <= 1 || flbr[j].flags & DEADEND)
542                         continue;
543                 if (!(flbr[j].flags & BEEN_SAME)) {
544                         dir_mask = 1 << j;
545                         dir_count = 1;
546                         break;
547                 }
548                 if (j == FRONT
549                 && num_turns > 4 + (random() %
550                                 ((flbr[FRONT].flags & BEEN) ? 7 : HEIGHT)))
551                         continue;
552                 dir_mask |= 1 << j;
553                 dir_count = 1;
554                 break;
555         }
556         if (dir_count == 0) {
557                 duck(random() % NUMDIRECTIONS);
558                 num_turns = 0;
559                 return;
560         } else {
561                 rel_dir = ffs(dir_mask) - 1;
562         }
563         if (rel_dir == FRONT)
564                 num_turns++;
565         else
566                 num_turns = 0;
567
568         face_and_move_direction(rel_dir, 1);
569 }
570
571 /* Otto always re-enters the game, cloaked. */
572 int
573 otto_quit(int old_status __unused)
574 {
575         return Q_CLOAK;
576 }
577
578 static void
579 _panic(file, line, msg)
580         const char *file;
581         int line;
582         const char *msg;
583 {
584
585         fprintf(stderr, "%s:%d: panic! %s\n", file, line, msg);
586         abort();
587 }