/* Roaming-blobs-on-mars-collect-some-dust-on-a-tropical-island-and-go-pearl-
   diving-before-population-goes-out-of-control. */

/* Each frame, a background virtual screen is copied to a virtual screen; */
/* sprites (well, pixels) are written on that virtual screen; and the */
/* virtual screen is copied to video memory. The background is updated as */
/* appropriate. This simple animation technique works well for 320x200 */
/* because it's so small. */


#include <stdlib.h>
#include <stdio.h>
#include <vga.h>
#include <vgagl.h>


/* This can be changed into any 256 color mode. */
/* For planar 256 color modes, enable page flipping. */
/* Even 16 color modes work (ugly colors). */
#define VGAMODE G320x200x256

/* #define USE_PAGEFLIPPING */

/* This is the size of the animated window. */
#define MAPWIDTH 320
#define MAPHEIGHT 190

#define MAXMOVERS 1000
#define MAXCITIES 1000
#define NUMBEROFCITIES 20
#define NUMBEROFMOVERS 400
#define MOVERTHRESHOLD 400
#define MOVERLIFETIME 1000
#define COLORTIME 2000

#define randomn( n ) (random() % n)
#define red(x) (32 + x)
#define green(x) (64 + x)
#define yellow(x) (96 + x)
#define blue(x) (128 + x)
#define magenta(x) (160 + x)
#define cyan(x) (192 + x)
#define white(x) (224 + x)


/* Data types */

typedef struct {
	int x;
	int y;
} Position;

#define STAT_ACTIVE 1

typedef struct {
	int stat;
	int x;
	int y;
	int vx;
	int vy;
	int color;
	int time;
} Mover;

typedef struct {
	int x;
	int y;
	int pop;
	int hit;
} City;

/* Global variables */

int map[MAPWIDTH][MAPHEIGHT];
/* Map encoding i: */
/* (0 - 0xffff	Mover number i) */
/* 0x10000...	Part of city (i - 0x10000) */  

Mover mover[MAXMOVERS];
int nu_movers = 0;
City city[MAXCITIES];
int nu_cities = 0;
int time = 0;
int pop = 0;
int framerate, framecount, frameclock;

GraphicsContext physicalscreen;
GraphicsContext backscreen;
GraphicsContext background;


void error( char *s ) {
	printf("%s\n", s);
	vga_setmode(TEXT);
	exit(0);
}

void setcustompalette() {
    /* colors 0-31 are a RGB mix (bits 0 and 1 red, 2 green, 3 and 4 blue) */
    /* 32-63    black to red */
    /* 64-95    black to green */
    /* 96-127   black to yellow */
    /* 128-159  black to blue */
    /* 160-191  black to magenta */
    /* 192-223  black to cyan */
    /* 224-255  black to white */
    Palette pal;
    int i;
    for (i = 0; i < 256; i++) {
        int r, g, b;
        r = g = b = 0;
        if ((i & 32) > 0) r = (i & 31) << 1;
        if ((i & 64) > 0) g = (i & 31) << 1;
        if ((i & 128) > 0) b = (i & 31) << 1;
        if (i < 32) {
            r = (i & 3) << 4;   /* 2 bits */
            g = (i & 4) << 3;   /* 1 bit */
            b = (i & 24) << 1;  /* 2 bits */
        }
        pal.color[i].red = r;
        pal.color[i].green = g;
        pal.color[i].blue = b;
    }
    gl_setpalette(&pal);
}

void initfont() {
	void *font;
	font = malloc(256 * 8 * 8 * BYTESPERPIXEL);
	gl_expandfont(8, 8, white(24), gl_font8x8, font);
	gl_setfont(8, 8, font);
}

int fsize( FILE *f ) {
	int oldpos, size;
	oldpos = ftell(f);
	fseek(f, 0, SEEK_END);
	size = ftell(f);
	fseek(f, oldpos, SEEK_SET);
	return size;
}

void loadfile( char **buf, char *fname ) {
	FILE *f;
	int size;
	f = fopen(fname, "rb");
	size = fsize(f);
	*buf = malloc(size);
	fread(*buf, 1, size, f);
	fclose(f);
}


/* Map */

