/* displayable
 * -----------
 * This source file implements an algorithm that crops the view area for
 * a tile-type adventure game.  A complete description may be found from
 * my home-page
 *  http://www.cs.cmu.edu/~crpalmer
 *
 * Email may be sent to me at crpalmer+@cs.cmu.edu
 *
 * This source code is pretty ugly.  I've grafted on a simple terminal
 * neutral interface.  Probably the best use for this source code would
 * be to strip out the routines that do the work and to write your
 * own map and graphics layer.
 *
 * However, compiling this source code will give you something that I
 * believe works.
 *
 * As always, this source code is copyright (c) 1996, Chris Palmer.  All
 * Rights Reserved.  You are granted the rights to use and redistribute
 * this source code provided that this copyright notice remains intact.
 * I take no responsibility for any effect of running this program, be it
 * good or bad.
 *
 * Chris Palmer
 * November 22, 1996.
 */

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>

#define TRUE		1
#define FALSE		0

/* You can see what happens when you go to a larger display area by
 * changing this parameter
 */

#if 1
#define DELTA		6		/* gives 13 tiles on the screen */
#else
#define DELTA		10		/* gives 21x21 */
#endif


/* Changing VOID_OPAQUE makes the viewable area more strict.  It is no
 * longer possible to "look around" small obstructions.  You can try it
 * and see what happens....
 */

#define VOID_OPAQUE	1

/* MAX_TILE_SQUARE is convenient */

#define MAX_TILE_SQUARE	(2*DELTA+1)	/* # of tiles being displayed */

/* basic math, you could write sign(u) without the division.  If you use
 * this software, I would recommend doing so!!!  It's coded like this
 * simply to illustrate the meaning of sign(u).
 */

#define abs(u)		((u) >= 0 ? (u) : -(u))
#define sign(u)		((u) == 0 ? 0 : abs(u)/u)


/* --------------------------------------------------------------------- */

/* UGLY SOURCE CODE ALERT:
 *
 *   I've spent several minutes writing this interface.  Whatever you do,
 *   never write code like this!
 *
 * screen[row][column] is the screen buffer of ascii characters
 * map_data[row][column] may be either '*' for a wall or '.' for floor
 * map0 and map are just stupidity to avoid changing the main algorithm
 * which I've previously written.
 *
 * The following functions are implemented:
 *   Map_Opaque(x,y): is map(x,y) opaque?
 *   Map_MayMove(x,y) : can you walk on map(x,y)?
 *   Graph_ClearTile(i,j) : clear screen position (i,j)
 *   Graph_DrawTile(i,j,x,y) : fill screen position (i,j) with map(x,y)
 *   Graph_Blit() : write the screen
 */

static char screen[2*DELTA+1][2*DELTA+1];

static char *map_data[] = {
	"****************************************",
	"*...............****...................*",
	"*.....*........**..**..................*",
	"*.....*.......**....**.................*",
	"***********.***......***********..******",
	"*......................................*",
	"*..........................****........*",
	"*........*****............**..**.......*",
	"*..........***...........**.**.**...**.*",
	"*...........**..................**.**..*",
	"*.....**...***...........**......****..*",
	"*.....**..****.........********........*",
	"*........***..........******...........*",
	"*....*****..........******.............*",
	"*..****.............***................*",
	"****************************************"
};

struct {
	int		width, height;
} map0, *map = &map0;

struct _vector {
	int		x, y;
};

static int
Map_Opaque(int x, int y)
{
    return(map_data[y][x] == '*');
}

static int
Map_MayMove(int x, int y)
{
    return(map_data[y][x] != '*');
}

static void
Graph_ClearTile(int i, int j)
{
    screen[j][i] = ' ';
}

static void
Graph_DrawTile(int i, int j, int x, int y)
{
    if (map_data[y][x] == ' ') printf("space at (%d,%d)\n", x, y);
    screen[j][i] = map_data[y][x];
}

static void
Graph_Blit(void)
{
	int	i;

	screen[DELTA][DELTA] = '&';
	for (i = 0; i < 2*DELTA+1+2; i++) putc('^', stdout);
	putc('\n', stdout);
	for (i = 0; i < 2*DELTA+1; i++) {
		printf("^%-*.*s^\n", 2*DELTA+1, 2*DELTA+1, screen[i]);
	}
	for (i = 0; i < 2*DELTA+1+2; i++) putc('^', stdout);
	putc('\n', stdout);

	memset(screen, '?', sizeof(screen));
}

