/* EOR maze game
   Copyright C.T.Stretch 1991
   Thu,16 May 1991
*/

#define PROGNAME "EOR"

#include "wimp.h"
#include "wimpt.h"
#include "win.h"
#include "event.h"
#include "baricon.h"
#include "res.h"
#include "menu.h"
#include "template.h"
#include "dbox.h"
#include "werr.h"
#include "xferrecv.h"
#include "saveas.h"
#include "sprite.h"
#include "coords.h"
#include "magnify.h"
#include "bbc.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <time.h>

#define XSIZE 80
#define YSIZE 80
#define NPLAY 5
#define SPRASIZE (556*(26+NPLAY)+128)
#define GSIZE  6000
#define TSIZE 80
#define VIEW 8
#define OFFSET ((1280-VIEW*64)/(NPLAY-1))
#define NAME 24
#define wkey_LEFT  0x18C
#define wkey_RIGHT 0x18D
#define wkey_DOWN  0x18E
#define wkey_UP    0x18F
#define BEEP bbc_sound(1,vol,100,5,0)
#define NONULL wimp_EMNULL|wimp_EMPTRENTER|wimp_EMPTRLEAVE
#define YESNULL wimp_EMPTRENTER|wimp_EMPTRLEAVE

typedef struct hero
{ wimp_w w;
  char name[NAME];
  char t[TSIZE];
  int x,y,vx,vy;
  int m,d,b;
  BOOL open,dead;
  sprite_factors sf;
} hero;

static menu barmenu,mazemenu;
static int maze[XSIZE][YSIZE];
static int players=0,coins,cash,level=1,vol=-15;
static int ino;
static char icbuf[12]="";
static sprite_area *spra;
static sprite_id sprites[26+NPLAY];
static hero heros[NPLAY];
static wimp_wind *mazewind;
static wimp_caretstr nocaret={0,-1,0,0,1<<25,0};
static sprite_pixtrans pt[16];
static char game[GSIZE],*nextg,*endg=game+GSIZE,*nexts,*ends;
static BOOL singlestep=FALSE;
static hero *steph;
static char currh;
static char hcols[]={9,15,10,14,13};

static void saveg(char c)
{ if(nextg==endg) return;
  if(isdigit(c))
  { if(c==currh) return;
    currh=c;
    if((nextg>game)&&(isdigit(nextg[-1])))
    { nextg[-1]=c;return;
    }
  }
  *nextg++=c;
  if(nextg==endg) werr(0,"Game store full");
}

static void readpt()
{ sprite_factors sf;
  wimp_readpixtrans(spra,&sprites['w'-'a'],&sf,pt);
}

static void pause(int t)
{ unsigned int n=clock();
  while(clock()-t<n);
}

static void setpixfact(hero *h)
{ int block[3];
  block[0]=4;block[1]=5;block[2]=-1;
  os_swi2(0x31,(int)block,(int)block);
  h->b=(64*h->m)/h->d;
  h->sf.xdiv=h->d<<block[0];h->sf.ydiv=h->d<<block[1];
  h->sf.xmag=2*h->m;h->sf.ymag=2*h->m;
}

static void showcash()
{ sprintf(icbuf,"%d/%d",cash,coins);
  wimpt_noerr(wimp_set_icon_state((wimp_w)-1,ino,0,0));
}

static void openup(hero *h)
{ wimp_wstate ws;
  int wsize=VIEW*h->b;
  wimp_get_wind_state(h->w,&ws);
  ws.o.box.x1=ws.o.box.x0+wsize;
  ws.o.box.y1=ws.o.box.y0+wsize;
  if(h->vx>XSIZE-VIEW) h->vx=XSIZE-VIEW;
  if(h->vx<1) h->vx=1;
  if(h->vy>YSIZE-VIEW) h->vy=YSIZE-VIEW;
  if(h->vy<1) h->vy=1;
  ws.o.x=h->b*h->vx;
  ws.o.y=h->b*(h->vy+VIEW);
  /* ws.o.behind=-1; */
  wimpt_noerr(wimp_open_wind(&(ws.o)));
  h->open=TRUE;
  nocaret.w=h->w;
  wimpt_noerr(wimp_set_caret_pos(&nocaret));
  saveg((h-heros)+'0');
}

static void settit(hero *h)
{ char tit[TSIZE];
  if(h->name[0]==0) sprintf(h->name,"Hero_%d",h-heros);
  sprintf(tit,"Level %d : %s ",level,h->name);
  win_settitle(h->w,tit);
}

