/* 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 #include #include #include #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); }