/* ----------------------------------------------------------------------- */

/* clip here....
 *
 * This starts the displayable algorithm proper.  calc_displayable_point
 * is the worker.  Map_DisplayAt() will call it for each square in the map.
 * If you have a compiler that inlines, this would be a great candidate!
 */

static void
calc_displayable_point(int x, int y, int i, int j, int mid, char opaque[MAX_TILE_SQUARE][MAX_TILE_SQUARE])
{
struct _vector p1, p2;

    if (mid + i < 0 || mid+i >= MAX_TILE_SQUARE || mid+j < 0 || mid+j >= MAX_TILE_SQUARE) {
	fprintf (stderr, "FATAL: calc_displayable_point is seriously broken!\n");
	exit (10);
    }

    if (x + i < 0 || x + i >= map->width || y + j < 0 || y + j >= map->height) {
        Graph_ClearTile (i+mid, j+mid);
	opaque [i + mid][j + mid] = TRUE;
	return;
    }

    /* We may now safely assume that all array indices will be safely
     * within the required ranges.  We shouldn't need the first check but,
     * hey, I never said I was perfect and I sometimes do violate my own
     * preconditions ...
     */


    /* Calculate the two points on the sight path to check */
    p1.x = -1 * sign (i) + i    + mid;
    p1.y = -1 * sign (j) + j    + mid;
    if (abs (j) > abs (i)) {
	p2.x = i                + mid;
	p2.y = -1*sign(j) + j   + mid;
    } else if (abs (j) < abs (i)) {
	p2.x = -1*sign(i) + i   + mid;
	p2.y = j                + mid;
    } else {
	p2.x = p1.x;
	p2.y = p1.y;
    }

    if (opaque[p1.x][p1.y] + opaque[p2.x][p2.y] >= 2) {
        Graph_ClearTile (i+mid, j+mid);
	opaque [i + mid][j + mid] = VOID_OPAQUE;
    } else {
	/* The player can see this square */
        Graph_DrawTile (i+mid, j+mid, x + i, y + j);
	opaque [i + mid][j + mid] = Map_Opaque (x+i, y+j);
    }
}

int Map_DisplayAt (int x, int y)
{
int i, j, dist, mid;
char opaque [MAX_TILE_SQUARE][MAX_TILE_SQUARE];

    mid = DELTA;
    Graph_DrawTile (mid, mid, x, y);

    opaque [mid][mid] = FALSE;

    for (dist = 1; dist <= DELTA; dist++) {
	for (i = -dist; i <= dist; i++)
	    for (j = -dist; j <= dist; j += (2 * dist))
		calc_displayable_point (x, y, i, j, mid, opaque);
	/* We've already covered (x +- dist, y +- dist), skip those corneri
         * points
         */
	for (j = -dist+1; j < dist; j++)
	    for (i = -dist; i <= dist; i += (2 * dist))
		calc_displayable_point (x, y, i, j, mid, opaque);
    }
    return (0);
}


/* ---------------------------------------------------------------------- */

/* back to the ugly code ..... */

int
main(int argc, char **argv)
{
    int x = 3, y = 10;
    int new_x, new_y;
    char line[10];

    map->width = strlen(map_data[0]);
    map->height = sizeof(map_data) / sizeof(map_data[0]);

    for(;;) {
	Map_DisplayAt(x,y);
	Graph_Blit();

	if (fgets(line, 10, stdin) == NULL) break;

	new_x = x;
	new_y = y;
	switch(line[0]) {
	case 'i': new_y--; break;
	case 'j': new_x--; break;
	case 'k': new_y++; break;
	case 'l': new_x++; break;
	default:
		printf("i = up, j = left, k = down, l = right, you are at (%d, %d)\n", x, y);
	}
	if (new_x < 0) new_x = 0;
	if (new_y < 0) new_y = 0;
	if (new_x >= map->width) new_x = map->width-1;
	if (new_y >= map->height) new_y = map->height-1;
	if (Map_MayMove(new_x, new_y)) {
	    x = new_x;
	    y = new_y;
	}
    }
    return(0);
}

