Obsahuje:
multispeed, keybindings, trochu mene trivialni AI a Ferdu

Index: Makefile
===================================================================
RCS file: /home/cvs/tunneler/tunneler/Makefile,v
retrieving revision 1.1.1.3
retrieving revision 1.5
diff -u -r1.1.1.3 -r1.5
--- Makefile	20 Jan 2005 22:15:18 -0000	1.1.1.3
+++ Makefile	21 Jan 2005 16:30:23 -0000	1.5
@@ -1,14 +1,15 @@
+#CC=/opt/gcc-3.3/bin/gcc-3.3
 CC=gcc
 CFLAGS=-Wall -ggdb -DEBUG
 LD=ld
-SRCS=server.c game.c client.c udp.c debug.c virtual.c sprite.c term.c viewpoint.c packet.c state.c xmalloc.c conn.c
+SRCS=ai.c server.c game.c client.c udp.c debug.c virtual.c sprite.c term.c viewpoint.c packet.c state.c xmalloc.c conn.c
 
 all: server client
 
 %.o : %.c
 	$(CC) $(CFLAGS) -c -o $@ $<
 
-server: server.o udp.o debug.o game.o sprite.o viewpoint.o packet.o state.o xmalloc.o conn.o bytef.o buffer.o
+server: ai.o server.o udp.o debug.o game.o sprite.o viewpoint.o packet.o state.o xmalloc.o conn.o bytef.o buffer.o
 	$(CC) -o $@ $^ 
 
 client: client.o udp.o debug.o virtual.o sprite.o term.o viewpoint.o packet.o state.o xmalloc.o conn.o buffer.o bytef.o
Index: README
===================================================================
RCS file: /home/cvs/tunneler/tunneler/README,v
retrieving revision 1.1.1.2
retrieving revision 1.4
diff -u -r1.1.1.2 -r1.4
--- README	20 Jan 2005 22:15:18 -0000	1.1.1.2
+++ README	21 Jan 2005 00:40:37 -0000	1.4
@@ -4,4 +4,11 @@
 	* tcp/udp chatovatko, bohuzel bez frontendu takze pouze do logu
 	* vytecny sdho fraktalovy log4n differ
 