void clearmap() {
	int x, y;
	for (y = 0; y < MAPHEIGHT; y++)
		for (x = 0; x < MAPWIDTH; x++)
			map[x][y] = 0;
}

Position findfreeposition() {
	int x, y;
	Position p;
	do {
		x = randomn(MAPWIDTH);
		y = randomn(MAPHEIGHT);
	} while (map[x][y] != 0);
	p.x = x;
	p.y = y;
	return p;
} 


/* Movers */

void initmovers() {
	int i;
	for (i = 0; i < MAXMOVERS; i++)
		mover[i].stat = 0;
}

int findfreemoverslot() {
	int i;
	for (i = 0; i < MAXMOVERS; i++)
		if (!(mover[i].stat & STAT_ACTIVE))
			return i;
	error("Mover table overflow");
}

void addrandommover() {
	Position p = findfreeposition();
	int i = findfreemoverslot();
	int c;
	mover[i].x = p.x;
	mover[i].y = p.y;
	do {
		mover[i].vx = randomn(3) - 1;
		mover[i].vy = randomn(3) - 1;
	} while (mover[i].vx == 0 && mover[i].vy == 0);
	mover[i].stat = STAT_ACTIVE;
	switch (randomn(4)) {
		case 0 :
			c = blue(20); break;
		case 1 :
			c = green(20); break;
		case 2 :
			c = magenta(20); break;
		case 3 :
			c = cyan(20); break;
	}
	mover[i].time = 0;
	mover[i].color = c;
	nu_movers++;
}

void killmover( int i ) {
	mover[i].stat = 0;
	nu_movers--;
}

void drawmover( int i ) {
	gl_setpixel(mover[i].x, mover[i].y, mover[i].color);
}


/* Cities */

void initcities() {
	nu_cities = 0;
}

void addcity( int x, int y ) {
	int i = nu_cities++;
	map[x][y] = i + 0x10000;
	city[i].x = x;
	city[i].y = y;
	city[i].pop = 1;
	city[i].hit = 0;
}

int cityat( int x, int y ) {
	if (map[x][y] >= 0x10000)
		return map[x][y] - 0x10000;
	else
		return -1;
}

int citycolor() {
static int colortable[5] =
	{ yellow(31), blue(31), white(31), green(31), cyan(31) };
	return colortable[(time / COLORTIME) % 5] 
		- (time % COLORTIME) * 25 / COLORTIME;   
}

void growcity( int cx, int cy, int x, int y, int ct ) {
/* add city unit at (x, y) adjacent to city unit (cx, cy) */
	int c;
	map[x][y] = ct + 0x10000;
	c = citycolor();
	gl_setpixel(x, y, c);	
	city[ct].pop++;
	city[ct].hit = 20;
	pop++;
}


/* Main components */

void createbackground() {
/* Create fancy dark red background */
	int x, y;
	for (y = 0; y < MAPHEIGHT; y++)
		for (x = 0; x < MAPWIDTH; x++) {
			int i = 0;
			int n = 0;
			int c;
			if (x > 0) {
				i += gl_getpixel(x - 1, y) - red(0);
				n++;
			}
			if (y > 0) {
				i += gl_getpixel(x, y - 1) - red(0);
				n++;
			}
			c = (i + randomn(16)) / (n + 1);
			if (c > 9)
				c = 9;
			gl_setpixel(x, y, red(0) + c);
		}
}

void drawbackground() {
/* Build up background from map data */
	int x, y;
	gl_setcontext(&background);
	gl_clearscreen(0);
	createbackground();
	for (y = 0; y < MAPHEIGHT; y++)
		for (x = 0; x < MAPWIDTH; x++) {
			int c = cityat(x, y);
			if (c != -1)
				gl_setpixel(x, y, citycolor());
		}
}

void createcities() {
	int i;
	for (i = 0; i < NUMBEROFCITIES; i++) {
		Position p = findfreeposition();
		addcity(p.x, p.y);
	}
}

