/*
 * Arr! 
 * Copyright 2009, Vlad Dumitru
 *  
*/

#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>

#define max_x		    80
#define max_y		    20

#define magic		    40
#define cellrad		    4

#define color_lit	    6
#define color_dark	    8
#define color_ent	    3
#define color_player	    15

#define fov_rad		    4

const int maxc_x=max_x/cellrad+1;
const int maxc_y=max_y/cellrad+1;

typedef struct{
    int x,y;
    char name[32];
    char look;
    int type,seen;
    int atk,def;
    int hp,maxhp;
    int level;
    }ent_;
    
ent_ ent[60];

int player_exp=0,player_level=0;
int level=0;

int map[max_x][max_y];
int losmap[max_x][max_y];
int cell[max_x/cellrad+1][max_y/cellrad+1];
int visited_cells=0,total_cells=0;

int cam_x=0,cam_y=0;

int ent_count=0;

char plmsg[256],enmsg[256];

enum tiletypes{
    t_floor,
    t_wall,
    t_dopen,
    t_dclosed,
    t_dhidden
    };



void lose()
{
    endwin();
    exit(0);
}

int isnear(int x1,int y1,int x2,int y2)
{
    if (((x1+1==x2) || (x1-1==x2) || (x1==x2)) && ((y1-1==y2) || (y1+1==y2) || (y1==y2))) return 1;
    return 0;
}