static void close(hero *h)
{ h->open=FALSE;
  wimpt_noerr(wimp_close_wind(h->w));
}

static void kill(int x,int y)
{ int i;
  for(i=0;i<players;i++)
  if(!heros[i].dead&&(heros[i].x==x)&&(heros[i].y==y))
  { bbc_sound(1,vol,60,25,0);
    pause(150);
    close(heros+i);
    heros[i].dead=TRUE;
  }
}

static BOOL readmaze(int n)
{ FILE *file;
  char fname[64];
  int x,y,c,i;
  sprintf(fname,"<EOR$Dir>.Mazes.%d",n);
  file=fopen(fname,"r");
  if(!file) return FALSE;
  for(i=0;i<players;i++) close(heros+i);
  for(x=0;x<XSIZE;x++) for(y=0;y<YSIZE;y++) maze[x][y]='z';
  x=1;y=YSIZE-2;
  players=0;coins=0;cash=0;nextg=game;currh='0';steph=heros;
  while((c=getc(file))!=EOF)
  { if(c=='\n'||x>XSIZE-2){y--;x=1;if(y<2) break;}
    if(islower(c)||isdigit(c))
    { maze[x++][y]=c;
      if(isdigit(c))
      { if(players<NPLAY)
        { heros[players].x=x-1;heros[players].y=y;
          heros[players].vx=x-1-VIEW/2;heros[players].vy=y-VIEW/2;
          settit(heros+players);
          heros[players].dead=FALSE;
          maze[x-1][y]=++players+'z';
        }
        else maze[x-1][y]=0;
      }
      if(c=='c') coins++;
    }
    if(c==' ') maze[x++][y]=0;
  }
  fclose(file);
  showcash();
  if(!players) return FALSE;
  openup(heros);
  return TRUE;
}

static void nextlevel()
{ if(readmaze(++level)) return;
  werr(1,"Level %d not found",level);
}

static void redr(hero *h)
{ wimp_redrawstr r;
  int more,i,j,sx,sy,a,b,c,d,xx;
  r.w=h->w;
  wimpt_noerr(wimp_redraw_wind(&r,&more));
  while(more)
  { coords_box_toworkarea(&(r.g),(coords_cvtstr*)&(r.box));
    a=r.g.y0/h->b;
    b=(r.g.y1+h->b-1)/h->b;
    if(a<0) a=0;
    if(b>=YSIZE) b=YSIZE-1;
    c=r.g.x0/h->b;
    d=(r.g.x1+h->b-1)/h->b;
    if(c<0) c=0;
    if(d>=YSIZE) d=YSIZE-1;
    sx=coords_x_toscreen(h->b*c,(coords_cvtstr *)&r.box);
    sy=coords_y_toscreen(h->b*a,(coords_cvtstr *)&r.box);
    for(i=a;i<=b;i++)
    { xx=sx;
      for(j=c;j<=d;j++)
      { if(maze[j][i])
       sprite_put_scaled(spra,sprites+(maze[j][i]-'a'),8,xx,sy,&(h->sf),pt);
        xx+=h->b;
      }
      sy+=h->b;
    }
    wimp_get_rectangle(&r,&more);
  }
}

static void mag(void *h)
{ wimp_close_wind(((hero*)h)->w);
  setpixfact(((hero*)h));
  openup(((hero*)h));
}

static void show(int x,int y)
{ int i,n,more,sx,sy,s;
  wimp_redrawstr r;
  for(i=0;i<players;i++) if(heros[i].open)
  if(x>=heros[i].vx&&x<=heros[i].vx+VIEW)
  if(y>=heros[i].vy&&y<=heros[i].vy+VIEW)
  { n=heros[i].b;
    r.w=heros[i].w;
    r.box.x0=x*n;r.box.x1=r.box.x0+n;
    r.box.y0=y*n;r.box.y1=r.box.y0+n;
    wimp_update_wind(&r,&more);
    while(more)
    { sx=coords_x_toscreen(n*x,(coords_cvtstr *)&r.box);
      sy=coords_y_toscreen(n*y,(coords_cvtstr *)&r.box);
      s=maze[x][y];if(!s) s='o';
      sprite_put_scaled(spra,sprites+(s-'a'),0,sx,sy,&(heros[i].sf),pt);
      wimp_get_rectangle(&r,&more);
    }
  }
}