void writestat() {
	char s[41];
	int i, x, y;
	int maxpopcity, maxpop;
	sprintf(s, "Pop %7d  Time %7d  Rate %5d.%d", pop, time,
		framerate / 10, framerate % 10);
	gl_setwritemode(WRITEMODE_OVERWRITE);
	gl_write(0, HEIGHT - 8, s);
	maxpop = -1;
	for (i = 0; i < nu_cities; i++)
		if (city[i].pop > maxpop) {
			maxpop = city[i].pop;
			maxpopcity = i;
		}

	gl_enableclipping();
	gl_circle(city[maxpopcity].x, city[maxpopcity].y, 10,
		blue(31));
	gl_disableclipping();

	gl_setwritemode(WRITEMODE_MASKED);
	x = city[maxpopcity].x;
	y = city[maxpopcity].y;
	sprintf(s, "%d", maxpop);
	/* clipping */
	if (x + strlen(s) * 8 > MAPWIDTH)
		x = MAPWIDTH - strlen(s) * 8;
	if (y + 8 > MAPHEIGHT)
		y = MAPHEIGHT - 8;
	gl_write(x, y, s);
}

void drawscreen() {
	int i;
	/* Copy background to backscreen. */
	gl_setcontext(&background);
	gl_copyscreen(&backscreen);
	
	/* Now draw the objects in backscreen. */
	gl_setcontext(&backscreen);

	for (i = 0; i < MAXMOVERS; i++)
		if (mover[i].stat & STAT_ACTIVE) {
			drawmover(i);
		}

	writestat();

	/* Copy backscreen to physical screen. */ 	
	gl_copyscreen(&physicalscreen);
}

void move() {
	int i;
	gl_setcontext(&background);
	for (i = 0; i < MAXMOVERS; i++)
		if (mover[i].stat & STAT_ACTIVE) {
			int nx, ny;
			int c;
			if (++mover[i].time == MOVERLIFETIME) {
				killmover(i);
				continue;
			}
			for (;;) {
				nx = mover[i].x + mover[i].vx;
				ny = mover[i].y + mover[i].vy;
				if (nx < 0 || nx >= MAPWIDTH) {
					mover[i].vx = -mover[i].vx;
					continue;
				}
				if (ny < 0 || ny >= MAPHEIGHT) {
					mover[i].vy = -mover[i].vy;
					continue;
				}
				break;
			}
			c = cityat(nx, ny);
			if (c != -1) {		/* found city */
				killmover(i);
				growcity(nx, ny, mover[i].x, mover[i].y, c);
				continue;	/* next mover */	
			}
			mover[i].x = nx;
			mover[i].y = ny;
		}
	if (pop >= MAPWIDTH * MAPHEIGHT * 255 / 256) {
		/* start all over again */
		printf("fun: new run.\n");
		pop = 0;
		time = 0;
		clearmap();
		initcities();
		createcities();
		drawbackground();
	}
}

void createmovers() {
	int i;
	for (i = 0; i < NUMBEROFMOVERS; i++)
		addrandommover();
}

void main() {
	vga_init();

	clearmap();
	initmovers();
	createcities();
	createmovers();

	vga_setmode(VGAMODE);
	gl_setcontextvga(VGAMODE);
	gl_getcontext(&physicalscreen);

#ifdef USE_PAGEFLIPPING	
	/* Try to enable page flipping. */
	gl_enablepageflipping(&physicalscreen);
#endif
	setcustompalette();
	/* initfont() here caused trouble with planar 256 color modes. */

	gl_setcontextvgavirtual(VGAMODE);
	gl_getcontext(&backscreen);
	initfont();

	gl_setcontextvgavirtual(VGAMODE);
	gl_getcontext(&background);

	drawbackground();

	framerate = 0;
	framecount = 0;
	frameclock = clock();

	for (;;) {
		int i;
		drawscreen();
		move();
		for (i = 0; i < 4; i++)
			if (nu_movers < MOVERTHRESHOLD)
				addrandommover();
		time++;
		/* Update frame rate every 3 seconds. */
		framecount++;
		if (clock() - frameclock >= 300) {
			framerate = framecount * 1000 / (clock() - frameclock);
			framecount = 0;
			frameclock = clock();
		}

	}

	freecontext(&backscreen);
	freecontext(&background);

	vga_setmode(TEXT);	
	exit(0);
}