int dist(int x1,int y1,int x2,int y2)
{
    return (int)sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

int is_walkable(int x,int y)
{
    if((map[x][y]==t_floor || map[x][y]==t_dopen) && ent_ex(x,y)==-1) return 1;
    return 0;
}

int los(int x1,int y1,int x2,int y2)
{
    int i=0, deltax=0, deltay=0, numtiles=0,d=0, dinc1=0, dinc2=0;
    int x=0, xinc1=0, xinc2=0,y=0, yinc1=0, yinc2=0;
    int isseen = 1;
    deltax = abs(x2 - x1); deltay = abs(y2 - y1);
    if(deltax >= deltay){
        numtiles = deltax + 1;
        d = (2 * deltay) - deltax;
        dinc1 = deltay * 2;
        dinc2 = (deltay - deltax) * 2;
        xinc1 = 1; xinc2 = 1; yinc1 = 0; yinc2 = 1;
    }else{
        numtiles = deltay + 1;
        d = (2 * deltax) - deltay;
        dinc1 = deltax * 2;
        dinc2 = (deltax - deltay) * 2;
        xinc1 = 0; xinc2 = 1; yinc1 = 1; yinc2 = 1;}
    if(x1 > x2){
        xinc1 = - xinc1;
	xinc2 = - xinc2;}
    if(y1 > y2){
        yinc1 = - yinc1;
	yinc2 = - yinc2;}
    x = x1; y = y1;
    for(i=2;i<=numtiles;i++){
      if(map[x][y]==t_wall || map[x][y]==t_dclosed || map[x][y]==t_dhidden){isseen = 0; break;}
      if(d < 0){
          d = d + dinc1;
	  x = x + xinc1;
	  y = y + yinc1;
      }else{
          d = d + dinc2;
	  x = x + xinc2;
	  y = y + yinc2;
      }
    }
    return isseen;
}

int inrange(int x,int y)
{
    if (x>1 && y>1 && x<max_x-1 && y<max_y-1) return 1; return 0;
}

void mkroom(int x,int y,int r)
{
    int ix,iy;
    if(x/cellrad>1 && y/cellrad>1){
	for(ix=x-r-1;ix<=x+r+1;ix++){
	    for(iy=y-r-1;iy<=y+r+1;iy++){
		if (inrange(ix,iy)==1){
		    if (ix==x-r-1 || ix==x+r+1 || iy==y-r-1 || iy==y+r+1){
			if(map[ix][iy]==0){
			    if(rand()%5==1)
				map[ix][iy]=t_dhidden;
			    else
				map[ix][iy]=t_dclosed;
			}
		    }else{
			map[ix][iy]=t_floor;
		    }
		}
	    }
	}
    }
}

void clink(int x1,int y1,int x2,int y2){
    int cx=x1,cy=y1;
    while(cx!=x2){
	if(x1>x2){cx--;}else{cx++;}
	if(inrange(cx,cy)==1) map[cx][cy]=t_floor;}
    while(cy!=y2){
	if(y1>y2){cy--;}else{cy++;}
	if(inrange(cx,cy)==1) map[cx][cy]=t_floor;}
}

void count_cells(void)
{
    int ix,iy;
    visited_cells=0; total_cells=0;
    for(ix=0;ix<maxc_x;ix++){
	for(iy=0;iy<maxc_y;iy++){
	    total_cells++;
	    if (cell[ix][iy]==1) visited_cells++;
	}
    }
}

void mkmaze(void)
{
    int rx=0,ry=0,rd=0;
    int ix,iy;
    int c=0;

    for(ix=0;ix<max_x;ix++){
	for(iy=0;iy<max_y;iy++){
	    map[ix][iy]=t_wall;
	}
    }
    
    for(ix=0;ix<maxc_x;ix++){
	for(iy=0;iy<maxc_y;iy++){
	    cell[ix][iy]=0;
	}
    }
    
    rx=maxc_x/2;
    ry=maxc_y/2;
    
    cell[rx][ry]=1;
    
    count_cells();
    
    while( visited_cells < total_cells ){

	c++;
	if (c>magic) goto hell;
	
	rd=rand()%4;
	
	if (rd==0){
	    if (inrange(rx*cellrad,(ry-1)*cellrad)==1 && (cell[rx][ry-1]==0 || rand()%7==6)){
		clink(rx*cellrad,ry*cellrad,rx*cellrad,(ry-1)*cellrad);
		ry--;
	    }else{
		while(1){
		    rx=rand()%(maxc_x-1)+1;
		    ry=rand()%(maxc_y-1)+1;
		    if (cell[rx][ry]==1) break;
		}
	    }
	}else if (rd==1) {
	    if (inrange(rx*cellrad,(ry+1)*cellrad)==1 && (cell[rx][ry+1]==0 || rand()%7==6)){
		clink(rx*cellrad,ry*cellrad,rx*cellrad,(ry+1)*cellrad);
		ry++;
	    }else{
		while(1){
		    rx=rand()%(maxc_x-1)+1;
		    ry=rand()%(maxc_y-1)+1;
		    if (cell[rx][ry]==1) break;
		}
	    }
	}else if (rd==2) {
	    if (inrange((rx+1)*cellrad,ry*cellrad)==1 && (cell[rx-1][ry]==0 || rand()%7==6)){
		clink((rx-1)*cellrad,ry*cellrad,rx*cellrad,ry*cellrad);
		rx--;
	    }else{
		while(1){
		    rx=rand()%(maxc_x-1)+1;
		    ry=rand()%(maxc_y-1)+1;
		    if (cell[rx][ry]==1) break;
		}
	    }
	}else if (rd==3) {
	    if (inrange((rx+1)*cellrad,ry*cellrad)==1 && (cell[rx+1][ry]==0 || rand()%7==6)){
		clink(rx*cellrad,ry*cellrad,(rx+1)*cellrad,ry*cellrad);
		rx++;
	    }else{
		while(1){
		    rx=rand()%(maxc_x-1)+1;
		    ry=rand()%(maxc_y-1)+1;
		    if (cell[rx][ry]==1) break;
		}
	    }
	}
	
	cell[rx][ry]=1;
	
	map[rx*cellrad][ry*cellrad]=0;
	
	count_cells();
    }
    
    hell:
    for(ix=0;ix<maxc_x;ix++){
	for(iy=0;iy<maxc_y;iy++){
	    if (cell[ix][iy]==1){
		map[ix*cellrad][iy*cellrad]=t_floor;
		if(rand()%3==2) mkroom(ix*cellrad,iy*cellrad,(cellrad-1)/2);
	    }
	}
    }
}

void init_curses()
{
    initscr();
    keypad( stdscr, TRUE ); 
    nodelay( stdscr, FALSE );
    cbreak();
    noecho();
    curs_set(0);
    leaveok(stdscr, 1);
    start_color();
    
    int c;
    for (c=0; c<=8; c++) 
	init_pair(c,c,0);
}

void setfgcol(int f)
{
    if(f>7){
	attrset(COLOR_PAIR(f%9) | A_BOLD );
    }else{
	attrset(COLOR_PAIR(f) | A_NORMAL );
    }
}

int ent_ex(int x, int y)
{
    int i;
    for (i=0; i<=ent_count; i++)
    {
	if (ent[i].x==x && ent[i].y==y && ent[i].hp>0) return i;
    }
    return -1;
}

void draw_map()
{
    int ix,iy,tx,ty;
    
    for (ix=ent[0].x-fov_rad; ix<=ent[0].x+fov_rad; ix++)
    {
	for (iy=ent[0].y-fov_rad; iy<=ent[0].y+fov_rad; iy++)
	{
	    if (losmap[ix][iy]==2) losmap[ix][iy]=1;
	    if (los(ent[0].x,ent[0].y,ix,iy)==1 && dist(ent[0].x,ent[0].y,ix,iy)<fov_rad) losmap[ix][iy]=2;
	}
    }
    
    for (tx=0; tx<80; tx++)
    {
	for(ty=0; ty<22; ty++)
	{
	    ix=tx+cam_x;
	    iy=ty+cam_y;
	
	    if(ix>=0 && iy>=0 && ix<max_x && iy<max_y){
		move(ty,tx);
	    
		if (losmap[ix][iy]==2)
		    setfgcol(color_lit);
	    
		if (losmap[ix][iy]==1)
		    setfgcol(color_dark);

		if (losmap[ix][iy]!=0)
		{
		    if (ent_ex(ix,iy)!=-1)
		    {
			if(losmap[ix][iy]==2){
			    setfgcol(color_ent);
			    addch(ent[ent_ex(ix,iy)].look);
			    ent[ent_ex(ix,iy)].seen=1;
			}
		    }else{
			switch (map[ix][iy])
			{
			    case t_floor:
				addch('.');
				break;
			    case t_wall:
				addch('#');
				break;
			    case t_dclosed:
				addch('+');
				break;
			    case t_dopen:
				addch('\'');
				break;
			    case t_dhidden:
				addch('#');
				break;
			}
		    }
		}
	    }
	}
    }
    
    move(21,0);	printw("Nimic 000 - hp:%i/%i",ent[0].hp,ent[0].maxhp);
    move(22,0); addstr(plmsg);
    move(23,0); addstr(enmsg);
}

void ent_zombie_ai(int e)
{
    char temp[256];
    if (isnear(ent[0].x,ent[0].y,ent[e].x,ent[e].y)==0){
	if ((ent[e].x > ent[0].x) && is_walkable(ent[e].x-1,ent[e].y)==1) ent[e].x--;
	if ((ent[e].x < ent[0].x) && is_walkable(ent[e].x+1,ent[e].y)==1) ent[e].x++;
	if ((ent[e].y > ent[0].y) && is_walkable(ent[e].x,ent[e].y-1)==1) ent[e].y--;
	if ((ent[e].y < ent[0].y) && is_walkable(ent[e].x,ent[e].y+1)==1) ent[e].y++;
    }else{
	int dmg = rand()%ent[e].atk;
	if (dmg==0)
	    snprintf(temp,256,"%s tries to hit you, but misses.",ent[e].name);
	if (dmg>0){
	    ent[0].hp-=dmg;
	    if (ent[0].hp>0)
		snprintf(temp,256,"%s punches you for %i damage!",ent[e].name,dmg);
	    else{
		snprintf(temp,256,"%s mortally punches you! You die!",ent[e].name);
		strcat(enmsg,temp);
		draw_map();
		getch();
		lose();
	    }
	}
    }
    strcat(enmsg,temp);
}

void move_ent(int* fx,int* fy,int xo,int yo){
    int t=map[*fx+xo][*fy+yo];
    int m=ent_ex(*fx+xo,*fy+yo);

    if(m==-1){
	switch(t){
	case t_floor:
	case t_dopen:
	    if(inrange(*fx+xo,*fy+yo)==1){*fx+=xo; *fy+=yo;}
	    break;
	case t_dclosed:
	    map[*fx+xo][*fy+yo]=t_dopen;
	    strcpy(plmsg,"You open the door.");
	    break;
	case t_dhidden:
	    map[*fx+xo][*fy+yo]=t_dclosed;
	    strcpy(plmsg,"You find a hidden door!");
	    break;
	case t_wall:
	    strcpy(plmsg,"Bump!");
	}
	if(*fx-cam_x>20) cam_x++;
	if(*fx-cam_x<60) cam_x--;
	if(*fy-cam_y>15) cam_y++;
	if(*fy-cam_y< 5) cam_y--;
    }else{
	int dmg = rand()%ent[0].atk;
	if (dmg==0)
	    snprintf(plmsg,256,"You try to hit %s, but miss.",ent[m].name);
	if (dmg>0){
	    ent[m].hp-=dmg;
	    if (ent[m].hp>0)
		snprintf(plmsg,256,"You punch %s for %i damage!",ent[m].name,dmg);
	    else{
		snprintf(plmsg,256,"You mortally punch %s for %i damage! You gain %i exp. points!",ent[m].name,dmg,ent[m].level);
		player_exp+=ent[m].level;
	    }
	}
    }
}

void put_ents()
{
    locplayer:
	ent[0].x=rand()%max_x;
	ent[0].y=rand()%max_y;
    if(map[ent[0].x][ent[0].y]==1) goto locplayer;
    ent[0].look='@';
    strcpy(ent[0].name,"The Dude");
    ent[0].atk=3;
    ent[0].def=4;
    ent[0].maxhp=10;
    ent[0].hp=10;

    int i;
    for (i=0; i<9; i++)
    {
	ent_count++;
	
	locent:
	    ent[ent_count].x=rand()%max_x;
	    ent[ent_count].y=rand()%max_y;
	if(map[ent[ent_count].x][ent[ent_count].y]==1) goto locent;
	    
	ent[ent_count].look=rand()%96+33;
	strcpy(ent[ent_count].name,"A monster");
	ent[ent_count].atk=rand()%3+2;
	ent[ent_count].def=rand()%3+2;
	ent[ent_count].maxhp=rand()%5+3;
	ent[ent_count].hp=ent[ent_count].maxhp;
	ent[ent_count].seen=0;
    }
}

int main()
{
    int k,kp=0,turns=0;

    init_curses();
    srand(time(0));
    
    mkmaze();
    
    put_ents();
    
    while(k!='q')
    {
	switch(k)
	{
	    case KEY_UP:
	    case '8':
		move_ent(&ent[0].x,&ent[0].y,0,-1);
		kp=1;
		break;
	    case KEY_DOWN:
	    case '2':
		move_ent(&ent[0].x,&ent[0].y,0,1);
		kp=1;
		break;
	    case KEY_LEFT:
	    case '4':
		move_ent(&ent[0].x,&ent[0].y,-1,0);
		kp=1;
		break;
	    case KEY_RIGHT:
	    case '6':
		move_ent(&ent[0].x,&ent[0].y,1,0);
		kp=1;
		break;
	}
	
	if (kp==1)
	{ 
	    int m;
	    for (m=1; m<=ent_count; m++)
	    {
		if (ent[m].seen==1) ent_zombie_ai(m);
	    }

	    clear();
	    draw_map();
	    refresh();  

	    turns++;
	    if (turns>999) turns=0;
	    
	    strcpy(plmsg,"");
	    strcpy(enmsg,"");
	}
	
	k=getch();
	
    }
    
    endwin();
}