static BOOL clear(int x,int y,int dx,int dy)
{ switch(maze[x][y])
  { default:return FALSE;
    case 'c':cash++;showcash();BEEP;return TRUE;
    case 's':if(maze[x+dx][y+dy])return FALSE;
             do
             { maze[x+dx][y+dy]=maze[x][y];
               maze[x][y]=0;
               show(x,y);
               x+=dx;y+=dy;
               show(x,y);
             } while(!maze[x+dx][y+dy]);
             return TRUE;
    case 'h':return !dy;
    case 'v':return !dx;
    case 'x':if(coins==cash)nextlevel();
             return FALSE;
    case 'u':case 'd':case 'b':case 'g':if(dy) return FALSE;
                      if(maze[x+dx][y]&&maze[x+dx][y]!='h') return FALSE;
                      maze[x+dx][y]=maze[x][y];
                      show(x+dx,y);
                      return TRUE;
    case 'l':case 'r':case 'a':case 'f':if(dx) return FALSE;
                      if(maze[x][y+dy]&&maze[x][y+dy]!='v') return FALSE;
                      maze[x][y+dy]=maze[x][y];
                      show(x,y+dy);
                      return TRUE;
    case 0:return TRUE;
  }
}

static void explode(int x,int y,int dx,int dy)
{ int i,xx,yy;
  bbc_sound(2,vol,53,20,0);
  for(i=-1;i<=1;i++) if(maze[xx=x+dx*i][yy=y+dy*i]!='z')
  {  maze[xx][yy]='e';show(xx,yy);
  }
  pause(100);
  for(i=-1;i<=1;i++) if(maze[xx=x+dx*i][yy=y+dy*i]=='e')
  {  maze[xx][yy]=0;show(xx,yy);
     kill(xx,yy);
  }
}

static BOOL through(int x,int y,int dx,int dy)
{ switch(maze[x+dx][y+dy])
  { case   0:return TRUE;
    case 'v':return (dx==0);
    case 'h':return (dy==0);
    default:return FALSE;
  }
}

static void scan()
{ int x,y,dx,dy,xx,yy;
  char c;
  BOOL changed,bang;
  do
  { changed=FALSE;
    for(x=1;x<XSIZE;x++) for(y=1;y<YSIZE;y++)
    { bang=FALSE;
      switch(maze[x][y])
      { default:continue;
        case 'g':bang=TRUE;
        case 'u':dx=0;dy=1;break;
        case 'b':bang=TRUE;
        case 'd':dx=0;dy=-1;break;
        case 'a':bang=TRUE;
        case 'l':dx=-1;dy=0;break;
        case 'f':bang=TRUE;
        case 'r':dx=1;dy=0;break;
      }
      if(!through(x,y,dx,dy)) continue;
      changed=TRUE;xx=x;yy=y;
      do
      { maze[xx+dx][yy+dy]=maze[xx][yy];
        maze[xx][yy]=0;
        show(xx,yy);
        xx+=dx;yy+=dy;
        show(xx,yy);
      } while(through(xx,yy,dx,dy));
      if(bang) explode(xx,yy,dx,dy);
      switch(c=maze[xx+dx][yy+dy])
      { case 'a':case 'f':explode(xx+dx,yy+dy,1,0);break;
        case 'b':case 'g':explode(xx+dx,yy+dy,0,1);break;
        default:if(c>'z')
        { maze[xx+dx][yy+dy]='y';
          show(xx+dx,yy+dy);
          kill(xx+dx,yy+dy);
        }
      }
    }
  } while(changed);
}

static void move(hero *h,int dx,int dy)
{ int x=h->x,y=h->y,xx,yy;
  BOOL scroll=FALSE;
  if(maze[h->x][h->y]<='z') return;
  xx=x+dx;yy=y+dy;
  if(clear(xx,yy,dx,dy))
  { saveg(dx?(dx==1?'r':'l'):(dy==1?'u':'d'));
    maze[xx][yy]=maze[x][y];maze[x][y]=0;
    h->x=xx;h->y=yy;
    show(xx,yy);show(x,y);
    if(dx<0&&x<=h->vx+1){scroll=TRUE;h->vx--;};
    if(dx>0&&x>=h->vx+VIEW-2){scroll=TRUE;h->vx++;};
    if(dy<0&&y<=h->vy+1){scroll=TRUE;h->vy--;};
    if(dy>0&&y>=h->vy+VIEW-2){scroll=TRUE;h->vy++;};
    if(scroll) openup(h);
    scan();
    if(maze[h->x][h->y]<='z') kill(h->x,h->y);
  }
}

