xref: /dragonfly/games/hunt/hunt/otto.c (revision ab709bfb)
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.2 2008/09/04 16:12:51 swildner 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(int y, int x, char face, char *buf, size_t buflen)
142 {
143 	int		i;
144 
145 	if (usleep(Otto_pause) < 0)
146 		panic("usleep");
147 
148 	/* save away parameters so other functions may use/update info */
149 	switch (face) {
150 	case '^':	facing = NORTH; break;
151 	case '<':	facing = WEST; break;
152 	case 'v':	facing = SOUTH; break;
153 	case '>':	facing = EAST; break;
154 	default:	panic("unknown face");
155 	}
156 	row = y; col = x;
157 	been_there[row][col] |= 1 << facing;
158 
159 	/* initially no commands to be sent */
160 	comlen = 0;
161 
162 	/* find something to do */
163 	look_around();
164 	for (i = 0; i < NUMDIRECTIONS; i++) {
165 		if (strchr(OPPONENT, flbr[i].what) != NULL) {
166 			attack(i, &flbr[i]);
167 			memset(been_there, 0, sizeof been_there);
168 			goto done;
169 		}
170 	}
171 
172 	if (strchr(SHOTS, bitem.what) != NULL && !(bitem.what & ON_SIDE)) {
173 		duck(BACK);
174 		memset(been_there, 0, sizeof been_there);
175 	} else if (go_for_ammo(BOOT_PAIR)) {
176 		memset(been_there, 0, sizeof been_there);
177 	} else if (go_for_ammo(BOOT)) {
178 		memset(been_there, 0, sizeof been_there);
179 	} else if (go_for_ammo(GMINE))
180 		memset(been_there, 0, sizeof been_there);
181 	else if (go_for_ammo(MINE))
182 		memset(been_there, 0, sizeof been_there);
183 	else
184 		wander();
185 
186 done:
187 	if (comlen) {
188 		if (comlen > (int)buflen)
189 			panic("not enough buffer space");
190 		memcpy(buf, command, comlen);
191 	}
192 	return comlen;
193 }
194 
195 static int
196 stop_look(struct item *itemp, char c, int dist, int side)
197 {
198 	switch (c) {
199 
200 	case SPACE:
201 		if (side)
202 			itemp->flags &= ~DEADEND;
203 		return 0;
204 
205 	case MINE:
206 	case GMINE:
207 	case BOOT:
208 	case BOOT_PAIR:
209 		if (itemp->distance == -1) {
210 			itemp->distance = dist;
211 			itemp->what = c;
212 			if (side < 0)
213 				itemp->flags |= ON_LEFT;
214 			else if (side > 0)
215 				itemp->flags |= ON_RIGHT;
216 		}
217 		return 0;
218 
219 	case SHOT:
220 	case GRENADE:
221 	case SATCHEL:
222 	case BOMB:
223 	case SLIME:
224 		if (itemp->distance == -1 || (!side
225 		    && (itemp->flags & ON_SIDE
226 		    || itemp->what == GMINE || itemp->what == MINE))) {
227 			itemp->distance = dist;
228 			itemp->what = c;
229 			itemp->flags &= ~ON_SIDE;
230 			if (side < 0)
231 				itemp->flags |= ON_LEFT;
232 			else if (side > 0)
233 				itemp->flags |= ON_RIGHT;
234 		}
235 		return 0;
236 
237 	case '{':
238 	case '}':
239 	case 'i':
240 	case '!':
241 		itemp->distance = dist;
242 		itemp->what = c;
243 		itemp->flags &= ~(ON_SIDE|DEADEND);
244 		if (side < 0)
245 			itemp->flags |= ON_LEFT;
246 		else if (side > 0)
247 			itemp->flags |= ON_RIGHT;
248 		return 1;
249 
250 	default:
251 		/* a wall or unknown object */
252 		if (side)
253 			return 0;
254 		if (itemp->distance == -1) {
255 			itemp->distance = dist;
256 			itemp->what = c;
257 		}
258 		return 1;
259 	}
260 }
261 
262 static void
263 ottolook(int rel_dir, struct item *itemp)
264 {
265 	int		r, c;
266 	char		ch;
267 
268 	r = 0;
269 	itemp->what = 0;
270 	itemp->distance = -1;
271 	itemp->flags = DEADEND|BEEN;		/* true until proven false */
272 
273 	switch (direction(facing, rel_dir)) {
274 
275 	case NORTH:
276 		if (been_there[row - 1][col] & NORTH)
277 			itemp->flags |= BEEN_SAME;
278 		for (r = row - 1; r >= 0; r--)
279 			for (c = col - 1; c < col + 2; c++) {
280 				ch = SCREEN(r, c);
281 				if (stop_look(itemp, ch, row - r, c - col))
282 					goto cont_north;
283 				if (c == col && !been_there[r][c])
284 					itemp->flags &= ~BEEN;
285 			}
286 	cont_north:
287 		if (itemp->flags & DEADEND) {
288 			itemp->flags |= BEEN;
289 			if (r >= 0)
290 				been_there[r][col] |= NORTH;
291 			for (r = row - 1; r > row - itemp->distance; r--)
292 				been_there[r][col] = ALLDIRS;
293 		}
294 		break;
295 
296 	case SOUTH:
297 		if (been_there[row + 1][col] & SOUTH)
298 			itemp->flags |= BEEN_SAME;
299 		for (r = row + 1; r < HEIGHT; r++)
300 			for (c = col - 1; c < col + 2; c++) {
301 				ch = SCREEN(r, c);
302 				if (stop_look(itemp, ch, r - row, col - c))
303 					goto cont_south;
304 				if (c == col && !been_there[r][c])
305 					itemp->flags &= ~BEEN;
306 			}
307 	cont_south:
308 		if (itemp->flags & DEADEND) {
309 			itemp->flags |= BEEN;
310 			if (r < HEIGHT)
311 				been_there[r][col] |= SOUTH;
312 			for (r = row + 1; r < row + itemp->distance; r++)
313 				been_there[r][col] = ALLDIRS;
314 		}
315 		break;
316 
317 	case WEST:
318 		if (been_there[row][col - 1] & WEST)
319 			itemp->flags |= BEEN_SAME;
320 		for (c = col - 1; c >= 0; c--)
321 			for (r = row - 1; r < row + 2; r++) {
322 				ch = SCREEN(r, c);
323 				if (stop_look(itemp, ch, col - c, row - r))
324 					goto cont_west;
325 				if (r == row && !been_there[r][c])
326 					itemp->flags &= ~BEEN;
327 			}
328 	cont_west:
329 		if (itemp->flags & DEADEND) {
330 			itemp->flags |= BEEN;
331 			been_there[r][col] |= WEST;
332 			for (c = col - 1; c > col - itemp->distance; c--)
333 				been_there[row][c] = ALLDIRS;
334 		}
335 		break;
336 
337 	case EAST:
338 		if (been_there[row][col + 1] & EAST)
339 			itemp->flags |= BEEN_SAME;
340 		for (c = col + 1; c < WIDTH; c++)
341 			for (r = row - 1; r < row + 2; r++) {
342 				ch = SCREEN(r, c);
343 				if (stop_look(itemp, ch, c - col, r - row))
344 					goto cont_east;
345 				if (r == row && !been_there[r][c])
346 					itemp->flags &= ~BEEN;
347 			}
348 	cont_east:
349 		if (itemp->flags & DEADEND) {
350 			itemp->flags |= BEEN;
351 			been_there[r][col] |= EAST;
352 			for (c = col + 1; c < col + itemp->distance; c++)
353 				been_there[row][c] = ALLDIRS;
354 		}
355 		break;
356 
357 	default:
358 		panic("unknown look");
359 	}
360 }
361 
362 static void
363 look_around(void)
364 {
365 	int	i;
366 
367 	for (i = 0; i < NUMDIRECTIONS; i++) {
368 		ottolook(i, &flbr[i]);
369 	}
370 }
371 
372 /*
373  *	as a side effect modifies facing and location (row, col)
374  */
375 
376 static void
377 face_and_move_direction(int rel_dir, int distance)
378 {
379 	int	old_facing;
380 	char	cmd;
381 
382 	old_facing = facing;
383 	cmd = DIRKEYS[facing = direction(facing, rel_dir)];
384 
385 	if (rel_dir != FRONT) {
386 		int	i;
387 		struct	item	items[NUMDIRECTIONS];
388 
389 		command[comlen++] = toupper(cmd);
390 		if (distance == 0) {
391 			/* rotate ottolook's to be in right position */
392 			for (i = 0; i < NUMDIRECTIONS; i++)
393 				items[i] =
394 					flbr[(i + old_facing) % NUMDIRECTIONS];
395 			memcpy(flbr, items, sizeof flbr);
396 		}
397 	}
398 	while (distance--) {
399 		command[comlen++] = cmd;
400 		switch (facing) {
401 
402 		case NORTH:	row--; break;
403 		case WEST:	col--; break;
404 		case SOUTH:	row++; break;
405 		case EAST:	col++; break;
406 		}
407 		if (distance == 0)
408 			look_around();
409 	}
410 }
411 
412 static void
413 attack(int rel_dir, struct item *itemp)
414 {
415 	if (!(itemp->flags & ON_SIDE)) {
416 		face_and_move_direction(rel_dir, 0);
417 		command[comlen++] = 'o';
418 		command[comlen++] = 'o';
419 		duck(FRONT);
420 		command[comlen++] = ' ';
421 	} else if (itemp->distance > 1) {
422 		face_and_move_direction(rel_dir, 2);
423 		duck(FRONT);
424 	} else {
425 		face_and_move_direction(rel_dir, 1);
426 		if (itemp->flags & ON_LEFT)
427 			rel_dir = LEFT;
428 		else
429 			rel_dir = RIGHT;
430 		(void) face_and_move_direction(rel_dir, 0);
431 		command[comlen++] = 'f';
432 		command[comlen++] = 'f';
433 		duck(FRONT);
434 		command[comlen++] = ' ';
435 	}
436 }
437 
438 static void
439 duck(int rel_dir)
440 {
441 	int	dir;
442 
443 	switch (dir = direction(facing, rel_dir)) {
444 
445 	case NORTH:
446 	case SOUTH:
447 		if (strchr(PUSHOVER, SCREEN(row, col - 1)) != NULL)
448 			command[comlen++] = 'h';
449 		else if (strchr(PUSHOVER, SCREEN(row, col + 1)) != NULL)
450 			command[comlen++] = 'l';
451 		else if (dir == NORTH
452 			&& strchr(PUSHOVER, SCREEN(row + 1, col)) != NULL)
453 				command[comlen++] = 'j';
454 		else if (dir == SOUTH
455 			&& strchr(PUSHOVER, SCREEN(row - 1, col)) != NULL)
456 				command[comlen++] = 'k';
457 		else if (dir == NORTH)
458 			command[comlen++] = 'k';
459 		else
460 			command[comlen++] = 'j';
461 		break;
462 
463 	case WEST:
464 	case EAST:
465 		if (strchr(PUSHOVER, SCREEN(row - 1, col)) != NULL)
466 			command[comlen++] = 'k';
467 		else if (strchr(PUSHOVER, SCREEN(row + 1, col)) != NULL)
468 			command[comlen++] = 'j';
469 		else if (dir == WEST
470 			&& strchr(PUSHOVER, SCREEN(row, col + 1)) != NULL)
471 				command[comlen++] = 'l';
472 		else if (dir == EAST
473 			&& strchr(PUSHOVER, SCREEN(row, col - 1)) != NULL)
474 				command[comlen++] = 'h';
475 		else if (dir == WEST)
476 			command[comlen++] = 'h';
477 		else
478 			command[comlen++] = 'l';
479 		break;
480 	}
481 }
482 
483 /*
484  *	go for the closest mine if possible
485  */
486 
487 static int
488 go_for_ammo(char mine)
489 {
490 	int	i, rel_dir, dist;
491 
492 	rel_dir = -1;
493 	dist = WIDTH;
494 	for (i = 0; i < NUMDIRECTIONS; i++) {
495 		if (flbr[i].what == mine && flbr[i].distance < dist) {
496 			rel_dir = i;
497 			dist = flbr[i].distance;
498 		}
499 	}
500 	if (rel_dir == -1)
501 		return FALSE;
502 
503 	if (!(flbr[rel_dir].flags & ON_SIDE)
504 	|| flbr[rel_dir].distance > 1) {
505 		if (dist > 4)
506 			dist = 4;
507 		face_and_move_direction(rel_dir, dist);
508 	} else
509 		return FALSE;		/* until it's done right */
510 	return TRUE;
511 }
512 
513 static void
514 wander(void)
515 {
516 	int	i, j, rel_dir, dir_mask, dir_count;
517 
518 	for (i = 0; i < NUMDIRECTIONS; i++)
519 		if (!(flbr[i].flags & BEEN) || flbr[i].distance <= 1)
520 			break;
521 	if (i == NUMDIRECTIONS)
522 		memset(been_there, 0, sizeof been_there);
523 	dir_mask = dir_count = 0;
524 	for (i = 0; i < NUMDIRECTIONS; i++) {
525 		j = (RIGHT + i) % NUMDIRECTIONS;
526 		if (flbr[j].distance <= 1 || flbr[j].flags & DEADEND)
527 			continue;
528 		if (!(flbr[j].flags & BEEN_SAME)) {
529 			dir_mask = 1 << j;
530 			dir_count = 1;
531 			break;
532 		}
533 		if (j == FRONT
534 		&& num_turns > 4 + (random() %
535 				((flbr[FRONT].flags & BEEN) ? 7 : HEIGHT)))
536 			continue;
537 		dir_mask |= 1 << j;
538 		dir_count = 1;
539 		break;
540 	}
541 	if (dir_count == 0) {
542 		duck(random() % NUMDIRECTIONS);
543 		num_turns = 0;
544 		return;
545 	} else {
546 		rel_dir = ffs(dir_mask) - 1;
547 	}
548 	if (rel_dir == FRONT)
549 		num_turns++;
550 	else
551 		num_turns = 0;
552 
553 	face_and_move_direction(rel_dir, 1);
554 }
555 
556 /* Otto always re-enters the game, cloaked. */
557 int
558 otto_quit(int old_status __unused)
559 {
560 	return Q_CLOAK;
561 }
562 
563 static void
564 _panic(const char *file, int line, const char *msg)
565 {
566 
567 	fprintf(stderr, "%s:%d: panic! %s\n", file, line, msg);
568 	abort();
569 }
570