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