static void key(hero *h,int c)
{ int i;
  if(singlestep)
  { if(nexts>=ends||c=='\r') {singlestep=FALSE;BEEP;return;}
    if(c!=' '){wimp_processkey(c);return;}
    c=*nexts++;
    if(isdigit(c)&&(i=c-'0')<players)
    { steph=heros+i;openup(steph);return;}
    h=steph;
  }
  switch(c)
  { default:wimp_processkey(c);break;
    case wkey_LEFT:case 'l':move(h,-1,0);break;
    case wkey_RIGHT:case 'r':move(h,1,0);break;
    case wkey_UP:case 'u':move(h,0,1);break;
    case wkey_DOWN:case 'd':move(h,0,-1);break;
  }
}

static void mouse(hero *h,wimp_mousestr *m)
{ int bx,by;
  wimp_wstate state;
  wimpt_noerr(wimp_get_wind_state(h->w,&state));
  bx=state.o.box.x0-state.o.x;
  by=state.o.box.y1-state.o.y;
  switch(m->bbits)
  { case wimp_BLEFT:if(singlestep) key(h,' ');
                    nocaret.w=h->w;
                    wimpt_noerr(wimp_set_caret_pos(&nocaret));
                    saveg((h-heros)+'0');
                    break;
  }
}

static BOOL loadgame(char *fn)
{ FILE *file=fopen(fn,"r");
  int i;
  if(!file) return FALSE;
  if(fscanf(file,"Level %d >",&level)!=1) return FALSE;
  if(!readmaze(level)) werr(1,"Cant read maze %d data",level);
  for(i=0;i<players;i++)
  { fscanf(file,"%23s >",&(heros[i].name));
    settit(heros+i);
  }
  ends=game+fread(game,1,GSIZE,file);
  nexts=game;singlestep=TRUE;
  fclose(file);
  event_setmask(YESNULL);
  return TRUE;
}

static void dhand(wimp_eventstr *e,hero *h)
{ int i;
  char *fn;
  switch(e->e)
  { case wimp_ENULL:if(singlestep){ pause(10);key(h,' ');}
                    else event_setmask(NONULL);
                    break;
    case wimp_ECLOSE:close(h);break;
    case wimp_EOPEN  :wimpt_noerr(wimp_open_wind(&e->data.o));break;
    case wimp_EREDRAW:redr(h);break;
    case wimp_EKEY:key(h,e->data.key.chcode);break;
    case wimp_EBUT:mouse(h,&(e->data.but.m));break;
    case wimp_ESEND  :case wimp_ESENDWANTACK:
                      switch(e->data.msg.hdr.action)
                      { case wimp_MDATALOAD:
                           xferrecv_checkinsert(&fn);
                           if(loadgame(fn)) xferrecv_insertfileok();
                           break;
                        case wimp_MMODECHANGE:readpt();
                        for(i=0;i<players;i++) if(heros[i].open) mag(h+i);
                                               else setpixfact(h+i);
                      } break;
  }
}

static BOOL savegame(char *fn,void *h)
{ FILE *file;
  int i;
  h=h;
  file=fopen(fn,"w");
  if(!file) return FALSE;
  fprintf(file,"Level %d\n>",level);
  for(i=0;i<players;i++) fprintf(file,"%s\n>",heros[i].name);
  fwrite(game,1,nextg-game,file);
  fclose(file);
  return TRUE;
}

static void mazemenuproc(void *wh,char *h)
{ switch (h[0])
  { case 1:settit(wh);break;
    case 2:magnify_select(&(((hero*)wh)->m),&(((hero*)wh)->d),
                                               16,16,mag,wh);break;
    case 3:saveas(0xFFF,"Game",0,savegame,0,0,0);break;
  }
}

static menu maker(void *h)
{ menu_make_writeable(mazemenu,1,((hero*)h)->name,NAME,"Aa-z_A-Z0-9");
  return mazemenu;
}

static void create(hero *h)
{ int i=h-heros;
  mazewind->title.indirecttext.buffer=h->t;h->t[0]=0;
  mazewind->title.indirecttext.bufflen=TSIZE;
  mazewind->colours[wimp_WCTITLEHI]=hcols[i];
  mazewind->box.x0=i*OFFSET;
  mazewind->box.x1=64*VIEW+i*OFFSET;
  sprintf(h->name,"Hero_%d",(h-heros)+1);
  h->m=1;h->d=1;h->open=FALSE;
  wimpt_complain(wimp_create_wind(mazewind,&(h->w)));
  win_register_event_handler(h->w,(void(*)(wimp_eventstr*,void*))dhand,h);
  event_attachmenumaker(h->w,maker,mazemenuproc,h);
  setpixfact(h);
}

