/* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.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 G800x600x256

//#define USE_PAGEFLIPPING

/* #define USE_SMALLOC */

/* This is the size of the animated window. */
#define MAPWIDTH 800
#define MAPHEIGHT 560

#define MAXMOVERS 2000
#define MAXCITIES 1000
#define NUMBEROFCITIES 20
#define NUMBEROFMOVERS 1400
#define MOVERTHRESHOLD 1400
#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 mytime = 0;			/* used to be "time" but collids w/libc function time() */
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(void)
{
    /* 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)
{
    void *font;
#ifdef USE_SMALLOC
    font = smalloc(256 * 8 * 8 * BYTESPERPIXEL);
#else
    font = malloc(256 * 8 * 8 * BYTESPERPIXEL);
#endif
    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(void)
{
    int x, y;
    for (y = 0; y < MAPHEIGHT; y++)
	for (x = 0; x < MAPWIDTH; x++)
	    map[x][y] = 0;
}

Position
findfreeposition(void)
{
    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(void)
{
    int i;
    for (i = 0; i < MAXMOVERS; i++)
	mover[i].stat = 0;
}

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

void addrandommover(void)
{
    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;
    default:
	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(void)
{
    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(void)
{
    static int colortable[5] =
    {yellow(31), blue(31), white(31), green(31), cyan(31)};
    return colortable[(mytime / COLORTIME) % 5]
	- (mytime % 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(void)
{
/* 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(void)
{
/* 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(void)
{
    int i;
    for (i = 0; i < NUMBEROFCITIES; i++) {
	Position p = findfreeposition();
	addcity(p.x, p.y);
    }
}

void writestat(void)
{
    char s[41];
    int i, x, y;
    int maxpopcity, maxpop;
    sprintf(s, "Pop %7d  Time %7d  Rate %5d.%d", pop, mytime,
	    framerate / 10, framerate % 10);
    gl_setwritemode(WRITEMODE_OVERWRITE);
    gl_write(0, HEIGHT - 8, s);
    maxpop = -1;
    maxpopcity = 0;
    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(void)
{
    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(void)
{
    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;
	mytime = 0;
	clearmap();
	initcities();
	createcities();
	drawbackground();
    }
}

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

int main(void)
{
    vga_init();

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

    vga_setmode(VGAMODE);
    gl_setcontextvga(VGAMODE);
    physicalscreen = gl_allocatecontext();
    gl_getcontext(physicalscreen);

#ifdef USE_PAGEFLIPPING
    /* Try to enable page flipping. */
printf("pf=%i\n",   gl_enablepageflipping(physicalscreen));
#endif
    setcustompalette();
    /* initfont() here caused trouble with planar 256 color modes. */

    gl_setcontextvgavirtual(VGAMODE);
    backscreen = gl_allocatecontext();
    gl_getcontext(backscreen);
#ifdef USE_SMALLOC
    free(backscreen->vbuf);
    backscreen->vbuf = smalloc(BYTEWIDTH * HEIGHT);
    gl_setcontext(backscreen);
#endif

    initfont();

    gl_setcontextvgavirtual(VGAMODE);
    background = gl_allocatecontext();
    gl_getcontext(background);
#ifdef USE_SMALLOC
    free(background->vbuf);
    background->vbuf = smalloc(BYTEWIDTH * HEIGHT);
    gl_setcontext(background);
#endif

    drawbackground();

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

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

#ifndef USE_SMALLOC
    gl_freecontext(backscreen);
    gl_freecontext(background);
#endif

    vga_setmode(TEXT);
    exit(0);
}