-	
+Parametry klienta:
+
+  m - vicerychlostni - 0,5 a z,a snizuje/zvysuje rychlost postupne
+
+Parametry serveru:
+
+  a - spawn AI (muzete a-cek napsat vic)
+  f - Ferda the Zametac
Index: TODO
===================================================================
RCS file: TODO
diff -N TODO
--- TODO	20 Jan 2005 17:39:53 -0000	1.1.1.1
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1 +0,0 @@
-color v konzoli pri zobrazovani hrace  (ooo)~~
Index: ai.c
===================================================================
RCS file: ai.c
diff -N ai.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ai.c	21 Jan 2005 18:13:52 -0000	1.4
@@ -0,0 +1,366 @@
+#include <assert.h>
+#include <stdlib.h>
+#include "game.h"
+#include "server.h"
+#include "debug.h"
+#include "xmalloc.h"
+#include "console.h"
+
+
+/* Some AI, really trivial for now. */
+/* Currently, the AI will notice only if you shoot it - then it will play
+ * a quick search'n'destroy game with you, leaving you no chance to
+ * survive >:).
+ *
+ * See .h for description of the states.
+ *
+ * TODO:
+ * * Self-preservation instinct - if low on stuff, seek home.
+ * * Be active - lay route for playing an explorer, kill enemies on sight.
+ * * Getting around larger stone blocks (i.e. home walls).
+ * * Difficulty levels - adjusts speed, intelligence, aggressivity
+ * * Characters, for that matter - each bot has those attributes laid
+ *                                 differently (sum of them being const, though)
+ * * Some strategy? Defensive - hiding behind stone and in homes,
+ *                              even tricks to move the enemy's attention
+ *                              to another bot?
+ *                  Agressive - ambushing in front of home, getting the start
+ *                              of an enemy?
+ *                  Cooperative - short time defensive/agressive alliances
+ *                                (based on score?)
+ * * Communication - shout at the player.
+ *                 - have an intelligent conversation about weapons,
+ *                   mobile warfare, culture and quantum physics
+ *                   with the player.
+ */
+
+
+/* Operational parameters. Mostly guessing. */
+
+/* Maximal distance on which it is worth shooting. */
+#define GOOD_SHOT_RADIUS    600
+/* How far to back off. 24 is =~ sqrt(16*16+16*16), 16 is height of one
+ * character. */
+#define BACKOFF_DISTANCE    24
+
+/* Threshold values before switching to AI_FLEE. */
+#define MIN_NORMAL_ENERGY   MAX_ENERGY/3
+#define MIN_NORMAL_SHIELDS  MAX_SHIELDS/4
+/* How long after being shot last time should the "revenge" last. */
+#define MAX_REVENGE_TICKS   FPS /* 1s */
+/* Threshold values in revenge mode. */
+#define MIN_REVENGE_ENERGY  MAX_ENERGY/6
+#define MIN_REVENGE_SHIELDS MAX_SHIELDS/6
+/* Threshold values before switching to critical mode (slow speed). */
+#define MAX_CRITICAL_ENERGY MAX_ENERGY/8
+#define MAX_CRITICAL_SHIELDS 0
+
+
+/* Local utility functions */
+
+#if 1
+#define AIDEBUG(x...) DEBUG("[ai] " x);
+#else
+#define AIDEBUG(x...)
+#endif
+
+#define in_revenge(bot_) (bot_->ai.last_shot <= MAX_REVENGE_TICKS)
+
+/* Returns squared distance. */
+static long
+distance (int x1, int y1, int x2, int y2)
+{
+	int dx = x1 - x2, dy = y1 - y2;
+
+	return dx * dx + dy * dy;
+}
+
+/* Determines whether <from> and <target> are well-aligned for shooting. */
+static int
+good_shot (TANK *from, TANK *target)
+{
+	int    fromx, fromy; /* Tank coords */
+	int    dx, dy;       /* Delta */
+	SPOS   bpos;         /* Our virtual bullet */
+
+	tankborder(from->current_pos.sprite, from->angle, &fromx, &fromy);
+	fromx = from->x + fromx*XPOINTS_PERCHAR;
+	fromy = from->y + fromy*YPOINTS_PERCHAR;
+	conv_angle(from->angle, BULLET_SPEED, &dx, &dy);
+	conv_coords(fromx, fromy, from->angle, gfx_bullet, &bpos);
+
+	while (!tank_in_region(target, bpos.sx, bpos.sy,
+                               bpos.sprite->width, bpos.sprite->height)) {
+		/* XXX: Instead, we should divide when calling tank_in_region(). */
+		bpos.sx += dx/XPOINTS_PERCHAR; bpos.sy += dy/YPOINTS_PERCHAR;
+		/* We allow shooting into dirt - we will eventually
+		 * dig to the enemy that way too and it speeds up
+		 * our approach. We don't mind shooting "through"
+		 * other players too, which needs a FIXME when (if)
+		 * we implement botpacks. */
+		if (distance(fromx, fromy, bpos.sx*XPOINTS_PERCHAR, bpos.sy*YPOINTS_PERCHAR) > GOOD_SHOT_RADIUS * GOOD_SHOT_RADIUS
+		    || surface_calc_collision(&bpos, G_STONE))
+			return 0;
+	}
+
+	return 1;
+}
+
+/* Determines the proper angle of <from> heading to <target>. */
+static int
+get_angle (int from_x, int from_y, int target_x, int target_y)
+{
+	/* XXX: Do we get it right even for short distances? */
+
+	/* Yes, I could probably construct a single huge logical expression
+	 * for this. No, I couldn't understand it in 20 seconds then. */
+
+	/* Assuming tanks are always at least 3 chars wide: */
+	if (abs(from_y - target_y) <= 1*YPOINTS_PERCHAR)
+		return 2 + (from_x < target_x ? 0 : 4);
+	if (abs(from_x - target_x) <= 1*XPOINTS_PERCHAR)
+		return 0 + (from_y < target_y ? 4 : 0);
+
+	if (from_x > target_x) {
+		return 4 + (from_y < target_y ? 1 : 3);
+	} else {
+		return 0 + (from_y < target_y ? 3 : 1);
+	}
+}
+
+/* Add some randomization to the angle. */
+static int
+fuzzy_angle (int angle)
+{
+	return (angle + 8 /* compensate for possible negatives */
+	        + (mkrand(7) == 0 ? mkrand(2) - 1 : 0)
+	        + (mkrand(9) == 0 ? mkrand(4) - 2 : 0)) % 8;
+}
+
+/* Compute difference between two angles. */
+static int
+delta_angle (int a1, int a2)
+{
+	int d = abs(a1 - a2);
+	return d < 8 - d ? d : 8 - d;
+}
+
+
+/* External AI control */
+
+void    reset_ai (AI *ai)
+{
+	ai->state = AI_IDLE;
+	ai->idle.tick_count = 0;
+	ai->backing_off = 0;
+	ai->last_shot = 100000;
+}
+
+void    stop_ai_pursue (plr_t *p)
+{
+	plr_t *bot;
+
+	/* Turn the pursuers to confused schizo-bots on lunch. */
+	foreach (bot, players)
+		if (bot->ai.state == AI_HUNT && bot->ai.hunt.pursuing == p)
+			reset_ai(&bot->ai);
+}
+
+
+/* AI hooks */
+
+void    hit_ai_hook (struct _plr_t *victim, struct _plr_t *trespasser)
+{
+	/* TODO: Keep some relation to all players and change the pursuee
+	 * only if relation difference is over certain threshold (increase
+	 * relation for each hostile act, decrease (parabolically) over time).
+	 * --pasky */
+	if (victim->tank.energy >= MIN_REVENGE_ENERGY &&
+	    victim->tank.shields >= MIN_REVENGE_SHIELDS) {
+		victim->ai.state = AI_HUNT;
+		victim->ai.hunt.pursuing = trespasser;
+	}
+	victim->ai.last_shot = 0;
+}
+
+void    stuck_ai_hook (struct _plr_t *victim)
+{
+	/* Don't back off if we can shoot anyway. We are probably
+	 * head-to-head with the enemy. */
+	if (victim->ai.state == AI_HUNT
+	    && good_shot(&victim->tank, &victim->ai.hunt.pursuing->tank))
+		return;
+
+	if (!victim->ai.backing_off) {
+		AIDEBUG("[%d] gonna back off...", victim->id);
+		victim->ai.backoff_x = victim->tank.x;
+		victim->ai.backoff_y = victim->tank.y;
+	}
+	victim->ai.backing_off++;
+
+	AIDEBUG("[%d] backoff old angle %d", victim->id,
+		victim->tank.next_angle);
+	/* Try some other direction (usually _almost_ opposite); we always
+	 * use next_angle instead of angle for maximal variability
+	 * of directions if we are consistently getting stuck in
+	 * one place. */
+	victim->tank.next_angle = fuzzy_angle((mkrand(2) ? 3 : 5)
+	                                      + victim->tank.next_angle);
+	AIDEBUG("[%d] backoff new angle %d", victim->id,
+		victim->tank.next_angle);
+}
+
+static void flee_home(struct _plr_t *bot)
+{
+	int x, y1, y2;
+
+	bot->ai.backing_off = 0;
+	bot->ai.state = AI_FLEE;
+
+	/* TODO: Find the nearest home if it's only matter of energy. */
+
+	/* We decide whether it's nearer to the south entry or the north
+	 * entry. */
+	/* TODO: If home wall in the way, set the target to the home
+	 * corner first. --pasky */
+	bot->ai.flee.home_x = x = bot->tank.home_x;
+	y1 = bot->tank.home_y - gfx_home->sprites[0]->center_y*YPOINTS_PERCHAR;
+	y2 = bot->tank.home_y + gfx_home->sprites[0]->center_y*YPOINTS_PERCHAR;
+	if (distance(bot->tank.x, bot->tank.y, x, y1)
+	    < distance(bot->tank.x, bot->tank.y, x, y2))
+		bot->ai.flee.home_y = y1;
+	else
+		bot->ai.flee.home_y = y2;
+}
+
+
+/* State bot_update handlers */
+
+typedef void (*state_hook)(struct _plr_t *bot);
+
+void    bot_idle (struct _plr_t *bot)
+{
+	/* TODO: Switch to AI_EXPLORE when tick_count is zero. */
+	/* *Yawn*. Make sure we aren't madly running around or something. */
+	bot->tank.speed = 0;
+	bot->tank.fire = 0;
+	AIDEBUG("[%d] idle.", bot->id);
+}
+
+void    bot_hunt (struct _plr_t *bot)
+{
+	plr_t *victim = bot->ai.hunt.pursuing;
+	int angle;
+	int speed = TANK_SPEED;
+
+	assert(victim);
+
+	/* Set the right angle if we didn't yet.
+	 * Possibly change it a bit randomly. */
+	angle = fuzzy_angle(get_angle(bot->tank.x, bot->tank.y,
+	                              victim->tank.x, victim->tank.y));
+	if (angle != bot->tank.angle) {
+		/* Try to make the AI less deadly - slow down when turning
+		 * around. */
+		speed /= delta_angle(angle, bot->tank.angle) + 1;
+		bot->tank.next_angle = angle;
+	}
+
+	/* Shooting angle? (We can sometimes hit the tank's corner even from
+	 * a "wrong" angle.) */
+	if (good_shot(&bot->tank, &victim->tank)) {
+		bot->tank.fire = 1; /* Rat-ta-tat-ta-ra-ta-tat-ta-ta! */
+	}
+
+	/* Pursue on. */
+	bot->tank.speed = speed;
+}
+
+void    bot_flee (struct _plr_t *bot)
+{
+	int angle;
+	int speed = TANK_SPEED;
+
+	AIDEBUG("[%d] Fleeing [%d,%d]->[%d,%d]...", bot->id,
+	        bot->tank.x, bot->tank.y,
+		bot->ai.flee.home_x, bot->ai.flee.home_y);
+
+	/* When we are aligned with the gate, update y to point to the
+	 * middle of the home. We assume the gate is at least 4 chars wide. */
+	if (abs(bot->tank.x - bot->ai.flee.home_x) <= 2*XPOINTS_PERCHAR) {
+		bot->ai.flee.home_y = bot->tank.home_y;
+
+		if (abs(bot->tank.y - bot->ai.flee.home_y) <= 1*YPOINTS_PERCHAR) {
+			bot->ai.state = AI_IDLE;
+			bot->ai.idle.tick_count = FPS / 2;
+			bot_idle(bot);
+			return;
+		}
+	}
+
+	/* TODO: Deal with tanks in the way (agressively >:). --pasky */
+
+	/* Set the right angle if we didn't yet.
+	 * No randomization nor speeding down, we must not waste time. */
+	angle = get_angle(bot->tank.x, bot->tank.y,
+	                  bot->ai.flee.home_x, bot->ai.flee.home_y);
+	if (angle != bot->tank.angle) {
+		bot->tank.next_angle = angle;
+	}
+
+	/* If the energy is critical, use lower speed. */
+	if (bot->tank.energy <= MAX_CRITICAL_ENERGY
+	    || bot->tank.shields <= MAX_CRITICAL_SHIELDS)
+		speed /= 2; /* maximal speed taking only 2PP per tick */
+
+	/* Aieeeeeeeeeee... */
+	bot->tank.speed = speed;
+}
+
+/* Returns 0 if backing off was finished. */
+int     bot_backoff (struct _plr_t *bot)
+{
+	int delta;
+
+	/* Resume normal operation if we already got far enough. */
+	delta = distance(bot->tank.x, bot->tank.y,
+			 bot->ai.backoff_x, bot->ai.backoff_y);
+	if (delta > bot->ai.backing_off * BACKOFF_DISTANCE * BACKOFF_DISTANCE) {
+		AIDEBUG("[%d] backed off! d((%d,%d),(%d,%d))^2 = %d", bot->id,
+		        bot->tank.x, bot->tank.y,
+			bot->ai.backoff_x, bot->ai.backoff_y,
+			delta);
+		bot->ai.backing_off = 0;
+		return 0;
+	}
+
+	AIDEBUG("[%d] backing off in angle %d...", bot->id, bot->tank.angle);
+	/* Angle is set in the stuck hook. If we fail in this
+	 * direction, the stuck hook will strike again and turn
+	 * around another time. Roar ahead! (Well, in fact, roar back. ;-) */
+	bot->tank.speed = TANK_SPEED;
+	bot->tank.fire = 0;
+	return 1;
+}
+
+void    bot_update (struct _plr_t *bot)
+{
+	static const state_hook states[] = {
+		bot_idle,
+		/* bot_explore, */
+		bot_hunt,
+		bot_flee,
+	};
+
+	bot->ai.last_shot++;
+
+	if (bot->ai.state != AI_FLEE
+	    && (bot->tank.energy < (in_revenge(bot) ? MIN_REVENGE_ENERGY : MIN_NORMAL_ENERGY)
+	        || bot->tank.shields < (in_revenge(bot) ? MIN_REVENGE_SHIELDS : MIN_NORMAL_SHIELDS))) {
+		/* Let's better go. */
+		flee_home(bot);
+	}
+
+	if (!bot->ai.backing_off || !bot_backoff(bot))
+		states[bot->ai.state](bot);
+}
Index: ai.h
===================================================================
RCS file: ai.h
diff -N ai.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ai.h	21 Jan 2005 18:13:52 -0000	1.3
@@ -0,0 +1,74 @@
+#ifndef __INCLUDED_AI_H
+#define __INCLUDED_AI_H
+
+#include "game.h"
+
+struct	_plr_t;
+
+/* AI life:
+ * AI_IDLE:    The bot has nothing to do, staying on a single spot.
+ *             It does so until idle_count is zero (it relaxes a little
+ *             after it kills an enemy, more realistic) and then
+ *             <TODO:>
+ *             until it has full energy if it finds inself in a home
+ *             and until it has full shields if it finds itself in
+ *             own home. Then it switches to the AI_EXPLORE state.
+ * AI_EXPLORE: <TODO:>
+ *             The bot plays an explorer. It follows either randomly
+ *             laid route or already digged paths, until it sees
+ *             any enemy (->AI_HUNT).
+ * AI_HUNT:    The bot pursues a chosen enemy and fires at will,
+ *             until it kills the victim (->AI_IDLE for a short period).
+ * AI_FLEE:    <TODO:>
+ *             The bot got to low energy and/or shields.
+ *             It heads to the nearest home (if energy) or to its own
+ *             home (if shields). Then it goes to AI_IDLE.
+ */
+
+typedef struct _ai_t {
+	enum ai_state {
+		AI_IDLE,
+		/* AI_EXPLORE, */
+		AI_HUNT,
+		AI_FLEE,
+		AI_BACKOFF,
+	} state;
+	union {
+		struct {
+			int tick_count;
+		} idle;
+		struct {
+			struct _plr_t *pursuing;
+		} hunt;
+		struct {
+			int home_x, home_y;
+		} flee;
+	};
+
+	/* Backing off is a temporary state when the bot experienced trouble
+	 * during turning around - this state kicks in for a few frames and
+	 * tries to head in a random opposite direction. Then the normal
+	 * state handler takes over again. */
+	/* We try to get to a certain distance from those coordinates. */
+	/* TODO: This is enough for simple stuff, but not if we got entangled
+	 * in a more complicated structure we need to find a way out from.
+	 * That would probably need a completely different algorithm with
+	 * own state etc. (Follow the stone until there is a hole in it.)
+	 * --pasky */
+	int backing_off; /* How far away to back off (in special units);
+	                  * 0 means no backing off. */
+	int backoff_x, backoff_y;
+
+	/* How many ticks ago did we get shot the last time? */
+	long last_shot;
+} AI;
+
+void    hit_ai_hook (struct _plr_t *victim, struct _plr_t *trespasser);
+void    stuck_ai_hook (struct _plr_t *victim);
+
+void    reset_ai (AI *ai);
+void    stop_ai_pursue (struct _plr_t *p);
+
+void    bot_update (struct _plr_t *bot);
+
+#endif
Index: client.c
===================================================================
RCS file: /home/cvs/tunneler/tunneler/client.c,v
retrieving revision 1.1.1.2
retrieving revision 1.6
diff -u -r1.1.1.2 -r1.6
--- client.c	20 Jan 2005 22:15:18 -0000	1.1.1.2
+++ client.c	21 Jan 2005 00:41:12 -0000	1.6
@@ -3,12 +3,14 @@
 #include <stdlib.h>
 #include <assert.h>
 #include <signal.h>