static BOOL readsprites()
{ sprite_id look;
  int i;
  char schar[2];
  spra=(sprite_area *)malloc(SPRASIZE);
  if(!spra) return FALSE;
  sprite_area_initialise(spra,SPRASIZE);
  wimpt_noerr(sprite_area_load(spra,"<EOR$Dir>.Sprites"));
  look.tag=sprite_id_name;
  look.s.name=schar;
  schar[0]='w';schar[1]=0;
  if(sprite_select_rp(spra,&look,&(sprites['w'-'a'].s.addr))) return FALSE;
  for(i=0;i<='z'-'a';i++)
  { schar[0]='a'+i;
    if(sprite_select_rp(spra,&look,&(sprites[i].s.addr)))
       sprites[i].s.addr=sprites['w'-'a'].s.addr;
    sprites[i].tag=sprite_id_addr;
  }
  for(i=1;i<=NPLAY;i++)
  { schar[0]='0'+i-1;
    if(sprite_select_rp(spra,&look,&(sprites[i+'z'-'a'].s.addr)))
       sprites[i+'z'-'a'].s.addr=sprites['w'-'a'].s.addr;
    sprites[i+'z'-'a'].tag=sprite_id_addr;
  }
  readpt();
  return TRUE;
}

static void barclick(wimp_eventstr *e,void *h)
{ e=e;h=h;
}

static void barmenuproc(void *wh,char *h)
{ dbox d;
  wh=wh;
  switch (h[0])
  { case 1:d=dbox_new("Info");
           if(d)
           { dbox_setfield(d,4,"0.0 "__DATE__);
             dbox_show(d);dbox_fillin(d);
             dbox_dispose(&d);
           }
           break;
    case 2:if(!readmaze(level)) werr(1,"Cant read maze %d",level);break;
    case 3:singlestep=TRUE;
           nexts=game;ends=nextg;
           if(!readmaze(level)) werr(1,"Cant read maze %d",level);
           event_setmask(YESNULL);
           break;
    case 4:singlestep=TRUE;
           nexts=game;ends=nextg;
           if(!readmaze(level)) werr(1,"Cant read maze %d",level);
           break;
    case 5:exit(0);
    default:if(h[0]>5&&h[0]<players+6) mag(heros+h[0]-6);
  }
}

static menu barmake(void *v)
{ char list[300];
  int i;
  v=v;
  menu_dispose(&barmenu,0);
  sprintf(list,">Info,Restart,Replay,Step,Quit");
  for(i=0;i<players;i++)
  strcat(strcat(list,heros[i].dead?",~":","),heros[i].name);
  barmenu=menu_new(PROGNAME,list);
  return barmenu;
}

static void mybaricon(void)
{ wimp_icreate cr[1];
  cr->w=(wimp_w)-1;
  cr->i.box.x0=0;cr->i.box.x1=80;
  cr->i.box.y0=-16;cr->i.box.y1=84;
  cr->i.flags=wimp_ISPRITE|wimp_INDIRECT|(wimp_IBTYPE*wimp_BCLICKDEBOUNCE);
  cr->i.flags|=wimp_ITEXT|(wimp_IFORECOL*7)|wimp_IFILLED|wimp_IHCENTRE;
  cr->i.flags|=wimp_IBACKCOL;
  cr->i.data.indirecttext.buffer=icbuf;
  cr->i.data.indirecttext.validstring="S!"PROGNAME;
  cr->i.data.indirecttext.bufflen=12;
  wimpt_noerr(wimp_create_icon(cr,&ino));
  win_register_event_handler(win_ICONBAR,barclick,0);
  win_activeinc();
}

static BOOL init(void)
{ int i;
  wimpt_init(PROGNAME);
  res_init(PROGNAME);
  template_init();
  dbox_init();
  barmenu=menu_new(PROGNAME,"Quit");
  if(!barmenu) return FALSE;
  mazemenu=menu_new(PROGNAME,"NAME,>  Zoom ,>  Save ");
  if(!mazemenu) return FALSE;
  mybaricon();
  if(!event_attachmenumaker(win_ICONBAR,barmake,barmenuproc,0)) return FALSE;
  mazewind=template_syshandle("maze");
  if(!readsprites()) return FALSE;
  for(i=0;i<NPLAY;i++) create(heros+i);
  win_claim_unknown_events(heros->w);
  win_claim_idle_events(heros->w);
  if(!readmaze(1)) return FALSE;
  bbc_voices(2);
  return TRUE;
}

int main()
{ if(init()) for(;;) event_process();
  return 0;
}
