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