+#include <sysexits.h>
 #include "xmalloc.h"
 #include "debug.h"
 #include "udp.h"
 #include "types.h"
 #include "packet.h"
 #include "term.h"
+#include "globgame.h"
 #include "sprite.h"
 #include "state.h"
 #include "virtual.h"
@@ -30,8 +32,30 @@
 _u8	doexit = 0, cansend=0;
 struct	timeval tv;
 
-_s8	drchr[] = { -8, 5, 4, 3, 6, -16, 2, 7, 0, 1 };
-_u16	key_speed=0, key_direction=0, key_shot=0;
+/* Options */
+int multispeed = 0;
+
+/* Keybindings */
+enum key_action {
+	ACT_NONE,
+	ACT_FIRE,
+	ACT_UP, ACT_URIGHT, ACT_RIGHT, ACT_DRIGHT, ACT_DOWN, ACT_DLEFT, ACT_LEFT, ACT_ULEFT,
+	ACT_ACCEL, ACT_DECCEL,
+	ACT_TALK, ACT_EXIT,
+	ACT_MAX
+};
+static enum key_action kbdbind[256];
+const char *key_actnames[] = {
+	"none",
+	"fire",
+	"up", "uright", "right", "dright", "down", "dleft", "left", "uleft",
+	"accel", "deccel",
+	"talk", "exit",
+};
+int talk_mode = 0;
+
+/* Direction and speed */
+int     speed = 0, key_direction = 0, key_shot = 0;
 
 GFXOBJ	*gfx_obj[4];
 
@@ -52,7 +76,7 @@
 	tick++;
 	tv.tv_sec = 0;
 	tv.tv_usec = TICKMS*1000;
-	pck_send_cupdate(server, cur_ack, (key_speed?128:0) + (key_shot?64:0) + key_direction);
+	pck_send_cupdate(server, cur_ack, (speed<<3) + (key_shot?128:0) + key_direction);
 }
 
 void	game_packet(pck_t *pck)
@@ -99,6 +123,83 @@
 }
 
 
