shithub: tbs

Download patch

ref: 38d8044975f8467c5ac4662259a89d6ea59d45bc
author: qwx <qwx@sciops.net>
date: Mon May 27 19:55:46 EDT 2019

initial import

diff: cannot open b/tbs1//null: 'b/tbs1//null' does not exist diff: cannot open b/tbs2//null: 'b/tbs2//null' does not exist diff: cannot open b/tchs//null: 'b/tchs//null' does not exist
--- /dev/null
+++ b/dat.h
@@ -1,0 +1,107 @@
+typedef struct Unit Unit;
+typedef struct Munit Munit;
+typedef struct Tunit Tunit;
+typedef struct Terrain Terrain;
+typedef struct Map Map;
+typedef struct Team Team;
+
+extern char **mov;
+extern int nmov;
+
+struct Unit{
+	char *name;
+	u32int **pic;
+	int atk;
+	int Δatk;
+	int rmin;
+	int rmax;
+	int def;
+	int *move;
+	int mp;
+	int vis;
+	int cost;
+	int unique;
+};
+extern Unit *unit;
+extern int nunit;
+
+struct Munit{
+	Unit *u;
+	int team;
+	int atkm;
+	int defm;
+	int eatk;
+	int edef;
+	int ecost;
+	int xp;
+	int hp;
+	int done;
+	int decaydt;
+};
+
+struct Tunit{
+	Unit **u;
+	Unit **e;
+};
+
+struct Terrain{
+	char *name;
+	u32int **pic;
+	int move;
+	int def;
+	int income;
+	Tunit spawn;
+	Tunit occupy;
+	Tunit *resupply;
+};
+extern Terrain *terrain;
+extern int nterrain;
+
+struct Map{
+	Terrain *t;
+	int team;
+	Munit *u;
+	int movep;
+	int canmove;
+	int atkp;
+	int canatk;
+};
+extern Map *map, *mape, *selected, *selected2, *saved, *unique;
+extern int mapwidth, mapheight;
+
+enum{
+	Nteam = 64,
+};
+
+struct Team{
+	int money;
+	int income;
+	int nunit;
+	int nbuild;
+	int nprod;
+	Munit *unique;
+};
+extern Team team[Nteam];
+extern int nteam, curteam;
+extern int turn, gameover;
+extern int initmoney, unitcap, firstturnnoinc, nocorpse;
+
+enum{
+	Pcur,
+	P1,
+	P2,
+	P3,
+	P4,
+	P5,
+	P6,
+	P7,
+	P8,
+	P9,
+	Pend
+};
+
+extern void (*selectfn)(void);
+extern int scale;
+extern int menuon;
+
+extern char *progname, *prefix, *dbname, *mapname;
--- /dev/null
+++ b/db.c
@@ -1,0 +1,509 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dat.h"
+#include "fns.h"
+
+char **mov;
+int nmov;
+Terrain *terrain;
+int nterrain;
+Unit *unit;
+int nunit;
+Map *map, *mape, *selected, *selected2;
+int mapwidth, mapheight;
+
+typedef struct Stackenblochen Stackenblochen;
+struct Stackenblochen{
+	char *name;
+	int v;
+	char *s;
+	Stackenblochen *b;
+	Stackenblochen *l;
+	Stackenblochen *r;
+};
+static Stackenblochen stack0 = {.l = &stack0, .r = &stack0}, *stack = &stack0;
+
+static Stackenblochen *
+allocblochen(char *name)
+{
+	Stackenblochen *b;
+
+	b = emalloc(sizeof *b);
+	if(name[0] != 0)
+		b->name = estrdup(name);
+	return b;
+}
+
+static void
+freeblochen(Stackenblochen *b)
+{
+	free(b->name);
+	free(b->s);
+	free(b);
+}
+
+static void
+popstackenblochen(Stackenblochen *stack)
+{
+	Stackenblochen *b, *p, *q;
+
+	for(b=stack->l; b!=stack; b=q){
+		for(p=b->r; p!=b; p=q){
+			q = p->r;
+			freeblochen(p);
+		}
+		q = b->l;
+		freeblochen(b);
+	}
+	for(b=stack; b->r!=stack; b=b->r)
+		;
+	b->r = b->r->r;
+	freeblochen(stack);
+}
+
+static Stackenblochen *
+pushstackenblochen(char *name, Stackenblochen *stack)
+{
+	Stackenblochen *b, *p;
+
+	b = allocblochen(name);
+	b->r = b;
+	b->l = stack;
+	b->v = stack->v++;
+	p = stack;
+	while(p->l != stack)
+		p = p->l;
+	p->l = b;
+	return b;
+}
+
+static Stackenblochen *
+stackenblochen(char *name, Stackenblochen *stack)
+{
+	Stackenblochen *b;
+
+	b = allocblochen(name);
+	b->l = b;
+	b->r = stack->r;
+	stack->r = b;
+	return b;
+}
+
+static Stackenblochen *
+findblochen(char *name, Stackenblochen *stack, int dir)
+{
+	Stackenblochen *b;
+
+	b = dir ? stack->l : stack->r;
+	while(b != stack){
+		if(b->name != nil && strcmp(b->name, name) == 0)
+			return b;
+		b = dir ? b->l : b->r;
+	}
+	return nil;
+}
+
+static void
+getblochen(Ndbtuple *t, Stackenblochen *stack)
+{
+	char *s;
+	Stackenblochen *b;
+
+	if((b = findblochen(t->attr, stack, 0)) == nil)
+		b = stackenblochen(t->attr, stack);
+	b = pushstackenblochen(t->val, b);
+	for(t=t->entry; t!=nil; t=t->entry){
+		b = stackenblochen(t->attr, b);
+		b->v = strtol(t->val, &s, 0);
+		b->s = estrdup(t->val);
+	}
+}
+
+static void
+loaddb(char *name, Stackenblochen *stack)
+{
+	char *s;
+	Ndb *db;
+	Ndbtuple *t;
+
+	if((db = ndbopen(name)) == nil){
+		s = smprint("%s/%s", prefix, name);
+		db = ndbopen(s);
+		free(s);
+		if(db == nil)
+			sysfatal("ndbopen: %r");
+	}
+	while((t = ndbparse(db)) != nil){
+		getblochen(t, stack);
+		ndbfree(t);
+	}
+	ndbclose(db);
+}
+
+static void
+initrules(void)
+{
+	Stackenblochen *b;
+
+	unitcap = 25;
+	if((b = findblochen("initmoney", stack, 0)) != nil){
+		initmoney = strtol(b->l->name, nil, 0);
+		if(initmoney < 0)
+			initmoney = 0;
+		popstackenblochen(b);
+	}
+	if((b = findblochen("unitcap", stack, 0)) != nil){
+		unitcap = strtol(b->l->name, nil, 0);
+		if(unitcap <= 0)
+			unitcap = mapwidth * mapheight / nteam;
+		popstackenblochen(b);
+	}
+	if((b = findblochen("firstturnnoinc", stack, 0)) != nil){
+		firstturnnoinc = 1;
+		popstackenblochen(b);
+	}
+	if((b = findblochen("nocorpse", stack, 0)) != nil){
+		nocorpse = 1;
+		popstackenblochen(b);
+	}
+}
+
+static void
+initmap(void)
+{
+	int i, l, n;
+	Map *m;
+	Unit *u;
+	Stackenblochen *b, *p, *q;
+
+	if((b = findblochen("mapwidth", stack, 0)) != nil){
+		mapwidth = strtol(b->l->name, nil, 0);
+		if(mapwidth <= 0)
+			mapwidth = 1;
+		popstackenblochen(b);
+	}
+	if((b = findblochen("map", stack, 0)) != nil){
+		if(mapwidth <= 0)
+			mapwidth = sqrt(b->v);
+		mapheight = b->v / mapwidth;
+	}else{
+		if(mapwidth <= 0)
+			mapwidth = 8;
+		mapheight = mapwidth;
+	}
+	n = mapwidth * mapheight;
+	map = emalloc(n * sizeof *map);
+	mape = map + n;
+	for(m=map; m<map+n; m++)
+		m->t = terrain;
+	if(b == nil)
+		return;
+	for(p=b->l, m=map; m<map+n; m++, p=p->l){
+		for(i=0; i<nterrain; i++){
+			l = strlen(terrain[i].name);
+			if(strncmp(p->name, terrain[i].name, l) == 0){
+				m->t = terrain + i;
+				if(strlen(p->name) > l)
+					m->team = strtol(p->name+l, nil, 10);
+				if(m->team < 0 || m->team > nelem(team))
+					m->team = 0;
+				else if(m->team > nteam)
+					nteam = m->team;
+				break;
+			}
+		}
+		if((q = findblochen("unit", p, 0)) != nil)
+			for(u=unit; u<unit+nunit; u++){
+				l = strlen(u->name);
+				if(strncmp(q->s, u->name, l) == 0){
+					i = 0;
+					if(strlen(q->s) > l)
+						i = strtol(q->s+l, nil, 10);
+					if(i < 0)
+						i = 0;
+					spawnunit(m, u, i);
+					break;
+				}
+			}
+	}
+	popstackenblochen(b);
+	for(m=map; m<map+mapwidth*mapheight; m++){
+		team[m->team].income += m->t->income;
+		team[m->team].nbuild++;
+		if(m->t->spawn.u != nil)
+			team[m->team].nprod++;
+	}
+}
+
+static void
+initoccupy(void)
+{
+	int n;
+	Terrain *t;
+	Unit *u;
+	Stackenblochen *b, *p, *q;
+
+	if((b = findblochen("occupy", stack, 0)) == nil)
+		return;
+	for(p=b->l; p!=b; p=p->l){
+		for(t=terrain; t<terrain+nterrain; t++)
+			if(strcmp(p->name, t->name) == 0)
+				break;
+		if(t == terrain + nterrain){
+			fprint(2, "initoccupy: unknown terrain %s\n", p->name);
+			continue;
+		}
+		for(q=p->r, n=0; q!=p; q=q->r)
+			n++;
+		t->occupy.u = emalloc(n * sizeof *t->occupy.u);
+		t->occupy.e = t->occupy.u + n;
+		for(q=p->r, n=0; q!=p; q=q->r){
+			for(u=unit; u<unit+nunit; u++)
+				if(strcmp(q->s, u->name) == 0)
+					break;
+			if(u == unit + nunit){
+				fprint(2, "initoccupy: unknown unit %s\n", q->s);
+				t->occupy.e--;
+				continue;
+			}
+			t->occupy.u[n++] = u;
+		}
+	}
+	popstackenblochen(b);
+}
+
+static void
+initresupply(void)
+{
+	Terrain *t, *r;
+	Stackenblochen *b, *p, *q;
+
+	if((b = findblochen("resupply", stack, 0)) == nil)
+		return;
+	for(p=b->l; p!=b; p=p->l){
+		for(t=terrain; t<terrain+nterrain; t++)
+			if(strcmp(p->name, t->name) == 0)
+				break;
+		if(t == terrain + nterrain){
+			fprint(2, "initresupply: unknown terrain %s\n", p->name);
+			continue;
+		}
+		for(q=p->r; q!=p; q=q->r){
+			for(r=terrain; r<terrain+nterrain; r++)
+				if(strcmp(q->s, r->name) == 0)
+					break;
+			if(r == terrain + nterrain){
+				fprint(2, "initresupply: unknown terrain %s\n", q->s);
+				continue;
+			}
+			t->resupply = &r->spawn;
+		}
+	}
+	popstackenblochen(b);
+}
+
+static void
+initspawn(void)
+{
+	int n;
+	Terrain *t;
+	Unit *u;
+	Stackenblochen *b, *p, *q;
+
+	if((b = findblochen("spawn", stack, 0)) == nil)
+		return;
+	for(p=b->l; p!=b; p=p->l){
+		for(t=terrain; t<terrain+nterrain; t++)
+			if(strcmp(p->name, t->name) == 0)
+				break;
+		if(t == terrain + nterrain){
+			fprint(2, "initspawn: unknown terrain %s\n", p->name);
+			continue;
+		}
+		for(q=p->r, n=0; q!=p; q=q->r)
+			n++;
+		t->spawn.u = emalloc(n * sizeof *t->spawn.u);
+		t->spawn.e = t->spawn.u + n;
+		for(q=p->r, n=0; q!=p; q=q->r){
+			for(u=unit; u<unit+nunit; u++)
+				if(strcmp(q->s, u->name) == 0)
+					break;
+			if(u == unit + nunit){
+				fprint(2, "initspawn: unknown unit %s\n", q->s);
+				t->spawn.e--;
+				continue;
+			}
+			t->spawn.u[n++] = u;
+		}
+	}
+	popstackenblochen(b);
+}
+
+static void
+initterrain(void)
+{
+	int n;
+	Terrain *t;
+	char **s;
+	Stackenblochen *b, *p, *q;
+
+	n = 1 + ((b = findblochen("terrain", stack, 0)) != nil ? b->v : 0);
+	terrain = emalloc(n * sizeof *terrain);
+	nterrain = n;
+	t = terrain;
+	t->name = "void";
+	t++;
+	if(n <= 1)
+		return;
+	for(p=b->l; t<terrain+n; t++, p=p->l){
+		t->name = estrdup(p->name);
+		for(q=p->r; q!=p; q=q->r){
+			if(strcmp(q->name, "def") == 0){
+				if((t->def = q->v) < 0)
+					t->def = 0;
+			}else if(strcmp(q->name, "income") == 0){
+				if((t->income = q->v) < 0)
+					t->income = 0;
+			}else if(strcmp(q->name, "move") == 0){
+				for(s=mov; s<mov+nmov; s++)
+					if(strcmp(*s, q->s) == 0)
+						break;
+				if(s != mov + nmov)
+					t->move = s - mov;
+				else
+					fprint(2, "initterrain: unknown move type %s\n", q->s);
+			}
+		}
+	}
+	popstackenblochen(b);
+}
+
+static void
+initmove(void)
+{
+	int n;
+	char **s;
+	Unit *u;
+	Stackenblochen *b, *p, *q;
+
+	n = 1 + ((b = findblochen("move", stack, 0)) != nil ? b->v : 0);
+	mov = emalloc(n * sizeof *mov);
+	nmov = n;
+	s = mov;
+	*s++ = "void";
+	for(u=unit; u<unit+nunit; u++)
+		u->move = emalloc(n * sizeof *u->move);
+	if(n <= 1)
+		return;
+	for(p=b->l, n=1; p!=b; p=p->l, n++){
+		*s++ = estrdup(p->name);
+		for(q=p->r; q!=p; q=q->r){
+			for(u=unit; u<unit+nunit; u++)
+				if(strcmp(q->name, u->name) == 0)
+					break;
+			if(u == unit + nunit){
+				fprint(2, "initmove: unknown unit %s\n", q->name);
+				continue;
+			}
+			u->move[n] = q->v;
+		}
+	}
+	popstackenblochen(b);
+}
+
+static void
+initunit(void)
+{
+	int n;
+	char *s;
+	Unit *u;
+	Stackenblochen *b, *p, *q;
+	static Unit u0 = {
+		.name = "void",
+		.atk = 100,
+		.Δatk = 10,
+		.rmin = 1,
+		.rmax = 1,
+		.vis = 2,
+		.mp = 3,
+		.cost = 1000,
+	};
+
+	n = 1 + ((b = findblochen("unit", stack, 0)) != nil ? b->v : 0);
+	unit = emalloc(n * sizeof *unit);
+	nunit = n;
+	u = unit;
+	*u++ = u0;
+	if(n <= 1)
+		return;
+	for(p=b->l; u<unit+n; u++, p=p->l){
+		*u = u0;
+		u->name = estrdup(p->name);
+		for(q=p->r; q!=p; q=q->r){
+			if(strcmp(q->name, "atk") == 0){
+				if((u->atk = q->v) < 0)
+					u->atk = 0;
+			}else if(strcmp(q->name, "Δatk") == 0){
+				if((u->Δatk = q->v) < 0)
+					u->Δatk = 0;
+			}else if(strcmp(q->name, "def") == 0){
+				if((u->def = q->v) < 0)
+					u->def = 0;
+			}else if(strcmp(q->name, "mp") == 0){
+				if((u->mp = q->v) < 0)
+					u->mp = 0;
+			}else if(strcmp(q->name, "vis") == 0){
+				if((u->vis = q->v) < 0)
+					u->vis = 0;
+			}else if(strcmp(q->name, "cost") == 0){
+				if((u->cost = q->v) < 0)
+					u->cost = 0;
+			}else if(strcmp(q->name, "range") == 0){
+				u->rmin = q->v;
+				if((s = strrchr(q->s, ',')) != nil)
+					u->rmax = strtol(s+1, nil, 10);
+				if(u->rmax < u->rmin)
+					u->rmax = u->rmin;
+			}else if(strcmp(q->name, "unique") == 0)
+				u->unique = q->v;
+			else
+				fprint(2, "initunit: unknown attr %s\n", q->name);
+		}
+	}
+	popstackenblochen(b);
+}
+
+static void
+initdb(void)
+{
+	initunit();
+	initmove();
+	initterrain();
+	initspawn();
+	initresupply();
+	initoccupy();
+	initmap();
+	initrules();
+}
+
+void
+init(void)
+{
+	Stackenblochen *b, *p, *q;
+
+	loaddb(dbname, stack);
+	loaddb(mapname, stack);
+	initdb();
+	initgame();
+	for(b=stack->r; b!=stack; b=b->r){
+		fprint(2, "stack %s\n", b->name);
+		for(p=b->l; p!=b; p=p->l){
+			fprint(2, "%s: ", p->name);
+			for(q=p->r; q!=p; q=q->r)
+				fprint(2, "%s=%s ", q->name, q->s);
+		}
+		fprint(2, "\n");
+	}
+}
--- /dev/null
+++ b/drw.c
@@ -1,0 +1,443 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+Point p0, pan;
+int scale;
+int menuon;
+void (*selectfn)(void);
+
+static int fbsz, fbh, fbw;
+static u32int *fb;
+static Image *fbi;
+static Rectangle fbr;
+static int tlwidth, tlheight;
+static u32int *pics[Pend];
+static u32int bgcol = 0x00ffff;
+
+void
+dopan(int dx, int dy)
+{
+	Rectangle r;
+
+	r = rectaddpt(fbr, pan);
+	if(dx > 0){
+		r.max.x = r.min.x + dx;
+		draw(screen, r, display->black, nil, ZP);
+	}else if(dx < 0){
+		r.min.x = r.max.x + dx;
+		draw(screen, r, display->black, nil, ZP);
+	}
+	r = rectaddpt(fbr, pan);
+	if(dy > 0){
+		r.max.y = r.min.y + dy;
+		draw(screen, r, display->black, nil, ZP);
+	}else if(dy < 0){
+		r.min.y = r.max.y + dy;
+		draw(screen, r, display->black, nil, ZP);
+	}
+	pan.x += dx;
+	pan.y += dy;
+	redraw(0);
+}
+
+void
+select(Point m)
+{
+	int n;
+	Rectangle r;
+
+	r = rectaddpt(fbr, pan);
+	if(r.max.y > p0.y)
+		r.max.y = p0.y;
+	if(!ptinrect(m, r))
+		return;
+	m = subpt(m, r.min);
+	n = m.y / (scale * tlheight) * mapwidth + m.x / (scale * tlwidth);
+	selected2 = map + n;
+	selectfn();
+	selected = selected2;
+	redraw(0);
+}
+
+static void
+drawscaled(void)
+{
+	Rectangle r, r2;
+	uchar *p;
+
+	r = rectaddpt(fbr, pan);
+	if(r.max.y > p0.y)
+		r.max.y = p0.y;
+	r2 = r;
+	p = (uchar *)fb;
+	if(scale == 1){
+		loadimage(fbi, fbi->r, p, fbsz);
+		draw(screen, r, fbi, nil, ZP);
+	}else{
+		while(r.min.y < r2.max.y){
+			r.max.y = r.min.y + scale;
+			p += loadimage(fbi, fbi->r, p, fbsz / fbh);
+			draw(screen, r, fbi, nil, ZP);
+			r.min.y = r.max.y;
+		}
+	}
+}
+
+static void
+drawpic(u32int *pp, u32int **pic)
+{
+	int n, m, w, x;
+	u32int v, *ip;
+
+	if(pic == nil){
+		fprint(2, "drawpic: missing pic\n");
+		return;
+	}
+	n = tlheight;
+	ip = *pic;
+	w = (fbw - tlwidth) * scale;
+	while(n-- > 0){
+		m = tlwidth;
+		while(m-- > 0){
+			v = *ip++;
+			if(v & 0xff << 24)
+				for(x=0; x<scale; x++)
+					*pp++ = v;
+			else
+				pp += scale;
+		}
+		pp += w;
+	}
+}
+
+static void
+drawextra(void)
+{
+	Map *m;
+
+	for(m=map; m<mape; m++)
+		if(m->u != nil){
+			if(m->u->hp < 99)
+				drawpicat(m, pics + m->u->hp / 11 + 1);
+			if(m->u->done)
+				composeat(m, 0x555555);
+		}
+}
+
+static void
+drawmenuselected(void)
+{
+	int cost;
+	char s[256];
+	Point sp;
+	Unit *u;
+	Tunit *tu;
+
+	tu = &saved->t->spawn;
+	if(selected == nil || tu->u + (selected - map) >= tu->e)
+		return;
+	u = tu->u[selected - map];
+	if(team[curteam].unique != nil && u == team[curteam].unique->u){
+		if(unique != nil)
+			return;
+		cost = team[curteam].unique->ecost;
+	}else
+		cost = u->cost;
+	sp = p0;
+	sp.y += font->height;
+	snprint(s, sizeof s, "unit: %s, atk %d±%d, def %d rng %d-%d, mp %d, cost %d", u->name, u->atk, u->Δatk, u->def, u->rmin, u->rmax, u->mp, cost);
+	string(screen, sp, display->white, ZP, font, s);
+}
+
+static void
+drawspawn(void)
+{
+	Tunit *tu;
+	Unit **u;
+	Map *m;
+
+	tu = &saved->t->spawn;
+	for(u=tu->u, m=map; u<tu->e; u++, m++){
+		drawpicat(m, (*u)->pic);
+		if(unique != nil && *u == team[curteam].unique->u)
+			composeat(m, 0x555555);
+	}
+}
+
+static void
+drawselected(void)
+{
+	char s[256];
+	Point sp;
+	Munit *mu;
+	Unit *u;
+
+	if(selected == nil)
+		return;
+	drawpicat(selected, pics + Pcur);
+	sp = p0;
+	sp.y += font->height;
+	snprint(s, sizeof s, "terrain: %s (+%d)", selected->t->name, selected->t->def);
+	string(screen, sp, display->white, ZP, font, s);
+	if(selected->u == nil)
+		return;
+	sp.y += font->height;
+	mu = selected->u;
+	u = mu->u;
+	snprint(s, sizeof s, "unit: %s, rank %d, atk %d±%d, def %d, rng %d-%d, mp %d", u->name, mu->xp / 100, mu->eatk, u->Δatk, mu->edef, u->rmin, u->rmax, u->mp);
+	string(screen, sp, display->white, ZP, font, s);
+	flushimage(display, 1);
+}
+
+static void
+drawlog(void)
+{
+	char s[64];
+
+	if(gameover)
+		snprint(s, sizeof s, "GAME OVER: team %d wins at turn %d", curteam, turn+1);
+	else
+		snprint(s, sizeof s, "turn %d, team %d: %d (+%d) gold, %d units %d buildings", turn+1, curteam, team[curteam].money, team[curteam].income, team[curteam].nunit, team[curteam].nbuild);
+	string(screen, p0, display->white, ZP, font, s);
+}
+
+static void
+drawmap(void)
+{
+	int n, w;
+	u32int *pp;
+	Map *m;
+
+	pp = fb;
+	n = 0;
+	w = tlwidth * mapwidth * (tlheight - 1) * scale;
+	for(m=map; m<mape; m++){
+		drawpic(pp, m->t->pic + m->team);
+		if(m->u != nil)
+			drawpic(pp, m->u->u->pic + m->u->team);
+		if(m->canmove)
+			composeat(m, 0xff0f0f);
+		if(m->canatk)
+			composeat(m, 0xf0ff0f);
+		pp += tlwidth * scale;
+		if(++n == mapwidth){
+			n = 0;
+			pp += w;
+		}
+	}
+}
+
+void
+composeat(Map *m, u32int c)
+{
+	int k, n, x, w;
+	u32int v, *pp;
+
+	w = fbw * scale * tlheight;
+	pp = fb + (m-map) / mapwidth * w
+		+ (m-map) % mapwidth * tlwidth * scale;
+	n = tlheight;
+	w = (fbw - tlwidth) * scale;
+	while(n-- > 0){
+		x = tlwidth;
+		while(x-- > 0){
+			v = *pp;
+			v = (v & 0xff0000) + (c & 0xff0000) >> 1 & 0xff0000
+				| (v & 0xff00) + (c & 0xff00) >> 1 & 0xff00
+				| (v & 0xff) + (c & 0xff) >> 1 & 0xff;
+			k = scale;
+			while(k-- > 0)
+				*pp++ = v;
+		}
+		pp += w;
+	}
+}
+
+void
+drawpicat(Map *m, u32int **pic)
+{
+	int w;
+	u32int *pp;
+
+	w = fbw * scale * tlheight;
+	pp = fb + (m-map) / mapwidth * w + (m-map) % mapwidth * tlwidth * scale;
+	drawpic(pp, pic);
+}
+
+void
+redraw(int all)
+{
+	if(all)
+		draw(screen, screen->r, display->black, nil, ZP);
+	else
+		draw(screen, Rpt(p0, screen->r.max), display->black, nil, ZP);
+	if(!menuon){
+		drawmap();
+		drawselected();
+		drawextra();
+	}else{
+		memset(fb, 0, mapwidth * tlwidth * scale * mapheight * tlheight * sizeof *fb);
+		drawspawn();
+		drawmenuselected();
+	}
+	drawlog();
+	drawscaled();
+	flushimage(display, 1);
+}
+
+void
+resetdraw(void)
+{
+	Point p, d;
+
+	if(scale < 1)
+		scale = 1;
+	else if(scale > 16)
+		scale = 16;
+	fbw = mapwidth * tlwidth;
+	fbh = mapheight * tlheight;
+	p = divpt(addpt(screen->r.min, screen->r.max), 2);
+	d = Pt(fbw * scale / 2, fbh * scale / 2);
+	fbr = Rpt(subpt(p, d), addpt(p, d));
+	p0 = Pt(screen->r.min.x + 8, screen->r.max.y - 3 * font->height);
+	fbsz = fbw * scale * fbh * sizeof *fb;
+	free(fb);
+	freeimage(fbi);
+	fb = emalloc(fbsz);
+	if((fbi = allocimage(display, Rect(0,0,fbw*scale,scale==1 ? fbh : 1), XRGB32, scale>1, 0)) == nil)
+		sysfatal("allocimage: %r");
+	redraw(1);
+}
+
+static Image*
+iconv(Image *i)
+{
+	Image *ni;
+
+	if(i->chan == RGB24)
+		return i;
+	if((ni = allocimage(display, i->r, RGB24, 0, DNofill)) == nil)
+		sysfatal("allocimage: %r");
+	draw(ni, ni->r, i, nil, ZP);
+	freeimage(i);
+	return ni;
+}
+
+static int
+openimage(char *name)
+{
+	int fd;
+	char *s;
+
+	if((fd = open(name, OREAD)) < 0){
+		s = smprint("%s/%s", prefix, name);
+		fd = open(s, OREAD);
+		free(s);
+		if(fd < 0)
+			fprint(2, "openimage: %r\n");
+	}
+	return fd;
+}
+
+static u32int *
+loadpic(char *name)
+{
+	int fd, n, m, dx, dy;
+	Image *i;
+	uchar *b, *s;
+	u32int v, *p, *pic;
+
+	if(name == nil || strlen(name) == 0)
+		sysfatal("loadpic: invalid name");
+	if((fd = openimage(name)) < 0)
+		return nil;
+	if((i = readimage(display, fd, 0)) == nil)
+		sysfatal("readimage: %r");
+	close(fd);
+	i = iconv(i);
+	dx = Dx(i->r);
+	dy = Dy(i->r);
+	if(tlwidth == 0 || tlheight == 0){
+		tlwidth = dx;
+		tlheight = dy;
+	}else if(tlwidth != dx || tlheight != dy)
+		sysfatal("loadpic: unequal tile size %dx%d not %dx%d",
+			dx, dy, tlwidth, tlheight);
+	n = dx * dy;
+	m = n * i->depth / 8;
+	b = emalloc(m);
+	unloadimage(i, i->r, b, m);
+	freeimage(i);
+	p = emalloc(n * sizeof *p);
+	pic = p;
+	s = b;
+	while(n-- > 0){
+		v = s[2] << 16 | s[1] << 8 | s[0];
+		if(v != bgcol)
+			v |= 0xff << 24;
+		*p++ = v;
+		s += 3;
+	}
+	free(b);
+	return pic;
+}
+
+static void
+initbaseimages(void)
+{
+	char *tab[] = {
+		[Pcur] "cursor.bit",
+		[P1] "1.bit",
+		[P2] "2.bit",
+		[P3] "3.bit",
+		[P4] "4.bit",
+		[P5] "5.bit",
+		[P6] "6.bit",
+		[P7] "7.bit",
+		[P8] "8.bit",
+		[P9] "9.bit",
+	};
+	int i;
+
+	for(i=0; i<nelem(tab); i++)
+		pics[i] = loadpic(tab[i]);
+}
+
+void
+initimages(void)
+{
+	int i, n;
+	char s[64];
+	Terrain *t;
+	Unit *u;
+
+	initbaseimages();
+	for(t=terrain; t<terrain+nterrain; t++){
+		n = t->occupy.u != nil ? nteam+1 : 1;
+		t->pic = emalloc(n * sizeof *t->pic);
+		if(strcmp(t->name, "void") == 0)
+			continue;
+		snprint(s, sizeof s, "%s.bit", t->name);
+		t->pic[0] = loadpic(s);
+		for(i=1; i<n; i++){
+			snprint(s, sizeof s, "%s%d.bit", t->name, i);
+			t->pic[i] = loadpic(s);
+		}
+	}
+	for(u=unit; u<unit+nunit; u++){
+		u->pic = emalloc((nteam+1) * sizeof *u->pic);
+		if(strcmp(u->name, "void") == 0)
+			continue;
+		snprint(s, sizeof s, "%s.bit", u->name);
+		u->pic[0] = loadpic(s);
+		if(strcmp(u->name, "corpse") == 0)
+			continue;
+		for(i=1; i<=nteam; i++){
+			snprint(s, sizeof s, "%s%d.bit", u->name, i);
+			u->pic[i] = loadpic(s);
+		}
+	}
+}
--- /dev/null
+++ b/fns.h
@@ -1,0 +1,14 @@
+void	dopan(int, int);
+void	composeat(Map*, u32int);
+void	drawpicat(Map*, u32int**);
+void	redraw(int);
+void	resetdraw(void);
+void	initimages(void);
+void	init(void);
+void	spawnunit(Map*, Unit*, int);
+void	endturn(void);
+void	initgame(void);
+void	lmenu(char**, void(*[])(void));
+char*	estrdup(char*);
+void*	emalloc(ulong);
+void*	erealloc(void*, ulong, ulong);
--- /dev/null
+++ b/game.c
@@ -1,0 +1,676 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+/* FIXME:
+ * - selectmove/selectspawnmove: manage corpses again
+ * - db: get rid of void types
+ * - fix selection/showmoverange
+ * - tchs: global rule: only one move per turn, no more (and no less?)
+ * - tchs: implement custom move/attack ranges and checks
+ * - tchs: implement no attack (attack -> move onto unit)
+ * - tchs: moving onto existing unit makes it disappear (cleanly)
+ * - resupply cost (default 0)
+ * - ratio, repair, destroy, launch, food, ammo, cloak lists
+ * - food/fuel consumption rate list
+ * - alternate attack mode ammo/...
+ * - bless, curse/slow, raise lists
+ * - terrain bonuses lists: atk, def, vis
+ *	-> tatkm=water elemental=15
+ *	-> tdefm=water elemental=3
+ *	-> tvism=mountain heavy=2 (and inf=2? or any unit?)
+ * - tbs2: unit and terrain sprites
+ * - tbs1: sprites for rest of units
+ * - manpages: tbs(1), tbs(6)?
+ * - proper victory screen
+ * - win condition: last team left standing (next turn -> current team)
+ * - quit: confirm with changed cursor
+ * - move validation and networking model
+ * - ai: optional plugin for an artificial networked player
+ * - savegames
+ * - fow
+ * - weather
+ * - diplomacy/trading
+ * - superteams (factions/groups), shared economies
+ */
+
+Team team[Nteam];
+int nteam, curteam;
+int turn, gameover;
+int initmoney, unitcap, firstturnnoinc, nocorpse;
+Map *saved, *unique;
+
+typedef struct Fn Fn;
+struct Fn{
+	char *name;
+	int (*check)(void);
+	void (*fn)(void);
+};
+
+static Munit *old, *new, *savedcorpse;
+
+static void	selectmenu(void);
+static void	selecttile(void);
+static void	selectaction(void);
+static void	selectspawnmove(void);
+static void	selectmove(void);
+static void	confirmmove(void);
+
+static void
+freeunit(Map *m)
+{
+	free(m->u);
+	m->u = nil;
+}
+
+static void
+updatecorpses(void)
+{
+	Map *m;
+	
+	for(m=map; m<mape; m++)
+		if(m->u != nil && m->u->decaydt && --m->u->decaydt == 0)
+			freeunit(m);
+}
+
+static int
+iscorpse(Map *m)
+{
+	Munit *mu;
+
+	mu = m->u;
+	if(mu != nil && strcmp(mu->u->name, "corpse") == 0)
+		return 1;
+	return 0;
+}
+
+static void
+canmove_(Map *m, Map *ref, Unit *u, int mp)
+{
+	if(m->movep >= mp)
+		return;
+	if(m != ref){
+		if(m->u != nil && m->u->team != curteam && !iscorpse(m))
+			return;
+		if(mp < u->move[m->t->move] + 1)
+			return;
+		mp -= u->move[m->t->move] + 1;
+	}
+	m->canmove = 1;
+	if(mp <= 0)
+		return;
+	m->movep = mp;
+	if((m+1-map) % mapwidth != 0)
+		canmove_(m+1, ref, u, mp);
+	if(m+mapwidth < mape)
+		canmove_(m+mapwidth, ref, u, mp);
+	if(m-mapwidth >= map)
+		canmove_(m-mapwidth, ref, u, mp);
+	if((m-map) % mapwidth != 0)
+		canmove_(m-1, ref, u, mp);
+}
+
+static void
+canmove(void)
+{
+	canmove_(selected2, selected2, selected2->u->u, selected2->u->u->mp);
+}
+
+static void
+canattack_(Map *m, int r1, int r2)
+{
+	if(m->canatk)
+		return;
+	if(r1-- <= 0 && m != selected2){
+		m->canatk = 1;
+		if(m->u != nil
+		&& m->u->team != curteam
+		&& !iscorpse(m)
+		&& selected2->u->team == curteam)
+			m->atkp = 1;
+	}
+	if(r2-- == 0)
+		return;
+	if((m+1-map) % mapwidth != 0)
+		canattack_(m+1, r1, r2);
+	if(m+mapwidth < mape)
+		canattack_(m+mapwidth, r1, r2);
+	if(m-mapwidth >= map)
+		canattack_(m-mapwidth, r1, r2);
+	if((m-map) % mapwidth != 0)
+		canattack_(m-1, r1, r2);
+}
+
+static void
+canattack(void)
+{
+	canattack_(selected2, selected2->u->u->rmin, selected2->u->u->rmax);
+}
+
+static void
+clrmoverange(int domove)
+{
+	Map *m;
+
+	for(m=map; m<mape; m++){
+		if(domove){
+			m->movep = 0;
+			m->canmove = 0;
+		}
+		m->atkp = 0;
+		m->canatk = 0;
+	}
+}
+
+static void
+showmoverange(int domove)
+{
+	clrmoverange(domove);
+	if(selected2->u == nil)
+		return;
+	if(domove)
+		canmove();
+	canattack();
+}
+
+static void
+exitmenu(int domove)
+{
+	selected2 = saved;
+	menuon = 0;
+	if(domove)
+		selectfn = selectspawnmove;
+	else{
+		saved = nil;
+		selectfn = selecttile;
+	}
+}
+
+static void
+spawn(void)
+{
+	Unit *u;
+
+	u = saved->t->spawn.u[selected2 - map];
+	old = saved->u;
+	spawnunit(saved, u, curteam);
+	new = saved->u;
+	team[curteam].money -= unique != nil && team[curteam].unique->u == u ? team[curteam].unique->ecost : u->cost;
+	exitmenu(1);
+}
+
+static void
+selectmenu(void)
+{
+	int cost;
+	char *i[] = {"spawn", nil};
+	void (*fn[])(void) = {spawn};
+	Tunit *tu;
+	Unit *u;
+	Map *m;
+
+	tu = &saved->t->spawn;
+	if(tu->u + (selected2 - map) >= tu->e){
+		exitmenu(0);
+		return;
+	}
+	if(selected2 != selected)
+		return;
+	u = tu->u[selected2 - map];
+	if(u->unique){
+		if(unique != nil)
+			return;
+		else
+			cost = team[curteam].unique->ecost;
+	}else
+		cost = u->cost;
+	selectfn = selectmenu;
+	clrmoverange(1);
+	canmove_(saved, saved, u, u->mp);
+	for(m=map; m<mape; m++)
+		if(m->canmove)
+			break;
+	if(m == mape || team[curteam].money < cost)
+		clrmoverange(1);
+	else
+		lmenu(i, fn);
+}
+
+static void
+spawnlist(void)
+{
+	Map *m;
+
+	unique = nil;
+	if(team[curteam].unique != nil)
+		for(m=map; m<mape; m++)
+			if(m->u == team[curteam].unique){
+				unique = m;
+				break;
+			}
+	saved = selected2;
+	selected2 = nil;
+	selectfn = selectmenu;
+	menuon = 1;
+}
+
+static int
+checkspawn(void)
+{
+	if(saved != nil
+	|| team[curteam].nunit >= unitcap
+	|| selected2->team != curteam
+	|| selected2->t->spawn.u == nil)
+		return 0;
+	return 1;
+}
+
+static void
+endmove(void)
+{
+	clrmoverange(1);
+	selected2->u->done = 1;
+	selected2->u->decaydt = 0;
+	selected2 = nil;
+	if(old != nil){
+		saved->u = old;
+		old = nil;
+	}
+	new = nil;
+	saved = nil;
+	selectfn = selecttile;
+}
+
+static int
+checkend(void)
+{
+	if(selected2->u == nil
+	|| selected2->u->team != curteam)
+		return 0;
+	return 1;
+}
+
+static void
+selectspawnmove(void)
+{
+	if(selected2 == nil || selected2 == saved && old != nil)
+		goto again;
+	showmoverange(0);
+	redraw(0);
+	if(selected2 == selected){
+		selectaction();
+		return;
+	}else if(selected2->canmove
+	&& (selected2->u == nil || iscorpse(selected2))
+	&& (old == nil || selected2 != saved)){
+		if(iscorpse(selected2))
+			savedcorpse = selected2->u;
+		selected2->u = selected->u;
+		selected->u = nil;
+		return;
+	}
+again:
+	if(selected != nil && selected->u == new)
+		selected->u = nil;
+	saved->u = new;
+	selected2 = saved;
+}
+
+static void
+commitmove(void)
+{
+	if(saved == nil){
+		selected2->u = selected->u;
+		selected->u = nil;
+		saved = selected;
+	}else{
+		saved->u = selected->u;
+		selected->u = nil;
+		saved = nil;
+	}
+}
+
+static void
+cancelmove(void)
+{
+	if(saved != nil){
+		commitmove();
+		if(savedcorpse != nil){
+			selected2->u = savedcorpse;
+			savedcorpse = nil;
+		}
+		if(old != nil){
+			saved->u = old;
+			old = nil;
+		}
+		saved = nil;
+		selectfn = selecttile;
+	}else
+		selectfn = selectmove;
+	selected2 = nil;
+	clrmoverange(1);
+}
+
+static void
+confirmmove(void)
+{
+	if(selected2 == selected){
+		showmoverange(1);
+		redraw(0);
+		selectaction();
+	}else
+		cancelmove();
+}
+
+static void
+selectmove(void)
+{
+	if(selected2 == nil)
+		return;
+	/* FIXME: manage corpses */
+	if(selected2->canmove && selected2->u == nil){
+		commitmove();
+		selectfn = confirmmove;
+	}else{
+		selectfn = selecttile;
+		selected2 = nil;
+	}
+}
+
+static void
+move(void)
+{
+	selectfn = selectmove;
+}
+
+static int
+checkmove(void)
+{
+	if(saved != nil
+	|| selected2->u == nil
+	|| selected2->u->team != curteam)
+		return 0;
+	return 1;
+}
+
+static void
+die(Map *m)
+{
+	Munit *mu;
+	Unit *u;
+
+	mu = m->u;
+	team[mu->team].nunit--;
+	if(mu == team[mu->team].unique){
+		mu->ecost += mu->u->cost;
+		m->u = nil;
+		return;
+	}
+	if(!nocorpse)
+		for(u=unit; u<unit+nunit; u++)
+			if(strcmp(u->name, "corpse") == 0){
+				mu->u = u;
+				mu->decaydt = 2;
+			}
+}
+
+static void
+rankup(Munit *mu)
+{
+	mu->eatk += 2;
+	mu->edef += 2;
+}
+
+static int
+attackunit(Map *to, Map *from)
+{
+	int a, d, r, n;
+	Munit *mu, *mv;
+
+	mu = from->u;
+	mv = to->u;
+	r = 100;	/* FIXME: damage ratio */
+	a = (mu->eatk + mu->atkm + nrand(mu->u->Δatk)) * r / 100 * mu->hp / 100;
+	if(a <= 0)
+		return 0;
+	d = mv->edef + mu->defm + to->t->def * 5;
+	n = a - d;
+	if(n < 0)
+		n = 0;
+	if(mv->hp < n)
+		n = mv->hp;
+	mv->hp -= n;
+	if(r = mv->hp == 0)
+		die(to);
+	n = mu->xp / 100;
+	mu->xp += a;
+	if(mu->xp / 100 > n)
+		rankup(mu);
+	return r;
+}
+
+static void
+selectattack(void)
+{
+	if(!selected2->atkp)
+		goto nope;
+	if(attackunit(selected2, selected) == 0
+	&& selected2->u->u->rmin == 1
+	&& (selected2 - selected == 1
+	|| selected2 - selected == -1
+	|| selected2 - selected == mapwidth
+	|| selected2 - selected == -mapwidth))
+		attackunit(selected, selected2);
+	selected2 = selected;
+	endmove();
+	return;
+nope:
+	selected2 = nil;
+	selectfn = selecttile;
+}
+
+static void
+attack(void)
+{
+	selectfn = selectattack;
+}
+
+static int
+checkattack(void)
+{
+	Map *m;
+
+	if(saved != nil && selected->u->u->rmin > 1
+	|| selected2->u == nil
+	|| selected2->u->team != curteam)
+		return 0;
+	for(m=map; m<mape; m++)
+		if(m->atkp)
+			return 1;
+	return 0;
+}
+
+static void
+occupy(void)
+{
+	team[selected2->team].nbuild--;
+	team[selected2->u->team].nbuild++;
+	if(selected2->t->spawn.u != nil){
+		team[selected2->team].nprod--;
+		team[selected2->u->team].nprod++;
+	}
+	selected2->team = selected2->u->team;
+	team[selected2->team].income += selected2->t->income;
+	endmove();
+}
+
+static int
+checkoccupy(void)
+{
+	Unit **u;
+	Tunit *tu;
+
+	if(selected2->u == nil
+	|| selected2->team == curteam)
+		return 0;
+	tu = &selected2->t->occupy;
+	if(selected2->u->team == curteam){
+		for(u=tu->u; u<tu->e; u++)
+			if(*u == selected2->u->u)
+				return 1;
+	}
+	return 0;
+}
+
+static Fn fn[] = {
+	{"spawn", checkspawn, spawnlist},
+	{"occupy", checkoccupy, occupy},
+	{"attack", checkattack, attack},
+	{"move", checkmove, move},
+	{"end", checkend, endmove},
+};
+
+static void
+selectaction(void)
+{
+	char *i[nelem(fn)+1] = {nil}, **ip = i;
+	void (*fns[nelem(fn)])(void) = {nil}, (**fp)(void) = fns;
+	Fn *f;
+
+	if(selected2 != selected)
+		goto end;
+	for(f=fn; f<fn+nelem(fn); f++)
+		if(f->check()){
+			*ip++ = f->name;
+			*fp++ = f->fn;
+		}
+	if(ip > i)
+		lmenu(i, fns);
+	else{
+end:
+		clrmoverange(1);
+		selectfn = selecttile;
+	}
+}
+
+static void
+selecttile(void)
+{
+	selectfn = selectaction;
+	showmoverange(1);
+}
+
+void
+spawnunit(Map *m, Unit *u, int t)
+{
+	Munit *mu;
+
+	if(t < 0 || t > nelem(team))
+		t = 0;
+	if(u->unique && curteam != 0 && team[curteam].unique != nil){
+		mu = team[curteam].unique;
+	}else{
+		mu = emalloc(sizeof *m->u);
+		if(t > nteam)
+			nteam = t;
+		if(u->unique){
+			if(team[t].unique != nil)
+				sysfatal("spawnunit: team %d uniqueness violation\n", t);
+			team[t].unique = mu;
+		}
+		mu->u = u;
+		mu->team = t;
+		mu->ecost = u->cost;
+		mu->eatk = u->atk;
+		mu->edef = u->def;
+	}
+	mu->hp = 100;
+	team[t].nunit++;
+	m->u = mu;
+}
+
+static void
+resupply(void)
+{
+	Map *m;
+	Unit **u;
+	Munit *mu;
+	Terrain *t;
+	Tunit *tu;
+
+	for(m=map; m<mape; m++){
+		mu = m->u;
+		t = m->t;
+		tu = t->resupply;
+		if(mu == nil
+		|| mu->team != curteam
+		|| mu->hp == 100
+		|| tu == nil)
+			continue;
+		for(u=tu->u; u<tu->e; u++)
+			if(mu->u == *u)
+				break;
+		if(u == tu->e)
+			continue;
+		mu->hp += 20;
+		if(mu->hp > 100)
+			mu->hp = 100;
+	}
+}
+
+static void
+newturn(void)
+{
+	if(turn > 0 || !firstturnnoinc)
+		team[curteam].money += team[curteam].income;
+	resupply();
+}
+
+void
+endturn(void)
+{
+	Team *t;
+	Map *m;
+
+	if(saved != nil)
+		cancelmove();
+	menuon = 0;
+	selectfn = selecttile;
+	selected = selected2 = nil;
+	for(m=map; m<mape; m++)
+		if(m->u != nil)
+			m->u->done = 0;
+	for(t=team+curteam+1; t<=team+nteam; t++)
+		if(t->nunit > 0 || t->nprod > 0)
+			break;
+	if(t == team+nteam+1){
+		turn++;
+		updatecorpses();
+		for(t=team+1; t<team+curteam; t++)
+			if(t->nunit > 0 || t->nprod > 0)
+				break;
+	}
+	if(t == team+curteam)
+		gameover = 1;
+	curteam = t - team;
+	newturn();
+	redraw(0);
+}
+
+void
+initgame(void)
+{
+	Team *t;
+
+	for(t=team; t<=team+nteam; t++){
+		t->money = initmoney;
+		if(t > team && curteam == 0
+		&& (t->nunit > 0 || t->nprod > 0))
+			curteam = t - team;
+	}
+	if(curteam == 0)
+		sysfatal("initgame: the only winning move is not to play");
+	newturn();
+	selectfn = selecttile;
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,30 @@
+</$objtype/mkfile
+TARG=\
+	tbs1\
+	tbs2\
+	tchs\
+
+OFILES=\
+	db.$O\
+	drw.$O\
+	game.$O\
+	tbs.$O\
+
+HFILES=dat.h fns.h
+</sys/src/cmd/mkmany
+BIN=$home/bin/$objtype
+
+$O.tbs1: $OFILES tbs1.$O
+	$LD -o $target $prereq
+
+$O.tbs2: $OFILES tbs2.$O
+	$LD -o $target $prereq
+
+$O.tchs: $OFILES tchs.$O
+	$LD -o $target $prereq
+
+sysinstall:V:
+	mkdir -p /sys/games/lib/^(tbs1 tbs2 tchs)
+	dircp tbs1 /sys/games/lib/tbs1
+	dircp tbs2 /sys/games/lib/tbs2
+	dircp tchs /sys/games/lib/tchs
--- /dev/null
+++ b/tbs.c
@@ -1,0 +1,171 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+extern Point pan;
+void	select(Point);
+
+char *dbname, *prefix, *mapname = "map1.db";
+
+static Keyboardctl *kctl;
+static Mousectl *mctl;
+
+void
+lmenu(char **i, void (*fn[])(void))
+{
+	int n;
+	static Menu m;
+
+	m.item = i;
+	if((n = menuhit(1, mctl, &m, nil)) >= 0)
+		fn[n]();
+}
+
+static void
+mmenu(void)
+{
+	enum{
+		END,
+		SPC,
+		QUIT
+	};
+	static char *i[] = {
+		[END] "end turn",
+		[SPC] "",
+		[QUIT] "quit",
+		nil
+	};
+	Menu m = {
+		.item = i
+	};
+
+	if(gameover)
+		m.item += 2;
+	switch(menuhit(2, mctl, &m, nil)){
+	case END: if(!gameover){ endturn(); break; }	/* wet floor */
+	case QUIT: threadexitsall(nil);
+	}
+}
+
+char *
+estrdup(char *s)
+{
+	if((s = strdup(s)) == nil)
+		sysfatal("estrdup: %r");
+	setmalloctag(s, getcallerpc(&s));
+	return s;
+}
+
+void *
+emalloc(ulong n)
+{
+	void *p;
+
+	if((p = mallocz(n, 1)) == nil)
+		sysfatal("emalloc: %r");
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+void *
+erealloc(void *p, ulong n, ulong on)
+{
+	void *q;
+
+	q = emalloc(n);
+	if(p != nil && on > 0)
+		memmove(q, p, on);
+	free(p);
+	return q;
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-d db] [-m map]\n", argv0);
+	threadexits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	Rune r;
+	Mouse m, om;
+
+	ARGBEGIN{
+	case 'd': dbname = EARGF(usage()); break;
+	case 'm': mapname = EARGF(usage()); break;
+	default: usage();
+	}ARGEND
+	if(dbname == nil)
+		dbname = smprint("%s.db", progname);
+	if(prefix == nil)
+		prefix = smprint("/sys/games/lib/%s", progname);
+	srand(time(nil));
+	init();
+	if(initdraw(nil, nil, progname) < 0)
+		sysfatal("initdraw: %r");
+	if((kctl = initkeyboard(nil)) == nil)
+		sysfatal("initkeyboard: %r");
+	if((mctl = initmouse(nil, screen)) == nil)
+		sysfatal("initmouse: %r");
+	initimages();
+	resetdraw();
+	Alt a[] = {
+		{mctl->c, &m, CHANRCV},
+		{mctl->resizec, nil, CHANRCV},
+		{kctl->c, &r, CHANRCV},
+		{nil, nil, CHANEND}
+	};
+	for(;;){
+		switch(alt(a)){
+		default:
+			sysfatal("alt: %r");
+		case 0:
+			if((m.buttons & 1) == 1 && (om.buttons & 1) == 0)
+				select(m.xy);
+			if(m.buttons & 2)
+				mmenu();
+			if(m.buttons & 4)
+				dopan(m.xy.x - om.xy.x, m.xy.y - om.xy.y);
+			om = m;
+			break;
+		case 1:
+			if(getwindow(display, Refnone) < 0)
+				sysfatal("getwindow: %r");
+			resetdraw();
+			break;
+		case 2:
+			switch(r){
+			case '+':
+			case '=':
+				if(scale < 16){
+					scale++;
+					resetdraw();
+				}
+				break;
+			case '-':
+				if(scale > 1){
+					scale--;
+					resetdraw();
+				}
+				break;
+			case 'r':
+				scale = 1;
+				pan = ZP;
+				resetdraw();
+				break;
+			case Kesc:
+				selected = nil;
+				redraw(0);
+				break;
+			}
+			break;
+		}
+	}
+}
--- /dev/null
+++ b/tbs1.c
@@ -1,0 +1,6 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+char *progname = "tbs1";
binary files /dev/null b/tbs1/1.bit differ
binary files /dev/null b/tbs1/2.bit differ
binary files /dev/null b/tbs1/3.bit differ
binary files /dev/null b/tbs1/4.bit differ
binary files /dev/null b/tbs1/5.bit differ
binary files /dev/null b/tbs1/6.bit differ
binary files /dev/null b/tbs1/7.bit differ
binary files /dev/null b/tbs1/8.bit differ
binary files /dev/null b/tbs1/9.bit differ
binary files /dev/null b/tbs1/castle.bit differ
binary files /dev/null b/tbs1/castle1.bit differ
binary files /dev/null b/tbs1/castle2.bit differ
binary files /dev/null b/tbs1/castle3.bit differ
binary files /dev/null b/tbs1/castle4.bit differ
binary files /dev/null b/tbs1/city.bit differ
binary files /dev/null b/tbs1/city1.bit differ
binary files /dev/null b/tbs1/city2.bit differ
binary files /dev/null b/tbs1/city3.bit differ
binary files /dev/null b/tbs1/city4.bit differ
binary files /dev/null b/tbs1/corpse.bit differ
binary files /dev/null b/tbs1/cursor.bit differ
binary files /dev/null b/tbs1/elemental.bit differ
binary files /dev/null b/tbs1/elemental1.bit differ
binary files /dev/null b/tbs1/elemental2.bit differ
binary files /dev/null b/tbs1/elemental3.bit differ
binary files /dev/null b/tbs1/elemental4.bit differ
binary files /dev/null b/tbs1/fighter.bit differ
binary files /dev/null b/tbs1/fighter1.bit differ
binary files /dev/null b/tbs1/fighter2.bit differ
binary files /dev/null b/tbs1/fighter3.bit differ
binary files /dev/null b/tbs1/fighter4.bit differ
binary files /dev/null b/tbs1/fort.bit differ
binary files /dev/null b/tbs1/hill.bit differ
--- /dev/null
+++ b/tbs1/map1.db
@@ -1,0 +1,17 @@
+unitcap=10
+map=castle1 unit=paladin1
+map=plain
+map=plain
+map=plain
+map=plain
+map=city
+map=plain
+map=plain
+map=plain
+map=plain
+map=city
+map=plain
+map=plain
+map=plain
+map=plain
+map=castle2 unit=paladin2
--- /dev/null
+++ b/tbs1/map2.db
@@ -1,0 +1,225 @@
+map=mountain
+map=plain
+map=road
+map=hill
+map=water
+map=water
+map=water
+map=wood
+map=hill
+map=mountain
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=castle1 unit=paladin1
+map=road
+map=city
+map=water
+map=water
+map=wood
+map=plain
+map=city
+map=wood
+map=wood
+map=mountain
+map=water
+map=water
+map=water
+map=road
+map=road
+map=road
+map=road
+map=road	# bridge
+map=road	# bridge
+map=road
+map=road
+map=plain
+map=hill
+map=mountain
+map=wood
+map=mountain
+map=hill
+map=wood
+map=mountain
+map=city1
+map=road
+map=wood
+map=water
+map=water
+map=mountain
+map=road
+map=mountain
+map=wood
+map=wood
+map=plain
+map=wood
+map=plain
+map=hill
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=water
+map=water
+map=road
+map=plain
+map=wood
+map=mountain
+map=hill
+map=plain
+map=castle
+map=mountain
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=water
+map=water
+map=road
+map=wood
+map=road
+map=road
+map=road
+map=road
+map=road
+map=plain
+map=wood
+map=hill
+map=plain
+map=wood
+map=mountain
+map=water
+map=water
+map=road
+map=city
+map=road
+map=plain
+map=city
+map=plain
+map=wood
+map=wood
+map=wood
+map=plain
+map=wood
+map=hill
+map=plain
+map=road
+map=road
+map=road
+map=road
+map=road
+map=hill
+map=wood
+map=wood
+map=wood
+map=mountain
+map=hill
+map=plain
+map=plain
+map=city
+map=mountain
+map=road
+map=city
+map=road
+map=water
+map=water
+map=plain
+map=wood
+map=mountain
+map=water
+map=wood
+map=wood
+map=road
+map=road
+map=road
+map=road
+map=road
+map=plain
+map=road
+map=water
+map=water
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=wood
+map=castle
+map=mountain
+map=plain
+map=hill
+map=wood
+map=wood
+map=road
+map=water
+map=water
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=mountain
+map=hill
+map=plain
+map=wood
+map=mountain
+map=wood
+map=mountain
+map=road
+map=mountain
+map=water
+map=water
+map=wood
+map=road
+map=city2
+map=mountain
+map=wood
+map=mountain
+map=hill
+map=mountain
+map=hill
+map=mountain
+map=plain
+map=road
+map=road
+map=road	# bridge
+map=road	# bridge
+map=road
+map=road
+map=road
+map=road
+map=water
+map=water
+map=water
+map=wood
+map=wood
+map=hill
+map=city
+map=plain
+map=wood
+map=water
+map=water
+map=city2
+map=road
+map=castle2 unit=paladin2
+map=road
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=wood
+map=plain
+map=wood
+map=water
+map=water
+map=water
+map=hill
+map=road
+map=plain
+map=mountain
--- /dev/null
+++ b/tbs1/map3.db
@@ -1,0 +1,161 @@
+mapwidth=16
+map=wood
+map=mountain
+map=hill
+map=wood
+map=water
+map=water
+map=water
+map=wood
+map=wood
+map=mountain
+map=castle
+map=plain
+map=mountain
+map=wood
+map=wood
+map=mountain
+map=mountain
+map=castle1 unit=paladin1
+map=plain
+map=city
+map=water
+map=water
+map=water
+map=wood
+map=plain
+map=road
+map=road
+map=road
+map=road
+map=hill
+map=mountain
+map=wood
+map=road
+map=road
+map=road
+map=road
+map=water
+map=water
+map=wood
+map=plain
+map=city
+map=road
+map=hill
+map=wood
+map=road
+map=plain
+map=plain
+map=wood
+map=wood
+map=hill
+map=wood
+map=road
+map=water
+map=water
+map=mountain
+map=hill
+map=plain
+map=road
+map=mountain
+map=plain
+map=road
+map=city
+map=hill
+map=mountain
+map=plain
+map=mountain
+map=city
+map=road
+map=water
+map=water
+map=plain
+map=road
+map=road
+map=road
+map=plain
+map=road
+map=road
+map=plain
+map=wood
+map=wood
+map=plain
+map=hill
+map=wood
+map=road
+map=wood
+map=wood
+map=plain
+map=road
+map=city
+map=water
+map=water
+map=road
+map=hill
+map=plain
+map=mountain
+map=wood
+map=wood
+map=plain
+map=hill
+map=road
+map=wood
+map=plain
+map=city
+map=road
+map=hill
+map=water
+map=water
+map=road
+map=city
+map=hill
+map=castle2 unit=paladin2
+map=mountain
+map=mountain
+map=plain
+map=mountain
+map=road
+map=hill
+map=wood
+map=mountain
+map=road
+map=water
+map=water
+map=water
+map=road
+map=road
+map=road
+map=road
+map=road
+map=wood
+map=hill
+map=plain
+map=road
+map=castle
+map=road
+map=road
+map=road
+map=water
+map=water
+map=water
+map=wood
+map=plain
+map=hill
+map=plain
+map=wood
+map=wood
+map=wood
+map=mountain
+map=road
+map=road
+map=road
+map=hill
+map=mountain
+map=water
+map=water
+map=water
+map=mountain
+map=wood
+map=mountain
+map=wood
+map=wood
--- /dev/null
+++ b/tbs1/map4.db
@@ -1,0 +1,210 @@
+mapwidth=11
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=wood
+map=mountain
+map=mountain
+map=wood
+map=water
+map=water
+map=water
+map=wood
+map=castle2 unit=paladin2
+map=hill
+map=plain
+map=wood
+map=mountain
+map=mountain
+map=wood
+map=plain
+map=castle3 unit=paladin3
+map=hill
+map=road
+map=road
+map=wood
+map=hill
+map=mountain
+map=mountain
+map=mountain
+map=road
+map=road
+map=road
+map=road
+map=city2
+map=road
+map=road
+map=road
+map=mountain
+map=mountain
+map=wood
+map=road
+map=mountain
+map=wood
+map=city3
+map=mountain
+map=plain
+map=wood
+map=road
+map=wood
+map=mountain
+map=wood
+map=road
+map=hill
+map=plain
+map=hill
+map=wood
+map=mountain
+map=plain
+map=road
+map=plain
+map=city
+map=hill
+map=road
+map=plain
+map=mountain
+map=wood
+map=water
+map=water
+map=wood
+map=road
+map=road
+map=road
+map=road
+map=road
+map=hill
+map=water
+map=water
+map=water
+map=water
+map=mountain
+map=city
+map=wood
+map=road
+map=wood
+map=city
+map=mountain
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=road
+map=road
+map=road
+map=road
+map=road
+map=wood
+map=water
+map=water
+map=mountain
+map=mountain
+map=plain
+map=road
+map=plain
+map=city
+map=plain
+map=road
+map=road
+map=road
+map=mountain
+map=hill
+map=plain
+map=wood
+map=road
+map=hill
+map=mountain
+map=wood
+map=hill
+map=wood
+map=road
+map=hill
+map=city4
+map=hill
+map=mountain
+map=road
+map=wood
+map=mountain
+map=wood
+map=mountain
+map=wood
+map=road
+map=city1
+map=road
+map=road
+map=road
+map=road
+map=mountain
+map=mountain
+map=mountain
+map=hill
+map=plain
+map=road
+map=road
+map=wood
+map=castle4 unit=paladin4
+map=hill
+map=plain
+map=wood
+map=mountain
+map=mountain
+map=wood
+map=plain
+map=castle1 unit=paladin1
+map=wood
+map=water
+map=water
+map=water
+map=wood
+map=mountain
+map=mountain
+map=wood
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
--- /dev/null
+++ b/tbs1/map5.db
@@ -1,0 +1,225 @@
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=hill
+map=plain
+map=wood
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=mountain
+map=hill
+map=plain
+map=city
+map=castle1 unit=paladin1
+map=plain
+map=wood
+map=mountain
+map=hill
+map=water
+map=water
+map=water
+map=water
+map=water
+map=mountain
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=mountain
+map=water
+map=water
+map=water
+map=water
+map=plain
+map=road
+map=city
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=city
+map=road
+map=plain
+map=water
+map=water
+map=water
+map=wood
+map=hill
+map=road
+map=water
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=water
+map=road
+map=hill
+map=wood
+map=water
+map=water
+map=hill
+map=plain
+map=road
+map=water
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=water
+map=road
+map=city
+map=plain
+map=water
+map=water
+map=plain
+map=castle3 unit=paladin3
+map=road
+map=road	# bridge
+map=road	# bridge
+map=road	# bridge
+map=city
+map=road	# bridge
+map=road	# bridge
+map=road	# bridge
+map=road
+map=castle4 unit=paladin4
+map=hill
+map=water
+map=water
+map=hill
+map=city
+map=road
+map=water
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=water
+map=road
+map=plain
+map=mountain
+map=water
+map=water
+map=wood
+map=wood
+map=road
+map=water
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=water
+map=road
+map=hill
+map=wood
+map=water
+map=water
+map=water
+map=mountain
+map=road
+map=city
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=city
+map=road
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=water
+map=mountain
+map=plain
+map=hill
+map=plain
+map=castle2 unit=paladin2
+map=city
+map=hill
+map=plain
+map=mountain
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=hill
+map=wood
+map=plain
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
--- /dev/null
+++ b/tbs1/map6.db
@@ -1,0 +1,225 @@
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=wood
+map=hill
+map=wood
+map=wood
+map=wood
+map=mountain
+map=hill
+map=wood
+map=plain
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=city1
+map=plain
+map=mountain
+map=plain
+map=castle1 unit=paladin1
+map=hill
+map=plain
+map=hill
+map=city1
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=water
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=hill
+map=water
+map=water
+map=water
+map=water
+map=water
+map=road
+map=hill
+map=mountain
+map=mountain
+map=mountain
+map=mountain
+map=mountain
+map=hill
+map=road
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=water
+map=road
+map=wood
+map=mountain
+map=city
+map=hill
+map=city
+map=mountain
+map=wood
+map=road
+map=city
+map=water
+map=water
+map=water
+map=water
+map=city
+map=road
+map=hill
+map=plain
+map=plain
+map=wood
+map=plain
+map=hill
+map=plain
+map=road
+map=water
+map=water
+map=water
+map=water
+map=water
+map=plain
+map=road
+map=wood
+map=mountain
+map=city
+map=plain
+map=city
+map=mountain
+map=wood
+map=road
+map=water
+map=water
+map=water
+map=water
+map=water
+map=hill
+map=road
+map=plain
+map=mountain
+map=mountain
+map=mountain
+map=mountain
+map=mountain
+map=plain
+map=road
+map=water
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=road
+map=water
+map=water
+map=water
+map=water
+map=water
+map=plain
+map=city2
+map=plain
+map=mountain
+map=hill
+map=castle2 unit=paladin2
+map=plain
+map=wood
+map=hill
+map=city2
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=water
+map=water
+map=wood
+map=wood
+map=plain
+map=wood
+map=mountain
+map=plain
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
--- /dev/null
+++ b/tbs1/map7.db
@@ -1,0 +1,225 @@
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=hill
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=hill
+map=mountain
+map=water
+map=water
+map=wood
+map=castle1 unit=paladin1
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=mountain
+map=wood
+map=mountain
+map=water
+map=water
+map=mountain
+map=plain
+map=plain
+map=city
+map=water
+map=water
+map=water
+map=water
+map=water
+map=city
+map=plain
+map=castle4 unit=paladin4
+map=hill
+map=water
+map=water
+map=water
+map=city
+map=hill
+map=wood
+map=mountain
+map=water
+map=water
+map=water
+map=wood
+map=road
+map=road
+map=road
+map=wood
+map=water
+map=water
+map=water
+map=water
+map=road
+map=road
+map=road
+map=road
+map=road	# bridge
+map=road	# bridge
+map=road
+map=road
+map=wood
+map=road
+map=city
+map=water
+map=water
+map=water
+map=water
+map=road
+map=mountain
+map=wood
+map=mountain
+map=water
+map=water
+map=water
+map=water
+map=wood
+map=road
+map=plain
+map=water
+map=water
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=city
+map=water
+map=water
+map=water
+map=water
+map=mountain
+map=road
+map=mountain
+map=water
+map=water
+map=water
+map=water
+map=road	# bridge
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=hill
+map=road
+map=wood
+map=water
+map=water
+map=city
+map=road
+map=road
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=city
+map=road
+map=hill
+map=water
+map=water
+map=mountain
+map=road
+map=city
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=mountain
+map=road
+map=city
+map=water
+map=water
+map=hill
+map=road
+map=road
+map=road	# bridge
+map=road	# bridge
+map=road
+map=road
+map=road	# bridge
+map=road	# bridge
+map=road	# bridge
+map=road
+map=road
+map=plain
+map=water
+map=water
+map=wood
+map=castle2 unit=paladin2
+map=wood
+map=water
+map=water
+map=mountain
+map=city
+map=water
+map=water
+map=water
+map=hill
+map=castle3 unit=paladin3
+map=wood
+map=water
+map=water
+map=mountain
+map=wood
+map=plain
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=mountain
+map=wood
+map=hill
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
+map=water
binary files /dev/null b/tbs1/mountain.bit differ
binary files /dev/null b/tbs1/paladin.bit differ
binary files /dev/null b/tbs1/paladin1.bit differ
binary files /dev/null b/tbs1/paladin2.bit differ
binary files /dev/null b/tbs1/paladin3.bit differ
binary files /dev/null b/tbs1/paladin4.bit differ
binary files /dev/null b/tbs1/plain.bit differ
binary files /dev/null b/tbs1/ranger.bit differ
binary files /dev/null b/tbs1/ranger1.bit differ
binary files /dev/null b/tbs1/ranger2.bit differ
binary files /dev/null b/tbs1/ranger3.bit differ
binary files /dev/null b/tbs1/ranger4.bit differ
binary files /dev/null b/tbs1/road.bit differ
binary files /dev/null b/tbs1/ruin.bit differ
--- /dev/null
+++ b/tbs1/tbs1.db
@@ -1,0 +1,56 @@
+initmoney=500
+firstturnnoinc=1
+
+unit=paladin	atk=55 Δatk=10 def=20 mp=4 cost=200 unique=1
+unit=fighter	atk=50 Δatk=5 def=5 mp=4 cost=150
+unit=ranger	atk=50 Δatk=5 range=1,2 mp=4 cost=250
+unit=elemental	atk=50 Δatk=5 mp=4 cost=300
+	# damage + defence bonus when on water
+	# (if move == water and move == terrain type?)
+#unit=necromancer atk=40 Δatk=5 def=5 mp=4 cost=400
+#	# animate dead
+#unit=priest atk=35 Δatk=5 def=10 mp=4 cost=500
+#	# bless units in 2 square grid
+#unit=mage atk=60 Δatk=5 def=15 mp=5 cost=600
+#	# slow/curse attacked unit
+#unit=golem atk=60 Δatk=10 def=30 mp=4 cost=600
+#unit=catapult atk=50 Δatk=20 range=2,4 def=10 mp=3 cost=700
+#unit=dragon atk=70 Δatk=10 def=25 mp=6 cost=1000
+#unit=risen atk=40 Δatk=10 def=2 mp=4
+unit=corpse
+#	# dies on the 2 turn after spawn, without spawning another
+#	# players can move on top of it, destroying it
+
+# FIXME: damage ratios, 100 by default
+#ratio=ranger	dragon=150
+
+# FIXME: terrain bonuses
+
+move=road
+move=hill	paladin=1 fighter=1 ranger=1 elemental=1
+move=mountain	paladin=2 fighter=2 ranger=2 elemental=2
+move=water	paladin=2 fighter=2 ranger=2
+
+terrain=road		move=road
+terrain=plain		def=1 move=road
+terrain=hill		def=2 move=hill
+terrain=wood		def=2 move=hill
+terrain=mountain	def=3 move=mountain
+terrain=water		move=water
+terrain=castle		def=3 move=road income=50
+terrain=city		def=3 move=road income=30
+terrain=ruin		def=3 move=road
+terrain=fort		def=3 move=road
+
+spawn=castle	unit=paladin unit=fighter unit=ranger unit=elemental
+
+resupply=castle spawn=castle
+resupply=city spawn=castle
+resupply=fort spawn=castle
+
+occupy=castle unit=paladin
+occupy=city unit=paladin unit=fighter
+
+#destroy=city terrain=ruin unit=catapult
+
+#repair=ruin terrain=city unit=paladin unit=fighter
binary files /dev/null b/tbs1/water.bit differ
binary files /dev/null b/tbs1/wood.bit differ
--- /dev/null
+++ b/tbs2.c
@@ -1,0 +1,6 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+char *progname = "tbs2";
binary files /dev/null b/tbs2/1.bit differ
binary files /dev/null b/tbs2/2.bit differ
binary files /dev/null b/tbs2/3.bit differ
binary files /dev/null b/tbs2/4.bit differ
binary files /dev/null b/tbs2/5.bit differ
binary files /dev/null b/tbs2/6.bit differ
binary files /dev/null b/tbs2/7.bit differ
binary files /dev/null b/tbs2/8.bit differ
binary files /dev/null b/tbs2/9.bit differ
binary files /dev/null b/tbs2/aab.bit differ
binary files /dev/null b/tbs2/aab1.bit differ
binary files /dev/null b/tbs2/aab2.bit differ
binary files /dev/null b/tbs2/aab3.bit differ
binary files /dev/null b/tbs2/aab4.bit differ
binary files /dev/null b/tbs2/aam.bit differ
binary files /dev/null b/tbs2/aam1.bit differ
binary files /dev/null b/tbs2/aam2.bit differ
binary files /dev/null b/tbs2/aam3.bit differ
binary files /dev/null b/tbs2/aam4.bit differ
binary files /dev/null b/tbs2/apu.bit differ
binary files /dev/null b/tbs2/apu1.bit differ
binary files /dev/null b/tbs2/apu2.bit differ
binary files /dev/null b/tbs2/apu3.bit differ
binary files /dev/null b/tbs2/apu4.bit differ
binary files /dev/null b/tbs2/arty.bit differ
binary files /dev/null b/tbs2/arty1.bit differ
binary files /dev/null b/tbs2/arty2.bit differ
binary files /dev/null b/tbs2/arty3.bit differ
binary files /dev/null b/tbs2/arty4.bit differ
binary files /dev/null b/tbs2/bomber.bit differ
binary files /dev/null b/tbs2/bomber1.bit differ
binary files /dev/null b/tbs2/bomber2.bit differ
binary files /dev/null b/tbs2/bomber3.bit differ
binary files /dev/null b/tbs2/bomber4.bit differ
binary files /dev/null b/tbs2/bridge.bit differ
binary files /dev/null b/tbs2/chopper.bit differ
binary files /dev/null b/tbs2/chopper1.bit differ
binary files /dev/null b/tbs2/chopper2.bit differ
binary files /dev/null b/tbs2/chopper3.bit differ
binary files /dev/null b/tbs2/chopper4.bit differ
binary files /dev/null b/tbs2/cursor.bit differ
binary files /dev/null b/tbs2/etank.bit differ
binary files /dev/null b/tbs2/etank1.bit differ
binary files /dev/null b/tbs2/etank2.bit differ
binary files /dev/null b/tbs2/etank3.bit differ
binary files /dev/null b/tbs2/etank4.bit differ
binary files /dev/null b/tbs2/factory.bit differ
binary files /dev/null b/tbs2/factory1.bit differ
binary files /dev/null b/tbs2/factory2.bit differ
binary files /dev/null b/tbs2/factory3.bit differ
binary files /dev/null b/tbs2/factory4.bit differ
binary files /dev/null b/tbs2/fighter.bit differ
binary files /dev/null b/tbs2/fighter1.bit differ
binary files /dev/null b/tbs2/fighter2.bit differ
binary files /dev/null b/tbs2/fighter3.bit differ
binary files /dev/null b/tbs2/fighter4.bit differ
binary files /dev/null b/tbs2/heavy.bit differ
binary files /dev/null b/tbs2/heavy1.bit differ
binary files /dev/null b/tbs2/heavy2.bit differ
binary files /dev/null b/tbs2/heavy3.bit differ
binary files /dev/null b/tbs2/heavy4.bit differ
binary files /dev/null b/tbs2/heli.bit differ
binary files /dev/null b/tbs2/heli1.bit differ
binary files /dev/null b/tbs2/heli2.bit differ
binary files /dev/null b/tbs2/heli3.bit differ
binary files /dev/null b/tbs2/heli4.bit differ
binary files /dev/null b/tbs2/htank.bit differ
binary files /dev/null b/tbs2/htank1.bit differ
binary files /dev/null b/tbs2/htank2.bit differ
binary files /dev/null b/tbs2/htank3.bit differ
binary files /dev/null b/tbs2/htank4.bit differ
binary files /dev/null b/tbs2/inf.bit differ
binary files /dev/null b/tbs2/inf1.bit differ
binary files /dev/null b/tbs2/inf2.bit differ
binary files /dev/null b/tbs2/inf3.bit differ
binary files /dev/null b/tbs2/inf4.bit differ
binary files /dev/null b/tbs2/lav.bit differ
binary files /dev/null b/tbs2/lav1.bit differ
binary files /dev/null b/tbs2/lav2.bit differ
binary files /dev/null b/tbs2/lav3.bit differ
binary files /dev/null b/tbs2/lav4.bit differ
binary files /dev/null b/tbs2/ltank.bit differ
binary files /dev/null b/tbs2/ltank1.bit differ
binary files /dev/null b/tbs2/ltank2.bit differ
binary files /dev/null b/tbs2/ltank3.bit differ
binary files /dev/null b/tbs2/ltank4.bit differ
--- /dev/null
+++ b/tbs2/map1.db
@@ -1,0 +1,16 @@
+map=factory1
+map=bridge
+map=bridge
+map=bridge
+map=bridge
+map=bridge
+map=bridge
+map=bridge
+map=bridge
+map=bridge
+map=bridge
+map=bridge
+map=bridge
+map=bridge
+map=bridge
+map=factory2
binary files /dev/null b/tbs2/missile.bit differ
binary files /dev/null b/tbs2/missile1.bit differ
binary files /dev/null b/tbs2/missile2.bit differ
binary files /dev/null b/tbs2/missile3.bit differ
binary files /dev/null b/tbs2/missile4.bit differ
binary files /dev/null b/tbs2/mtank.bit differ
binary files /dev/null b/tbs2/mtank1.bit differ
binary files /dev/null b/tbs2/mtank2.bit differ
binary files /dev/null b/tbs2/mtank3.bit differ
binary files /dev/null b/tbs2/mtank4.bit differ
binary files /dev/null b/tbs2/rkt.bit differ
binary files /dev/null b/tbs2/rkt1.bit differ
binary files /dev/null b/tbs2/rkt2.bit differ
binary files /dev/null b/tbs2/rkt3.bit differ
binary files /dev/null b/tbs2/rkt4.bit differ
binary files /dev/null b/tbs2/stealth.bit differ
binary files /dev/null b/tbs2/stealth1.bit differ
binary files /dev/null b/tbs2/stealth2.bit differ
binary files /dev/null b/tbs2/stealth3.bit differ
binary files /dev/null b/tbs2/stealth4.bit differ
--- /dev/null
+++ b/tbs2/tbs2.db
@@ -1,0 +1,137 @@
+nocorpse=
+
+unit=inf	vis=2 mp=3 cost=1000
+	# ammo 0, food 99 (-1)
+unit=heavy	vis=2 mp=2 cost=3000
+	# ammo 3,-1, food 70
+unit=lav	vis=5 mp=8 cost=4000
+	# ammo -1, food 80
+unit=ltank	vis=3 mp=6 cost=7000
+	# ammo 9,-1, food 70
+unit=mtank	vis=1 mp=5 cost=16000
+	# ammo 8,-1, food 50
+unit=etank	vis=1 mp=6 cost=22000
+	# ammo 9,-1, food 99
+unit=htank	vis=1 mp=4 cost=28000
+	# ammo 3,-1, food 50
+unit=arty	vis=1 mp=5 range=2,3 cost=6000
+	# ammo 9, food 50
+unit=rkt	vis=1 mp=5 range=3,5 cost=15000
+	# ammo 6, food 50
+unit=aab	vis=2 mp=6 cost=8000
+	# ammo 9, food 60
+unit=aam	vis=4 mp=5 range=3,5 cost=12000
+	# ammo 6, food 50
+unit=apu	vis=1 mp=6 cost=5000
+	# no weapon
+	# resupply fuel and ammo to adjacent units
+	# load up to 1 inf/heavy
+	# food 70
+unit=fighter	vis=2 mp=9 cost=20000
+	# ammo 9, food 99
+unit=bomber	vis=2 mp=7 cost=22000
+	# ammo 9, food 99
+unit=stealth	vis=4 mp=6 cost=24000
+	# cloak/uncloak
+	# ammo 6, food 60
+unit=chopper	vis=3 mp=6 cost=9000
+	# ammo 6,-1, food 99
+unit=heli	vis=2 mp=6 cost=5000
+	# no weapon
+	# load up to 1 inf/heavy
+	# food 99
+unit=missile	vis=1 mp=9 cost=24000
+	# no weapon
+	# trigger -> aoe 1,3 damage=50
+unit=lander	vis=1 mp=6 cost=12000
+	# no weapon
+	# load up to 1 inf/heavy
+	# food 99
+unit=repboat	vis=1 mp=7 cost=7500
+	# no weapon
+	# load 2 inf/heavy
+	# repair unit
+	# food 60
+unit=cruiser	vis=3 mp=6 cost=18000
+	# ammo 9, food 99
+	# can carry 2 heli?
+unit=sub	vis=5 mp=5 cost=20000
+	# ammo 6, food 60
+	# cloak/uncloak
+unit=btlship	vis=2 mp=5 range=2,6 cost=28000
+	# ammo 9, food 99
+unit=carrier	vis=4 mp=5 range=3,8 cost=30000
+	# ammo 9, food 99
+	# load/unload/resupply (but not repair?) 2 air units
+
+move=bridge	# changed to allow any unit
+move=road
+	lander=9 repboat=9 cruiser=9 sub=9 btlship=9 carrier=9
+move=plain
+	lav=1 arty=1 rkt=1 aam=1
+	lander=9 repboat=9 cruiser=9 sub=9 btlship=9 carrier=9
+move=wood
+	lav=2 arty=2 rkt=2 aam=2
+	ltank=1 mtank=1 etank=1 htank=1 aab=1 apu=1
+	lander=9 repboat=9 cruiser=9 sub=9 btlship=9 carrier=9
+move=mountain
+	inf=1
+	lav=9 arty=9 rkt=9 aam=9
+	ltank=9 mtank=9 etank=9 htank=9 aab=9 apu=9
+	lander=9 repboat=9 cruiser=9 sub=9 btlship=9 carrier=9
+move=sea
+	inf=9 heavy=9
+	lav=9 arty=9 rkt=9 aam=9
+	ltank=9 mtank=9 etank=9 htank=9 aab=9 apu=9
+move=shoal
+	cruiser=9 sub=9 btlship=9 carrier=9
+move=reef
+	inf=9 heavy=9
+	lav=9 arty=9 rkt=9 aam=9
+	ltank=9 mtank=9 etank=9 htank=9 aab=9 apu=9
+	lander=1 repboat=1 cruiser=1 sub=1 btlship=1 carrier=1
+move=mordor
+	inf=9 heavy=9
+	lav=9 arty=9 rkt=9 aam=9
+	ltank=9 mtank=9 etank=9 htank=9 aab=9 apu=9
+	fighter=9 bomber=9 stealth=9 missile=9 chopper=9 heli=9
+	lander=9 repboat=9 cruiser=9 sub=9 btlship=9 carrier=9
+
+terrain=bridge		move=bridge
+terrain=road		move=road
+terrain=plain		move=plain
+terrain=wood		move=wood def=1
+terrain=mountain	move=mountain def=3
+terrain=river		move=mountain
+terrain=sea		move=sea
+terrain=shoal		move=shoal
+terrain=reef		move=reef
+terrain=mordor		move=mordor
+terrain=hq		move=road def=3 income=1000
+terrain=city		move=road def=2 income=1000
+terrain=factory		move=road def=2 income=1000
+terrain=airport		move=road def=2 income=1000
+terrain=shipyard	move=bridge def=2 income=1000
+terrain=silo		move=road def=2
+	# launch: aoe 1,3 damage=30, only once
+terrain=tower		move=road def=2
+	# global atk bonus
+
+spawn=factory	unit=inf unit=heavy unit=lav unit=arty unit=rkt unit=aab unit=aam unit=apu unit=ltank unit=mtank unit=etank unit=htank
+spawn=airport	unit=fighter unit=bomber unit=stealth unit=missile unit=chopper unit=heli
+spawn=shipyard	unit=lander unit=repboat unit=cruiser unit=sub unit=btlship unit=carrier
+
+resupply=hq spawn=factory
+resupply=city spawn=factory
+resupply=factory spawn=factory
+resupply=airport spawn=airport
+resupply=shipyard spawn=shipyard
+
+occupy=hq unit=inf unit=heavy
+occupy=city unit=inf unit=heavy
+occupy=factory unit=inf unit=heavy
+occupy=airport unit=inf unit=heavy
+occupy=shipyard unit=inf unit=heavy
+occupy=tower unit=inf unit=heavy
+
+# FIXME: http://warswiki.org/wiki/Damage/Advance_Wars:_Dual_Strike_chart
--- /dev/null
+++ b/tchs.c
@@ -1,0 +1,6 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+char *progname = "tchs";
binary files /dev/null b/tchs/bishop1.bit differ
binary files /dev/null b/tchs/bishop2.bit differ
--- /dev/null
+++ b/tchs/chess.db
@@ -1,0 +1,6 @@
+unit=pawn
+unit=bishop
+unit=knight
+unit=rook
+unit=queen
+unit=king
binary files /dev/null b/tchs/cursor.bit differ
binary files /dev/null b/tchs/king1.bit differ
binary files /dev/null b/tchs/king2.bit differ
binary files /dev/null b/tchs/knight1.bit differ
binary files /dev/null b/tchs/knight2.bit differ
--- /dev/null
+++ b/tchs/map1.db
@@ -1,0 +1,64 @@
+map=tile0 unit=rook2
+map=tile1 unit=knight2
+map=tile0 unit=bishop2
+map=tile1 unit=king2
+map=tile0 unit=queen2
+map=tile1 unit=bishop2
+map=tile0 unit=knight2
+map=tile1 unit=rook2
+map=tile1 unit=pawn2
+map=tile0 unit=pawn2
+map=tile1 unit=pawn2
+map=tile0 unit=pawn2
+map=tile1 unit=pawn2
+map=tile0 unit=pawn2
+map=tile1 unit=pawn2
+map=tile0 unit=pawn2
+map=tile0
+map=tile1
+map=tile0
+map=tile1
+map=tile0
+map=tile1
+map=tile0
+map=tile1
+map=tile1
+map=tile0
+map=tile1
+map=tile0
+map=tile1
+map=tile0
+map=tile1
+map=tile0
+map=tile0
+map=tile1
+map=tile0
+map=tile1
+map=tile0
+map=tile1
+map=tile0
+map=tile1
+map=tile1
+map=tile0
+map=tile1
+map=tile0
+map=tile1
+map=tile0
+map=tile1
+map=tile0
+map=tile0 unit=pawn1
+map=tile1 unit=pawn1
+map=tile0 unit=pawn1
+map=tile1 unit=pawn1
+map=tile0 unit=pawn1
+map=tile1 unit=pawn1
+map=tile0 unit=pawn1
+map=tile1 unit=pawn1
+map=tile1 unit=rook1
+map=tile0 unit=knight1
+map=tile1 unit=bishop1
+map=tile0 unit=king1
+map=tile1 unit=queen1
+map=tile0 unit=bishop1
+map=tile1 unit=knight1
+map=tile0 unit=rook1
binary files /dev/null b/tchs/pawn1.bit differ
binary files /dev/null b/tchs/pawn2.bit differ
binary files /dev/null b/tchs/queen1.bit differ
binary files /dev/null b/tchs/queen2.bit differ
binary files /dev/null b/tchs/rook1.bit differ
binary files /dev/null b/tchs/rook2.bit differ
--- /dev/null
+++ b/tchs/tchs.db
@@ -1,0 +1,9 @@
+unit=pawn
+unit=bishop
+unit=knight
+unit=rook
+unit=queen
+unit=king
+
+terrain=tile0
+terrain=tile1
binary files /dev/null b/tchs/tile0.bit differ
binary files /dev/null b/tchs/tile1.bit differ