+void    load_kbdbind(void)
+{
+	FILE *f;
+	char line[128];
+	int lineno;
+
+	/* TODO: sysconfpath or smt. --pasky */
+	f = fopen("kbdbind.conf", "r");
+	if (!f) {
+		perror("kbdbind.conf");
+		exit(EX_UNAVAILABLE);
+	}
+
+	for (lineno = 0; fgets(line, 128, f); lineno++) {
+		/* <char><sep><..action..> (<sep> can be anything, usually \t) */
+		/* /^##/ is a comment; blank lines permitted */
+		unsigned char key = *line, *action = line + 2;
+		enum key_action i;
+
+		if (line[0] == '\0') continue; /* blank line */
+		if (line[0] == '#' && line[1] == '#') continue; /* comment */
+
+		action[strlen(action) - 1] = 0; /* chop */
+		for (i = 0; i < ACT_MAX; i++) {
+			if (!strcasecmp(action, key_actnames[i])) {
+				kbdbind[key] = i;
+				goto next_line;
+			}
+		}
+
+		fprintf(stderr, "kbdbind.conf: Unknown action %s on line %d\n", action, lineno);
+next_line:;
+	}
+}
+
+void    input_char(int chr)
+{
+	switch (kbdbind[chr]) {
+		case ACT_FIRE:    key_shot = !key_shot; break;
+
+		case ACT_UP:      key_direction = 0; break;
+		case ACT_URIGHT:  key_direction = 1; break;
+		case ACT_RIGHT:   key_direction = 2; break;
+		case ACT_DRIGHT:  key_direction = 3; break;
+		case ACT_DOWN:    key_direction = 4; break;
+		case ACT_DLEFT:   key_direction = 5; break;
+		case ACT_LEFT:    key_direction = 6; break;
+		case ACT_ULEFT:   key_direction = 7; break;
+
+		case ACT_ACCEL:
+			if (multispeed) {
+				if (speed < 1) speed = 1;
+				speed *= 2;
+				if (speed > TANK_SPEED) speed = TANK_SPEED;
+			} else {
+				speed = TANK_SPEED;
+			}
+			break;
+
+		case ACT_DECCEL:
+			if (multispeed) {
+				speed /= 2;
+			} else {
+				speed = 0;
+			}
+			break;
+
+		case ACT_TALK:    talk_mode = 1; break;
+		case ACT_EXIT:    doexit = 1; break;
+
+		case ACT_NONE:    break;
+		default:          assert(0); break;
+	}
+	DEBUG("keys: %d %d %d", key_shot, speed, key_direction);
+}
+
+
 void	parse(void)
 {
 	_u8	*ptr;
@@ -202,24 +303,20 @@
 			else
 				tv.tv_sec = 1;
 		} else if (FD_ISSET(0, &fdt)) {
-			DEBUG("*** KEY...");
 			read(0, &chr, 1);
-			if (chr == ' ') {
-				key_shot = !key_shot;
-			} else if (chr >= '0' && chr <= '9') {
-				chr = drchr[chr-'0'];
-				if (chr < 0)
-					key_speed = -chr-8;
-				else 
-					key_direction = chr;
-			} else if (chr >= 'a' && chr <= 'z') {
-				*mesgp++ = chr;
-			} else if (chr == '\n') {
-				*mesgp = 0;
-				sendmssg(message, mesgp-message);
-				mesgp = message;
+			DEBUG("*** KEY...%c", chr);
+			if (talk_mode) {
+				if (chr >= 'a' && chr <= 'z') {
+					*mesgp++ = chr;
+				} else if (chr == '\n') {
+					*mesgp = 0;
+					sendmssg(message, mesgp-message);
+					mesgp = message;
+					talk_mode = 0;
+				}
+			} else {
+				input_char(chr);
 			}
-			//DEBUG("keys: %d %d %d", key_shot, key_speed, key_direction);
 		} else if (FD_ISSET(server->fd, &fdt)) {
 			cansend = 1;
 			DEBUG("*** UDP PACKET");
@@ -242,7 +339,7 @@
 	udp_close(server);
 	conn_destroy(chatserver);
 	printf("bye...\n");
-	exit(0);
+	exit(EX_OK);
 }
 
 
@@ -255,10 +352,12 @@
 	debuglog("client.log");
 	DEBUG("tunneler client starting...");
 	if (argc < 6) {
-		printf("tunneler client -- 2005,ment\nusage: %s <ip> <udpport> <tcpport> <nickname> <fps>\n", argv[0]);
-		exit(1);
+		printf("tunneler client -- 2005,ment\nusage: %s <ip> <udpport> <tcpport> <nickname> <fps> [opts]\n", argv[0]);
+		exit(EX_USAGE);
 	}
 
+	load_kbdbind();
+
 	gfx_obj[0] = load_gfxobj("tank", 8);
 	gfx_obj[1] = load_gfxobj("explosion", 8);
 	gfx_obj[2] = load_gfxobj("bullet", 8);
@@ -272,9 +371,12 @@
 	server = udp_client(argv[1], uport);
 	if (server->state == UCS_ERROR) {
 		perror("socket");
-		exit(43);
+		exit(EX_OSERR);
 	}
 
+	/* Options */
+	multispeed = (argc > 6 && argv[6] && strchr(argv[6], 's') != NULL);
+
 	chatserver = conn_connect_resolv(argv[1], tport);
 	if (chatserver->state != CS_CONNECTED) {
 		perror("connect");
@@ -291,12 +393,12 @@
 	bufp = bytef(buffer, 128, "wwwwq", CH_JOIN, strlen(nickname), fps, udp_localport(server), nickname, strlen(nickname));
 	if (!bufp) {
 		printf("cannot format buffer!?\n");
-		exit(44);
+		exit(EX_SOFTWARE);
 	}
 	write(chatserver->fd, buffer, bufp-buffer);
 
 	DEBUG("socket created, looping");
 	mainloop();
-	return 0;
+	return EX_OK;
 }
 
Index: fcomp.patch
===================================================================
RCS file: fcomp.patch
diff -N fcomp.patch
--- fcomp.patch	20 Jan 2005 17:47:44 -0000	1.1.1.1
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1,264 +0,0 @@
---- viewpoint.old	2005-01-20 01:16:56.000000000 +0000
-+++ viewpoint.c	2005-01-20 17:43:31.000000000 +0000
-@@ -2,12 +2,10 @@
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
-+#include <assert.h>
- #include "viewpoint.h"
- #include "debug.h"
- 
--
--#define PACK(s) 	(((s)[0]&3) + (((s)[1]&3)<<2) + (((s)[2]&3)<<4) + (((s)[3]&3)<<6))
--#define UNPACK(s,n) 	do { (s)[0] = (n)&3; (s)[1]=((n)>>2)&3; (s)[2]=((n)>>4)&3; (s)[3]=((n)>>6)&3; } while(0)
- #define	MOVE(x,y,dst, src); do { \
- 	_s32 _size = yoff*VIEWPNT_WIDTH + xoff; \
- 		DEBUG("size: %d", _size); \
-@@ -17,96 +15,170 @@
- 		memcpy(((_u8 *)dst), ((_u8 *)src)-_size, VIEWPNT_SIZE+_size); \
- } while(0);
- 
-+/* fractal compression by sd@fucksheep.org */
-+#define MAXDEPTH 4096
-+int	diffstack[MAXDEPTH];
-+int	diffstack_ptr;
-+#define STACK_INIT() diffstack_ptr = MAXDEPTH;
-+#define PUSH(x) { assert(diffstack_ptr); diffstack[--diffstack_ptr] = x; }
-+#define POP() diffstack[diffstack_ptr++]
-+_u32	*bitptr;	/* pointer to bitmap */
-+int	bitsleft;	/* number of bits left */
-+
-+
-+#define INIT_BITS(ptr,w) \
-+{ \
-+	bitptr = (void *) ptr; \
-+	if (w) \
-+		*bitptr = 0; \
-+	bitsleft = 32; \
-+}
-+#define PUT_BITS(x,n) \
-+{ \
-+	if (bitsleft < n) { \
-+		*bitptr++ |= x & ((1<<bitsleft)-1); \
-+		*bitptr = 0; \
-+		*bitptr |= (x>>bitsleft) << (bitsleft = 32 - (n-bitsleft)); \
-+	} else { \
-+		*bitptr |= x << (bitsleft = bitsleft - n); \
-+	} \
-+}
-+#define GET_BITS(to, n) \
-+{ \
-+	if (bitsleft<n) { \
-+		to = (*bitptr & ((1<<bitsleft)-1)) | ((*(bitptr+1) >> (32 - (n-bitsleft))) << bitsleft); \
-+		bitptr++; \
-+		bitsleft = 32 - (n-bitsleft); \
-+	} else { \
-+		bitsleft -= n; \
-+		to = (*bitptr >> bitsleft) & ((1<<n)-1); \
-+	} \
-+}
-+
-+#define PIXEL(buf,x,y) buf[x + y*VIEWPNT_WIDTH]
-+
-+/* compare two regions, return 0 if same, 1 if not */
-+static	inline int cmp_region(_u8 *a, _u8 *b, int x, int y, int w, int h)
-+{
-+	int sy, ey;
-+
-+	ey = y + h;
-+	for (sy = y; sy < ey; sy++)
-+		if (memcmp(&PIXEL(a, x, sy), &PIXEL(b, x, sy), w))
-+			return 1;
-+	return 0;
-+}
-+
- /*
--   	tohle by se dalo jeste celkem zoptimalizovat pouzitim memrdiff a ne memcmp :/
--	(a taky do toho nacpat RLE)
-+ * 1|2
-+ *--|--
-+   3|4
-+ *
-  */
--#define	COEF	4
--_u8	*diff (_u8 *old, _u8 *new, int len, _u8 *out, _u8 *end)
-+
-+
-+/* fractal tree compression */
-+_u8	*diff(_u8 *old, _u8 *new, int len, _u8 *out, _u8 *end)
- {
--	_u8 cnt=0, val;
--	//DEBUG("old: %x new: %x len: %d out: %x end: %x", old, new, len, out, end);
--	while (len && out != end) {
--		if (*(_u32 *) old == *(_u32 *) new) {
--			if (++cnt >= 127) {
--				*out++ = cnt;
--				cnt = 0;
--			}
--			new += 4;
--			old += 4;
--			len --;
--		} else {
--			if (cnt > 0)
--				*out++ = cnt;
--			if (out == end)
--				break;
--			cnt = 0;
--
--			while (cnt < 128-COEF && end - out > cnt + 1) {
--				if (len < COEF) {
--					cnt += len;
--					len = 0;
--					goto write;
--				}
--				if (! memcmp(old+cnt*4, new+cnt*4, COEF*4))
--					goto write;
--				cnt += COEF;
--				len -= COEF;
-+	int x, y, w, h;
-+	/* stack emulation */
-+	STACK_INIT();
-+	/* bit output */
-+	INIT_BITS(out, 1);
-+	PUSH(0);
-+	PUSH(0); PUSH(0); PUSH(VIEWPNT_WIDTH); PUSH(VIEWPNT_HEIGHT);
-+	PUSH(1);
-+
-+	while (1) {
-+		if (!POP())
-+			break;
-+		h = POP();
-+		w = POP();
-+		y = POP();
-+		x = POP();
-+		/* cannot be - ignore */
-+		if (!w || !h)
-+			continue;
-+		/* here we go :) */
-+		/* is the region same? */
-+		if (cmp_region(old, new, x, y, w, h)) {
-+//			printf("region %d %d, %d %d not same, old=%c new=%c\n", x, y, w, h, PIXEL(old, x, y), PIXEL(new, x, y));
-+			PUT_BITS(0, 1);
-+	
-+			/* did we reached the infinity? */
-+			if ((w == 1) && (h == 1)) {
-+				_u32 pix = PIXEL(new, x, y);
-+				PUT_BITS(pix, 2);
-+				continue;
- 			}
--write:
--			*out++ = 128+cnt;
--			while (cnt--) {
--				val = PACK(new);
--				*out++ = val;
--				old += 4;
--				new += 4;
--			}
--			cnt = 0;
-+
-+			/* first quadrant */
-+			PUSH(x); PUSH(y); PUSH(w/2); PUSH(h/2); PUSH(1);
-+			/* second quadrant */
-+			PUSH(x + w/2); PUSH(y); PUSH(w-(w/2)); PUSH(h/2); PUSH(1);
-+			/* third quadrant */
-+			PUSH(x); PUSH(y + h/2); PUSH(w/2); PUSH(h-h/2); PUSH(1);
-+			/* fourth quadrant */
-+			PUSH(x + w/2); PUSH(y + h/2); PUSH(w-w/2); PUSH(h-h/2); PUSH(1);
-+			continue;
- 		}
-+		/* they're same, no data */
-+		PUT_BITS(1, 1);
-+//		printf("region %d %d, %d %d are same\n", x, y, w, h);
- 	}
--	if (out == end)
-+	if (((_u8 *) (bitptr + 1)) > end)
- 		return NULL;
--	if (cnt > 0)
--		*out++ = cnt;
--	return out;
-+	return (void *) (bitptr + 1);
- }
- 
--_u8	*patch (_u8 *old, _u8 *oend, _u8 *patch, _u8 *pend)
-+_u8	*patch(_u8 *old, _u8 *oend, _u8 *patch, _u8 *pend)
- {
--	_u8 c, n;
--	_u8 *od = old;
--
--	while (patch != pend) {
--		c = *patch++;
--		n = c&127;
--		if (c&128) {
--			if (old+n*4 > oend) {
--				DEBUG("write(a) behind end of data %d : %d", old-od, n*4);
--				return NULL;
--			} 
--			if (patch+n > pend) {
--				DEBUG("jump behind end of patch");
--				return NULL;
-+	int x, y, w, h;
-+	/* stack emulation */
-+	STACK_INIT();
-+	/* bit output */
-+	INIT_BITS(patch, 0);
-+	PUSH(0);
-+	PUSH(0); PUSH(0); PUSH(VIEWPNT_WIDTH); PUSH(VIEWPNT_HEIGHT);
-+	PUSH(1);
-+	
-+	while (1) {
-+		_u32 bit;
-+		if (!POP())
-+			break;
-+		h = POP();
-+		w = POP();
-+		y = POP();
-+		x = POP();
-+		/* cannot be - ignore */
-+		if (!w || !h)
-+			continue;
-+
-+		/* here we go :) */
-+		/* is the region same? */
-+		GET_BITS(bit, 1)
-+		if (!bit) {
-+//			printf("region %d %d, %d %d not same\n", x, y, w, h);
-+			/* did we reached the infinity? */
-+			if ((w == 1) && (h == 1)) {
-+				GET_BITS(bit, 2);
-+				PIXEL(old, x, y) = bit;
-+				continue;
- 			}
--			while(n--) {
--				c = *patch++;
--				UNPACK(old, c);
--				old+=4;
--			}
--		} else {
--			if (old+n*4 > oend) {
--				DEBUG("write(b) behind end of data %d %d : %d", old-od, oend-od, n*4);
--				return NULL;
--			}
--			while (n--)
--				old += 4;
-+
-+			/* first quadrant */
-+			PUSH(x); PUSH(y); PUSH(w/2); PUSH(h/2); PUSH(1);
-+			/* second quadrant */
-+			PUSH(x + w/2); PUSH(y); PUSH(w-(w/2)); PUSH(h/2); PUSH(1);
-+			/* third quadrant */
-+			PUSH(x); PUSH(y + h/2); PUSH(w/2); PUSH(h-h/2); PUSH(1);
-+			/* fourth quadrant */
-+			PUSH(x + w/2); PUSH(y + h/2); PUSH(w-w/2); PUSH(h-h/2); PUSH(1);
-+			continue;
- 		}
-+//		printf("region %d %d, %d %d are same\n", x, y, w, h);
- 	}
--	if (old != oend) {
--		DEBUG("BAD PATCH");
--		return NULL;
--	}
--	return old;
-+	return oend;
- }
- 
- 
Index: game.c
===================================================================
RCS file: /home/cvs/tunneler/tunneler/game.c,v
retrieving revision 1.1.1.2
retrieving revision 1.6
diff -u -r1.1.1.2 -r1.6
--- game.c	20 Jan 2005 22:15:18 -0000	1.1.1.2
+++ game.c	21 Jan 2005 16:30:23 -0000	1.6
@@ -8,9 +8,10 @@
 _u8	surface[SURFACE_WIDTH][SURFACE_HEIGHT];
 BULLET	bullets;
 SPOS	sprites;
+BOT     ferda;
 
-#define	IF_INTERSEC(x1,y1,w1,h1,x2,y2,w2,h2) \
-	if (((x1)+(w1) > (x2)) && ((y1)+(h1) > (y2)) && ((x1) <= (x2)+(w2)) && ((y1) <= (y2)+(h2)))
+#define	INTERSEC(x1,y1,w1,h1,x2,y2,w2,h2) \
+	(((x1)+(w1) > (x2)) && ((y1)+(h1) > (y2)) && ((x1) <= (x2)+(w2)) && ((y1) <= (y2)+(h2)))
 
 /****************************************************************************/
 
@@ -67,7 +68,7 @@
 	DEBUG("x:%d y:%d angle:%d sx:%d sy:%d", x, y, angle, p->sx, p->sy);
 }
 
-void	conv_angle(_u16 angle, _s16 add, _s16 *ax, _s16 *ay)
+void	conv_angle(_u16 angle, _s16 add, int *ax, int *ay)
 {
 	*ax = *ay = 0;
 
@@ -84,7 +85,7 @@
 	DEBUG("angle:%d speed:%d ax:%d ay:%d", angle, add, *ax, *ay);
 }
 
-void    tankborder(SPRITE *s, _u16 angle, _s16 *sx, _s16 *sy)
+void    tankborder(SPRITE *s, _u16 angle, int *sx, int *sy)
 {
         *sx = -s->center_x;
         *sy = -s->center_y;
@@ -154,11 +155,11 @@
 			surface[s->sx+i][s->sy+j] = set_to;
 }
 
-void	player_move (plr_t *p, _u8 direction, _u8 move, _u8 firing)
+void	player_move (plr_t *p, _u8 direction, _u8 speed, _u8 firing)
 {
 	if (direction != p->tank.angle)
 		p->tank.next_angle = direction%8;
-	p->tank.speed = move?TANK_SPEED:0;
+	p->tank.speed = speed;
 	if (p->tank.firing != firing) {
 		p->tank.fire = 1;
 		p->tank.firing = firing;
@@ -172,7 +173,7 @@
 BULLET	*new_bullet (TANK *t)
 {
 	BULLET	*b = ALLOC(sizeof(BULLET));
-	_s16 	sx, sy;
+	int 	sx, sy;
 
 	tankborder(t->current_pos.sprite, t->angle, &sx, &sy);
 	
@@ -203,6 +204,7 @@
 
 void	explode(plr_t *p)
 {
+	stop_ai_pursue(p);
 	p->tank.exploding = 1;
 	surface_drc_sprite(&(p->tank.current_pos), G_EMPTY, G_HOME);
 	p->tank.current_pos.gfxid = GFX_EXPL_ID;
@@ -210,17 +212,25 @@
 	return;
 }
 
+void    reset_tank(TANK *tank)
+{
+	tank->x = tank->home_x;
+	tank->y = tank->home_y;
+	tank->speed = tank->speed_deficit = 0;
+	tank->angle = 0;
+	tank->next_angle = -1;
+	tank->fire = tank->firing = 0;
+	tank->energy = MAX_ENERGY;
+	tank->shields = MAX_SHIELDS;
+	tank->exploding = 0;
+}
+
 void	spawn(plr_t *p)
 {
-	p->tank.x = p->tank.home_x;
-	p->tank.y = p->tank.home_y;
-	p->tank.speed = p->tank.speed_deficit = 0;
-	p->tank.angle = 0;
-	p->tank.next_angle = -1;
-	p->tank.fire = p->tank.firing = 0;
-	p->tank.energy = MAX_ENERGY;
-	p->tank.shields = MAX_SHIELDS;
-	p->tank.exploding = 0;
+	reset_tank(&p->tank);
+	stop_ai_pursue(p); /* Just to be sure... */
+	if (player_is_ai(p))
+		reset_ai(&p->ai);
 
 	p->tank.current_pos.gfxid = GFX_TANK_ID;
 	p->tank.current_pos.color = p->tank.color;
@@ -239,32 +249,39 @@
 		send->score++;
 		explode(recv->player);
 	}
+	if (player_is_ai(recv->player))
+		hit_ai_hook(recv->player, send->player);
 }
 
 /* 
-   	FERDA
+   	FERDA (da ultimate NPC)
 */
-#if 0
 void	runferda(void)
 {
 	BOT	*b = &ferda;
 	_u16 	i, best=8, min=100, max=0, eat;
-	_s16	x_add, y_add;
+	int	x_add, y_add;
 	SPOS 	new_pos = b->current_pos, *np = &new_pos, *cp = &(b->current_pos);
 
-	printf("mypos: %d %d\n", b->x, b->y);
+	/*printf("mypos: %d %d\n", b->x, b->y);*/
 	surface_drc_sprite(cp, G_DIRT, G_EMPTY);
 	for (i=0; i<8; i+=2) {
-		conv_angle(i, b->speed, &x_add, &y_add);
+		conv_angle(i, 24 /* b->speed, but wider radius */, &x_add, &y_add);
 		conv_coords(b->x+x_add, b->y+y_add, 1, gfx_home, np);
-		if (! surface_calc_collision(np, G_STONE|G_PLAYER|G_HOME)) {
-			eat = surface_calc_collision(np, G_DIRT);
-			if (eat < min) {
-				min = eat;
-				best = i;
-			} 
-			if (eat > max) 
-				max = eat;
+
+		if (surface_calc_collision(np, G_STONE|G_PLAYER|G_HOME))
+			continue;
+
+		eat = surface_calc_collision(np, G_DIRT);
+		if (eat == np->sprite->width * np->sprite->height /* all dirt */)
+			continue;
+
+		if (eat < min) {
+			min = eat;
+			best = i;
+		}
+		if (eat > max) {
+			max = eat;
 		}
 	}
 	
@@ -277,7 +294,6 @@
 		surface_drc_sprite(cp, G_STONE, 0);
 	}
 }
-#endif
 
 
 #define	MIN(a,b) ((a)<(b)?(a):(b))	/*predefinovat na inlajn! */
@@ -286,8 +302,9 @@
 	TANK	*t = &(p->tank);
 	plr_t	*q;
 	SPOS 	new_pos = t->current_pos, *np = &new_pos, *cp = &(t->current_pos);
-	_s16	x_add, y_add;
+	int	x_add, y_add;
 	_u16	dirt;
+	int     got_stuck = 0;
 
 
 	if (t->exploding != 0) {
@@ -311,10 +328,13 @@
 
 	if (t->next_angle != -1) {
 		conv_coords(t->x, t->y, t->next_angle, gfx_tank, np);
-		if (! surface_calc_collision(np, G_STONE|G_PLAYER)) 
+		if (! surface_calc_collision(np, G_STONE|G_PLAYER)) {
 			t->angle = t->next_angle;
-		else
+		} else {
+			DEBUG("[%d] STUCK, need backoff", p->id);
 			t->speed = 0;
+			got_stuck = 1;
+		}
 	}
 
 	if (t->speed_deficit >= t->speed) {
@@ -333,7 +353,9 @@
 		t->speed_deficit = dirt * DIRT_SLOWING;
 		*cp = new_pos;
 	} else {
+		DEBUG("[%d] STUCKtoo, need backoff", p->id);
 		t->speed = t->speed_deficit = 0;
+		got_stuck = 1;
 	}
 
 	surface_drc_sprite(cp, G_PLAYER, G_HOME);
@@ -350,7 +372,7 @@
 		cp = &(p->tank.home_pos);
 		foreach (q, players) {
 			np = &(q->tank.current_pos);
-			IF_INTERSEC(np->sx, np->sy, np->sprite->width, np->sprite->height, cp->sx, cp->sy, cp->sprite->width, cp->sprite->height) {
+			if (INTERSEC(np->sx, np->sy, np->sprite->width, np->sprite->height, cp->sx, cp->sy, cp->sprite->width, cp->sprite->height)) {
 				_u16 adde = (p==q?ADDE_HOME:ADDE_FOREIGN);
 				_u16 adds = (p==q?ADDS_HOME:0);
 				if (q->tank.energy < MAX_ENERGY && (q->tank.energy += adde) > MAX_ENERGY)
@@ -362,7 +384,7 @@
 	}
 
 	if (t->speed > 0) 
-		t->energy -= 3;
+		t->energy -= 2 + (t->speed >> 3);
 	else if (tick % 3)
 		t->energy -= 1;
 
@@ -370,20 +392,28 @@
 		t->score--;
 		explode(p);
 	}
+
+	if (got_stuck && player_is_ai(p))
+		stuck_ai_hook(p);
 }
 
 
+int     tank_in_region(TANK *tank, int x, int y, int wx, int wy)
+{
+	SPOS *cp = &(tank->current_pos);
+
+	//DEBUG("x1(%d)y1(%d)w1(%d)h1(%d) x2(%d)y2(%d)w1(%d)h1(%d)", x,y,wx,wy,cp->sx,cp->sy
+	return INTERSEC(x,y,wx,wy,cp->sx,cp->sy,cp->sprite->width,cp->sprite->height);
+}
+
 plr_t	*find_player_by_xy (_u16 x, _u16 y, _u16 wx, _u16 wy)
 {
 	plr_t	*p;
-	SPOS 	*cp;
-	foreach(p, players) {
-		cp = &(p->tank.current_pos);
-		//DEBUG("x1(%d)y1(%d)w1(%d)h1(%d) x2(%d)y2(%d)w1(%d)h1(%d)", x,y,wx,wy,cp->sx,cp->sy
-		IF_INTERSEC(x,y,wx,wy,cp->sx,cp->sy,cp->sprite->width,cp->sprite->height) {
+
+	foreach(p, players)
+		if (tank_in_region(&p->tank, x, y, wx, wy))
 			return p;
-		}
-	}
+
 	return NULL;
 }
 
@@ -459,7 +489,7 @@
 	i = 0;
 
 	foreach(s, sprites) {
-		IF_INTERSEC(sx,sy,VIEWPNT_WIDTH,VIEWPNT_HEIGHT,s->sx, s->sy, s->sprite->width, s->sprite->height) {
+		if (INTERSEC(sx,sy,VIEWPNT_WIDTH,VIEWPNT_HEIGHT,s->sx, s->sy, s->sprite->width, s->sprite->height)) {
 			v->obj[i].ox = s->sx - sx;
 			v->obj[i].oy = s->sy - sy;
 			v->obj[i].angle = ((s->angle)&7) + (((s->gfxid)&3)<<3) + (((s->color)&7)<<5);
@@ -522,6 +552,30 @@
 }
 
 
+void    init_ferda(BOT *bot)
+{
+	while (1) {
+		bot->x = mkrand((SURFACE_WIDTH - 40) * XPOINTS_PERCHAR);
+		bot->y = mkrand((SURFACE_HEIGHT - 20) * YPOINTS_PERCHAR);
+		conv_coords(bot->x, bot->y, 1, gfx_home, &(bot->current_pos));
+		if (!surface_calc_collision(&(bot->current_pos), G_STONE|G_HOME))
+			break;
+	}
+	bot->speed = 4;
+	bot->current_pos.gfxid = GFX_HOME_ID;
+	bot->current_pos.color = 7;
+	bot->dir = 0;
+	add_to_list_end(sprites, (&(bot->current_pos)));
+}
+
+void    init_players(void)
+{
+	init_list(players);
+
+	if (do_ferda) init_ferda(&ferda);
+}
+
+
 void	init_player(plr_t *p, _u32 id)
 {
 	p->tank.score = 0;
@@ -556,6 +610,7 @@
 			FREE(b);
 		}
 	}
+	stop_ai_pursue(p);
 
 	surface_drc_sprite(&(p->tank.current_pos), G_EMPTY, G_DIRT);
 	del_from_list(&(p->tank.current_pos));
Index: game.h
===================================================================
RCS file: /home/cvs/tunneler/tunneler/game.h,v
retrieving revision 1.1.1.1
retrieving revision 1.5
diff -u -r1.1.1.1 -r1.5
--- game.h	20 Jan 2005 17:39:53 -0000	1.1.1.1
+++ game.h	21 Jan 2005 16:30:23 -0000	1.5
@@ -1,5 +1,8 @@
 #ifndef __INCLUDED_GAME_H
 #define __INCLUDED_GAME_H
+
+#include "globgame.h"
+
 #include "types.h"
 #include "sprite.h"
 #include "viewpoint.h"
@@ -18,7 +21,6 @@
 #define G_BULLET	16
 
 #define	BULLET_SPEED	24
-#define	TANK_SPEED	16
 #define BULLET_POWER	8
 #define DIRT_SLOWING	4
 #define BULLET_BURST	4
@@ -69,27 +71,33 @@
 
 	LIST_HEAD(struct _bullet_t);
 	_u16	x, y;
-	_s16 	xadd, yadd;
+	int 	xadd, yadd;
 	_s16	power;
 	_u16	angle;
 	TANK	*owner;
 } BULLET;
 
-#if 0
 typedef struct _bot_t {
 	SPOS	current_pos;
 	_u16	x, y, speed, dir;
 } BOT;
-#endif
 
 
 extern	_u8 surface[SURFACE_WIDTH][SURFACE_HEIGHT];
 
+_u32	mkrand(long max);
+void    tankborder(SPRITE *s, _u16 angle, int *sx, int *sy);
+int     tank_in_region(TANK *tank, int x, int y, int wx, int wy);
+void	conv_angle(_u16 angle, _s16 add, int *ax, int *ay);
+void	conv_coords(int x, int y, int angle, GFXOBJ *g, SPOS *p);
+_u16	surface_calc_collision(SPOS *s, _u8 flags);
+
 void	player_update (struct _plr_t *p);
 void	bullets_update (void);
-void	player_move (struct _plr_t *p, _u8 direction, _u8 move, _u8 firing);
+void	player_move (struct _plr_t *p, _u8 direction, _u8 speed, _u8 firing);
 void	calc_view(TANK *t, VIEWPOINT *v);
 void	init_surface (void);
+void	init_players (void);
 void	init_player (struct _plr_t *p, _u32 id);
 void	finit_player (struct _plr_t *p);
 void	runferda(void);
Index: globgame.h
===================================================================
RCS file: globgame.h
diff -N globgame.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ globgame.h	20 Jan 2005 22:44:51 -0000	1.1
@@ -0,0 +1,8 @@
+#ifndef __INCLUDED_GLOBGAME_H
+#define __INCLUDED_GLOBGAME_H
+
+/* Game stuff shared by both the server and the client. */
+
+#define	TANK_SPEED	15
+
+#endif
Index: kbdbind.conf
===================================================================
RCS file: kbdbind.conf
diff -N kbdbind.conf
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ kbdbind.conf	21 Jan 2005 00:41:12 -0000	1.2
@@ -0,0 +1,24 @@
+## Format: <character><separator><action>
+## Note that <separator> can be anything, but only a _single_ character.
+## Using [TAB] as <separator> is recommended.
+
+ 	fire
+
+8	up
+9	uright
+6	right
+3	dright
+2	down
+1	dleft
+4	left
+7	uleft
+
+5	accel
+0	deccel
+
+## Stunts/Grandprix like:
+a	accel
+z	deccel
+
+t	talk
+q	exit
Index: packet.h
===================================================================
RCS file: /home/cvs/tunneler/tunneler/packet.h,v
retrieving revision 1.1.1.2
retrieving revision 1.3
diff -u -r1.1.1.2 -r1.3
--- packet.h	20 Jan 2005 22:15:18 -0000	1.1.1.2
+++ packet.h	20 Jan 2005 22:26:14 -0000	1.3
@@ -44,6 +44,10 @@
 		} su;
 		struct {
 			_u16	ackseq;
+			/* Bitmap:
+			 * 0..2: direction
+			 * 3..6: speed
+			 * 7   : firing */
 			_u8	direction;
 		} cu;
 	};
Index: server.c
===================================================================
RCS file: /home/cvs/tunneler/tunneler/server.c,v
retrieving revision 1.1.1.2
retrieving revision 1.6
diff -u -r1.1.1.2 -r1.6
--- server.c	20 Jan 2005 22:15:18 -0000	1.1.1.2
+++ server.c	21 Jan 2005 18:13:22 -0000	1.6
@@ -24,6 +24,10 @@
 plr_t 	players;
 _u16	num_players;
 
+/* Options */
+int     do_ferda = 0;
+int     bots_to_spawn = 0;
+
 GFXOBJ	*gfx_tank, *gfx_bullet, *gfx_home, *gfx_explosion;
 
 
@@ -37,7 +41,7 @@
 {
 	plr_t *p;
 	foreach(p, players) 
-		if (p->initialized) 
+		if (p->initialized && !player_is_ai(p)) 
 			unicast(p, msg, len);
 }
 
@@ -58,7 +62,7 @@
 	DEBUG("::: BCAST_JOIN @%d as '%s'", p->id, p->name);
 	bytef(msg1, 40, "wwwwq", CH_MSGJOIN, len1, p->id, p->tank.score, p->name, len1);
 	foreach (q, players) {
-		if (q->initialized) {
+		if (q->initialized && !player_is_ai(q)) {
 			len2 = strlen(q->name);
 			bytef(msg2, 40, "wwwwq", CH_MSGJOIN, len2, q->id, q->tank.score, q->name, len2);
 			if (p != q)
@@ -104,6 +108,19 @@
 	return p;
 }
 
+plr_t   *alloc_bot (void)
+{
+	plr_t *p;
+	/* FIXME: No CALLOC :-(. */
+	p = (plr_t *) calloc(1, sizeof(plr_t));
+	p->id = newobj();
+	p->initialized = 0;
+	reset_ai(&p->ai);
+
+	add_to_list(players, p);
+	return p;
+}
+
 void	join (plr_t *p)
 {
 	if (p->initialized)
@@ -127,14 +144,50 @@
 		finit_player(p);
 		free(p->name);
 
-		p->udp->fd = -1;
-		udp_destroy(p->udp);
+		if (p->udp) {
+			p->udp->fd = -1;
+			udp_destroy(p->udp);
+		}
 	}
-	conn_destroy(p->tcp);
+	if (p->tcp) conn_destroy(p->tcp);
 	del_from_list(p);
 	FREE(p);
 }
 
+char *  name_bot (void)
+{
+#define BOT_NAMES 17
+	static char *names[BOT_NAMES] = {
+		"brm", "Muaddib", "Antonov", "len1n", "Karkulka", "Bonk",
+		"miruz", "id9848948", "matfyzak", "[zl]", "vlk", "Potemkin",
+		"Snoopy", "FragHunter", "3v1l0r", "kacenka", "l33tm4st4",
+	};
+	static int avail_names = BOT_NAMES;
+	char dyn[16];
+
+	int i;
+
+	if (avail_names <= 0) {
+		snprintf(dyn, 16, "[b] %c%d", 'A' + (char)mkrand('Z' - 'A' + 1), -avail_names);
+		avail_names--;
+	} else {
+		do { i = mkrand(BOT_NAMES); } while (names[i] == NULL);
+		snprintf(dyn, 64, "[b] %s", names[i]);
+		avail_names--; names[i] = NULL;
+	}
+	return strdup(dyn);
+#undef BOT_NAMES
+}
+
+void    spawn_bot (void)
+{
+	plr_t *bot = alloc_bot ();
+
+	/* Figure out some name. */
+	bot->name = name_bot ();
+	join (bot);
+}
+
 /**********/
 
 void	parse(plr_t *p)
@@ -235,13 +288,17 @@
 	tick++;
 
 	DEBUG("game update!");
-	foreachsafe(p, n, players) 
+	foreachsafe(p, n, players)
+		if (p->initialized && player_is_ai(p))
+			bot_update(p);
+	foreachsafe(p, n, players)
 		if (p->initialized)
 			player_update(p);
 	foreachsafe(p, n, players)
-		if (p->initialized)
+		if (p->initialized && !player_is_ai(p))
 			send_update(p);
 	bullets_update();
+	if (do_ferda) runferda();
 			
 	tv.tv_sec = 0;
 	tv.tv_usec = TICKMS*1000;
@@ -260,7 +317,7 @@
 			p->current_direction = pck->cu.direction;
 			p->last_seq = pck->cu.ackseq;
 			rmlt_q(&(p->state), p->last_seq);
-			player_move(p, (p->current_direction)&63, (p->current_direction)&128, (p->current_direction)&64);
+			player_move(p, (p->current_direction)&7, (p->current_direction>>3)&15, (p->current_direction)&128);
 			return;
 	}
 }
@@ -274,14 +331,18 @@
 	pck_t 	packet;
 	plr_t	*p, *n;
 	conn_t	*newc;
+	int i;
 
 	FD_ZERO(&fdr);
 	FD_SET(server->fd, &fdr);
 	FD_SET(chatserver->fd, &fdr);
 
 	init_surface();
-	init_list(players);
+	init_players();
 	num_players = 0;
+	for (i = 0; i < bots_to_spawn; i++)
+		spawn_bot();
+
 	tv.tv_sec = tv.tv_usec = 0;
 
 	DEBUG("main loop");
@@ -297,7 +358,7 @@
 				DEBUG("*** UDP PACKET");
 			 	pck_recv(server, &packet);
 				foreachsafe(p, n, players) {
-					if (udp_cmp(p->udp, &(packet.from))) 
+					if (!player_is_ai(p) && udp_cmp(p->udp, &(packet.from))) 
 			 			game_packet(p, &packet);
 				}
 			} else if (FD_ISSET(chatserver->fd, &fdt)) {
@@ -308,7 +369,7 @@
 				alloc_client(newc);
 			} else {
 				DEBUG("*** TCP ACTIVITY");
-				foreachsafe (p, n, players) if (FD_ISSET(p->tcp->fd, &fdt)) {
+				foreachsafe (p, n, players) if (!player_is_ai(p) && FD_ISSET(p->tcp->fd, &fdt)) {
 					if (buffer_read(p->tcp->in, p->tcp->fd) <= 0) {
 						DEBUG("`--- KICKING, no data");
 						FD_CLR(p->tcp->fd, &fdr);
@@ -334,7 +395,7 @@
 	signal(SIGPIPE, SIG_IGN);
 
 	if (argc < 3) {
-		printf("tunneler server -- 2005, ment\nusage: %s <udp_port> <tcp_port>\n", argv[0]);
+		printf("tunneler server -- 2005, ment\nusage: %s <udp_port> <tcp_port> [opts]\n", argv[0]);
 		exit(1);
 	}
 	gfx_tank = load_gfxobj("tank", 8);
@@ -343,6 +404,7 @@
 	gfx_home = load_gfxobj("home", 2);
 
 	srandom(time(NULL));
+
 	uport = atoi(argv[1]);
 	tport = atoi(argv[2]);
 	while (1) {
@@ -362,6 +424,18 @@
 	}
 	
 	CONSOLE("bound ports %d/tcp and %d/udp", tport, uport);
+
+	/* Options */
+	if (argc > 3 && argv[3]) {
+		char *opt;
+		for (opt = argv[3]; *opt; opt++) {
+			switch (*opt) {
+				case 'f': do_ferda = 1; break;
+				case 'a': bots_to_spawn++; break;
+			}
+		}
+	}
+
 	mainloop();
 	return 0;
 }
Index: server.h
===================================================================
RCS file: /home/cvs/tunneler/tunneler/server.h,v
retrieving revision 1.1.1.2
retrieving revision 1.4
diff -u -r1.1.1.2 -r1.4
--- server.h	20 Jan 2005 22:15:18 -0000	1.1.1.2
+++ server.h	21 Jan 2005 00:40:37 -0000	1.4
@@ -5,6 +5,7 @@
 #include "udp.h"
 #include "conn.h"
 #include "game.h"
+#include "ai.h"
 #include "packet.h"
 #include "state.h"
 
@@ -19,12 +20,17 @@
 	uconn_t	*udp;
 	_u8	current_direction;
 	TANK	tank;
+	AI	ai; /* TODO: union with some other struct containing udp,tcp,seqs,... */
 	_u16	cur_seq, last_seq;
 	stq_t	state;
 } plr_t;
 
+#define player_is_ai(player) (player->udp == NULL && player->tcp == NULL)
+
 extern	plr_t players;
 extern _u32 tick;
 
+extern int do_ferda;
+
 void	bcast_kill(_u16 recv, _u16 send);
 #endif
